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.
- package/CHANGELOG.md +402 -0
- package/README.md +38 -0
- package/adapters/ansible.mjs +1 -1
- package/adapters/cache.mjs +9 -2
- package/adapters/contract-consumers.mjs +1 -1
- package/adapters/database.mjs +19 -4
- package/adapters/deploy-config.mjs +3 -2
- package/adapters/dockerfile.mjs +3 -2
- package/adapters/events.mjs +1 -1
- package/adapters/generic.mjs +2 -1
- package/adapters/github-actions.mjs +245 -85
- package/adapters/go.mjs +5 -4
- package/adapters/index.mjs +3 -1
- package/adapters/node.mjs +3 -2
- package/adapters/object-store.mjs +2 -2
- package/adapters/python.mjs +1142 -0
- package/adapters/resource-client.mjs +1 -1
- package/adapters/rust.mjs +990 -0
- package/adapters/turborepo.mjs +3 -2
- package/adapters/typescript.mjs +7 -5
- package/adapters/wrangler.mjs +3 -2
- package/bin/agentdoc.mjs +11 -4
- package/bin/usage.txt +2 -0
- package/core/audit.mjs +12 -8
- package/core/codes.mjs +4 -0
- package/core/compile.mjs +131 -16
- package/core/dependents.mjs +42 -0
- package/core/derive.mjs +4 -3
- package/core/descriptors.mjs +3 -2
- package/core/discover.mjs +61 -9
- package/core/fsx.mjs +185 -36
- package/core/graph.mjs +1 -1
- package/core/impact.mjs +66 -48
- package/core/paths.mjs +71 -0
- package/core/query.mjs +76 -31
- package/core/scaffold.mjs +216 -44
- package/core/sourcescan.mjs +14 -2
- package/core/toml.mjs +463 -140
- package/docs/ADAPTERS.md +196 -6
- package/docs/BENCHMARK.md +130 -0
- package/docs/CI.md +7 -1
- package/docs/EVALS.md +239 -16
- package/docs/MIGRATION.md +26 -6
- package/docs/PROVENANCE.md +6 -0
- package/docs/QUERY.md +78 -7
- package/docs/SPEC.md +69 -2
- package/docs/ci/github-actions.yml +1 -1
- package/evals/baseline-search.mjs +272 -0
- package/evals/facts.mjs +28 -0
- package/evals/harness.mjs +149 -119
- package/evals/scenarios/fixture-b-03-change-shared-lib.json +8 -1
- package/evals/scenarios/fixture-c-03-shared-vocabulary.json +7 -1
- package/evals/scenarios/fixture-e-01-change-shared-lib.json +14 -0
- package/evals/scenarios/fixture-e-02-change-manifest.json +9 -0
- package/evals/scenarios/fixture-e-03-local-module.json +9 -0
- package/evals/scenarios/fixture-e-04-ambiguous-crate.json +14 -0
- package/evals/scenarios/fixture-f-01-change-shared-lib.json +9 -0
- package/evals/scenarios/fixture-f-02-change-manifest.json +9 -0
- package/evals/scenarios/fixture-f-03-shadowed-import.json +9 -0
- package/evals/scenarios/fixture-f-04-nested-workspace.json +9 -0
- package/evals/scenarios/fixture-f-05-alembic-database.json +9 -0
- package/evals/scenarios/fixture-f-06-unittest-job.json +9 -0
- package/package.json +5 -2
- package/skills/agent-doc-system/SKILL.md +2 -1
- package/skills/agent-doc-system/references/cli.md +6 -1
- package/skills/agent-doc-system/references/create-migrate.md +21 -5
- 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
|
-
//
|
|
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
|
|
39
|
-
for (const
|
|
40
|
-
for (const
|
|
41
|
-
for (const
|
|
42
|
-
for (const
|
|
43
|
-
for (const a of ctx.apis.
|
|
44
|
-
for (const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
for (const r of ctx.resources.
|
|
48
|
-
for (const
|
|
49
|
-
for (const e of ctx.
|
|
50
|
-
for (const
|
|
51
|
-
for (const
|
|
52
|
-
for (const
|
|
53
|
-
for (const
|
|
54
|
-
for (const
|
|
55
|
-
for (const
|
|
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
|
|
65
|
-
for (const p of ctx.constraints.constraints) out.add("constraint
|
|
66
|
-
for (const c of ctx.authority.conflicts.items) out.add("conflict
|
|
67
|
-
for (const o of ctx.authority.observations) out.add("observation
|
|
68
|
-
for (const r of ctx.resources.used) out.add("uses
|
|
69
|
-
for (const r of ctx.resources.usedBy) out.add("usedBy
|
|
70
|
-
for (const a of ctx.apis.consumed) out.add("consumes
|
|
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
|
|
74
|
-
for (const c of ctx.apis.consumers || []) out.add("consumer
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
95
|
-
|
|
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
|
|
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
|
-
//
|
|
113
|
-
//
|
|
114
|
-
for (const f of new Set([...repoDocPaths(graph)])) out.add("
|
|
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
|
-
//
|
|
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
|
|
139
|
-
for (const p of d.constraints || []) out.add("constraint
|
|
140
|
-
for (const p of d.runbooks || []) out.add("runbook
|
|
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
|
|
143
|
-
for (const
|
|
144
|
-
for (const
|
|
145
|
-
for (const x of graph.
|
|
146
|
-
for (const
|
|
147
|
-
for (const
|
|
148
|
-
for (const
|
|
149
|
-
for (const
|
|
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
|
-
//
|
|
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.
|
|
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
|
|
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
|
|
218
|
-
for (const p of d.constraints || []) out.add("constraint
|
|
219
|
-
for (const p of d.runbooks || []) out.add("runbook
|
|
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
|
|
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
|
|
226
|
-
for (const x of graph.capabilities) if (x.componentRef === e.ref) out.add("capability
|
|
227
|
-
for (const v of graph.verification) if (v.componentRefs.includes(e.ref)) out.add("verify
|
|
228
|
-
for (const c of graph.conflicts) if (c.subject === e.ref) out.add("conflict
|
|
229
|
-
for (const o of graph.observations) if (o.facts.some((f) => f.subject === e.ref)) out.add("observation
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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-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.
|
|
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": "
|
|
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),
|
|
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.
|
|
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
|
|