Anchorfile
A command-line tool that reads a repository and writes the context file an
AI agent needs.
npm package · Source
The problem
An AI coding agent starts every session knowing nothing about the
repository it is in. Claude Code reads a CLAUDE.md file at
the root for that: the stack, the scripts, the layout, the conventions.
Written by hand, that file is accurate on the day it is written and then
goes stale, because nothing connects it to the files it describes. A
dependency changes, a script is renamed, and the agent goes on acting on
the old description.
Why the obvious approach was not used
The obvious tool for writing a description of a codebase now is a language model: point it at the repository and ask for the file. That replaces one unchecked text with another. A model's summary can be wrong in ways that read as right, and a context file is the one document every later session trusts without looking. A wrong line there is repeated session after session.
So Anchorfile reads only what is on disk: package.json,
lockfiles, config files, requirements.txt,
pyproject.toml and the file tree. Every line it writes comes from a file a reader can open, and the
same repository always produces the same output. The judgement calls a
file tree cannot answer, what to avoid and which files matter most, are
left as marked sections for a person to fill in.
One trade-off
Detection is deliberately literal. Frameworks are matched by exact
package name, a tsconfig.json makes a project TypeScript
without counting anything, and the walk that counts file extensions stops
at a fixed depth and file count so a very large repository stays fast.
The cost is that Anchorfile misses things a model would have guessed. The
reason to accept it is that a miss is visible: the file says "None
detected" and a person can correct it, while a confident wrong guess looks
exactly like a right one.
What should change
The package was on npm before it had a single test. The suite came later: fixture repositories for each kind of project the README says it reads, the documented limits pinned so they change only on purpose, a snapshot of the emitted file, and the built CLI run end to end. Writing it found one defect straight away: top-level folders were listed in whatever order the filesystem returned them, so the same repository could produce two different files on two machines. For a tool whose whole claim is that its output follows from the files, that is the wrong defect to have shipped.
The suite then made it safe to remove three of the limits it had pinned.
Dependencies are now read from pyproject.toml as well
as requirements.txt, ESLint flat config is recognised, and
the language is counted across the tree instead of one folder. Turning
those tests around found a second defect: .ts
and .tsx files were counted as two languages, so a project
split between them could lose to fewer JavaScript files.
What remains: the pyproject.toml reader is a small one and
skips Poetry dependency groups, a Poetry project is still reported as
using pip, and Python dependencies are not yet listed in the generated
file's dependency section.
The rest of this page is fetched from the Anchorfile README at build time and rendered here, so it is maintained in the repository and not in this site. It covers the architecture, the tests and, under "Known limits", what does not work.
Architecture
654 lines of TypeScript in five files, with detection and emission kept apart:
src/index.ts(46 lines): the CLI, built on commander. Definesinit, resolves--output, calls the scanner and generator, writes the file.src/scanner.ts(343 lines): all detection. Every filesystem read is wrapped, so a missing or malformed file yields a default rather than an exception. Returns oneRepoContext.src/pyproject.ts(137 lines): reads dependency names from the threepyproject.tomltables Anchorfile uses. It is a small line-based reader rather than a TOML library: three tables did not justify a new runtime dependency, and a reader this size is tested directly against the TOML shapes it accepts and the ones it skips.src/generator.ts(111 lines): turns aRepoContextinto markdown by pushing lines into an array. No filesystem access, so it is tested without a fixture repository.src/types.ts(17 lines): theRepoContextinterface, the only contract between the two halves.
The split is what makes the tool checkable. The scanner is a function of a directory, so each kind of project it claims to understand is a small fixture directory in test/fixtures/, and its report is compared field by field with what the README says it detects. The generator is a function of one object, so its whole output is pinned by a snapshot.
Tests
npm test builds dist/ and runs 58 Vitest tests in 4 files:
test/scanner.test.ts(43 tests) runs the scanner against seven fixture repositories (a TypeScript app with npm, Vitest, ESLint, Docker and CI; a JavaScript app with Yarn and a flat ESLint config; a JavaScript app with a legacy.eslintrc.jsonand pnpm; a Python app withrequirements.txt,pyproject.tomlandtests/; a PEP 621 project with onlypyproject.toml; a Poetry project; a repository whose code lives inlib/whilesrc/holds two build scripts) and an empty directory. Directories generated at test time cover each flat config name and both tree walk caps. Limits that remain are tested too, so they change only on purpose.test/pyproject.test.ts(10 tests) feeds thepyproject.tomlreader strings: arrays across lines, comments, both quote styles, inline tables, quoted keys and headers, tables it must ignore, and text it cannot follow.test/generator.test.ts(3 tests) snapshots the full emitted file and checks the wording used when nothing was found.test/cli.test.ts(2 tests) runs the built CLI:--versionmust equalpackage.json(0.1.2 shipped to fix exactly that drift), andinitrun inside a fixture must write the file.
npm run typecheck checks the source and the tests under strict mode. .github/workflows/ci.yml runs typecheck, test and npm pack --dry-run on every push and pull request to main. There is no linter.
Writing the fixture tests turned up one defect: top-level folders were listed in the order the filesystem returned them, which differs between machines, so the same repository could produce two different files. They are now sorted. Rewriting language detection turned up a second: .ts and .tsx files were counted as separate extensions, so a project split between them could lose to fewer .js files. They now count as one language.
Known limits
- Detection is deliberately shallow. Frameworks are matched by exact package name, so anything outside the list above goes unreported. A
tsconfig.jsonmakes the project TypeScript without counting, and in a repository larger than the walk caps the count covers only the shallower part of the tree. pyproject.tomlis read by a small reader, not a full TOML parser. It skips Poetry dependency groups ([tool.poetry.group.<name>.dependencies]), dotted keys such as a top-levelproject.dependencies = [...], and multi-line strings.- Python dependencies feed framework and test detection but are not listed under Dependencies in the output, which comes from
package.jsononly, and a Poetry project is reported as using pip. - The output is a starting point rather than a finished context file: the sections it cannot infer are left marked for a human.