agent-doc-system 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +402 -0
  2. package/README.md +38 -0
  3. package/adapters/ansible.mjs +1 -1
  4. package/adapters/cache.mjs +9 -2
  5. package/adapters/contract-consumers.mjs +1 -1
  6. package/adapters/database.mjs +19 -4
  7. package/adapters/deploy-config.mjs +3 -2
  8. package/adapters/dockerfile.mjs +3 -2
  9. package/adapters/events.mjs +1 -1
  10. package/adapters/generic.mjs +2 -1
  11. package/adapters/github-actions.mjs +245 -85
  12. package/adapters/go.mjs +5 -4
  13. package/adapters/index.mjs +3 -1
  14. package/adapters/node.mjs +3 -2
  15. package/adapters/object-store.mjs +2 -2
  16. package/adapters/python.mjs +1142 -0
  17. package/adapters/resource-client.mjs +1 -1
  18. package/adapters/rust.mjs +990 -0
  19. package/adapters/turborepo.mjs +3 -2
  20. package/adapters/typescript.mjs +7 -5
  21. package/adapters/wrangler.mjs +3 -2
  22. package/bin/agentdoc.mjs +11 -4
  23. package/bin/usage.txt +2 -0
  24. package/core/audit.mjs +12 -8
  25. package/core/codes.mjs +4 -0
  26. package/core/compile.mjs +131 -16
  27. package/core/dependents.mjs +42 -0
  28. package/core/derive.mjs +4 -3
  29. package/core/descriptors.mjs +3 -2
  30. package/core/discover.mjs +61 -9
  31. package/core/fsx.mjs +185 -36
  32. package/core/graph.mjs +1 -1
  33. package/core/impact.mjs +66 -48
  34. package/core/paths.mjs +71 -0
  35. package/core/query.mjs +76 -31
  36. package/core/scaffold.mjs +216 -44
  37. package/core/sourcescan.mjs +14 -2
  38. package/core/toml.mjs +463 -140
  39. package/docs/ADAPTERS.md +196 -6
  40. package/docs/BENCHMARK.md +130 -0
  41. package/docs/CI.md +7 -1
  42. package/docs/EVALS.md +239 -16
  43. package/docs/MIGRATION.md +26 -6
  44. package/docs/PROVENANCE.md +6 -0
  45. package/docs/QUERY.md +78 -7
  46. package/docs/SPEC.md +69 -2
  47. package/docs/ci/github-actions.yml +1 -1
  48. package/evals/baseline-search.mjs +272 -0
  49. package/evals/facts.mjs +28 -0
  50. package/evals/harness.mjs +149 -119
  51. package/evals/scenarios/fixture-b-03-change-shared-lib.json +8 -1
  52. package/evals/scenarios/fixture-c-03-shared-vocabulary.json +7 -1
  53. package/evals/scenarios/fixture-e-01-change-shared-lib.json +14 -0
  54. package/evals/scenarios/fixture-e-02-change-manifest.json +9 -0
  55. package/evals/scenarios/fixture-e-03-local-module.json +9 -0
  56. package/evals/scenarios/fixture-e-04-ambiguous-crate.json +14 -0
  57. package/evals/scenarios/fixture-f-01-change-shared-lib.json +9 -0
  58. package/evals/scenarios/fixture-f-02-change-manifest.json +9 -0
  59. package/evals/scenarios/fixture-f-03-shadowed-import.json +9 -0
  60. package/evals/scenarios/fixture-f-04-nested-workspace.json +9 -0
  61. package/evals/scenarios/fixture-f-05-alembic-database.json +9 -0
  62. package/evals/scenarios/fixture-f-06-unittest-job.json +9 -0
  63. package/package.json +5 -2
  64. package/skills/agent-doc-system/SKILL.md +2 -1
  65. package/skills/agent-doc-system/references/cli.md +6 -1
  66. package/skills/agent-doc-system/references/create-migrate.md +21 -5
  67. package/templates/ci.yml +1 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,402 @@
1
+ # Changelog
2
+
3
+ All notable changes to AgentDoc are documented here.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
6
+ the project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The
7
+ compiler version is the package version: a graph compiled by one release is
8
+ refused by the next with `AGENTDOC_COMPILER_MISMATCH` until it is recompiled.
9
+
10
+ ## [Unreleased]
11
+
12
+ ## [1.3.0] - 2026-11-07
13
+
14
+ A graph compiled on your machine now passes `check` in CI and in a clean
15
+ checkout. Files git ignores no longer count as compiler inputs, so a local
16
+ build that fills an ignored directory no longer changes `inputHash`. A
17
+ component whose descriptor sits at the repository root now owns the root files
18
+ no other component owns, so `query` and `impact` route them. `init` and
19
+ `scaffold --write` no longer write into your product's `docs/`: they follow the
20
+ configured `docs.*` paths, and a new install without a configuration gets its
21
+ skeleton under `agentdoc/docs/`. Releases are now published from CI with npm
22
+ provenance, so `npm audit signatures` can verify that a release was built from
23
+ this repository. The benchmark claim made in the 1.2.0 release notes is
24
+ withdrawn, because its gap came from the evaluation and not from routing.
25
+
26
+ Before you upgrade:
27
+
28
+ - **Recompile committed graphs.** As with every release, a graph compiled by
29
+ 1.2.0 is refused with `AGENTDOC_COMPILER_MISMATCH` until you run
30
+ `agentdoc compile` again.
31
+ - **A descriptor or configuration path that points at a git-ignored file now
32
+ fails** with `AGENTDOC_PATH_UNRESOLVED`, as it would in a clean checkout.
33
+ Commit the file or stop ignoring it ([#45]).
34
+ - **New installs write the documentation skeleton to `agentdoc/docs/`**
35
+ instead of `docs/`. A configuration that declares `docs.*` keeps its paths,
36
+ and no existing file is moved ([#59]).
37
+
38
+ ### Added
39
+
40
+ - A deterministic `baseline-search` routing evaluation using working-tree file
41
+ reads and text search, without compiled relations ([#46]). CLI reports compare
42
+ catalog, corrected targeted and search recall/noise/check selectivity per
43
+ scenario, corpus and aggregate; `docs/EVALS.md` documents every heuristic.
44
+
45
+ ### Changed
46
+
47
+ - Releases are now published only through a manual GitHub Actions workflow,
48
+ using npm trusted publishing (no token) with provenance, so `npm audit
49
+ signatures` can verify that a release was built from this repository
50
+ ([#51]). `RELEASING.md` in the repository describes the process.
51
+
52
+ - **Behaviour change for new installs only:** without a configuration,
53
+ `agentdoc init` (and `scaffold --write`) now writes the documentation
54
+ skeleton to `agentdoc/docs/PRODUCT.md`, `agentdoc/docs/ARCHITECTURE.md`,
55
+ `agentdoc/docs/CONSTRAINTS.md` and `agentdoc/docs/adr/`, and records those
56
+ paths in the `docs:` block of the generated `agentdoc/agentdoc.config.yaml`
57
+ ([#59]). It used to write to `docs/`, which in many repositories is the
58
+ product's own user documentation and the source of a published docs site
59
+ (MkDocs, Docusaurus, GitHub Pages). Authored AgentDoc material now lives in
60
+ `agentdoc/`; generated output stays in `.agentdoc/`. Existing configurations
61
+ are unaffected: a configuration that declares `docs.*` keeps its paths,
62
+ `docs/...` included, and no existing file is moved. To keep `docs/` on a new
63
+ install, declare `docs.product`, `docs.architecture`, `docs.constraints` and
64
+ `docs.adrDir` in `agentdoc/agentdoc.config.yaml` before running `init`.
65
+
66
+ ### Fixed
67
+
68
+ - A component whose descriptor sits at the repository root now owns the files
69
+ no nested component owns ([#50]). Before, `query vite.config.ts` failed with
70
+ `AGENTDOC_REF_UNRESOLVED` and `impact` listed the file in `unmatchedFiles`,
71
+ for both `vite.config.ts` and `./vite.config.ts`. Paths are now compared in
72
+ one canonical repository-relative form (no `./`), by whole path segment
73
+ (`src` never owns `src2/x`), and the longest owning root wins, so nested
74
+ components keep precedence and a contract file still routes to its API. A
75
+ root unit's derived paths (`README.md`, `package.json`, markers, provenance,
76
+ verification config paths) are no longer recorded as `./README.md`, so
77
+ `impact` now recognises them as documentation and compiler inputs. Without a
78
+ root descriptor, root files stay unmatched. Graphs of repositories without a
79
+ root component are unchanged.
80
+
81
+ - `agentdoc scaffold --write` and `agentdoc init` now write the documentation
82
+ skeleton (`PRODUCT.md`, `ARCHITECTURE.md`, `CONSTRAINTS.md`, the ADR README)
83
+ to the configured `docs.product`, `docs.architecture`, `docs.constraints` and
84
+ `docs.adrDir` paths ([#49]). Both used to write to `docs/` even when the
85
+ configuration said otherwise, and `docs/` is often the product's own user
86
+ documentation. The paths are read through the same loader as `compile` and
87
+ `audit`. A role the configuration omits is listed under `notConfigured`
88
+ instead of falling back to `docs/`. An existing file is still skipped unless
89
+ `init --force` is given. A path that leaves the repository, including through
90
+ a symlink, fails with `AGENTDOC_PATH_ESCAPE` before anything is written.
91
+ Without a configuration, `init` uses the `agentdoc/docs/` defaults (see
92
+ [#59] under Changed).
93
+
94
+ - Files git ignores no longer enter the compiler's inputs or `inputHash`
95
+ ([#45]). A local build writing into an ignored directory (`astro build`
96
+ filling `.astro/`, a `build/` tree, `*.log`) used to change the hash, so a
97
+ graph compiled after it failed `check` with `AGENTDOC_GRAPH_FRESHNESS` in CI
98
+ and in every clean checkout, while passing on the author's machine. Reads,
99
+ existence probes, listings and discovery now exclude git-ignored paths, with
100
+ git itself as the oracle: tracked files always count, and nested
101
+ `.gitignore`, `.git/info/exclude`, `core.excludesFile` and negations behave
102
+ exactly as in git. A non-ignored untracked file is still an input. Without
103
+ git the previous behaviour (filesystem minus `discovery.generatedDirs`)
104
+ holds. Committed graphs keep their hash. A descriptor or configuration
105
+ that points at a git-ignored file (for example a spec generated into an
106
+ ignored directory before `compile`) now fails with
107
+ `AGENTDOC_PATH_UNRESOLVED`, as it would in a clean checkout: commit the file
108
+ or stop ignoring it. The `git clean -fdX` before
109
+ `compile` documented as a workaround in lbframe/klozeo's `AGENTS.md` is no
110
+ longer needed.
111
+
112
+ - `npm publish` no longer auto-corrects `package.json`: the `bin` path is
113
+ committed in npm's normalized form, `bin/agentdoc.mjs` ([#55]).
114
+
115
+ - All routing evaluation systems and the noise closure now share fact labels
116
+ ([#46]). Corrected targeted micro-recall is 100/101 (0.9901), rather than
117
+ the previously reported gap. Search matches catalog recall in 21/30 scenarios;
118
+ measurements and limitations are in `docs/BENCHMARK.md`. Normalized full fact
119
+ counts fall below the existing size floor, so aggregate size reduction is
120
+ reported as unmeasured. Thresholds, scenarios and core routing are unchanged.
121
+
122
+ - **Withdrawn benchmark claim.** The README, `docs/BENCHMARK.md` and the 1.2.0
123
+ release notes reported that routing retrieves 100% of the facts a change
124
+ needs against 58% for a baseline that reads only the touched component, with
125
+ noise under 9% against 40%, and an answer about 70% smaller than the whole
126
+ catalog. The gap came from the evaluation, not from
127
+ routing: the baseline already held almost every expected fact under a
128
+ different label, and it read relations from the compiled graph that an agent
129
+ without AgentDoc does not have. The size figure counted the same component
130
+ once per relation label; deduplicated, every corpus falls below the size
131
+ floor. Both claims are
132
+ removed. A baseline that finds
133
+ relations with search alone is now measured in the benchmark. The
134
+ [correction](docs/BENCHMARK.md#correction-the-fact-recall-comparison-is-withdrawn)
135
+ gives the details.
136
+
137
+ ## [1.2.0] - 2026-10-27
138
+
139
+ Rust and Python are now supported at the same level as Node and Go. Before
140
+ this release, a Cargo crate or a Python package was invisible to the compiler:
141
+ `agentdoc audit` reported zero units, and a hand-written descriptor produced a
142
+ component with no language, no dependency edges and no verification commands.
143
+
144
+ The release also fixes how a change is routed. `impact` and `query` now follow
145
+ the components that depend on the changed one, not the ones it depends on, and
146
+ recommend those dependents' checks.
147
+
148
+ ### Added
149
+
150
+ - **`rust` adapter** ([#17]). Reads `Cargo.toml` and never runs Cargo or reads
151
+ `Cargo.lock`.
152
+ - A `[package]` is a unit. `[[bin]]`, `src/main.rs` or `src/bin/` make it
153
+ deployable; `[lib]` or `src/lib.rs` make it importable.
154
+ - Workspace members come from every `[workspace]` manifest, including one
155
+ below the repository root (`rust/Cargo.toml` next to other languages).
156
+ - Sibling crates become `buildDependsOn` / `testDependsOn` edges when the
157
+ manifest says so: a `path`, `workspace = true` resolved through the nearest
158
+ `[workspace.dependencies]`, or a `[patch.<registry>]` / `[replace]` path
159
+ redirect inside the repository. `use`, `extern crate` and `<crate>::` paths
160
+ add provenance to those edges but never create one, because Rust cannot
161
+ use a crate the manifest does not declare.
162
+ - Verification commands: `cargo test -p <name>` and `cargo clippy -p <name>`.
163
+ - Resource vocabulary: sqlx, diesel (`diesel.toml`), tokio-postgres,
164
+ deadpool, sea-orm, the `redis` crate and `aws-sdk-s3`.
165
+ - **`python` adapter** ([#18]). Reads `pyproject.toml` (PEP 621 and Poetry),
166
+ `setup.cfg`, `setup.py` and `requirements*.txt`. It never runs Python,
167
+ resolves a lockfile or inspects a virtualenv.
168
+ - Shape comes from evidence. An entrypoint (`[project.scripts]`,
169
+ `__main__.py`, `manage.py`, an ASGI/WSGI app object) makes a unit
170
+ deployable; a build backend plus a package makes it importable.
171
+ - uv workspace members come from the repository root and from workspaces
172
+ below it. A member with no build backend is still a unit.
173
+ - Sibling packages become edges from `[project] dependencies`,
174
+ `[tool.uv.sources]`, Poetry `path` dependencies, `-e <dir>` requirements,
175
+ and `import` / `from` statements. Dev groups and dev/test extras give
176
+ `testDependsOn`. An import that the importing unit provides itself, such
177
+ as a local `utils.py`, is not read as a sibling.
178
+ - Verification commands are derived only from evidence: `pytest`,
179
+ `python -m unittest discover` for unittest-only suites, `ruff check` and
180
+ `mypy`. `uv run` / `poetry run` is prefixed only when the lockfile names
181
+ the tool.
182
+ - Also derived: `requiresPython` when declared, and Alembic migrations as
183
+ database evidence.
184
+ - Resource vocabulary: SQLAlchemy, asyncpg, psycopg, Django `DATABASES`,
185
+ `redis.Redis` and boto3.
186
+ - `agentdoc init` enables `rust` and `python` when it finds their manifests.
187
+ `agentdoc scaffold` names crates and distributions from their manifests and
188
+ types them from their artifact shape: `service`, `library`, or `other` when
189
+ neither is proven. A virtual Cargo or uv workspace manifest is not scaffolded.
190
+ - A reproducible benchmark, `scripts/bench/run.mjs` ([docs/BENCHMARK.md]).
191
+ It clones four public repositories at pinned commits, runs a fresh
192
+ `init` → `scaffold` → `compile`, counts every hand edit the setup still
193
+ needs (zero for all four), checks that the graph is byte-identical across
194
+ compiles, and measures `query` answers.
195
+ - The routing evaluation reports **check selectivity**: the share of the
196
+ repository's checks that the answer for a change contains ([#43]). The
197
+ baseline's verification recall is now scored with the same rule as the
198
+ catalog's. It is 1.0 by construction, because the baseline runs every check.
199
+ - Rust and Python sample repositories (`fixtures/e-rust-workspace`,
200
+ `fixtures/f-python-workspace`), checked in CI and used to test context
201
+ routing ([#23]).
202
+
203
+ ### Changed
204
+
205
+ - **Change routing follows dependents** ([#30], [#25]).
206
+ - A change to a component reaches the components that depend on it,
207
+ transitively, through reverse `buildDependsOn` / `testDependsOn` edges.
208
+ Before, `impact` also listed the components it depends on as affected,
209
+ and did not follow transitive dependents.
210
+ - Dependencies stay visible as typed relations in `query`. They are no
211
+ longer reported as affected components.
212
+ - `query` and `impact` recommend the dependents' checks after the changed
213
+ component's own: every tier for build dependents, unit checks only for test
214
+ dependents.
215
+ - `impact` adds `recommendedVerificationTotal` and
216
+ `recommendedVerificationTruncated`, so a capped list is never mistaken for
217
+ the whole.
218
+ - **`query` markdown shows an entity's context** ([#28]). Resources and
219
+ Systems now list their `docs`, `CONSTRAINTS` and `runbooks`, as components
220
+ already did. Constraints are never capped by default. Docs and runbooks show
221
+ 12 paths each, and `--budget docs=…,constraints=…,runbooks=…` sets explicit
222
+ caps with a `[truncated: x of y]` marker.
223
+ - **`discovery.notUnitRoots` excludes a whole subtree from inferred units**
224
+ ([#27]). An adapter marker or a scaffold proposal at or below an excluded
225
+ root is no longer a unit. A hand-written descriptor below an excluded root
226
+ is still honoured. A descriptor exactly at an excluded root is refused with
227
+ a message that names the exclusion.
228
+ - **GitHub Actions checks are scoped to the components they run for**
229
+ ([#29]). A step becomes a component check only when its command is a
230
+ recognised test, lint or build invocation, scoped by `working-directory`,
231
+ `cd <dir> &&`, a path argument, a package selector (`--filter`, turbo, nx,
232
+ `make -C`) or a `paths:` filter. Repository scripts whose names say what
233
+ they check (`./scripts/test.sh`, `bash scripts/lint.sh`) are recognised
234
+ with the same scoping ([#40]). Anything else is a repository gate.
235
+ - Before, every step was attached to every component whose path or package
236
+ name appeared anywhere in the workflow, including multi-line ops scripts.
237
+ A shell `test -n` was also recorded as a unit test.
238
+ - `impact` on a workflow file now names the components and gates derived
239
+ from it, plus their dependents' checks.
240
+ - **The TOML reader conforms to TOML 1.0** ([#18], [#31]). It reads
241
+ `Cargo.toml`, `pyproject.toml` and `wrangler.toml`, and passes every case of
242
+ the official [toml-test](https://github.com/toml-lang/toml-test) 1.0.0
243
+ suite: 208 valid and 501 invalid documents. Before, it rejected valid
244
+ documents, such as multi-line strings and lowercase date-time delimiters,
245
+ which lost every fact in the file. It also accepted invalid ones, such as a
246
+ trailing comma in an inline table or a table declared twice. Invalid TOML
247
+ is rejected with `AGENTDOC_YAML_PARSE`.
248
+
249
+ ### Fixed
250
+
251
+ - **A unit nested inside another unit no longer leaks its facts into the
252
+ parent** ([#21]). Every per-unit scan stops at nested units, so each file
253
+ belongs to the deepest unit that contains it. Before, an enclosing unit also
254
+ claimed its nested unit's health routes, ports, capabilities, resource and
255
+ migration evidence, imports, contract consumers, TypeScript routes, Ansible
256
+ playbooks and test markers. This affects any layout with nested units, such
257
+ as a service containing a tool package, a Cargo root package with member
258
+ crates below it, or a uv workspace root with its own `[project]`.
259
+ - An Alembic `alembic/versions` directory was listed as a migration location
260
+ but never recognised. It now counts as migration evidence. The evidence file
261
+ is a qualifying `alembic.ini` or `[tool.alembic]` configuration when one
262
+ exists, and otherwise a revision file.
263
+ - A changed migration, including an Alembic revision, now lists the logical
264
+ database its component uses in `impact`'s `affectedResources` ([#30]).
265
+ Before, the list stayed empty.
266
+ - The audit no longer reports a document as orphaned when it is linked only
267
+ from a Resource's or System's `context` ([#28]).
268
+ - `agentdoc scaffold --write` no longer writes descriptors into directories
269
+ listed in `discovery.notUnitRoots` ([#27]).
270
+ - **A fresh setup compiles without hand edits** ([#37], [#38]).
271
+ - `scaffold` now types every unit from its artifact evidence: `service`,
272
+ `library`, or `other` when neither is proven. Before, a Node app without
273
+ `exports` or `main`, or a Go module, was written as `library` and failed
274
+ `validate` straight away.
275
+ - `init` now discovers descriptors with the `*/**/agentdoc.yaml` glob
276
+ instead of an explicit list, and `scaffold` covers every unit an adapter
277
+ declares, including workspace members under `examples/`.
278
+ - A `setup.py` unit is named after the distribution it declares, and the
279
+ `python` adapter reads `name`, `python_requires` and `install_requires`
280
+ inside a one-line `setup(…)` or `setuptools.setup(…)` call ([#41]).
281
+ - On the four public repositories used for benchmarking (tokio-rs/axum,
282
+ pydantic/pydantic-ai, fastapi/full-stack-fastapi-template and
283
+ t3-oss/create-t3-turbo), `init` + `scaffold --write` + `compile` now pass
284
+ with no edits other than choosing each component's placement.
285
+
286
+ ### Upgrading
287
+
288
+ - The compiler version is now `1.2.0`. A graph committed with 1.1.0 fails
289
+ `agentdoc check` with `AGENTDOC_COMPILER_MISMATCH`. Run `agentdoc compile`
290
+ and commit the graph.
291
+ - An existing catalog keeps its adapter list. To use the new adapters, add
292
+ `rust` and/or `python` to `adapters:` in `agentdoc/agentdoc.config.yaml`,
293
+ then run `agentdoc audit`: units that were invisible appear as
294
+ `AGENTDOC_COVERAGE_ZERO` until they have a descriptor.
295
+ `agentdoc scaffold --write` writes those descriptors.
296
+ - If your repository has nested units, recompiling removes facts that were
297
+ wrongly attributed to an enclosing component. Review the affected facts,
298
+ dependency edges and verification commands, and any descriptor or document
299
+ that relied on them.
300
+ - Tools that read `impact --json` will see fewer `affectedComponents`
301
+ (dependencies are gone) and more `recommendedVerification` entries
302
+ (dependents' checks).
303
+ - A unit inferred below a `notUnitRoots` entry, with no descriptor, is no
304
+ longer reported as `AGENTDOC_COVERAGE_ZERO`. If it should be a component,
305
+ write its descriptor, or remove the directory from `notUnitRoots`.
306
+ - If your workflows run checks for specific components, recompiling changes
307
+ which checks are attached to which component. Review the diff: checks that
308
+ were attached only because a path was mentioned elsewhere in the workflow
309
+ move to repository gates.
310
+ - Recompiling always updates the compiler version, source commit and input
311
+ hash. Review the other derived changes, especially in repositories with
312
+ nested units, Alembic migrations or Wrangler TOML manifests: a manifest the
313
+ old TOML reader could not parse now yields facts.
314
+
315
+ ## [1.1.0] - 2026-10-18
316
+
317
+ ### Added
318
+
319
+ - **MIGRATE gates normative requirements and section coverage** ([#5]).
320
+ Documentation checks now state their real scope, and the strict gate refuses
321
+ unverified requirements.
322
+ - **`ansible` adapter** ([#6]). Reads playbooks without running anything and
323
+ derives installed paths and managed services. A requirement with
324
+ `satisfiedBy: [ansible]` and the new `expects` field
325
+ (`installs: <path>` | `service: <name>`) is checked against them, so a missing
326
+ install ends in a conflict instead of `AGENTDOC_REQUIREMENT_UNVERIFIED`.
327
+ - `agentdoc <command> --help` lists each command's flags and exit codes ([#7]).
328
+
329
+ ### Changed
330
+
331
+ - **The CLI rejects unknown flags** ([#7]) and names the closest valid flag. A
332
+ typo such as `check --strcit` no longer silently runs a non-strict check.
333
+ Scripts that passed flags a command does not accept now exit 2.
334
+
335
+ ### Fixed
336
+
337
+ - A committed graph passes CI ([#11]). `check` no longer fails with
338
+ `AGENTDOC_GRAPH_FRESHNESS` after `compile` on a clean checkout, where the
339
+ graph file itself was counted as a changed input.
340
+ - A nested `.github/workflows` directory no longer fails `validate` ([#12]).
341
+ Only the repository-root directory is read, as GitHub does.
342
+
343
+ ### Upgrading
344
+
345
+ - A graph committed with 1.0.0 fails `agentdoc check` with
346
+ `AGENTDOC_COMPILER_MISMATCH` until it is recompiled.
347
+
348
+ ## [1.0.0] - 2026-09-29
349
+
350
+ First public release.
351
+
352
+ ### Added
353
+
354
+ - The machine-installed `agentdoc` CLI for Node 22, 24 and 26, with zero
355
+ runtime dependencies.
356
+ - A deterministic repository graph: repeated compiles of the same tree are
357
+ byte-identical.
358
+ - Evidence separation between AUTHORED documentation, DERIVED facts extracted
359
+ from code, and OBSERVED runtime evidence.
360
+ - A fail-closed authority model: contradictions surface as conflicts and are
361
+ never silently overwritten.
362
+ - `agentdoc query`, which routes minimal, budgeted context for a task, and
363
+ `agentdoc impact`, which reports what a change forces you to keep consistent.
364
+ - CREATE and MIGRATE workflows through `init`, `audit`, `scaffold`,
365
+ `validate`, `compile` and `check`.
366
+ - `agentdoc doctor` for machine diagnostics, and `agentdoc doctor --project`
367
+ for project health.
368
+ - An Agent Skill under `skills/agent-doc-system/`.
369
+
370
+ [Unreleased]: https://github.com/lbframe/agent-doc-system/compare/v1.3.0...HEAD
371
+ [1.3.0]: https://github.com/lbframe/agent-doc-system/compare/v1.2.0...v1.3.0
372
+ [1.2.0]: https://github.com/lbframe/agent-doc-system/compare/v1.1.0...v1.2.0
373
+ [1.1.0]: https://github.com/lbframe/agent-doc-system/compare/v1.0.0...v1.1.0
374
+ [1.0.0]: https://github.com/lbframe/agent-doc-system/releases/tag/v1.0.0
375
+ [docs/BENCHMARK.md]: docs/BENCHMARK.md
376
+ [#5]: https://github.com/lbframe/agent-doc-system/issues/5
377
+ [#6]: https://github.com/lbframe/agent-doc-system/issues/6
378
+ [#7]: https://github.com/lbframe/agent-doc-system/issues/7
379
+ [#11]: https://github.com/lbframe/agent-doc-system/issues/11
380
+ [#12]: https://github.com/lbframe/agent-doc-system/issues/12
381
+ [#17]: https://github.com/lbframe/agent-doc-system/issues/17
382
+ [#18]: https://github.com/lbframe/agent-doc-system/issues/18
383
+ [#21]: https://github.com/lbframe/agent-doc-system/issues/21
384
+ [#23]: https://github.com/lbframe/agent-doc-system/issues/23
385
+ [#25]: https://github.com/lbframe/agent-doc-system/issues/25
386
+ [#27]: https://github.com/lbframe/agent-doc-system/issues/27
387
+ [#28]: https://github.com/lbframe/agent-doc-system/issues/28
388
+ [#29]: https://github.com/lbframe/agent-doc-system/issues/29
389
+ [#30]: https://github.com/lbframe/agent-doc-system/issues/30
390
+ [#31]: https://github.com/lbframe/agent-doc-system/issues/31
391
+ [#37]: https://github.com/lbframe/agent-doc-system/issues/37
392
+ [#38]: https://github.com/lbframe/agent-doc-system/issues/38
393
+ [#40]: https://github.com/lbframe/agent-doc-system/issues/40
394
+ [#41]: https://github.com/lbframe/agent-doc-system/issues/41
395
+ [#43]: https://github.com/lbframe/agent-doc-system/issues/43
396
+ [#45]: https://github.com/lbframe/agent-doc-system/issues/45
397
+ [#46]: https://github.com/lbframe/agent-doc-system/issues/46
398
+ [#49]: https://github.com/lbframe/agent-doc-system/issues/49
399
+ [#50]: https://github.com/lbframe/agent-doc-system/issues/50
400
+ [#51]: https://github.com/lbframe/agent-doc-system/issues/51
401
+ [#55]: https://github.com/lbframe/agent-doc-system/issues/55
402
+ [#59]: https://github.com/lbframe/agent-doc-system/issues/59
package/README.md CHANGED
@@ -69,6 +69,41 @@ AgentDoc turns scattered repository knowledge into task-specific context your co
69
69
 
70
70
  ![How AgentDoc works](docs/assets/readme/how-agentdoc-works.webp)
71
71
 
72
+ ## What it understands
73
+
74
+ AgentDoc reads manifests, contracts and configuration. It never runs your toolchain.
75
+
76
+ Support comes from adapters and from built-in contract validation (AsyncAPI needs no adapter).
77
+ - `agentdoc init` picks `node`, `go`, `rust`, `python`, `dockerfile` and `wrangler` from the files it finds.
78
+ - It always includes `database`, `openapi`, `events` and `github-actions`.
79
+ - Add the others in `agentdoc/agentdoc.config.yaml`.
80
+
81
+ | area | supported |
82
+ |---|---|
83
+ | languages & packages | Node (npm, pnpm workspaces, Turborepo), TypeScript, Go (modules, `go.work`), Rust (Cargo packages and workspaces), Python (`pyproject.toml` with PEP 621 or Poetry, uv workspaces, `setup.cfg` / `setup.py`, `requirements*.txt`) |
84
+ | contracts | OpenAPI, protobuf, GraphQL, AsyncAPI, OIDC discovery |
85
+ | deployment & CI | Dockerfile / Containerfile, `fly.toml`, `Procfile` and similar deploy configs, Cloudflare Wrangler, Ansible playbooks, GitHub Actions |
86
+ | resources | databases (migrations, ORM config, connection evidence), caches, object stores, event subjects |
87
+
88
+ For each language, AgentDoc derives:
89
+
90
+ - the units;
91
+ - whether each one is deployable or importable;
92
+ - dependency edges between sibling units;
93
+ - verification commands taken from the repository's own evidence (`go test`, `cargo test -p`, `pytest`, `python -m unittest`, npm scripts, …).
94
+
95
+ What every adapter derives, and what it deliberately cannot see, is in [docs/ADAPTERS.md](docs/ADAPTERS.md).
96
+
97
+ ## By the numbers
98
+
99
+ Measured, reproducible, and with no model in the loop. Method and caveats are in [docs/BENCHMARK.md](docs/BENCHMARK.md).
100
+
101
+ - **The checks a change needs, not the whole suite.** Across 30 routing scenarios (Node, Go, Rust, Python), every check the change requires is in the answer, and that answer holds about half of the repository's checks (29% on the largest corpus), not all of them. The thresholds were fixed before the first run and have not moved since.
102
+ - **Seconds to map a real repository.** A fresh `init` → `scaffold` → `compile` takes 1.0–4.6 s on four public repositories, from 141 to 5,142 files (axum, pydantic-ai, full-stack-fastapi-template, create-t3-turbo). No edit was needed beyond choosing each component's placement.
103
+ - **A briefing, not a dump.** A `query` answer is about 100–260 tokens: what the change reaches, what to read, and which checks to run.
104
+ - **The same graph every time.** Compiles are byte-identical, and CI can verify it.
105
+ - **Small and strict.** Zero runtime dependencies, a package of about 285 kB packed, 21 adapters, and 709/709 cases on the official TOML 1.0 conformance suite.
106
+
72
107
  ## Install manually
73
108
 
74
109
  Requires Node.js 22+.
@@ -92,7 +127,10 @@ AgentDoc also ships with an Agent Skill at [`skills/agent-doc-system/`](skills/a
92
127
  - [Authority & conflicts](docs/AUTHORITY.md)
93
128
  - [CI & verification](docs/CI.md)
94
129
  - [Specification](docs/SPEC.md)
130
+ - [Adapters](docs/ADAPTERS.md)
131
+ - [Benchmark](docs/BENCHMARK.md)
95
132
  - [Agent Skill](skills/agent-doc-system/SKILL.md)
133
+ - [Changelog](CHANGELOG.md)
96
134
 
97
135
  ## License
98
136
 
@@ -132,7 +132,7 @@ function isYaml(f) {
132
132
 
133
133
  function playbooks(repo, root) {
134
134
  const out = [];
135
- for (const f of repo.walk(root)) {
135
+ for (const f of repo.walkUnit(root)) {
136
136
  if (!isYaml(f) || /(^|\/)(tasks|handlers|vars|defaults|meta|group_vars|host_vars|templates|files)\//.test(f.slice(root.length))) continue;
137
137
  const docs = parse(repo, f);
138
138
  if (!docs) continue;
@@ -1,7 +1,8 @@
1
1
  // Cache adapter: Redis / in-memory cache client evidence as a derived binding.
2
2
  import { Adapter } from "./registry.mjs";
3
+ import { joinPath } from "../core/paths.mjs";
3
4
 
4
- const CACHE_HINT = /(@upstash\/redis|ioredis|redis|go-redis|redis\.go|valkey|memcache|@memcached)/i;
5
+ const CACHE_HINT = /(@upstash\/redis|ioredis|redis\.Redis|redis|go-redis|redis\.go|valkey|memcache|@memcached)/i;
5
6
 
6
7
  export class CacheAdapter extends Adapter {
7
8
  static adapterName = "cache";
@@ -10,7 +11,13 @@ export class CacheAdapter extends Adapter {
10
11
  }
11
12
  extract(ctx, unit) {
12
13
  const { repo, prov, compRef } = ctx;
13
- const evFile = unit.goModule ? unit.root + "/go.mod" : unit.root + "/package.json";
14
+ const evFile = unit.goModule
15
+ ? joinPath(unit.root, "go.mod")
16
+ : unit.pythonManifest
17
+ ? unit.pythonManifest
18
+ : repo.exists(joinPath(unit.root, "package.json"))
19
+ ? joinPath(unit.root, "package.json")
20
+ : joinPath(unit.root, "Cargo.toml");
14
21
  if (!repo.exists(evFile)) return {};
15
22
  const text = repo.readText(evFile);
16
23
  if (!CACHE_HINT.test(text)) return {};
@@ -18,7 +18,7 @@ const DOC_EXT = /\.(md|mdx|txt|adoc|rst)$/;
18
18
 
19
19
  export function contractReferenceSites(repo, unit) {
20
20
  const out = new Map(); // file -> true
21
- for (const f of repo.walk(unit.root)) {
21
+ for (const f of repo.walkUnit(unit.root)) {
22
22
  const base = path.posix.basename(f);
23
23
  if (DOC_EXT.test(f)) continue;
24
24
  if (f.includes("/docs/") || f.startsWith("docs/")) continue;
@@ -9,12 +9,16 @@
9
9
  // library shipped migrations but explicitly did not own a database.
10
10
  import { Adapter } from "./registry.mjs";
11
11
  import { firstExistingFile, findMigrationDirs } from "../core/sourcescan.mjs";
12
+ import { joinPath } from "../core/paths.mjs";
12
13
 
13
14
  const ORM_CONFIG = [
14
15
  "drizzle.config.ts", "drizzle.config.js", "drizzle.config.mjs",
15
16
  "prisma/schema.prisma", "schema.prisma",
16
17
  "knexfile.js", "knexfile.ts", "sequelize.config.js", "ormconfig.json",
17
18
  ];
19
+ // Probed only for a Cargo package. Adding it to the list above would record
20
+ // a new existence probe on every non-Rust unit and change those graphs.
21
+ const RUST_ORM_CONFIG = ["diesel.toml"];
18
22
 
19
23
  export class DatabaseAdapter extends Adapter {
20
24
  static adapterName = "database";
@@ -25,12 +29,23 @@ export class DatabaseAdapter extends Adapter {
25
29
  const { repo, prov, compRef } = ctx;
26
30
  const root = unit.root;
27
31
  const migrationDirs = findMigrationDirs(repo, root);
28
- const ormConfig = firstExistingFile(repo, root, ORM_CONFIG);
32
+ const ormConfig = firstExistingFile(repo, root, unit.cargoPackage ? ORM_CONFIG.concat(RUST_ORM_CONFIG) : ORM_CONFIG);
29
33
  if (!migrationDirs.length && !ormConfig) return {};
30
34
 
31
35
  let evidencePath = ormConfig;
32
36
  if (migrationDirs.length) {
33
- evidencePath = repo.walk(migrationDirs[0])[0] || migrationDirs[0];
37
+ evidencePath = repo.walkUnit(root, migrationDirs[0])[0] || migrationDirs[0];
38
+ }
39
+ const alembicDir = migrationDirs.find((d) => d === "alembic/versions" || d.endsWith("/alembic/versions"));
40
+ if (alembicDir) {
41
+ const ini = joinPath(root, "alembic.ini");
42
+ const pyproject = joinPath(root, "pyproject.toml");
43
+ if (repo.exists(ini)) {
44
+ const text = repo.readText(ini);
45
+ if (/^\s*\[alembic\]/m.test(text) || /^\s*script_location\s*=/m.test(text)) evidencePath = ini;
46
+ } else if (repo.exists(pyproject) && /\[tool\.alembic\]/.test(repo.readText(pyproject))) {
47
+ evidencePath = pyproject;
48
+ }
34
49
  }
35
50
  const pr = prov.add("observed", evidencePath, "migration-extractor", ["der:" + compRef + ":/bindings"]);
36
51
 
@@ -93,10 +108,10 @@ export class DatabaseAdapter extends Adapter {
93
108
  }
94
109
  }
95
110
 
96
- const POOL_HINT = /(createPool|new Pool|pg\.Pool|psycopg2?\.connect|sql\.Open|mysql\.createConnection|Pool\(|DataSource|prisma\.(datasource|client)|DATABASE_URL|DB_DSN)/;
111
+ const POOL_HINT = /(createPool|new Pool|pg\.Pool|PgPool|psycopg2?\.connect|asyncpg\.(?:connect|create_pool)|sql\.Open|mysql\.createConnection|Pool\(|DataSource|prisma\.(datasource|client)|DATABASE_URL|DB_DSN|sqlx::migrate!|tokio_postgres|tokio-postgres|deadpool|sea_orm|sea-orm|diesel::|create_engine\s*\(|\bDATABASES\s*=)/;
97
112
 
98
113
  function hasConnectionEvidence(ctx, unit) {
99
- for (const f of ctx.repo.walk(unit.root)) {
114
+ for (const f of ctx.repo.walkUnit(unit.root)) {
100
115
  if (/\.(ts|tsx|js|mjs|go|py|rb|java|kt|rs|ex|cs|php)$/.test(f)) {
101
116
  if (/(^|\/)(tests?|__tests__)\//.test(f)) continue;
102
117
  if (POOL_HINT.test(ctx.repo.readText(f))) return true;
@@ -2,6 +2,7 @@
2
2
  // Only the file's existence is treated as evidence; no value from these files
3
3
  // is ever reported as deployed state.
4
4
  import { Adapter } from "./registry.mjs";
5
+ import { joinPath } from "../core/paths.mjs";
5
6
 
6
7
  const DEPLOY_FILES = [
7
8
  "fly.toml", "app.yaml", "render.yaml", "railway.json", "vercel.json",
@@ -16,7 +17,7 @@ export class DeployConfigAdapter extends Adapter {
16
17
  }
17
18
  detect(ctx, root) {
18
19
  for (const n of DEPLOY_FILES) {
19
- if (ctx.repo.exists(root + "/" + n)) return { path: root + "/" + n, shape: "deployable" };
20
+ if (ctx.repo.exists(joinPath(root, n))) return { path: joinPath(root, n), shape: "deployable" };
20
21
  }
21
22
  return null;
22
23
  }
@@ -24,7 +25,7 @@ export class DeployConfigAdapter extends Adapter {
24
25
  const { repo, prov, compRef } = ctx;
25
26
  const out = { deployConfigs: [] };
26
27
  for (const n of DEPLOY_FILES) {
27
- const p = unit.root + "/" + n;
28
+ const p = joinPath(unit.root, n);
28
29
  if (!repo.exists(p)) continue;
29
30
  prov.add("observed", p, "deploy-extractor", ["der:" + compRef + ":/artifact"]);
30
31
  out.deployConfigs.push(p);
@@ -1,5 +1,6 @@
1
1
  // Dockerfile adapter: a Dockerfile is deployable-artifact evidence.
2
2
  import { Adapter } from "./registry.mjs";
3
+ import { joinPath } from "../core/paths.mjs";
3
4
 
4
5
  export class DockerfileAdapter extends Adapter {
5
6
  static adapterName = "dockerfile";
@@ -8,7 +9,7 @@ export class DockerfileAdapter extends Adapter {
8
9
  }
9
10
  detect(ctx, root) {
10
11
  for (const n of ["Dockerfile", "Dockerfile.prod", "Containerfile"]) {
11
- if (ctx.repo.exists(root + "/" + n)) return { path: root + "/" + n, shape: "deployable" };
12
+ if (ctx.repo.exists(joinPath(root, n))) return { path: joinPath(root, n), shape: "deployable" };
12
13
  }
13
14
  return null;
14
15
  }
@@ -16,7 +17,7 @@ export class DockerfileAdapter extends Adapter {
16
17
  const { repo, prov, compRef } = ctx;
17
18
  const out = { dockerfiles: [] };
18
19
  for (const n of ["Dockerfile", "Dockerfile.prod", "Containerfile"]) {
19
- const p = unit.root + "/" + n;
20
+ const p = joinPath(unit.root, n);
20
21
  if (!repo.exists(p)) continue;
21
22
  prov.add("observed", p, "deploy-extractor", ["der:" + compRef + ":/artifact"]);
22
23
  out.dockerfiles.push(p);
@@ -70,7 +70,7 @@ export class EventsAdapter extends Adapter {
70
70
  if (other.component && visible.has(other.component.ref) && other.component.ref !== compRef) collectDecls(other.root);
71
71
  }
72
72
  const families = new Set();
73
- for (const f of repo.walk(unit.root)) {
73
+ for (const f of repo.walkUnit(unit.root)) {
74
74
  if (!SRC_EXT.test(f)) continue;
75
75
  if (TEST_PATH.test(f) || TEST_FILE.test(f)) continue;
76
76
  const text = repo.readText(f);
@@ -7,6 +7,7 @@
7
7
  import { Adapter } from "./registry.mjs";
8
8
  import { resolveClientResources } from "./resource-client.mjs";
9
9
  import { sourceFiles } from "../core/sourcescan.mjs";
10
+ import { joinPath } from "../core/paths.mjs";
10
11
 
11
12
  // Conservative: a health route is an absolute string literal that looks like a
12
13
  // liveness or readiness endpoint. Anything more specific belongs to a
@@ -50,7 +51,7 @@ export class GenericAdapter extends Adapter {
50
51
  // Provenance must name a file that exists. The dependency evidence is the
51
52
  // manifest when there is one, and otherwise the unit's descriptor.
52
53
  const manifest = ["package.json", "go.mod", "Cargo.toml", "pyproject.toml", "pom.xml"]
53
- .map((n) => unit.root + "/" + n)
54
+ .map((n) => joinPath(unit.root, n))
54
55
  .find((p) => repo.exists(p)) || ctx.componentEntity().file;
55
56
  const clientProv = prov.add("observed", manifest, "client-resolver", ["rel:pending", "der:" + compRef + ":/bindings"]);
56
57
  resolveClientResources(ctx, unit, compRef, clientProv);