import { strict as assert } from 'node:assert' import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { test } from 'node:test' import { DEFAULT_FOCUS_MARKERS, ancestorsTo, discoverScopes, focusKey, isInside, resolveFocus, } from '../lib/focus.js' const WORKSPACE = '/repo' /** * The two filesystem injection points, faked. * * `exists` and `isDirectory` are separate because the real code distinguishes * them: a focus path must not merely exist, it must be a directory. A single * predicate would let a test pass a file where a directory is required. * * @param {string[]} files paths that exist * @param {string[]} [directories] paths that are directories (defaults to `files`) */ function fakeFs(files, directories = files) { const fileSet = new Set(files) const dirSet = new Set(directories) return { exists: (path) => fileSet.has(path), isDirectory: (path) => dirSet.has(path) } } test('isInside treats a directory as inside itself and rejects siblings', () => { assert.equal(isInside('/repo', '/repo'), true) assert.equal(isInside('/repo', '/repo/src'), true) assert.equal(isInside('/repo', '/repository'), false, 'a shared prefix is not a child') assert.equal(isInside('/repo', '/other'), false) }) test('ancestorsTo walks up to the stop directory and no further', () => { assert.deepEqual(ancestorsTo('/repo/src/a/b', '/repo'), ['/repo/src/a/b', '/repo/src/a', '/repo/src', '/repo']) assert.deepEqual(ancestorsTo('/repo', '/repo'), ['/repo']) // A start outside the stop does not run away to the filesystem root. assert.deepEqual(ancestorsTo('/elsewhere/x', '/repo'), ['/elsewhere/x']) }) test('no markers anywhere means the workspace, which is the old behaviour', () => { const result = resolveFocus({ workspace: WORKSPACE, cwd: WORKSPACE, ...fakeFs([]) }) assert.equal(result.root, '/repo') assert.equal(result.source, 'workspace') }) test('a marker at the workspace root keeps the scope at the workspace', () => { // This is the live case today: AGENTS.md sits at the repository root, so // adopting focus changes nothing until a nested folder carries a marker. const result = resolveFocus({ workspace: WORKSPACE, cwd: WORKSPACE, ...fakeFs(['/repo/AGENTS.md']) }) assert.equal(result.root, '/repo') assert.equal(result.source, 'marker') assert.equal(result.marker, 'AGENTS.md') }) test('a nested controller marker narrows the scope to that controller', () => { const controller = '/repo/src/controllers/room/occupancy' const result = resolveFocus({ workspace: WORKSPACE, cwd: controller, ...fakeFs(['/repo/AGENTS.md', `${controller}/CONTROLLER.md`]), }) assert.equal(result.root, controller) assert.equal(result.source, 'marker') assert.equal(result.marker, 'CONTROLLER.md') }) test('the nearest marker wins over a further one', () => { const result = resolveFocus({ workspace: WORKSPACE, cwd: '/repo/src/controllers/room/occupancy/state_machine', ...fakeFs(['/repo/AGENTS.md', '/repo/src/controllers/room/occupancy/CONTROLLER.md']), }) assert.equal(result.root, '/repo/src/controllers/room/occupancy') }) test('CONTROLLER.md outranks AGENTS.md in the same directory', () => { const result = resolveFocus({ workspace: WORKSPACE, cwd: WORKSPACE, ...fakeFs(['/repo/AGENTS.md', '/repo/CONTROLLER.md']), }) assert.equal(result.marker, 'CONTROLLER.md', 'the more specific marker should be reported') }) test('an explicit focus wins over any marker', () => { const lights = '/repo/src/controllers/room/device/lights' const result = resolveFocus({ workspace: WORKSPACE, cwd: WORKSPACE, explicit: 'src/controllers/room/device/lights', ...fakeFs(['/repo/AGENTS.md', lights]), }) assert.equal(result.root, lights) assert.equal(result.source, 'explicit') }) test('an explicit focus may be absolute', () => { const result = resolveFocus({ workspace: WORKSPACE, explicit: '/repo/src/x', ...fakeFs(['/repo/src/x']) }) assert.equal(result.root, '/repo/src/x') assert.equal(result.source, 'explicit') }) test('an explicit focus that is a file, not a directory, is refused', () => { // The two predicates must genuinely differ, or this passes by accident. const result = resolveFocus({ workspace: WORKSPACE, explicit: 'src/AGENTS.md', ...fakeFs(['/repo/src/AGENTS.md'], ['/repo', '/repo/src']), }) assert.equal(result.root, '/repo') assert.match(result.problem, /not a directory/) }) test('a focus outside the workspace is refused and reported, not obeyed', () => { const result = resolveFocus({ workspace: WORKSPACE, explicit: '../elsewhere', ...fakeFs(['/elsewhere'], ['/elsewhere']), }) assert.equal(result.root, '/repo', 'must fall back to the workspace') assert.match(result.problem, /outside the workspace/) }) test('a focus that does not exist is refused and reported', () => { const result = resolveFocus({ workspace: WORKSPACE, explicit: 'src/typo', ...fakeFs([]) }) assert.equal(result.root, '/repo') assert.match(result.problem, /not a directory/) }) test('an empty marker list still resolves, to the workspace', () => { const result = resolveFocus({ workspace: WORKSPACE, cwd: WORKSPACE, markers: [], ...fakeFs(['/repo/AGENTS.md']), }) assert.equal(result.root, '/repo') assert.equal(result.source, 'workspace') }) test('a cwd outside the workspace cannot escape it', () => { const result = resolveFocus({ workspace: WORKSPACE, cwd: '/somewhere/else', ...fakeFs(['/somewhere/else/AGENTS.md', '/repo/AGENTS.md']), }) assert.equal(result.root, '/repo', 'a stray cwd must not pull the scope out of the workspace') }) test('focusKey is a stable relative path, with "." for the workspace itself', () => { assert.equal(focusKey('/repo', '/repo'), '.') assert.equal(focusKey('/repo', '/repo/src/controllers/room/occupancy'), 'src/controllers/room/occupancy') }) test('the default markers include the controller instruction files', () => { assert.ok(DEFAULT_FOCUS_MARKERS.includes('CONTROLLER.md')) assert.ok(DEFAULT_FOCUS_MARKERS.includes('DEBUG_UI.md')) // CONTROLLER.md must be tried before AGENTS.md, since both may be present. assert.ok(DEFAULT_FOCUS_MARKERS.indexOf('CONTROLLER.md') < DEFAULT_FOCUS_MARKERS.indexOf('AGENTS.md')) }) test('discoverScopes finds marker directories, sorted and workspace-relative', async () => { const root = await mkdtemp(join(tmpdir(), 'recall-scopes-')) try { await mkdir(join(root, 'src', 'weather'), { recursive: true }) await mkdir(join(root, 'src', 'auth'), { recursive: true }) await mkdir(join(root, 'node_modules', 'dep'), { recursive: true }) await writeFile(join(root, 'src', 'auth', 'CONTROLLER.md'), '# auth\n') await writeFile(join(root, 'src', 'weather', 'CONTROLLER.md'), '# weather\n') await writeFile(join(root, 'AGENTS.md'), '# repo\n') // A marker inside a dependency describes somebody else's project. await writeFile(join(root, 'node_modules', 'dep', 'CONTROLLER.md'), '# not ours\n') await writeFile(join(root, 'src', 'code.ts'), 'export const x = 1\n') const scopes = await discoverScopes({ workspace: root }) assert.deepEqual( scopes.map((scope) => scope.path), ['.', 'src/auth', 'src/weather'], 'sorted, relative, and node_modules excluded', ) assert.equal(scopes[0].marker, 'AGENTS.md') assert.equal(scopes[1].marker, 'CONTROLLER.md') } finally { await rm(root, { recursive: true, force: true }) } }) test('discoverScopes honours a custom marker list and maxDepth', async () => { const root = await mkdtemp(join(tmpdir(), 'recall-scopes-')) try { await mkdir(join(root, 'deep', 'deeper', 'deepest'), { recursive: true }) await writeFile(join(root, 'deep', 'SCOPE.md'), '# scope\n') await writeFile(join(root, 'deep', 'deeper', 'deepest', 'SCOPE.md'), '# scope\n') const shallow = await discoverScopes({ workspace: root, markers: ['SCOPE.md'], maxDepth: 1 }) assert.deepEqual(shallow.map((scope) => scope.path), ['deep']) const deep = await discoverScopes({ workspace: root, markers: ['SCOPE.md'], maxDepth: 4 }) assert.deepEqual(deep.map((scope) => scope.path), ['deep', 'deep/deeper/deepest']) } finally { await rm(root, { recursive: true, force: true }) } }) test('discoverScopes returns nothing for a directory with no markers', async () => { const root = await mkdtemp(join(tmpdir(), 'recall-scopes-')) try { await mkdir(join(root, 'src'), { recursive: true }) await writeFile(join(root, 'src', 'a.ts'), 'a\n') assert.deepEqual(await discoverScopes({ workspace: root }), []) } finally { await rm(root, { recursive: true, force: true }) } })