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/evals/harness.mjs CHANGED
@@ -4,14 +4,8 @@
4
4
  // an agent through it retrieve the right context, and less noise than unassisted
5
5
  // repository exploration".
6
6
  //
7
- // Two retrieval systems are measured on the same scenarios:
8
- //
9
- // baseline what an agent gets by exploring the repository the obvious way:
10
- // every documentation file, every descriptor, every source file
11
- // under the touched component. This is the honest shape of "just
12
- // read the repo", and it is what the catalog has to beat.
13
- //
14
- // catalog what `agentdoc query` returns for the same scenario.
7
+ // Compare catalog routing with a graph-assisted targeted reference and a
8
+ // working-tree search baseline. The full graph is only a size yardstick.
15
9
  //
16
10
  // Retrieval-set overlap against a per-scenario ground truth gives precision,
17
11
  // recall, critical-constraint recall, verification-command recall and noise.
@@ -28,31 +22,35 @@ import { AgentDocError, CODES } from "../core/codes.mjs";
28
22
  import { queryContext, ContextIndex } from "../core/query.mjs";
29
23
  import { impact } from "../core/impact.mjs";
30
24
 
25
+ import { fact, addRelationFacts, addGraphRelations } from "./facts.mjs";
26
+ import { searchContext } from "./baseline-search.mjs";
27
+
31
28
  const HERE = path.dirname(fileURLToPath(import.meta.url));
32
29
 
33
30
  // A retrieved item is a "fact reference" the agent would have to understand:
34
31
  // an entity ref, a constraint path, a verification command, a contract ref.
35
32
  export function factsFromContext(ctx) {
36
33
  const out = new Set();
37
- out.add(ctx.entity.ref);
38
- for (const r of ctx.relations.items) out.add(r.type + ":" + r.other);
39
- for (const p of ctx.journeyPeers || []) out.add(p);
40
- for (const s2 of ctx.placement.systems) out.add(s2);
41
- for (const d2 of ctx.placement.domains) out.add(d2);
42
- for (const a of ctx.apis.provided) out.add("provides:" + a.api);
43
- for (const a of ctx.apis.consumed) out.add("consumes:" + a.api);
44
- for (const c of ctx.apis.consumers || []) out.add("consumer:" + c);
45
- if (ctx.apis.provider) out.add("provider:" + ctx.apis.provider);
46
- for (const r of ctx.resources.used) out.add("uses:" + r);
47
- for (const r of ctx.resources.usedBy) out.add("usedBy:" + r);
48
- for (const e of ctx.events.items) out.add("event:" + e.pattern);
49
- for (const e of ctx.externalDependencies.items) out.add("external:" + e.name);
50
- for (const v of ctx.verification.items) out.add("verify:" + v.command);
51
- for (const p of ctx.constraints.constraints) out.add("constraint:" + p);
52
- for (const d of ctx.constraints.docs) out.add("doc:" + d);
53
- for (const r of ctx.constraints.runbooks) out.add("runbook:" + r);
54
- for (const c of ctx.authority.conflicts.items) out.add("conflict:" + c.key);
55
- for (const o of ctx.authority.observations) out.add("observation:" + o.id);
34
+ out.add(fact("ref", ctx.entity.ref));
35
+ for (const n of ctx.neighbours) out.add(fact("ref", n.ref));
36
+ for (const r of ctx.relations.items) addRelationFacts(out, r.type, r.other, r.direction);
37
+ for (const p of ctx.journeyPeers || []) out.add(fact("ref", p));
38
+ for (const s2 of ctx.placement.systems) out.add(fact("ref", s2));
39
+ for (const d2 of ctx.placement.domains) out.add(fact("ref", d2));
40
+ for (const a of ctx.apis.provided) out.add(fact("provides", a.api));
41
+ for (const a of ctx.apis.consumed) out.add(fact("consumes", a.api));
42
+ for (const c of ctx.apis.consumers || []) out.add(fact("consumer", c));
43
+ if (ctx.apis.provider) out.add(fact("provider", ctx.apis.provider));
44
+ for (const r of ctx.resources.used) out.add(fact("uses", r));
45
+ for (const r of ctx.resources.usedBy) out.add(fact("usedBy", r));
46
+ for (const e of ctx.events.items) out.add(fact("event", e.pattern));
47
+ for (const e of ctx.externalDependencies.items) out.add(fact("external", e.name));
48
+ for (const v of ctx.verification.items) out.add(fact("verify", v.command));
49
+ for (const p of ctx.constraints.constraints) out.add(fact("constraint", p));
50
+ for (const d of ctx.constraints.docs) out.add(fact("doc", d));
51
+ for (const r of ctx.constraints.runbooks) out.add(fact("runbook", r));
52
+ for (const c of ctx.authority.conflicts.items) out.add(fact("conflict", c.key));
53
+ for (const o of ctx.authority.observations) out.add(fact("observation", o.id));
56
54
  return out;
57
55
  }
58
56
 
@@ -61,25 +59,28 @@ export function factsFromContext(ctx) {
61
59
  // resource, an API boundary, or an event subject it publishes or consumes.
62
60
  export function criticalFromContext(ctx) {
63
61
  const out = new Set();
64
- for (const e of ctx.events.items) out.add("event:" + e.pattern);
65
- for (const p of ctx.constraints.constraints) out.add("constraint:" + p);
66
- for (const c of ctx.authority.conflicts.items) out.add("conflict:" + c.key);
67
- for (const o of ctx.authority.observations) out.add("observation:" + o.id);
68
- for (const r of ctx.resources.used) out.add("uses:" + r);
69
- for (const r of ctx.resources.usedBy) out.add("usedBy:" + r);
70
- for (const a of ctx.apis.consumed) out.add("consumes:" + a.api);
71
- for (const s2 of ctx.placement.systems) out.add(s2);
72
- for (const d2 of ctx.placement.domains) out.add(d2);
73
- for (const a of ctx.apis.provided) out.add("provides:" + a.api);
74
- for (const c of ctx.apis.consumers || []) out.add("consumer:" + c);
62
+ for (const e of ctx.events.items) out.add(fact("event", e.pattern));
63
+ for (const p of ctx.constraints.constraints) out.add(fact("constraint", p));
64
+ for (const c of ctx.authority.conflicts.items) out.add(fact("conflict", c.key));
65
+ for (const o of ctx.authority.observations) out.add(fact("observation", o.id));
66
+ for (const r of ctx.resources.used) out.add(fact("uses", r));
67
+ for (const r of ctx.resources.usedBy) out.add(fact("usedBy", r));
68
+ for (const a of ctx.apis.consumed) out.add(fact("consumes", a.api));
69
+ for (const s2 of ctx.placement.systems) out.add(fact("ref", s2));
70
+ for (const d2 of ctx.placement.domains) out.add(fact("ref", d2));
71
+ for (const a of ctx.apis.provided) out.add(fact("provides", a.api));
72
+ for (const c of ctx.apis.consumers || []) out.add(fact("consumer", c));
75
73
  return out;
76
74
  }
77
75
 
78
76
  export function verificationFromContext(ctx) {
79
- return new Set(ctx.verification.items.map((v) => "verify:" + v.command));
77
+ // Commands such as `go test ./...` recur in multiple modules. Scenarios can
78
+ // require a scoped check ID as well as a command to measure dependent checks
79
+ // without letting the library's identical command satisfy the expectation.
80
+ return new Set(ctx.verification.items.flatMap((v) => [fact("verify", v.command), fact("verify-id", v.id)]));
80
81
  }
81
82
 
82
- // ── baseline: unassisted repository exploration ────────────────────────
83
+ // ── baseline-targeted: graph-assisted reference ────────────────────────
83
84
  export function baselineContext(graph, touch) {
84
85
  const idx = new ContextIndex(graph);
85
86
  const seeds = touch.map((t) => idx.resolve(t)).filter(Boolean);
@@ -90,71 +91,73 @@ export function baselineContext(graph, touch) {
90
91
  const inSeed = seeds.includes(e);
91
92
  const underRoot = roots.some((r) => e.ref && r && d.sourcePath && (d.sourcePath === r || d.sourcePath.startsWith(r + "/")));
92
93
  if (!inSeed && !underRoot) continue;
93
- out.add(e.ref);
94
- for (const r of graph.relations) {
95
- if (r.sourceRef === e.ref) out.add(r.type + ":" + r.targetRef);
96
- if (r.targetRef === e.ref) out.add(r.type + ":" + r.sourceRef);
97
- }
94
+ out.add(fact("ref", e.ref));
95
+ if (e.kind === "API") out.add(fact("provides", e.ref));
96
+ addGraphRelations(out, graph, e.ref);
98
97
  for (const ev of graph.interfaces.events) {
99
- if (ev.producers.includes(e.ref) || ev.consumers.includes(e.ref)) out.add("event:" + ev.pattern);
100
- }
101
- for (const x of graph.externalDependencies) if (x.componentRef === e.ref) out.add("external:" + x.name);
102
- for (const v of graph.verification) if (v.componentRefs.includes(e.ref)) out.add("verify:" + v.command);
103
- for (const c of graph.conflicts) if (c.subject === e.ref) out.add("conflict:" + c.key);
104
- for (const o of graph.observations) if (o.facts.some((f) => f.subject === e.ref)) out.add("observation:" + o.id);
105
- for (const p of d.docs || []) out.add("doc:" + p);
106
- for (const p of d.constraints || []) out.add("constraint:" + p);
107
- for (const p of d.runbooks || []) out.add("runbook:" + p);
108
- for (const r of graph.relations) {
109
- if (r.sourceRef === e.ref || r.targetRef === e.ref) out.add(r.type + ":" + (r.sourceRef === e.ref ? r.targetRef : r.sourceRef));
98
+ if (ev.producers.includes(e.ref) || ev.consumers.includes(e.ref)) out.add(fact("event", ev.pattern));
110
99
  }
100
+ for (const x of graph.externalDependencies) if (x.componentRef === e.ref) out.add(fact("external", x.name));
101
+ for (const v of graph.verification) if (v.componentRefs.includes(e.ref)) out.add(fact("verify", v.command));
102
+ for (const c of graph.conflicts) if (c.subject === e.ref) out.add(fact("conflict", c.key));
103
+ for (const o of graph.observations) if (o.facts.some((f) => f.subject === e.ref)) out.add(fact("observation", o.id));
104
+ for (const p of d.docs || []) out.add(fact("doc", p));
105
+ for (const p of d.constraints || []) out.add(fact("constraint", p));
106
+ for (const p of d.runbooks || []) out.add(fact("runbook", p));
111
107
  }
112
- // A repository-wide documentation sweep is what an agent does when the
113
- // routing is not available: read everything that looks like documentation.
114
- for (const f of new Set([...repoDocPaths(graph)])) out.add("repodoc:" + f);
108
+ // Preserve the original targeted reference's repository-wide doc sweep,
109
+ // now using the same labels as query rather than unmatched repodoc labels.
110
+ for (const f of new Set([...repoDocPaths(graph)])) out.add(fact("doc", f));
115
111
  return out;
116
112
  }
117
113
 
118
114
  function repoDocPaths(graph) {
119
115
  const out = new Set();
120
116
  for (const e of graph.entities) {
121
- for (const p of (e.derived || {}).docs || []) out.add(p);
117
+ for (const p of (e.derived || {}).docs || []) out.add(fact("ref", p));
122
118
  }
123
- for (const cfgp of graph.verification) for (const p of cfgp.configPaths || []) out.add(p);
119
+ for (const cfgp of graph.verification) for (const p of cfgp.configPaths || []) out.add(fact("ref", p));
124
120
  return [...out].sort();
125
121
  }
126
122
 
127
- // Baseline verification recall: without routing, an agent runs the union of
128
- // every check in the repository (or nothing). The realistic reading of
129
- // "unassisted" is the union, since no routing exists to narrow it.
130
- // The whole-repository baseline: what an agent that has no routing ends up
131
- // holding. Every entity, every relation, every subject, every check, every
132
- // document. This is the honest cost of "just read the repository".
123
+ // Whole-graph fact inventory: a size yardstick, not unaided exploration.
133
124
  export function fullRepositoryContext(graph) {
134
125
  const out = new Set();
135
126
  for (const e of graph.entities) {
136
- out.add(e.ref);
127
+ out.add(fact("ref", e.ref));
128
+ if (e.kind === "API") out.add(fact("provides", e.ref));
137
129
  const d = e.derived || {};
138
- for (const p of d.docs || []) out.add("doc:" + p);
139
- for (const p of d.constraints || []) out.add("constraint:" + p);
140
- for (const p of d.runbooks || []) out.add("runbook:" + p);
130
+ for (const p of d.docs || []) out.add(fact("doc", p));
131
+ for (const p of d.constraints || []) out.add(fact("constraint", p));
132
+ for (const p of d.runbooks || []) out.add(fact("runbook", p));
141
133
  }
142
- for (const r of graph.relations) out.add(r.type + ":" + (r.sourceRef === undefined ? "" : r.targetRef));
143
- for (const r of graph.relations) out.add(r.type + ":" + r.sourceRef);
144
- for (const ev of graph.interfaces.events) out.add("event:" + ev.pattern);
145
- for (const x of graph.externalDependencies) out.add("external:" + x.name);
146
- for (const x of graph.capabilities) out.add("capability:" + x.name);
147
- for (const v of graph.verification) out.add("verify:" + v.command);
148
- for (const c of graph.conflicts) out.add("conflict:" + c.key);
149
- for (const o of graph.observations) out.add("observation:" + o.id);
150
- for (const p of repoDocPaths(graph)) out.add("repodoc:" + p);
134
+ for (const e of graph.entities) addGraphRelations(out, graph, e.ref);
135
+ for (const ev of graph.interfaces.events) out.add(fact("event", ev.pattern));
136
+ for (const x of graph.externalDependencies) out.add(fact("external", x.name));
137
+ for (const x of graph.capabilities) out.add(fact("capability", x.name));
138
+ for (const v of graph.verification) out.add(fact("verify", v.command));
139
+ for (const c of graph.conflicts) out.add(fact("conflict", c.key));
140
+ for (const o of graph.observations) out.add(fact("observation", o.id));
141
+ for (const p of repoDocPaths(graph)) out.add(fact("doc", p));
151
142
  return out;
152
143
  }
153
144
 
154
- // Baseline verification: without routing, an agent runs the union of every
155
- // check in the repository, because nothing narrows it.
145
+ // The historical targeted reference runs every registered check and gate.
156
146
  function baselineVerification(graph) {
157
- return new Set(graph.verification.map((v) => "verify:" + v.command));
147
+ return new Set([...graph.verification, ...graph.gates].flatMap((v) => [fact("verify", v.command), fact("verify-id", v.id)]));
148
+ }
149
+
150
+ export function verificationRecall(retrieved, expected) {
151
+ const { hit, total } = score(retrieved, expected);
152
+ return total ? hit / total : 1;
153
+ }
154
+
155
+ // Count scoped check IDs, not commands: identical commands in different
156
+ // components are separate checks. Gates are repository-scoped checks too.
157
+ export function checkSelectivity(graph, recommendedIds) {
158
+ const all = new Set([...graph.verification, ...graph.gates].map((v) => v.id));
159
+ const recommended = [...all].filter((id) => recommendedIds.has(id)).length;
160
+ return all.size ? recommended / all.size : 1;
158
161
  }
159
162
 
160
163
  function score(retrieved, truth) {
@@ -167,13 +170,6 @@ function score(retrieved, truth) {
167
170
  return { hit, total: truth.size, missed, precision: truth.size ? hit / truth.size : 1 };
168
171
  }
169
172
 
170
- function noiseRatio(retrieved, truth) {
171
- if (!retrieved.size) return 0;
172
- let n = 0;
173
- for (const r of retrieved) if (!truth.has(r)) n++;
174
- return n / retrieved.size;
175
- }
176
-
177
173
  // ── Irrelevance, measured against reachability rather than against the
178
174
  // ground truth.
179
175
  //
@@ -206,35 +202,25 @@ export function relevantClosure(graph, touch) {
206
202
  if (seeds2.has(r.targetRef)) refs.add(r.sourceRef);
207
203
  }
208
204
  const out = new Set();
209
- for (const ref of refs) out.add(ref);
210
- for (const r of graph.relations) {
211
- if (refs.has(r.sourceRef)) out.add(r.type + ":" + r.targetRef);
212
- if (refs.has(r.targetRef)) out.add(r.type + ":" + r.sourceRef);
213
- }
205
+ for (const ref of refs) out.add(fact("ref", ref));
206
+ for (const ref of refs) addGraphRelations(out, graph, ref);
214
207
  for (const e of graph.entities) {
215
208
  if (!refs.has(e.ref)) continue;
209
+ if (e.kind === "API") out.add(fact("provides", e.ref));
216
210
  const d = e.derived || {};
217
- for (const p of d.docs || []) out.add("doc:" + p);
218
- for (const p of d.constraints || []) out.add("constraint:" + p);
219
- for (const p of d.runbooks || []) out.add("runbook:" + p);
211
+ for (const p of d.docs || []) out.add(fact("doc", p));
212
+ for (const p of d.constraints || []) out.add(fact("constraint", p));
213
+ for (const p of d.runbooks || []) out.add(fact("runbook", p));
220
214
  for (const ev of graph.interfaces.events) {
221
215
  if (ev.producers.includes(e.ref) || ev.consumers.includes(e.ref) || (d.sourcePath && ev.contract.ref.startsWith(d.sourcePath + "/"))) {
222
- out.add("event:" + ev.pattern);
216
+ out.add(fact("event", ev.pattern));
223
217
  }
224
218
  }
225
- for (const x of graph.externalDependencies) if (x.componentRef === e.ref) out.add("external:" + x.name);
226
- for (const x of graph.capabilities) if (x.componentRef === e.ref) out.add("capability:" + x.name);
227
- for (const v of graph.verification) if (v.componentRefs.includes(e.ref)) out.add("verify:" + v.command);
228
- for (const c of graph.conflicts) if (c.subject === e.ref) out.add("conflict:" + c.key);
229
- for (const o of graph.observations) if (o.facts.some((f) => f.subject === e.ref)) out.add("observation:" + o.id);
230
- for (const r of graph.relations) {
231
- if (r.type === "usesResource" && r.sourceRef === e.ref) out.add("uses:" + r.targetRef);
232
- if (r.type === "usesResource" && r.targetRef === e.ref) out.add("usedBy:" + r.sourceRef);
233
- if (r.type === "consumesApi" && r.sourceRef === e.ref) out.add("consumes:" + r.targetRef);
234
- if (r.type === "consumesApi" && r.targetRef === e.ref) out.add("consumer:" + r.sourceRef);
235
- if (r.type === "providesApi" && r.sourceRef === e.ref) out.add("provides:" + r.targetRef);
236
- if (r.type === "providesApi" && r.targetRef === e.ref) out.add("provider:" + r.sourceRef);
237
- }
219
+ for (const x of graph.externalDependencies) if (x.componentRef === e.ref) out.add(fact("external", x.name));
220
+ for (const x of graph.capabilities) if (x.componentRef === e.ref) out.add(fact("capability", x.name));
221
+ for (const v of graph.verification) if (v.componentRefs.includes(e.ref)) out.add(fact("verify", v.command));
222
+ for (const c of graph.conflicts) if (c.subject === e.ref) out.add(fact("conflict", c.key));
223
+ for (const o of graph.observations) if (o.facts.some((f) => f.subject === e.ref)) out.add(fact("observation", o.id));
238
224
  }
239
225
  return out;
240
226
  }
@@ -271,6 +257,10 @@ export function runRoutingEval(root, { json = false } = {}) {
271
257
  const retrieved = new Set();
272
258
  const critical = new Set();
273
259
  const verif = new Set();
260
+ const recommendedIds = new Set();
261
+ const collectChecks = (ctx) => {
262
+ for (const v of [...ctx.verification.items, ...ctx.globalGates]) recommendedIds.add(v.id);
263
+ };
274
264
  let unresolved = 0;
275
265
  for (const t of s.touch) {
276
266
  let ctx;
@@ -285,28 +275,39 @@ export function runRoutingEval(root, { json = false } = {}) {
285
275
  for (const x of factsFromContext(ctx)) retrieved.add(x);
286
276
  for (const x of criticalFromContext(ctx)) critical.add(x);
287
277
  for (const x of verificationFromContext(ctx)) verif.add(x);
278
+ collectChecks(ctx);
288
279
  }
289
280
  // Change impact is part of routing: it is what tells the agent which other
290
281
  // components a change reaches. Its output is legitimately part of context.
291
282
  const im = impact(graph, s.touch);
283
+ for (const v of im.recommendedVerification) recommendedIds.add(v.id);
292
284
  for (const c of im.affectedComponents) {
293
285
  const ctx = queryContext(graph, c.ref, {});
294
286
  for (const x of factsFromContext(ctx)) retrieved.add(x);
287
+ collectChecks(ctx);
295
288
  }
296
289
 
297
290
  const base = baselineContext(graph, s.touch);
298
291
  const baseV = baselineVerification(graph);
299
292
  const full = fullRepositoryContext(graph);
293
+ const search = searchContext(root, s.touch);
294
+ const searchAll = score(search.facts, new Set(s.truth));
295
+ // Retrieval has no graph access. Only scoring maps discovered scoped
296
+ // commands to the registered check IDs for a common selectivity denominator.
297
+ const searchIds = new Set([...graph.verification, ...graph.gates]
298
+ .filter((v) => search.checks.some((c) => c.command === v.command &&
299
+ (c.componentRef === null ? !(v.componentRefs || []).length : (v.componentRefs || []).includes(c.componentRef))))
300
+ .map((v) => v.id));
301
+ const searchV = new Set(search.checks.map((c) => fact("verify", c.command)));
302
+ for (const id of searchIds) searchV.add(fact("verify-id", id));
300
303
 
301
304
  const closure = relevantClosure(graph, s.touch);
302
- const baseClosure = relevantClosure(graph, s.touch);
305
+
303
306
  const all = score(retrieved, new Set(s.truth));
304
307
  const baseAll = score(base, new Set(s.truth));
305
308
  const crit = score(critical, new Set(s.critical));
306
- const vf = score(verif, new Set(s.verification));
307
- const baseVf = score(baseV, new Set(s.verification));
308
309
  const noise = irrelevanceRatio(retrieved, closure);
309
- const baseNoise = irrelevanceRatio(base, baseClosure);
310
+ const baseNoise = irrelevanceRatio(base, closure);
310
311
 
311
312
  perScenario.push({
312
313
  id: s.id,
@@ -314,13 +315,27 @@ export function runRoutingEval(root, { json = false } = {}) {
314
315
  recall: all.hit / (all.total || 1),
315
316
  precision: 1 - noise,
316
317
  criticalRecall: crit.total ? crit.hit / crit.total : 1,
317
- verificationRecall: vf.total ? vf.hit / vf.total : 1,
318
+ verificationRecall: verificationRecall(verif, new Set(s.verification)),
319
+ checkSelectivity: checkSelectivity(graph, recommendedIds),
318
320
  noise,
319
321
  baseline: {
320
322
  recall: baseAll.hit / (baseAll.total || 1),
321
323
  noise: baseNoise,
322
- verificationRecall: baseVf.hit / (baseVf.total || 1),
324
+ verificationRecall: verificationRecall(baseV, new Set(s.verification)),
325
+ checkSelectivity: 1, // The baseline runs every verification entry and gate.
323
326
  },
327
+ searchBaseline: {
328
+ recall: searchAll.hit / (searchAll.total || 1),
329
+ noise: irrelevanceRatio(search.facts, closure),
330
+ verificationRecall: verificationRecall(searchV, new Set(s.verification)),
331
+ checkSelectivity: checkSelectivity(graph, searchIds),
332
+ missed: searchAll.missed,
333
+ },
334
+ truthCount: all.total,
335
+ recalledCount: all.hit,
336
+ baselineRecalledCount: baseAll.hit,
337
+ searchRecalledCount: searchAll.hit,
338
+ searchCount: search.facts.size,
324
339
  missed: all.missed.slice(0, 8),
325
340
  offClosure: [...retrieved].filter((r) => !closure.has(r)).slice(0, 8),
326
341
  unresolvedTouches: unresolved,
@@ -333,19 +348,34 @@ export function runRoutingEval(root, { json = false } = {}) {
333
348
 
334
349
  const mean = (k) => (perScenario.length ? perScenario.reduce((a, b) => a + b[k], 0) / perScenario.length : 0);
335
350
  const meanB = (k) => (perScenario.length ? perScenario.reduce((a, b) => a + b.baseline[k], 0) / perScenario.length : 0);
336
- void meanB;
351
+ const meanS = (k) => perScenario.length ? perScenario.reduce((a, b) => a + b.searchBaseline[k], 0) / perScenario.length : 0;
352
+ const truthCount = perScenario.reduce((sum, s) => sum + s.truthCount, 0);
353
+ const micro = (key) => truthCount ? round(perScenario.reduce((sum, s) => sum + s[key], 0) / truthCount) : 0;
337
354
  const metrics = {
338
355
  scenarios: perScenario.length,
339
356
  recall: round(mean("recall")),
357
+ microRecall: micro("recalledCount"),
340
358
  precision: round(mean("precision")),
341
359
  criticalRecall: round(mean("criticalRecall")),
342
360
  verificationRecall: round(mean("verificationRecall")),
361
+ checkSelectivity: round(mean("checkSelectivity")),
343
362
  noise: round(mean("noise")),
344
363
  retrievalReduction: 0,
364
+ searchBaseline: {
365
+ recall: round(meanS("recall")),
366
+ microRecall: micro("searchRecalledCount"),
367
+ size: round(mean("searchCount")),
368
+ noise: round(meanS("noise")),
369
+ verificationRecall: round(meanS("verificationRecall")),
370
+ checkSelectivity: round(meanS("checkSelectivity")),
371
+ },
345
372
  baseline: {
346
373
  recall: round(meanB("recall")),
374
+ microRecall: micro("baselineRecalledCount"),
375
+ size: round(mean("baselineCount")),
347
376
  noise: round(meanB("noise")),
348
377
  verificationRecall: round(meanB("verificationRecall")),
378
+ checkSelectivity: round(meanB("checkSelectivity")),
349
379
  },
350
380
  };
351
381
  // Size proxy: retrieved fact count versus the whole-repository baseline.
@@ -5,5 +5,12 @@
5
5
  "touch": ["libs/protocol/ledger.pb.go"],
6
6
  "truth": ["component:default/protocol", "component:default/ingest", "component:default/gateway", "doc:libs/protocol/README.md"],
7
7
  "critical": [],
8
- "verification": []
8
+ "verification": [
9
+ "verify:go test ./...",
10
+ "verify:go vet ./...",
11
+ "verify-id:component:default/gateway:go-test",
12
+ "verify-id:component:default/gateway:go-vet",
13
+ "verify-id:component:default/ingest:go-test",
14
+ "verify-id:component:default/ingest:go-vet"
15
+ ]
9
16
  }
@@ -5,5 +5,11 @@
5
5
  "touch": ["shared/index.js"],
6
6
  "truth": ["component:default/shared", "component:default/edge", "component:default/console", "event:corp.telemetry.ingest.v1"],
7
7
  "critical": ["event:corp.telemetry.ingest.v1"],
8
- "verification": []
8
+ "verification": [
9
+ "verify:vitest run",
10
+ "verify:tsc --noEmit",
11
+ "verify-id:component:default/console:test",
12
+ "verify-id:component:default/console:typecheck",
13
+ "verify-id:component:default/edge:test"
14
+ ]
9
15
  }
@@ -0,0 +1,14 @@
1
+ {
2
+ "id": "e-01",
3
+ "repository": "fixture-e",
4
+ "title": "Change the shared engine crate",
5
+ "touch": ["crates/engine/src/lib.rs"],
6
+ "truth": ["component:default/engine", "component:default/cli"],
7
+ "critical": [],
8
+ "verification": [
9
+ "verify:cargo test -p engine",
10
+ "verify:cargo clippy -p engine",
11
+ "verify:cargo test -p cli",
12
+ "verify:cargo clippy -p cli"
13
+ ]
14
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "e-02",
3
+ "repository": "fixture-e",
4
+ "title": "Change the CLI crate manifest",
5
+ "touch": ["apps/cli/Cargo.toml"],
6
+ "truth": ["component:default/cli", "component:default/engine", "component:default/testutil", "external:serde"],
7
+ "critical": [],
8
+ "verification": ["verify:cargo test -p cli", "verify:cargo clippy -p cli"]
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "e-03",
3
+ "repository": "fixture-e",
4
+ "title": "Change CLI code that looks like the logging crate and an ambiguous foo_bar import",
5
+ "touch": ["apps/cli/src/logging.rs", "apps/cli/src/main.rs"],
6
+ "truth": ["component:default/cli", "component:default/engine"],
7
+ "critical": [],
8
+ "verification": ["verify:cargo test -p cli", "verify:cargo clippy -p cli"]
9
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "id": "e-04",
3
+ "repository": "fixture-e",
4
+ "title": "Change each crate of the foo-bar / foo_bar pair",
5
+ "touch": ["crates/foo-bar/src/lib.rs", "crates/foo_bar/src/lib.rs"],
6
+ "truth": ["component:default/foo-bar", "component:default/foo-u-bar"],
7
+ "critical": [],
8
+ "verification": [
9
+ "verify:cargo test -p foo-bar",
10
+ "verify:cargo clippy -p foo-bar",
11
+ "verify:cargo test -p foo_bar",
12
+ "verify:cargo clippy -p foo_bar"
13
+ ]
14
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "f-01",
3
+ "repository": "fixture-f",
4
+ "title": "Change the shared ledger-core library",
5
+ "touch": ["libs/ledger-core/src/ledger_core/models.py"],
6
+ "truth": ["component:default/ledger-core", "component:default/ledger-api"],
7
+ "critical": [],
8
+ "verification": ["verify:uv run pytest", "verify:uv run ruff check", "verify:uv run mypy"]
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "f-02",
3
+ "repository": "fixture-f",
4
+ "title": "Change the ledger API manifest",
5
+ "touch": ["services/ledger-api/pyproject.toml"],
6
+ "truth": ["component:default/ledger-api", "component:default/ledger-core", "component:default/ledger-testing", "external:sqlalchemy"],
7
+ "critical": [],
8
+ "verification": ["verify:uv run pytest", "verify:uv run ruff check", "verify:uv run mypy"]
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "f-03",
3
+ "repository": "fixture-f",
4
+ "title": "Change the app that imports its own utils module",
5
+ "touch": ["services/b-app/b_app/main.py"],
6
+ "truth": ["component:default/b-app"],
7
+ "critical": [],
8
+ "verification": []
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "f-04",
3
+ "repository": "fixture-f",
4
+ "title": "Change a nested workspace member, not its parent",
5
+ "touch": ["python/packages/api/src/api_svc/main.py"],
6
+ "truth": ["component:default/api-svc", "component:default/core-lib"],
7
+ "critical": [],
8
+ "verification": []
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "f-05",
3
+ "repository": "fixture-f",
4
+ "title": "Change the ledger API Alembic migration",
5
+ "touch": ["services/ledger-api/alembic/versions/0001_init.py"],
6
+ "truth": ["component:default/ledger-api", "uses:resource:default/ledger-api-postgres"],
7
+ "critical": ["uses:resource:default/ledger-api-postgres"],
8
+ "verification": ["verify:uv run pytest", "verify:uv run ruff check", "verify:uv run mypy"]
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "f-06",
3
+ "repository": "fixture-f",
4
+ "title": "Change the ledger job exercised by unittest",
5
+ "touch": ["jobs/ledger-jobs/tests/test_jobs.py"],
6
+ "truth": ["component:default/ledger-jobs"],
7
+ "critical": [],
8
+ "verification": ["verify:python -m unittest discover"]
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-doc-system",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "Portable documentation and software-catalog system with an explicit authority model, for AI coding agents",
6
6
  "keywords": [
@@ -18,7 +18,7 @@
18
18
  "homepage": "https://github.com/lbframe/agent-doc-system",
19
19
  "bugs": "https://github.com/lbframe/agent-doc-system/issues",
20
20
  "bin": {
21
- "agentdoc": "./bin/agentdoc.mjs"
21
+ "agentdoc": "bin/agentdoc.mjs"
22
22
  },
23
23
  "engines": {
24
24
  "node": ">=22"
@@ -37,11 +37,14 @@
37
37
  "core",
38
38
  "docs",
39
39
  "evals/harness.mjs",
40
+ "evals/facts.mjs",
41
+ "evals/baseline-search.mjs",
40
42
  "evals/scenarios",
41
43
  "evals/thresholds.json",
42
44
  "schemas",
43
45
  "skills",
44
46
  "templates",
47
+ "CHANGELOG.md",
45
48
  "INSTALL_FOR_AGENTS.md"
46
49
  ],
47
50
  "license": "MIT"
@@ -83,7 +83,8 @@ not work around it: run `agentdoc compile`, review the diff, commit the graph.
83
83
  ## Rules that are never optional
84
84
 
85
85
  - **Never vendor the CLI.** A project gets `agentdoc/` project data (config,
86
- descriptors, observations), `docs/` skeletons, `.agentdoc/graph.json` and a
86
+ descriptors, observations), documentation skeletons at the configured
87
+ `docs.*` paths (`agentdoc/docs/` by default), `.agentdoc/graph.json` and a
87
88
  CI workflow — never a copy of the agentdoc source tree (`core/`, `bin/`,
88
89
  `adapters/`, `schemas/`, `templates/`, `evals/`, `tests/`).
89
90
  - **Never promote uncertainty.** Heuristic guesses do not become authored
@@ -38,7 +38,12 @@ agentdoc scaffold [--write] # generate descriptors for what can be proven
38
38
  subdirectory of a cataloged repository it writes at the catalog root, not the
39
39
  current directory. `scaffold` is a dry run without `--write`; with `--write` it
40
40
  is a superset of `init` — it also installs anything missing from the config,
41
- doc skeleton and CI workflow, not only component descriptors. `audit` works
41
+ doc skeleton and CI workflow, not only component descriptors. Both write the
42
+ doc skeleton to the configured `docs.*` paths when a configuration exists, and
43
+ to `agentdoc/docs/` when none does; an existing file is reported as skipped.
44
+ Authored AgentDoc material lives in `agentdoc/`, generated output in
45
+ `.agentdoc/`; `docs/` is left to the product's own user documentation unless a
46
+ configuration declares `docs.*` paths there. `audit` works
42
47
  even when the repository cannot compile yet; it reports the compilation
43
48
  failures as part of its output.
44
49