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/docs/EVALS.md CHANGED
@@ -7,34 +7,182 @@ noise than exploring the repository unaided?
7
7
 
8
8
  The question is not "does it compile". A catalog can compile perfectly and still
9
9
  route an agent into the wrong corner of the repository. The measurement is
10
- retrieval quality, on the same scenarios, for two retrieval systems.
10
+ retrieval quality on the same scenarios for three retrieval systems.
11
11
 
12
- **baseline-targeted** — what an agent that has no catalog and looks only where
13
- the change is: the touched component, its subtree, its relations, its documents
14
- and its checks.
12
+ **baseline-targeted** — the historical, graph-assisted reference: the touched
13
+ entity, its subtree, directly connected entities, documents, events and checks,
14
+ plus the original repository-wide documentation sweep. It reads compiled
15
+ relations and authority facts. It is not an agent without AgentDoc.
15
16
 
16
- **baseline-full** — what an agent with no routing ends up holding: every entity,
17
- every relation, every event subject, every check, every document in the
18
- repository. This is the honest cost of "just read the repository", and it is the
19
- size yardstick for reduction.
17
+ **baseline-search** — a deterministic model of an agent with file reads and
18
+ text search over the working tree, starting at the touched paths. It searches
19
+ for importers and importees to a fixed point, then reads the reached components'
20
+ docs, manifests and checks. It has no compiled graph or compiler-derived
21
+ relations. Its complete heuristics are below.
20
22
 
21
23
  **catalog** — what `agentdoc query` returns for the touched paths, plus the
22
24
  context of the components `impact` says are affected.
23
25
 
24
- No model is involved. Retrieval sets are compared as sets, so the measurement
25
- is deterministic and cannot be moved by phrasing.
26
+ **baseline-full** is retained only as a size yardstick: all graph entities,
27
+ relations, event subjects, checks and documents. It is a graph inventory, not
28
+ unaided repository exploration.
29
+
30
+ No model is involved. Retrieval sets are compared as sets. All systems and the
31
+ noise closure use `evals/facts.mjs`: resource links use `uses:` / `usedBy:`, API
32
+ links use `provides:` / `consumes:` / `provider:` / `consumer:`, and component
33
+ and placement links use bare entity refs. A relation also identifies its other
34
+ endpoint, as query's neighbour summaries do. Inverse edges respect direction.
35
+ An API subject supplies `provides:<its-ref>`, as query does. Documents use
36
+ `doc:` throughout, including the old targeted sweep (formerly `repodoc:`).
37
+ Compiler relation names are not separate facts. Tests pin this vocabulary.
26
38
 
27
39
  ## Metrics
28
40
 
29
41
  | metric | definition |
30
42
  |---|---|
31
- | recall | fraction of the scenario's ground-truth facts the catalog retrieved |
43
+ | recall | fraction of the scenario's ground-truth facts retrieved by each system |
32
44
  | critical recall | the same, restricted to critical context: constraints, conflicts, observations, resources, API boundaries, event subjects |
33
- | verification recall | fraction of the checks that must be run, that were returned |
45
+ | verification recall | fraction of the scenario's verification expectations returned; 1 when there are no expectations, for every system |
46
+ | check selectivity | distinct recommended check IDs / distinct check IDs in the repository graph (`verification` + `gates`); reported only, not gated; lower means fewer checks recommended |
34
47
  | precision | `1 - irrelevance` |
35
48
  | irrelevance (noise) | fraction of retrieved facts outside the graph's one-hop closure of the touched entities |
36
49
  | retrieval reduction | `1 - catalog facts / baseline-full facts` |
37
50
 
51
+ Scenario verification expectations accept `verify:<command>` and
52
+ `verify-id:<check-id>`. Use check IDs when multiple components have the same
53
+ command (for example `go test ./...`), so the library's own check cannot stand in
54
+ for a dependent's. Retrieved context includes the entity refs already returned
55
+ in query's neighbour summaries, even when those neighbours are dependencies and
56
+ therefore absent from impact's affected components.
57
+
58
+ Check selectivity counts the union of checks returned by queries for touched
59
+ and impact-affected entities (`verification.items` and `globalGates`), plus
60
+ impact's `recommendedVerification`. It measures the returned lists, respecting
61
+ their output caps. IDs deduplicate repeated recommendations while preserving
62
+ separate scoped checks that happen to share a command. With no checks in the
63
+ graph, selectivity is 1 (there is nothing to narrow). Corpus values are scenario
64
+ means; the aggregate is scenario-weighted across corpora. Both CLI reports
65
+ include per-scenario values in their `--json` output.
66
+
67
+ Targeted baseline verification recall is 1 by construction: its verification strategy
68
+ runs every graph verification entry and repository gate, so it cannot miss a
69
+ registered expectation. Empty expectations also score 1 on both sides. Its
70
+ check selectivity is therefore 1. Search discovers its own checks; it does not
71
+ inherit this union. Selectivity is the meaningful comparison of
72
+ how much routing narrows the check set, read alongside catalog verification
73
+ recall so that omitting a required check is not mistaken for an improvement.
74
+
75
+ `recall` is a scenario mean. `microRecall` instead divides total fact hits by
76
+ total truth facts; per-scenario hit/total counts make this independently
77
+ recomputable. Aggregate micro-recall uses these raw counts, not corpus means.
78
+ The legacy JSON keys remain: catalog metrics are at the top level, targeted
79
+ metrics are under `baseline`, and search metrics are under `searchBaseline`.
80
+
81
+ Search returns scoped `(componentRef, command)` pairs without graph check IDs.
82
+ Only the scorer matches these pairs to graph verification entries and gates,
83
+ then counts their IDs for selectivity and `verify-id:` expectations. A root
84
+ command matches only a repository-scoped check. Identical commands in different
85
+ components remain distinct. Unregistered commands remain retrieved facts and
86
+ can contribute noise, but do not select a registered ID. Read selectivity with
87
+ verification recall; a low value can mean a missed check.
88
+
89
+ ### Search baseline heuristics
90
+
91
+ `evals/baseline-search.mjs` imports only filesystem/path utilities, the existing
92
+ YAML/TOML syntax readers and the shared fact helper. It never invokes adapters,
93
+ routing, a package manager, a model or a shell search. The following rules are
94
+ fixed independently of scenario truth:
95
+
96
+ 1. **Working tree.** Walk regular files in sorted name order, including
97
+ uncommitted files. Do not follow symlinks. Skip `.git`, `.agentdoc`,
98
+ `node_modules`, `.venv`, `venv`, `__pycache__`, `target`, `dist`, `build` and
99
+ `.next`, and any `graph.json`. Read JS/TS (including CJS/MJS/JSX/TSX), Go,
100
+ Rust, Python, proto, SQL, Markdown, RST, TXT, JSON, YAML, TOML, INI, CFG,
101
+ MOD, WORK, LOCK and Dockerfile text; skip NUL-containing files. This is a
102
+ literal exclusion list, not `.gitignore` or a tracked-file snapshot.
103
+ 2. **Ownership only.** YAML documents with `apiVersion: agentdoc.dev/...`
104
+ are excluded from search. For `Component` documents only, take the metadata
105
+ name/namespace as identity, the descriptor directory as source root, and
106
+ string arrays under `spec.context` as owned document paths. Explicit document
107
+ ownership wins, otherwise the longest component root wins. No placement,
108
+ relations, API/resource declarations, journeys, observations or authority
109
+ rules are used. Other files under `agentdoc/` are excluded too.
110
+ 3. **Import names.** Read each component's root `package.json` name, Cargo
111
+ package name and its underscore spelling, `go.mod` module path, Python
112
+ distribution names, and top-level Python packages with `__init__.py` or
113
+ `__main__.py` (under the root or `src/`). These are search terms, not
114
+ compiler-resolved units. A component without an import name can still own
115
+ touched paths and participate through relative imports or endpoint strings.
116
+ 4. **Importers and importees.** Search source for JS/TS `from`, `import` and
117
+ `require` quoted specifiers; Go quoted strings; Rust `use`, `extern crate`
118
+ and `name::`; Python line-leading `import` / `from` names. Match exact names
119
+ or slash/dot subpaths. Search manifests for names at word/path boundaries,
120
+ and for local `path =`, `file:` and `link:` dependencies relative to that
121
+ manifest. Relative source specifiers are resolved against their file's
122
+ directory and mapped to an owner. Rust names declared with `mod` in the
123
+ same file are suppressed; Python imports with a matching local `.py` file
124
+ anywhere in the importing component are suppressed. Ambiguous sibling names
125
+ may reach multiple components. There is no symbol resolver, alias resolver
126
+ or full import grammar (for example Python comma imports are incomplete).
127
+ 5. **Fixed point.** Import evidence connects components in both directions.
128
+ Start with owners of touched files (or explicitly touched component refs),
129
+ add all importers and importees, and repeat until no component is added.
130
+ API/endpoint text links described next participate in the same closure.
131
+ There is no hop limit or output cap.
132
+ 6. **APIs and endpoints.** Read OpenAPI YAML `info.title` and `paths`, and
133
+ protobuf `service` names. A lowercase hyphenated title plus `-api` (without
134
+ duplicating an existing `-api` suffix) names an OpenAPI fact; a hyphenated
135
+ service name names a protobuf fact. CamelCase boundaries are split. These
136
+ labels are guesses, not lookups in the API catalog; namespaces default to
137
+ `default`. Find readers by literal contract file paths in component files.
138
+ Find providers by exact endpoint strings in source, Next-style
139
+ `/app/<path>/route.ts` files, or `Register<Service>Server` text. Contract
140
+ readers/providers are connected. Touching a contract seeds them and emits
141
+ the API ref and `provides:` fact; reached readers/providers emit API roles,
142
+ with a provider excluded from the consumer set. Other API kinds, dynamic
143
+ routes, semantic serving roles and arbitrary catalog names are not inferred.
144
+ Also connect source/TOML files sharing an exact slash-prefixed endpoint or
145
+ HTTP(S) URL pathname; Next `route.js`/`route.ts` paths count. Ignore `/`.
146
+ Hostnames are ignored. Common paths such as `/api/health` can consequently
147
+ connect unrelated services; the measured noise includes this overreach.
148
+ 7. **Docs and events.** Read all text belonging to reached components. Markdown,
149
+ RST and TXT files count as `doc:`, except a basename containing `constraint`
150
+ gives `constraint:` and a path containing `runbook` gives `runbook:`.
151
+ In source lines mentioning subject, event, topic, publish or subscribe
152
+ (case insensitive), quoted lowercase-leading dotted strings with at least
153
+ three segments count as events. There are no configured event prefixes or
154
+ compiler event contracts. Imports reach a shared subject's defining file.
155
+ 8. **Resources and externals.** PostgreSQL URL schemes, `sql.Open("postgres"`,
156
+ `pg`, `psycopg`, `asyncpg`, `sqlx`, `diesel`, or SQL files under `migrations/`
157
+ suggest `resource:default/<component-name>-postgres`. The word `redis`
158
+ suggests `<component-name>-redis`. TOML-style `bucket =` / `queue_name =`
159
+ string literals supply their slugged names. These guesses emit resource refs
160
+ and `uses:`; no catalog resource names are read. Plain environment variable
161
+ names without one of these signals are insufficient to identify a resource.
162
+ External names come from Node dependency maps, Cargo dependency tables,
163
+ Go requirements, Python project dependencies/extras/dev groups and
164
+ requirements text. Names already identifying local units are excluded.
165
+ Conditional dependencies and comments are not semantically evaluated.
166
+ 9. **Checks.** Node scripts whose names mention test/lint/typecheck/check,
167
+ clippy/vet/pytest/ruff/mypy/unittest return their literal commands; root Turbo
168
+ tasks with those names generate `turbo run <task> --filter=<package>` for
169
+ reached packages. A Go manifest suggests `go test ./...` and `go vet ./...`;
170
+ a Cargo package suggests `cargo test -p <name>` and `cargo clippy -p <name>`.
171
+ Python component text mentioning pytest, ruff or mypy suggests that command
172
+ (`ruff check` for ruff); the nearest ancestor uv/Poetry lockfile selects
173
+ `uv run` / `poetry run`. Unittest without pytest suggests the standard-library
174
+ command `python -m unittest discover`. These are conventional suggestions,
175
+ not proof the checks pass. GitHub Actions YAML run steps are split by lines:
176
+ check-like lines select reached components through explicit package names
177
+ or step/job working directories; unscoped root lines are repository checks.
178
+ Filtered commands, `cd` and matrix expressions without a resolved scope are
179
+ skipped. Expressions, shell semantics and workflow matrices are not expanded.
180
+ Checks deduplicate by component and command.
181
+ 10. **No hidden knowledge.** There is no scenario-specific branch, truth-set
182
+ lookup or graph membership filter in retrieval. Unsupported constructs may
183
+ miss facts; naming guesses may add facts absent from the graph. Search does
184
+ not infer placement, runtime observations or authority conflicts from code.
185
+
38
186
  ### Why noise is measured against reachability, not against the truth set
39
187
 
40
188
  The first definition of noise was the complement of truth recall. It is
@@ -58,6 +206,65 @@ repository; a router that returned *every fact in the repository* then scored as
58
206
  precise, because everything was technically adjacent to something. The metric
59
207
  was measuring its own closure, not the router.
60
208
 
209
+ ## Measured comparison (2026-10-27)
210
+
211
+ Values are scenario means; the aggregate weights corpora by scenario count.
212
+ Recall is higher-is-better; noise and check selectivity are lower-is-better
213
+ when read alongside recall. All three systems have verification recall 1.0 on
214
+ these scenarios.
215
+
216
+ | Corpus (scenarios) | System | Mean recall | Noise | Check selectivity |
217
+ |---|---|---:|---:|---:|
218
+ | Koda (9) | baseline-targeted (corrected) | 100.00% | 41.23% | 100.00% |
219
+ | Koda (9) | baseline-search | 71.90% | 71.55% | 100.00% |
220
+ | Koda (9) | catalog | 100.00% | 4.02% | 28.79% |
221
+ | Node/TS (3) | baseline-targeted (corrected) | 100.00% | 17.86% | 100.00% |
222
+ | Node/TS (3) | baseline-search | 91.67% | 30.64% | 83.33% |
223
+ | Node/TS (3) | catalog | 100.00% | 0.00% | 100.00% |
224
+ | Go (4) | baseline-targeted (corrected) | 93.75% | 28.94% | 100.00% |
225
+ | Go (4) | baseline-search | 83.33% | 26.01% | 100.00% |
226
+ | Go (4) | catalog | 100.00% | 5.88% | 66.67% |
227
+ | Mixed monorepo (4) | baseline-targeted (corrected) | 100.00% | 37.82% | 100.00% |
228
+ | Mixed monorepo (4) | baseline-search | 100.00% | 41.25% | 100.00% |
229
+ | Mixed monorepo (4) | catalog | 100.00% | 5.88% | 54.17% |
230
+ | Rust (4) | baseline-targeted (corrected) | 100.00% | 48.25% | 100.00% |
231
+ | Rust (4) | baseline-search | 100.00% | 32.81% | 83.33% |
232
+ | Rust (4) | catalog | 100.00% | 0.00% | 25.00% |
233
+ | Python (6) | baseline-targeted (corrected) | 100.00% | 46.70% | 100.00% |
234
+ | Python (6) | baseline-search | 100.00% | 23.68% | 41.67% |
235
+ | Python (6) | catalog | 100.00% | 0.00% | 41.67% |
236
+ | Aggregate (30) | baseline-targeted (corrected) | 99.17% | 38.83% | 100.00% |
237
+ | Aggregate (30) | baseline-search | 88.51% | 42.61% | 84.44% |
238
+ | Aggregate (30) | catalog | 100.00% | 2.77% | 46.42% |
239
+
240
+ Corrected targeted micro-recall is **100/101 = 0.9901** (mean recall 0.9917).
241
+ Its only missing truth fact is `component:default/ingest` in b-04. Search
242
+ micro-recall is **85/101 = 0.8416** (mean recall 0.8851); catalog micro- and
243
+ mean recall are both 1.0. These averages answer different questions and should
244
+ not be interchanged.
245
+
246
+ Search matches catalog recall in 21 scenarios: koda-04, koda-07, a-02, a-03,
247
+ b-01, b-03, b-04, c-01, c-02, c-03, c-04, e-01, e-02, e-03, e-04, f-01,
248
+ f-02, f-03, f-04, f-05 and f-06. It exceeds catalog recall in none.
249
+
250
+ The label correction removes the old recall gap against targeted retrieval.
251
+ Search recovers all expected facts in many import-related changes, but misses
252
+ some catalog identity/placement and authority facts. Its broad fixed point
253
+ also reads unrelated context, especially through common endpoint strings in
254
+ Koda. These numbers compare deterministic retrieval rules on small authored
255
+ corpora; they do not establish an agent productivity or token-saving benefit,
256
+ or an upper bound on search. Noise is judged by the catalog's graph closure,
257
+ so it is not independent evidence that every extra source fact is irrelevant.
258
+
259
+ `recallNotWorseThanTargetedBaseline` still **passes**: catalog and corrected
260
+ targeted recall tie in five corpora; catalog is higher in Go. Every other
261
+ applicable pre-registered gate passes. No threshold or scenario changed and
262
+ search has no new gate. Normalizing the vocabulary changes noise and deduplicates
263
+ fact counts, so old noise/size figures are not directly comparable. The largest
264
+ full inventory now has 86 facts (Koda), below the unchanged 100-fact floor.
265
+ Aggregate retrieval reduction is therefore **not measured** (`null`), with no
266
+ corpus included in that aggregate. Per-corpus raw ratios remain reported.
267
+
61
268
  ## Thresholds
62
269
 
63
270
  Pre-registered in `evals/thresholds.json`, dated, and not moved since:
@@ -101,6 +308,8 @@ Two additional gates use the same numbers:
101
308
  | `fixtures/a-node-ts-postgres` | 3 | single Node/TypeScript service, REST, Postgres, GitHub Actions |
102
309
  | `fixtures/b-go-multi-service` | 4 | multi-service Go, protobuf contracts, shared library, resources |
103
310
  | `fixtures/c-alt-monorepo` | 4 | a layout that is deliberately not `apps`+`packages`+`workers`, mixed runtimes, inconsistent pre-existing documentation |
311
+ | `fixtures/e-rust-workspace` | 4 | Cargo workspace, shared crate and its dependent, member manifest, local module, ambiguous crate names |
312
+ | `fixtures/f-python-workspace` | 6 | uv workspace, shared library and its dependent, member manifest, shadowed import, nested workspace, Alembic database, unittest |
104
313
 
105
314
  Scenarios are repository-scoped: a scenario only runs against the corpus it was
106
315
  written for, declared by a `repository` tag in a `.agentdoc-fixture.json` at the
@@ -117,7 +326,12 @@ cron; debug a production mismatch.
117
326
  The synthetic fixtures add: change an API contract; change a write path; change
118
327
  the only-writer rule; break an end-to-end journey; change an edge worker's flush
119
328
  schedule; follow stale documentation while debugging; change a subject vocabulary
120
- shared across runtimes; add an operation to a contract.
329
+ shared across runtimes; add an operation to a contract. The Rust workspace adds
330
+ a shared crate, the manifest that declares its dependents, a local module that
331
+ shares a name with another crate, and a hyphenated crate beside its underscored
332
+ twin. The Python workspace adds a shared library, a service manifest, an import
333
+ shadowed by a local module, a nested workspace member, an Alembic migration
334
+ bound to its database, and a job whose check is `python -m unittest discover`.
121
335
 
122
336
  ## Running it
123
337
 
@@ -157,9 +371,18 @@ Each was fixed in the engine, not by relaxing a threshold.
157
371
  - Ground truth is hand-authored per scenario. It is written from the
158
372
  repository's own manifests and contracts, not from the system's output, but it
159
373
  is still a human judgement about what matters.
160
- - The baseline is a model of unassisted exploration, not a record of a real
161
- agent session. It is generous to the baseline by construction (baseline-full
162
- is everything), which makes the reduction claim conservative.
374
+ - Search is one explicit model of exploration, not a record of an agent
375
+ session or an upper bound on what search can find. Its import and endpoint
376
+ closure deliberately errs toward reading more; different heuristics can
377
+ produce different recall and noise.
378
+ - The targeted baseline's old label mismatch is corrected. It still reads the
379
+ compiled graph, so its near-perfect recall does not measure unaided search.
380
+ The former 100%-versus-58% comparison remains withdrawn; see
381
+ [BENCHMARK.md](BENCHMARK.md#correction-the-fact-recall-comparison-is-withdrawn).
382
+ - Noise is judged by the catalog's one-hop graph closure, including for search.
383
+ Useful source facts absent from that graph can count as noise, and legitimate
384
+ transitive importer context can fall outside one hop. This is not an
385
+ independent ground truth for irrelevant work or an agent time/token metric.
163
386
  - `retrievalReduction` is a fact-count proxy, not a token count. It does not
164
387
  weight a long constraint file against a short ref.
165
388
  - Scenarios touch one or two files. A change spanning twenty files is not
package/docs/MIGRATION.md CHANGED
@@ -164,9 +164,27 @@ agentdoc check
164
164
  ```
165
165
 
166
166
  `init` writes `agentdoc/agentdoc.config.yaml`, the four central descriptor
167
- files, `agentdoc/journeys.yaml`, the documentation hierarchy
168
- (`docs/PRODUCT.md`, `docs/ARCHITECTURE.md`, `docs/CONSTRAINTS.md`,
169
- `docs/adr/`), the observations README, and a CI workflow. It never overwrites an
167
+ files, `agentdoc/journeys.yaml`, the documentation hierarchy, the observations
168
+ README, and a CI workflow. It never overwrites an existing file without
169
+ `--force`, and reports every path it wrote or skipped.
170
+
171
+ The documentation hierarchy goes where the configuration says. When
172
+ `agentdoc/agentdoc.config.yaml` already exists, `init` and `scaffold --write`
173
+ write `PRODUCT.md`, `ARCHITECTURE.md`, `CONSTRAINTS.md` and the ADR README to
174
+ the configured `docs.product`, `docs.architecture`, `docs.constraints` and
175
+ `docs.adrDir`. A role the configuration leaves out is not written anywhere and
176
+ is listed under `notConfigured`. Without a configuration, `init` uses
177
+ `agentdoc/docs/PRODUCT.md`, `agentdoc/docs/ARCHITECTURE.md`,
178
+ `agentdoc/docs/CONSTRAINTS.md` and `agentdoc/docs/adr/`, and records those
179
+ paths in the configuration it writes.
180
+
181
+ Authored AgentDoc material lives in `agentdoc/`; generated output lives in
182
+ `.agentdoc/`. `docs/` stays the product's own user documentation, which is
183
+ often published as-is (MkDocs, Docusaurus, GitHub Pages). To keep the skeleton
184
+ in `docs/`, or anywhere else, declare `docs.product`, `docs.architecture`,
185
+ `docs.constraints` and `docs.adrDir` in `agentdoc/agentdoc.config.yaml` before
186
+ running `init` or `scaffold --write`. A configuration that already declares
187
+ `docs.*` keeps its paths, `docs/...` included, and nothing ever moves an
170
188
  existing file.
171
189
 
172
190
  `scaffold` is a dry run unless `--write` is passed.
@@ -204,8 +222,10 @@ Then, in order:
204
222
  documentation, fix the code, or record an authority rule saying which class
205
223
  wins for that key.
206
224
  3. **Orphan documentation.** `documentation.orphanDocs`. For each: link it to a
207
- component via `context.docs`, or delete it. An orphan is a document nothing
208
- will ever route to.
225
+ Component, Resource or System via `context.docs`, `context.constraints` or
226
+ `context.runbooks`, or delete it. Conventional component documents and
227
+ configured repository-wide roles also count as routed. An orphan is a
228
+ document nothing will ever route to.
209
229
  4. **Missing documentation.** `documentation.undocumentedComponents` and
210
230
  `documentation.unverifiedComponents`. These are the components an agent will
211
231
  get wrong.
@@ -237,7 +257,7 @@ worth discovering before documenting it.
237
257
  | `PRODUCT.md` | what it is, for whom, what it does not do | describe components |
238
258
  | `ARCHITECTURE.md` | why the shape is what it is | enumerate components or contracts |
239
259
  | `CONSTRAINTS.md` | repository-wide rules, one rule one reason one check | restate a component's constraints |
240
- | `docs/adr/NNNN-*.md` | one decision, its consequences, superseded not edited | be a design document |
260
+ | `<docs.adrDir>/NNNN-*.md` | one decision, its consequences, superseded not edited | be a design document |
241
261
  | `<component>/CONSTRAINTS.md` | rules for that component only | duplicate a repository-wide rule |
242
262
  | `<component>/README.md` | how to run it, what it is for | restate the catalog |
243
263
  | runbooks | operational procedure | contain architectural decisions |
@@ -89,6 +89,12 @@ record is complete by construction rather than by remembering to register files.
89
89
  A change to any input changes the hash; `check` refuses a graph whose hash
90
90
  disagrees.
91
91
 
92
+ Paths git ignores are not inputs (SPEC §7.2): the tracked read path, probes and
93
+ listings all treat them as absent, so local build output in an ignored
94
+ directory cannot move the hash. Tracked files always count, even when they
95
+ match an ignore pattern. Outside git the input set is the filesystem minus
96
+ `discovery.generatedDirs`.
97
+
92
98
  ## Secrets
93
99
 
94
100
  Two independent layers, because derivation can synthesise a secret-shaped string
package/docs/QUERY.md CHANGED
@@ -15,12 +15,29 @@ Resolution order:
15
15
  1. an exact entity ref;
16
16
  2. a documentation or constraint file a descriptor links, even outside the
17
17
  component's source root;
18
- 3. a directory that is exactly a unit root;
19
- 4. the longest unit root that is a prefix of the path;
20
- 5. a canonical contract file, which routes to the API entity that claims it.
18
+ 3. the component whose source root most specifically owns the path: a
19
+ directory that is exactly a unit root, or else the longest unit root
20
+ containing it;
21
+ 4. a canonical contract file, which routes to the API entity that claims it;
22
+ 5. a root component (a descriptor at the repository root, `sourcePath: "."`),
23
+ which owns every file no step above claims.
24
+
25
+ Paths are compared in one canonical, repository-relative form, the one git uses
26
+ for pathspecs and `ls-files`: `./vite.config.ts`, `vite.config.ts` and
27
+ `tooling//config.ts` are the same paths as `vite.config.ts` and
28
+ `tooling/config.ts`. Ownership is decided on whole path segments, never on a
29
+ raw string prefix, so a unit at `src` owns `src/x.ts` and never `src2/x.ts`.
30
+ When several unit roots contain a path, the longest one wins, as in a routing
31
+ table or a CODEOWNERS file. The repository root is the shortest possible root,
32
+ so a nested component always takes precedence over a root component. A path
33
+ that is absolute or climbs out of the repository with `..` resolves to nothing.
34
+ A needle shaped like an entity ref (`kind:namespace/name`) that names no entity
35
+ is unresolved; it is never treated as a file a root component owns.
21
36
 
22
37
  If nothing resolves, the command fails with `AGENTDOC_REF_UNRESOLVED` and exits
23
38
  non-zero. It never falls back to "the nearest component" or to a global dump.
39
+ Without a root descriptor, a file outside every unit root stays unresolved: the
40
+ repository root is owned only when a descriptor claims it.
24
41
 
25
42
  ## Freshness refusal
26
43
 
@@ -84,6 +101,17 @@ withheld.
84
101
  endpoint is proved by running their suites, so `verification` for an API subject
85
102
  is the union over the provider and its consumers.
86
103
 
104
+ **A component change carries its dependents' checks.** Verification follows the
105
+ reverse `buildDependsOn` / `testDependsOn` closure (including their inverse
106
+ `buildDependencyOf` / `testDependencyOf` spellings). Build dependents contribute
107
+ all tiers; a route containing a test dependency contributes only `unit` checks.
108
+ If an all-build route also reaches that dependent, all tiers apply. Cycles are
109
+ visited safely. The subject's own checks come first, then the dependents', with
110
+ each group sorted by check ID. A check shared by multiple components appears
111
+ once by ID; identical command text in different component checks remains scoped
112
+ by its ID and `configPaths`. The usual verification budget and truncation fields
113
+ apply to the combined list.
114
+
87
115
  **Journey peers are named.** If a component appears in a journey, the other
88
116
  components in that journey are in scope and are listed explicitly rather than
89
117
  left for the agent to rediscover.
@@ -127,9 +155,25 @@ the slice is never mistaken for the whole.
127
155
  | assertions | 25 |
128
156
  | provenance | 20 |
129
157
  | contracts | 10 |
158
+ | docs (markdown) | 12 |
159
+ | constraints (markdown) | uncapped |
160
+ | runbooks (markdown) | 12 |
130
161
 
131
162
  ## Markdown rendering
132
163
 
164
+ Component, Resource and System descriptors may carry `spec.context`. Their
165
+ documentation, constraints and runbooks appear under `docs`, `CONSTRAINTS` and
166
+ `runbooks`, respectively. Components also include conventional files discovered
167
+ in their source root. API and Domain schemas do not allow `context`.
168
+
169
+ Markdown docs and runbooks default to 12 paths each. Constraints are uncapped by
170
+ default so critical routing context is preserved. Set an explicit cap with, for
171
+ example, `--budget docs=1,constraints=5,runbooks=1`. A shortened line ends with
172
+ `[truncated: 1 of 2]`, reporting the number shown and the total. JSON retains the
173
+ complete arrays under `constraints.docs`, `constraints.constraints` and
174
+ `constraints.runbooks`; these markdown-only caps do not shorten the JSON arrays
175
+ or `entity.spec.context`.
176
+
133
177
  `--md` (the default) produces a compact block suitable for a prompt or a human:
134
178
 
135
179
  ```
@@ -164,10 +208,37 @@ contracts, documentation, resources, verification and catalog sources; whether
164
208
  the graph will be invalidated; the recommended verification commands; any open
165
209
  conflicts on the affected subjects; and related journeys.
166
210
 
167
- Reachability rules: a contract change reaches its provider and every consumer; a
168
- component change reaches its transitive dependents two hops out, and everything
169
- connected to it by a runtime call or a schedule. Two hops is deliberate — a
170
- third hop is where the graph stops being a routing aid and becomes a dump.
211
+ Changed paths resolve to components with the same rules as `query`: a `./`
212
+ prefix is ignored, the longest owning unit root wins, and a root component
213
+ receives the files no other component owns. A file nothing owns is listed in
214
+ `unmatchedFiles`.
215
+
216
+ Reachability rules: a contract change reaches its provider and every consumer;
217
+ a Resource change reaches components bound to that exact Resource. A migration
218
+ change (including Alembic `alembic/versions`) reports its changed path and the
219
+ logical databases bound to its enclosing component through existing
220
+ `usesResource` / `resourceUsedBy` relations. Migration authority alone never
221
+ establishes ownership or invents a Resource binding.
222
+
223
+ A component change reaches its reverse build/test dependency closure, plus
224
+ direct runtime-call and schedule peers. Dependencies are not affected just
225
+ because the changed component uses them; they remain explicitly typed context
226
+ in query's `relations`. The dependent closure has no hop cutoff, so a long chain
227
+ cannot silently omit an affected consumer.
228
+
229
+ Changing a workflow file follows recorded verification `configPaths` to the
230
+ components its checks cover. Those components seed the reverse build/test
231
+ dependency closure just like directly changed components. Matching repository
232
+ gates appear in `affectedGates`; a workflow matched by any check or gate is
233
+ removed from `unmatchedFiles`.
234
+
235
+ Impact uses the same verification selection and ordering as query, including
236
+ the test-only tier restriction. Directly affected components' checks precede
237
+ their dependents' checks, followed by the matching repository gates. The
238
+ combined `recommendedVerification` array is capped at 25;
239
+ `recommendedVerificationTotal` gives the uncapped count including gates and
240
+ `recommendedVerificationTruncated` reports overflow. Query's default cap is 12
241
+ and can be adjusted with `--budget verification=...`.
171
242
 
172
243
  ## Using it in a prompt
173
244
 
package/docs/SPEC.md CHANGED
@@ -195,6 +195,34 @@ Every byte flows through the read tracker, so the record is complete by
195
195
  construction. A consumer must refuse a graph whose `inputHash` or `dirty` flag
196
196
  disagrees with the current checkout. There is no fallback.
197
197
 
198
+ **The input set is the checkout as git sees it.** Reads, probes, listings and
199
+ walks — and therefore discovery as well as the hash — never see a path git
200
+ ignores: a local build writing into an ignored directory (`.astro/`, `build/`,
201
+ `*.log`) changes nothing, and a graph compiled after that build is
202
+ byte-identical to the one a clean checkout or CI produces. An ignored path is
203
+ treated as absent: probed as `false`, left out of listings and walks, and a
204
+ tracked read of it fails like a read of a missing file. Git is the oracle
205
+ (`git status --ignored=matching`, one call per compile, shared with the dirty
206
+ check), so its documented semantics apply exactly:
207
+
208
+ - a tracked file stays an input even when it matches an ignore pattern;
209
+ - nested `.gitignore` files, `$GIT_DIR/info/exclude` and the user's
210
+ `core.excludesFile` apply with gitignore(5) precedence, negations included,
211
+ and a file whose parent directory is excluded cannot be re-included;
212
+ - `core.excludesFile` counts on purpose. An ignored untracked file is absent
213
+ from every clean checkout whatever rule ignored it, so excluding it only ever
214
+ brings the local input set closer to CI's — and it keeps the hash in step
215
+ with `git status`, which drives `dirty`;
216
+ - an ignored *ancestor* of the project root (a project nested in a larger
217
+ checkout) is not honoured: the directory the compiler is pointed at is
218
+ compiled whole.
219
+
220
+ `discovery.generatedDirs` (or its built-in list) is excluded in addition, in a
221
+ git checkout or not. Without git — no repository, no git binary — there is no
222
+ oracle: the input set is the filesystem minus those directories, and
223
+ `.gitignore` files are not interpreted. `check` recomputes the hash by running
224
+ the same compile, so compile and check cannot disagree on the input set.
225
+
198
226
  The recorded `commit` is **provenance, not a gate**: committing the graph changes
199
227
  `HEAD`, so a commit-equality check could never be satisfied by a committed graph.
200
228
  What determines whether the graph still describes the repository is the input
@@ -235,7 +263,31 @@ that references the contract file from a source or build-configuration file.
235
263
 
236
264
  The core has no knowledge of folder names. A directory is a unit when it holds
237
265
  a component descriptor, when an adapter recognises it from a workspace file, or
238
- when it is listed in `discovery.supplementalRoots`.
266
+ when it is listed in `discovery.supplementalRoots`. Discovery sees only the
267
+ input set of §7.2: a descriptor or manifest under a git-ignored path is not
268
+ discovered.
269
+
270
+ `init` configures the recursive component descriptor glob `*/**/agentdoc.yaml`,
271
+ so descriptors for adapter-discovered units and units added later are loaded
272
+ without regenerating a path list. The scaffold layout scan supplements its
273
+ shallow marker heuristics with adapter discovery: declared workspace members
274
+ under `examples`, `example`, `test`, or `tests`, or beyond the scan depth, remain
275
+ units. Those names alone do not create units. Scaffold selects artifact types
276
+ from adapter shape: deployable (including both shapes) → `service`, importable
277
+ only → `library`, neither → `other`. Placement remains a human choice.
278
+
279
+ `discovery.notUnitRoots` excludes each listed root and its whole subtree from
280
+ inferred units (adapter markers, adapter `roots()`, and supplemental roots) and
281
+ from scaffold layout scans and descriptor proposals, in both dry run and
282
+ `--write`. The defaults are `agentdoc`, `docs`, `scripts`, `node_modules`, and
283
+ `.github`; a configured list replaces them, and `[]` disables these exclusions.
284
+ An existing authored component descriptor strictly below an excluded root still
285
+ claims that directory as a unit. Its own root must supply the usual adapter
286
+ evidence for its artifact type; the exclusion does not waive corroboration.
287
+ Scaffold preserves that descriptor and never proposes or writes one there.
288
+ A descriptor exactly at an excluded root is refused with
289
+ `AGENTDOC_COMPONENT_UNCORROBORATED` naming the exclusion: remove that root from
290
+ `notUnitRoots` or move the descriptor.
239
291
 
240
292
  - Every discovered unit must have exactly one descriptor at its root, or
241
293
  `AGENTDOC_COVERAGE_ZERO`.
@@ -269,7 +321,7 @@ only the codes in `ACCEPTABLE_WARNING_CODES` may be accepted.
269
321
  | command | contract |
270
322
  |---|---|
271
323
  | `init [--force]` | write a configuration and documentation skeleton; never clobber unless `--force` |
272
- | `scaffold [--write]` | generate descriptors from provable evidence; dry run by default |
324
+ | `scaffold [--write]` | generate descriptors from provable evidence; dry run by default; never clobbers |
273
325
  | `audit` | classify every fact; runs even when the repository cannot compile |
274
326
  | `validate` | sources, schemas, semantic checks; no artifact written |
275
327
  | `compile [--require-clean]` | deterministic build, atomic replace |
@@ -280,6 +332,21 @@ only the codes in `ACCEPTABLE_WARNING_CODES` may be accepted.
280
332
  | `pin-evidence [--json]` | check the digests pinned by reviewed overrides |
281
333
  | `doctor` | environment self-check: node version, schema bundle, git, writability |
282
334
 
335
+ `init` and `scaffold` write the documentation skeleton to the paths an
336
+ existing configuration declares in `docs.product`, `docs.architecture`,
337
+ `docs.constraints` and `docs.adrDir`, read through the same loader as
338
+ `compile` and `audit`. A role the configuration omits is not written and is
339
+ reported in `notConfigured`; nothing falls back to a default. Only when no
340
+ configuration exists does `init` use the defaults `agentdoc/docs/PRODUCT.md`,
341
+ `agentdoc/docs/ARCHITECTURE.md`, `agentdoc/docs/CONSTRAINTS.md` and
342
+ `agentdoc/docs/adr/`, and record them in the `docs:` block of the configuration
343
+ it writes. Authored AgentDoc material lives in `agentdoc/`; generated output
344
+ lives in `.agentdoc/`. `docs/` is left to the product's own user documentation:
345
+ `init` and `scaffold` write there only when a configuration declares `docs.*`
346
+ paths under it, and they never move an existing file. A target outside the
347
+ repository, including through a symlink, fails with `AGENTDOC_PATH_ESCAPE`
348
+ before anything is written.
349
+
283
350
  Exit codes: `0` ok, `1` failure, `2` usage.
284
351
 
285
352
  ## 13. Error codes
@@ -21,7 +21,7 @@ jobs:
21
21
  with: { node-version: '24' }
22
22
  - name: Install the agentdoc CLI
23
23
  # Unpinned tracks main. For CI reproducibility pin a tag or commit:
24
- # github:lbframe/agent-doc-system#v1.1.0
24
+ # github:lbframe/agent-doc-system#v1.3.0
25
25
  # A compiler upgrade intentionally invalidates committed graphs.
26
26
  run: npm install -g github:lbframe/agent-doc-system
27
27
  - name: Validate and compile