init
This commit is contained in:
commit
0964819cdc
34 files changed
+7912
No files matched your search
+215
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* Focus resolution: which slice of the workspace an agent is confined to.
|
||||
*
|
||||
* ## Why this exists
|
||||
*
|
||||
* A workspace-wide index has to rank across every part of a repository, and in
|
||||
* a codebase built from many near-identical modules that is exactly where
|
||||
* embedding similarity fails. In the repository this was written against there
|
||||
* are eighteen controllers — each a state machine with the same idioms, the same
|
||||
* naming and the same shape — spread across 228k lines. A query about one of
|
||||
* them returns plausible fragments of all eighteen.
|
||||
*
|
||||
* Narrowing the index to the one being worked on removes the confusability
|
||||
* rather than trying to rank through it, and it does three things at once:
|
||||
*
|
||||
* 1. **precision** — there are no near-duplicates to outrank the right answer;
|
||||
* 2. **cold start** — ten files embed in seconds where six hundred took minutes;
|
||||
* 3. **context** — code the agent never sees is code that is never re-sent on
|
||||
* every later step, which is where the money actually is.
|
||||
*
|
||||
* ## Resolution order
|
||||
*
|
||||
* Most explicit wins, and the last fallback is the workspace root — which is the
|
||||
* behaviour that existed before focus did, so adopting this cannot regress a
|
||||
* deployment that never configures it.
|
||||
*
|
||||
* The marker step is the interesting one: a folder that contains its own
|
||||
* instructions *is* a scope. A controller directory holding a `CONTROLLER.md`
|
||||
* becomes the world the agent sees, and the document that describes the module
|
||||
* also defines its boundary. Nothing needed to be invented for that — the
|
||||
* convention already means "this folder is self-describing".
|
||||
*/
|
||||
|
||||
import { existsSync, statSync } from 'node:fs'
|
||||
import { readdir } from 'node:fs/promises'
|
||||
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'
|
||||
|
||||
/**
|
||||
* Files whose presence marks a directory as a scope root.
|
||||
*
|
||||
* Ordered by how specific they are: a `CONTROLLER.md` is a stronger claim about
|
||||
* a directory than the `AGENTS.md` that sits at every repository root.
|
||||
*/
|
||||
export const DEFAULT_FOCUS_MARKERS = Object.freeze(['CONTROLLER.md', 'DEBUG_UI.md', 'AGENTS.md'])
|
||||
|
||||
/** True when `path` is `root` itself or sits underneath it. */
|
||||
export function isInside(root, path) {
|
||||
const prefix = root.endsWith(sep) ? root : root + sep
|
||||
return path === root || path.startsWith(prefix)
|
||||
}
|
||||
|
||||
/** True when `path` exists and is a directory. */
|
||||
function statIsDirectory(path) {
|
||||
try {
|
||||
return statSync(path).isDirectory()
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/** Every ancestor of `from` up to and including `stop`, nearest first. */
|
||||
export function ancestorsTo(from, stop) {
|
||||
const out = []
|
||||
let current = resolve(from)
|
||||
const limit = resolve(stop)
|
||||
for (;;) {
|
||||
out.push(current)
|
||||
if (current === limit) break
|
||||
const parent = dirname(current)
|
||||
// A path that is not under `stop`, or a filesystem root, ends the walk
|
||||
// rather than looping.
|
||||
if (parent === current || !isInside(limit, parent)) break
|
||||
current = parent
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the focus directory for one session.
|
||||
*
|
||||
* @param {object} input
|
||||
* @param {string} input.workspace absolute workspace root
|
||||
* @param {string} [input.cwd] directory the session is working from
|
||||
* @param {string} [input.explicit] a configured or caller-supplied focus
|
||||
* @param {(path: string) => boolean} [input.isDirectory] injection point for tests
|
||||
* @param {readonly string[]} [input.markers] scope-marker filenames
|
||||
* @param {(path: string) => boolean} [input.exists] injection point for tests
|
||||
* @param {{path: string, marker: string}[]} [input.scopes] scopes found by {@link discoverScopes}
|
||||
* @returns {{root: string, source: 'explicit'|'scope'|'marker'|'workspace', marker?: string, problem?: string}}
|
||||
*/
|
||||
export function resolveFocus(input) {
|
||||
const {
|
||||
workspace,
|
||||
cwd = workspace,
|
||||
explicit = '',
|
||||
markers = DEFAULT_FOCUS_MARKERS,
|
||||
scopes = [],
|
||||
exists = existsSync,
|
||||
isDirectory = statIsDirectory,
|
||||
} = input
|
||||
|
||||
// 1. An explicit focus wins, but a bad one is reported rather than silently
|
||||
// ignored: a typo that quietly fell back to the whole workspace would look
|
||||
// like the feature doing nothing.
|
||||
const requested = String(explicit ?? '').trim()
|
||||
if (requested !== '') {
|
||||
const absolute = isAbsolute(requested) ? resolve(requested) : resolve(workspace, requested)
|
||||
if (!isInside(workspace, absolute)) {
|
||||
return {
|
||||
root: resolve(workspace),
|
||||
source: 'workspace',
|
||||
problem: `focus "${requested}" is outside the workspace`,
|
||||
}
|
||||
}
|
||||
if (!isDirectory(absolute)) {
|
||||
return {
|
||||
root: resolve(workspace),
|
||||
source: 'workspace',
|
||||
problem: `focus "${requested}" is not a directory`,
|
||||
}
|
||||
}
|
||||
return { root: absolute, source: 'explicit' }
|
||||
}
|
||||
|
||||
// 2. Nothing here adopts a discovered scope automatically, and that is
|
||||
// deliberate. A session sitting at the repository root is not thereby
|
||||
// *about* any one controller, and silently confining it to whichever scope
|
||||
// sorted first would hide code the session legitimately needs. Discovery
|
||||
// exists so a caller can be shown the scopes and name one; choosing is an
|
||||
// explicit act, which is what `focus` is for.
|
||||
|
||||
// 3. The nearest ancestor carrying a marker file. `cwd` is normally the
|
||||
// workspace root, at which point the root's own AGENTS.md matches and the
|
||||
// result is the workspace — the same answer the old code gave.
|
||||
const start = isInside(workspace, resolve(cwd)) ? resolve(cwd) : resolve(workspace)
|
||||
for (const directory of ancestorsTo(start, workspace)) {
|
||||
for (const marker of markers) {
|
||||
if (marker !== '' && exists(join(directory, marker))) {
|
||||
return { root: directory, source: 'marker', marker }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. The workspace.
|
||||
return { root: resolve(workspace), source: 'workspace' }
|
||||
}
|
||||
|
||||
/** A stable key for one focus directory, for index filenames. */
|
||||
export function focusKey(workspace, root) {
|
||||
const rel = relative(resolve(workspace), resolve(root)).split(sep).join('/')
|
||||
return rel === '' ? '.' : rel
|
||||
}
|
||||
|
||||
/**
|
||||
* Directories skipped while looking for scopes.
|
||||
*
|
||||
* A marker inside a dependency or a build directory describes somebody else's
|
||||
* project, so discovering it would offer the user a scope they do not own.
|
||||
*/
|
||||
const DISCOVERY_SKIP_DIRS = new Set([
|
||||
'node_modules', '.git', 'dist', 'build', 'out', 'coverage', 'target',
|
||||
'.next', '.nuxt', '.venv', 'venv', '__pycache__', 'vendor', '.cache',
|
||||
])
|
||||
|
||||
/**
|
||||
* Find every directory that declares itself a scope.
|
||||
*
|
||||
* This exists because walking up from the working directory cannot find a
|
||||
* marker in a subdirectory, and in the repository this was written for that is
|
||||
* the whole point: the session sits at the workspace root while the eighteen
|
||||
* controllers each carry their own instructions further down. Inheritance and
|
||||
* discovery are different questions — "what contains me?" versus "what is
|
||||
* available?" — and a scope needs both.
|
||||
*
|
||||
* Results are workspace-relative and sorted, so a list shown to a user or a
|
||||
* model is stable between calls.
|
||||
*
|
||||
* @param {object} input
|
||||
* @param {string} input.workspace absolute workspace root
|
||||
* @param {readonly string[]} [input.markers] marker filenames
|
||||
* @param {number} [input.maxDepth] deepest directory level to scan
|
||||
* @returns {Promise<{path: string, marker: string}[]>} discovered scopes
|
||||
*/
|
||||
export async function discoverScopes(input) {
|
||||
const { workspace, markers = DEFAULT_FOCUS_MARKERS, maxDepth = 8 } = input
|
||||
const root = resolve(workspace)
|
||||
const found = []
|
||||
|
||||
const walk = async (directory, depth) => {
|
||||
if (depth > maxDepth) return
|
||||
let entries
|
||||
try {
|
||||
entries = await readdir(directory, { withFileTypes: true })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
const names = new Set(entries.filter((entry) => entry.isFile()).map((entry) => entry.name))
|
||||
for (const marker of markers) {
|
||||
if (names.has(marker)) {
|
||||
const rel = relative(root, directory).split(sep).join('/')
|
||||
found.push({ path: rel === '' ? '.' : rel, marker })
|
||||
break
|
||||
}
|
||||
}
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory() || entry.isSymbolicLink()) continue
|
||||
if (DISCOVERY_SKIP_DIRS.has(entry.name)) continue
|
||||
await walk(join(directory, entry.name), depth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
await walk(root, 0)
|
||||
found.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0))
|
||||
return found
|
||||
}
|
||||
Reference in new issue
Block a user