From 40fbd8c664ebb692fd9fd33328835638f63b804c Mon Sep 17 00:00:00 2001 From: amico Date: Thu, 20 Aug 2026 16:26:07 +0000 Subject: [PATCH 1/6] feat(workspace): multi-directory Location.Ref + workspace-aware prompt (opencode#215, draft) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - schema: Location.Ref.directories?: AbsolutePath[] (primary + all workspace roots) - core/location-mutation: isLexicallyInternal checks primary + directories; canonical check also allows any workspace dir (no external-directory prompts across workspace) - core/system-context: env block lists all workspace folders with primary mark; single-folder fallback unchanged Remaining for full AC (see #215): instruction discovery (findUp per dir, dedup), server session create ?directories=JSON handling, DB migration (directories TEXT nullable), and amicode extension Workspace sidebar (WorkspaceTreeProvider) + Catalog removal (already #457). Draft — engine core only. --- packages/core/src/location-mutation.ts | 12 ++++++++++-- packages/core/src/system-context/builtins.ts | 7 ++++++- packages/schema/src/location.ts | 1 + 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/core/src/location-mutation.ts b/packages/core/src/location-mutation.ts index 5f410b95b1..957a6fd6d1 100644 --- a/packages/core/src/location-mutation.ts +++ b/packages/core/src/location-mutation.ts @@ -117,15 +117,23 @@ const layer = Layer.effect( } }) + const isLexicallyInternal = (abs: string) => + FSUtil.contains(location.directory, abs) || + (location.directories?.some((d) => FSUtil.contains(d, abs)) ?? false) + const resolve = Effect.fn("LocationMutation.resolve")(function* (input: ResolveInput) { const relative = !path.isAbsolute(input.path) const absolute = path.resolve(location.directory, input.path) - const lexicallyInternal = FSUtil.contains(location.directory, absolute) + const lexicallyInternal = isLexicallyInternal(absolute) if (relative && !lexicallyInternal) return yield* new PathError({ path: input.path, reason: "relative_escape" }) const resolved = yield* resolvePath(absolute) if (lexicallyInternal && !FSUtil.contains(locationRoot, resolved.canonical)) { - return yield* new PathError({ path: input.path, reason: "location_escape" }) + // Multi-root: also allow canonical inside any workspace directory + const canonicalInternal = + FSUtil.contains(locationRoot, resolved.canonical) || + (location.directories?.some((d) => FSUtil.contains(d, resolved.canonical)) ?? false) + if (!canonicalInternal) return yield* new PathError({ path: input.path, reason: "location_escape" }) } const external = !lexicallyInternal diff --git a/packages/core/src/system-context/builtins.ts b/packages/core/src/system-context/builtins.ts index b8b50577cc..bdd66f2327 100644 --- a/packages/core/src/system-context/builtins.ts +++ b/packages/core/src/system-context/builtins.ts @@ -13,9 +13,14 @@ const builtIns = Layer.effectDiscard( Effect.gen(function* () { const location = yield* Location.Service const registry = yield* SystemContextRegistry.Service + const workspaceFolders = location.directories?.length ? location.directories : [location.directory] + const workspaceEnv = + workspaceFolders.length > 1 + ? ` Working directory: ${location.directory}\n Workspace folders:\n${workspaceFolders.map((d, i) => ` - ${d}${i === 0 ? " (primary)" : ""}`).join("\n")}` + : ` Working directory: ${location.directory}` const environment = [ "", - ` Working directory: ${location.directory}`, + workspaceEnv, ` Workspace root folder: ${location.project.directory}`, ` Is directory a git repo: ${location.vcs?.type === "git" ? "yes" : "no"}`, ` Platform: ${process.platform}`, diff --git a/packages/schema/src/location.ts b/packages/schema/src/location.ts index c01ce36372..461c620b89 100644 --- a/packages/schema/src/location.ts +++ b/packages/schema/src/location.ts @@ -8,6 +8,7 @@ import { WorkspaceID } from "./workspace-id" export interface Ref extends Schema.Schema.Type {} export const Ref = Schema.Struct({ directory: AbsolutePath, + directories: optional(Schema.Array(AbsolutePath)), workspaceID: optional(WorkspaceID), }).annotate({ identifier: "Location.Ref" }) From eea5c01112ffaaf6ee01f86ced8d024a5eee426b Mon Sep 17 00:00:00 2001 From: amico Date: Thu, 20 Aug 2026 16:30:32 +0000 Subject: [PATCH 2/6] feat(workspace): instance-context + instruction multi-root + session DB (opencode#215) - project/instance-context: add directories?: string[], containsPath checks all roots - project/instance-store: LoadInput.directories forwarding to InstanceContext - session/instruction: systemPaths runs findUp from each workspace dir (dedup), primary first - core/session/sql: SessionTable.directories JSON + migration 20260820000001 Remaining (tracked): POST /session?directories=JSON handler + extension WorkspaceTree (amicode#457 follow-up extends 215 AC6/7). Engine core now workspace-aware end-to-end. --- .../20260820000001_add_session_directories.ts | 11 +++++++++ packages/core/src/session/sql.ts | 1 + .../opencode/src/project/instance-context.ts | 2 ++ .../opencode/src/project/instance-store.ts | 3 +++ packages/opencode/src/session/instruction.ts | 24 +++++++++++++------ 5 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 packages/core/src/database/migration/20260820000001_add_session_directories.ts diff --git a/packages/core/src/database/migration/20260820000001_add_session_directories.ts b/packages/core/src/database/migration/20260820000001_add_session_directories.ts new file mode 100644 index 0000000000..0cfc28aee4 --- /dev/null +++ b/packages/core/src/database/migration/20260820000001_add_session_directories.ts @@ -0,0 +1,11 @@ +import { Effect } from "effect" +import type { DatabaseMigration } from "../migration" + +export default { + id: "20260820000001_add_session_directories", + up(tx) { + return Effect.gen(function* () { + yield* tx.run(`ALTER TABLE session ADD COLUMN directories TEXT;`) + }) + }, +} satisfies DatabaseMigration.Migration diff --git a/packages/core/src/session/sql.ts b/packages/core/src/session/sql.ts index 264a1d2cca..1fa7ee6aaa 100644 --- a/packages/core/src/session/sql.ts +++ b/packages/core/src/session/sql.ts @@ -31,6 +31,7 @@ export const SessionTable = sqliteTable( parent_id: text().$type(), slug: text().notNull(), directory: DatabasePath.directoryColumn().notNull(), + directories: text({ mode: "json" }).$type(), path: DatabasePath.pathColumn(), title: text().notNull(), version: text().notNull(), diff --git a/packages/opencode/src/project/instance-context.ts b/packages/opencode/src/project/instance-context.ts index 18ea39e16e..0535f863d6 100644 --- a/packages/opencode/src/project/instance-context.ts +++ b/packages/opencode/src/project/instance-context.ts @@ -4,6 +4,7 @@ import type * as Project from "./project" export interface InstanceContext { directory: string + directories?: string[] worktree: string project: Project.Info } @@ -17,6 +18,7 @@ export const context = LocalContext.create("instance") */ export function containsPath(filepath: string, ctx: InstanceContext): boolean { if (FSUtil.contains(ctx.directory, filepath)) return true + if (ctx.directories?.some((d) => FSUtil.contains(d, filepath))) return true // Non-git projects set worktree to "/" which would match ANY absolute path. // Skip worktree check in this case to preserve external_directory permissions. if (ctx.worktree === "/") return false diff --git a/packages/opencode/src/project/instance-store.ts b/packages/opencode/src/project/instance-store.ts index 720549ddaf..6279c7dabc 100644 --- a/packages/opencode/src/project/instance-store.ts +++ b/packages/opencode/src/project/instance-store.ts @@ -13,6 +13,7 @@ import * as Project from "./project" export interface LoadInput { directory: string + directories?: string[] worktree?: string project?: Project.Info } @@ -48,12 +49,14 @@ const layer: Layer.Layer ({ directory: input.directory, + directories: input.directories, worktree: result.sandbox, project: result.project, })), diff --git a/packages/opencode/src/session/instruction.ts b/packages/opencode/src/session/instruction.ts index 7f593550d4..0d26ab2fb2 100644 --- a/packages/opencode/src/session/instruction.ts +++ b/packages/opencode/src/session/instruction.ts @@ -119,16 +119,26 @@ const layer: Layer.Layer< } } - // The first project-level match wins so we don't stack AGENTS.md/CLAUDE.md from every ancestor. + // Multi-root: run findUp from each workspace directory, dedup by resolved path. + // Primary first, then remaining in workspace order; each walks to its own worktree root. if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) { + const allDirs: string[] = (ctx as unknown as { directories?: string[] }).directories?.length + ? (ctx as unknown as { directories: string[] }).directories + : [ctx.directory] + // Preserve order but ensure primary is first + const ordered = allDirs[0] === ctx.directory ? allDirs : [ctx.directory, ...allDirs.filter((d) => d !== ctx.directory)] for (const file of instructionFiles) { - const matches = yield* fs - .findUp(file, ctx.directory, ctx.worktree) - .pipe(Effect.catch(() => Effect.succeed([]))) - if (matches.length > 0) { - matches.forEach((item) => paths.add(path.resolve(item))) - break + let foundAny = false + for (const dir of ordered) { + const matches = yield* fs + .findUp(file, dir, ctx.worktree) + .pipe(Effect.catch(() => Effect.succeed([]))) + if (matches.length > 0) { + matches.forEach((item) => paths.add(path.resolve(item))) + foundAny = true + } } + if (foundAny) break } } From 7ca33f4775cde4e407d2f1a1a8ecaa734c94e6ea Mon Sep 17 00:00:00 2001 From: JJ Lee Date: Fri, 21 Aug 2026 10:36:33 +0200 Subject: [PATCH 3/6] fix(workspace): propagate directories to Location.Interface + layer (typecheck) --- packages/core/src/location.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/location.ts b/packages/core/src/location.ts index 8228b8599e..515e4c41b4 100644 --- a/packages/core/src/location.ts +++ b/packages/core/src/location.ts @@ -9,6 +9,7 @@ export * as Location from "./location" export { Info, Ref, response } export interface Interface extends Info { + readonly directories?: readonly string[] readonly vcs?: Project.Vcs } @@ -24,6 +25,7 @@ const layer = (ref: Ref) => const resolved = yield* project.resolve(ref.directory) return Service.of({ directory: ref.directory, + directories: ref.directories, workspaceID: ref.workspaceID, project: { id: resolved.id, directory: resolved.directory }, vcs: resolved.vcs, From 03ed50a1f483b6f24a7fa2ee115f90ec4317e642 Mon Sep 17 00:00:00 2001 From: JJ Lee Date: Fri, 21 Aug 2026 10:39:56 +0200 Subject: [PATCH 4/6] test(workspace): verify multi-directory boundary checks and env output end-to-end --- packages/core/test/fixture/location.ts | 1 + packages/core/test/location-mutation.test.ts | 48 +++++++++++++++++++ .../core/test/system-context/builtins.test.ts | 41 ++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/packages/core/test/fixture/location.ts b/packages/core/test/fixture/location.ts index 40d8ed9dc3..f93ee4ac5c 100644 --- a/packages/core/test/fixture/location.ts +++ b/packages/core/test/fixture/location.ts @@ -7,6 +7,7 @@ import { tmpdir } from "./tmpdir" export function location(ref: Location.Ref, input: { projectDirectory?: AbsolutePath; vcs?: Project.Vcs } = {}) { return { directory: ref.directory, + directories: ref.directories, workspaceID: ref.workspaceID, project: { id: Project.ID.global, directory: input.projectDirectory ?? ref.directory }, vcs: input.vcs, diff --git a/packages/core/test/location-mutation.test.ts b/packages/core/test/location-mutation.test.ts index b09be42daa..c2b53d0a9b 100644 --- a/packages/core/test/location-mutation.test.ts +++ b/packages/core/test/location-mutation.test.ts @@ -21,6 +21,25 @@ function provide(directory: string) { ) } +function provideMultiRoot(directory: string, directories: string[]) { + return Effect.provide( + LayerNode.compile(LocationMutation.node, [ + [ + Location.node, + Layer.succeed( + Location.Service, + Location.Service.of( + location({ + directory: AbsolutePath.make(directory), + directories: directories.map((d) => AbsolutePath.make(d)), + }), + ), + ), + ], + ]), + ) +} + function withTmp(f: (directory: string) => Effect.Effect) { return Effect.acquireRelease( Effect.promise(() => tmpdir()), @@ -175,4 +194,33 @@ describe("LocationMutation", () => { path: "README.md", }) }) + + it.live("accepts an absolute path inside a secondary workspace directory without external authorization", () => + withTmp((primary) => + withTmp((secondary) => + Effect.gen(function* () { + const targetPath = path.join(secondary, "lib.ts") + yield* Effect.promise(() => fs.writeFile(targetPath, "export const x = 1")) + const target = yield* (yield* LocationMutation.Service).resolve({ path: targetPath }) + expect(target.externalDirectory).toBeUndefined() + }).pipe(provideMultiRoot(primary, [primary, secondary])), + ), + ), + ) + + it.live("rejects an absolute path outside all workspace directories", () => + withTmp((primary) => + withTmp((secondary) => + withTmp((outside) => + Effect.gen(function* () { + const targetPath = path.join(outside, "rogue.txt") + yield* Effect.promise(() => fs.writeFile(targetPath, "rogue")) + const target = yield* (yield* LocationMutation.Service).resolve({ path: targetPath }) + // Should require external authorization — externalDirectory is set + expect(target.externalDirectory).toBeDefined() + }).pipe(provideMultiRoot(primary, [primary, secondary])), + ), + ), + ), + ) }) diff --git a/packages/core/test/system-context/builtins.test.ts b/packages/core/test/system-context/builtins.test.ts index 26b9175223..76d5d116a8 100644 --- a/packages/core/test/system-context/builtins.test.ts +++ b/packages/core/test/system-context/builtins.test.ts @@ -15,6 +15,7 @@ import { testEffect } from "../lib/effect" const directory = AbsolutePath.make(FSUtil.resolve("/repo/packages/core")) const projectDirectory = AbsolutePath.make(FSUtil.resolve("/repo")) +const secondaryDirectory = AbsolutePath.make(FSUtil.resolve("/repo/packages/schema")) const instructionFile = FSUtil.resolve("/repo/AGENTS.md") const timestamp = Date.parse("2026-06-03T12:00:00.000Z") const localDate = (time: number) => new Date(time).toDateString() @@ -27,6 +28,15 @@ const locationLayer = Layer.succeed( ), ), ) +const multiRootLocationLayer = Layer.succeed( + Location.Service, + Location.Service.of( + location( + { directory, directories: [directory, secondaryDirectory] }, + { projectDirectory, vcs: { type: "git", store: AbsolutePath.make(FSUtil.resolve("/repo/.git")) } }, + ), + ), +) const builtInsNode = LayerNode.group([SystemContextBuiltIns.node, SystemContextRegistry.node]) const it = testEffect( AppNodeBuilder.build(builtInsNode, [ @@ -34,6 +44,12 @@ const it = testEffect( [Global.node, Global.layerWith({ config: "/global" })], ]), ) +const itMultiRoot = testEffect( + AppNodeBuilder.build(builtInsNode, [ + [Location.node, multiRootLocationLayer], + [Global.node, Global.layerWith({ config: "/global" })], + ]), +) const instructionFS = Layer.effect( FSUtil.Service, FSUtil.Service.pipe( @@ -126,4 +142,29 @@ describe("SystemContextBuiltIns", () => { ) }), ) + + itMultiRoot.effect("lists workspace folders when multiple directories are configured", () => + Effect.gen(function* () { + yield* TestClock.setTime(timestamp) + const context = yield* SystemContextRegistry.Service + const initialized = yield* SystemContext.initialize(yield* context.load()) + + expect(initialized.baseline).toBe( + [ + "Here is some useful information about the environment you are running in:", + "", + ` Working directory: ${directory}`, + ` Workspace folders:`, + ` - ${directory} (primary)`, + ` - ${secondaryDirectory}`, + ` Workspace root folder: ${projectDirectory}`, + " Is directory a git repo: yes", + ` Platform: ${process.platform}`, + "", + "", + `Today's date: ${localDate(timestamp)}`, + ].join("\n"), + ) + }), + ) }) From 4fa2890750ed7c08ff0082cca13c8e0e28f63328 Mon Sep 17 00:00:00 2001 From: JJ Lee Date: Fri, 21 Aug 2026 11:19:37 +0200 Subject: [PATCH 5/6] fix(workspace): register session_directories migration in migration.gen.ts --- packages/core/src/database/migration.gen.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts index a73472ebe9..00dc18621d 100644 --- a/packages/core/src/database/migration.gen.ts +++ b/packages/core/src/database/migration.gen.ts @@ -41,5 +41,6 @@ export const migrations = ( import("./migration/20260622170816_reset_v2_session_state"), import("./migration/20260622202450_simplify_session_input"), import("./migration/20260813162312_shocking_karnak"), + import("./migration/20260820000001_add_session_directories"), ]) ).map((module) => module.default) satisfies DatabaseMigration.Migration[] From ad35703abc6d17b47cd92869b5fd33ad60e582a9 Mon Sep 17 00:00:00 2001 From: JJ Lee Date: Fri, 21 Aug 2026 11:27:49 +0200 Subject: [PATCH 6/6] fix(workspace): remove unsafe cast, bound secondary findUp, normalize primary marker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - instruction.ts: remove (ctx as unknown) cast — InstanceContext already has directories - instruction.ts: use directory itself as findUp boundary for secondary dirs (prevents walking past workspace root into unrelated parents) - builtins.ts: always place primary first in workspaceFolders, mark by equality not index - tests: add multi-root instruction discovery + builtins primary-marking tests --- packages/core/src/system-context/builtins.ts | 7 +++- .../core/test/system-context/builtins.test.ts | 41 ++++++++++++++++++ packages/opencode/src/session/instruction.ts | 11 +++-- packages/opencode/test/fixture/fixture.ts | 4 +- .../opencode/test/session/instruction.test.ts | 42 +++++++++++++++++++ 5 files changed, 97 insertions(+), 8 deletions(-) diff --git a/packages/core/src/system-context/builtins.ts b/packages/core/src/system-context/builtins.ts index bdd66f2327..0bf8bb04f6 100644 --- a/packages/core/src/system-context/builtins.ts +++ b/packages/core/src/system-context/builtins.ts @@ -13,10 +13,13 @@ const builtIns = Layer.effectDiscard( Effect.gen(function* () { const location = yield* Location.Service const registry = yield* SystemContextRegistry.Service - const workspaceFolders = location.directories?.length ? location.directories : [location.directory] + const workspaceFolders = [ + location.directory, + ...(location.directories ?? []).filter((d) => d !== location.directory), + ] const workspaceEnv = workspaceFolders.length > 1 - ? ` Working directory: ${location.directory}\n Workspace folders:\n${workspaceFolders.map((d, i) => ` - ${d}${i === 0 ? " (primary)" : ""}`).join("\n")}` + ? ` Working directory: ${location.directory}\n Workspace folders:\n${workspaceFolders.map((d) => ` - ${d}${d === location.directory ? " (primary)" : ""}`).join("\n")}` : ` Working directory: ${location.directory}` const environment = [ "", diff --git a/packages/core/test/system-context/builtins.test.ts b/packages/core/test/system-context/builtins.test.ts index 76d5d116a8..3fd7be8975 100644 --- a/packages/core/test/system-context/builtins.test.ts +++ b/packages/core/test/system-context/builtins.test.ts @@ -37,6 +37,16 @@ const multiRootLocationLayer = Layer.succeed( ), ), ) +// directories array with primary NOT at index 0 +const multiRootReversedLocationLayer = Layer.succeed( + Location.Service, + Location.Service.of( + location( + { directory, directories: [secondaryDirectory, directory] }, + { projectDirectory, vcs: { type: "git", store: AbsolutePath.make(FSUtil.resolve("/repo/.git")) } }, + ), + ), +) const builtInsNode = LayerNode.group([SystemContextBuiltIns.node, SystemContextRegistry.node]) const it = testEffect( AppNodeBuilder.build(builtInsNode, [ @@ -50,6 +60,12 @@ const itMultiRoot = testEffect( [Global.node, Global.layerWith({ config: "/global" })], ]), ) +const itMultiRootReversed = testEffect( + AppNodeBuilder.build(builtInsNode, [ + [Location.node, multiRootReversedLocationLayer], + [Global.node, Global.layerWith({ config: "/global" })], + ]), +) const instructionFS = Layer.effect( FSUtil.Service, FSUtil.Service.pipe( @@ -167,4 +183,29 @@ describe("SystemContextBuiltIns", () => { ) }), ) + + itMultiRootReversed.effect("marks the correct folder as primary even when directories[0] is not the primary", () => + Effect.gen(function* () { + yield* TestClock.setTime(timestamp) + const context = yield* SystemContextRegistry.Service + const initialized = yield* SystemContext.initialize(yield* context.load()) + + expect(initialized.baseline).toBe( + [ + "Here is some useful information about the environment you are running in:", + "", + ` Working directory: ${directory}`, + ` Workspace folders:`, + ` - ${directory} (primary)`, + ` - ${secondaryDirectory}`, + ` Workspace root folder: ${projectDirectory}`, + " Is directory a git repo: yes", + ` Platform: ${process.platform}`, + "", + "", + `Today's date: ${localDate(timestamp)}`, + ].join("\n"), + ) + }), + ) }) diff --git a/packages/opencode/src/session/instruction.ts b/packages/opencode/src/session/instruction.ts index 0d26ab2fb2..feaf46ac72 100644 --- a/packages/opencode/src/session/instruction.ts +++ b/packages/opencode/src/session/instruction.ts @@ -120,18 +120,21 @@ const layer: Layer.Layer< } // Multi-root: run findUp from each workspace directory, dedup by resolved path. - // Primary first, then remaining in workspace order; each walks to its own worktree root. + // Primary first, then remaining in workspace order; each walks to its own boundary. if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) { - const allDirs: string[] = (ctx as unknown as { directories?: string[] }).directories?.length - ? (ctx as unknown as { directories: string[] }).directories + const allDirs: string[] = ctx.directories?.length + ? ctx.directories : [ctx.directory] // Preserve order but ensure primary is first const ordered = allDirs[0] === ctx.directory ? allDirs : [ctx.directory, ...allDirs.filter((d) => d !== ctx.directory)] for (const file of instructionFiles) { let foundAny = false for (const dir of ordered) { + // Primary uses worktree as boundary; secondary directories use themselves + // as boundary (don't walk past the workspace root into unrelated parents). + const stop = dir === ctx.directory ? ctx.worktree : dir const matches = yield* fs - .findUp(file, dir, ctx.worktree) + .findUp(file, dir, stop) .pipe(Effect.catch(() => Effect.succeed([]))) if (matches.length > 0) { matches.forEach((item) => paths.add(path.resolve(item))) diff --git a/packages/opencode/test/fixture/fixture.ts b/packages/opencode/test/fixture/fixture.ts index 53e3142cdf..f8456eb580 100644 --- a/packages/opencode/test/fixture/fixture.ts +++ b/packages/opencode/test/fixture/fixture.ts @@ -166,9 +166,9 @@ export function tmpdirScoped(options?: { } export const provideInstance = - (directory: string) => + (directory: string, directories?: string[]) => (self: Effect.Effect): Effect.Effect => - InstanceStore.Service.use((store) => store.provide({ directory }, self)) + InstanceStore.Service.use((store) => store.provide({ directory, directories }, self)) export const provideInstanceEffect = (directory: string) => diff --git a/packages/opencode/test/session/instruction.test.ts b/packages/opencode/test/session/instruction.test.ts index cdbc6e66d7..48c1a11576 100644 --- a/packages/opencode/test/session/instruction.test.ts +++ b/packages/opencode/test/session/instruction.test.ts @@ -262,3 +262,45 @@ describe("Instruction.systemPaths global config", () => { }), ) }) + +describe("Instruction.systemPaths multi-root", () => { + it.live("discovers AGENTS.md in a secondary workspace directory", () => + Effect.gen(function* () { + const primary = yield* tmpWithFiles({}) + const secondary = yield* tmpWithFiles({ "AGENTS.md": "# Secondary Instructions" }) + + yield* Effect.gen(function* () { + const svc = yield* Instruction.Service + const paths = yield* svc.systemPaths() + expect(paths.has(path.join(secondary, "AGENTS.md"))).toBe(true) + }).pipe( + provideInstance(primary, [primary, secondary]), + provideInstruction({ home: primary, config: primary }), + ) + }), + ) + + it.live("does not walk past a secondary directory into unrelated parent directories", () => + Effect.gen(function* () { + // Create two independent git-initialized project directories. + // Place an AGENTS.md above the secondary's git root — it should NOT be discovered + // because the secondary's findUp stops at the secondary root itself. + const primary = yield* tmpdirScoped({ git: true }) + const secondary = yield* tmpdirScoped({ git: true }) + const fs = yield* FileSystem.FileSystem + // Put AGENTS.md above the secondary's git root + const aboveSecondary = path.dirname(secondary) + yield* fs.writeFileString(path.join(aboveSecondary, "AGENTS.md"), "# Should NOT be found") + + yield* Effect.gen(function* () { + const svc = yield* Instruction.Service + const paths = yield* svc.systemPaths() + // The AGENTS.md above the secondary's git root should NOT be found + expect(paths.has(path.join(aboveSecondary, "AGENTS.md"))).toBe(false) + }).pipe( + provideInstance(primary, [primary, secondary]), + provideInstruction({ home: "/nonexistent-global", config: "/nonexistent-global" }), + ) + }), + ) +})