agent-doc-system 1.2.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 +134 -1
- package/README.md +1 -1
- package/adapters/cache.mjs +5 -4
- package/adapters/database.mjs +3 -2
- package/adapters/deploy-config.mjs +3 -2
- package/adapters/dockerfile.mjs +3 -2
- package/adapters/generic.mjs +2 -1
- package/adapters/github-actions.mjs +2 -1
- package/adapters/go.mjs +4 -3
- package/adapters/node.mjs +3 -2
- package/adapters/rust.mjs +3 -0
- package/adapters/turborepo.mjs +3 -2
- package/adapters/typescript.mjs +7 -5
- package/adapters/wrangler.mjs +3 -2
- package/bin/agentdoc.mjs +9 -2
- package/bin/usage.txt +2 -0
- package/core/audit.mjs +7 -5
- package/core/compile.mjs +7 -5
- package/core/derive.mjs +4 -3
- package/core/descriptors.mjs +3 -2
- package/core/discover.mjs +3 -2
- package/core/fsx.mjs +174 -42
- package/core/graph.mjs +1 -1
- package/core/impact.mjs +11 -4
- package/core/paths.mjs +71 -0
- package/core/query.mjs +58 -23
- package/core/scaffold.mjs +130 -23
- package/core/sourcescan.mjs +4 -1
- package/docs/ADAPTERS.md +1 -1
- package/docs/BENCHMARK.md +83 -21
- package/docs/CI.md +7 -1
- package/docs/EVALS.md +209 -17
- package/docs/MIGRATION.md +22 -4
- package/docs/PROVENANCE.md +6 -0
- package/docs/QUERY.md +25 -3
- package/docs/SPEC.md +47 -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 +120 -116
- package/package.json +4 -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 +8 -2
- package/templates/ci.yml +1 -1
package/core/scaffold.mjs
CHANGED
|
@@ -13,7 +13,8 @@ import fs from "node:fs";
|
|
|
13
13
|
import path from "node:path";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { AgentDocError, CODES } from "./codes.mjs";
|
|
16
|
-
import { Repo } from "./fsx.mjs";
|
|
16
|
+
import { Repo, assertRepoPath } from "./fsx.mjs";
|
|
17
|
+
import { CONFIG_PATH } from "./descriptors.mjs";
|
|
17
18
|
import { discoverUnits, isNotUnitRoot } from "./discover.mjs";
|
|
18
19
|
import { classifyContractFile } from "./contracts.mjs";
|
|
19
20
|
import { parse as parseToml } from "./toml.mjs";
|
|
@@ -45,23 +46,96 @@ function sentence(s) {
|
|
|
45
46
|
return /[.!?]$/.test(t) ? t : t + ".";
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
// Where the documentation skeleton goes when no configuration exists yet. Once
|
|
50
|
+
// a configuration exists, it is the only authority: `docs.*` is what audit and
|
|
51
|
+
// compile read, so the scaffolder writes there and nowhere else (issue #49 —
|
|
52
|
+
// `docs/` is often the product's own user documentation).
|
|
53
|
+
//
|
|
54
|
+
// The default lives under `agentdoc/` (issue #59): authored AgentDoc material
|
|
55
|
+
// lives in `agentdoc/`, generated output in `.agentdoc/`. Not `docs/`, which is
|
|
56
|
+
// the product's user documentation (and a MkDocs/Docusaurus/Pages source), and
|
|
57
|
+
// not `.agentdoc/`, which the compiler skips and many repositories git-ignore.
|
|
58
|
+
export const DEFAULT_DOCS = Object.freeze({
|
|
59
|
+
product: "agentdoc/docs/PRODUCT.md",
|
|
60
|
+
architecture: "agentdoc/docs/ARCHITECTURE.md",
|
|
61
|
+
constraints: "agentdoc/docs/CONSTRAINTS.md",
|
|
62
|
+
adrDir: "agentdoc/docs/adr",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// The skeleton roles that have a template. `runbookDir` and `normative` are
|
|
66
|
+
// honoured by never writing anything for them: there is no runbook template,
|
|
67
|
+
// and normative documents are the project's own.
|
|
68
|
+
const SKELETON_ROLES = [
|
|
69
|
+
{ key: "product", template: "PRODUCT.md", file: (p) => p },
|
|
70
|
+
{ key: "architecture", template: "ARCHITECTURE.md", file: (p) => p },
|
|
71
|
+
{ key: "constraints", template: "CONSTRAINTS.md", file: (p) => p },
|
|
72
|
+
{ key: "adrDir", template: "ADR.md", file: (p) => p.replace(/\/+$/, "") + "/README.md" },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
// Resolve the documentation skeleton for a loaded configuration (`null` when
|
|
76
|
+
// there is none). A configured role is written to its configured path; a role
|
|
77
|
+
// a configuration leaves out is not written anywhere and is reported, so an
|
|
78
|
+
// omitted role can never fall back into `docs/`.
|
|
79
|
+
export function docsSkeleton(cfg) {
|
|
80
|
+
const docs = cfg ? cfg.docs || {} : DEFAULT_DOCS;
|
|
81
|
+
const targets = [];
|
|
82
|
+
const notConfigured = [];
|
|
83
|
+
for (const role of SKELETON_ROLES) {
|
|
84
|
+
const configured = docs[role.key];
|
|
85
|
+
if (!configured) { notConfigured.push("docs." + role.key); continue; }
|
|
86
|
+
// loadConfig already validated these; re-asserting keeps this function
|
|
87
|
+
// safe for any caller that hands it an unvalidated object.
|
|
88
|
+
assertRepoPath(configured, CONFIG_PATH);
|
|
89
|
+
const rel = role.file(configured);
|
|
90
|
+
assertRepoPath(rel, CONFIG_PATH);
|
|
91
|
+
targets.push({ key: role.key, path: rel, template: role.template });
|
|
92
|
+
}
|
|
93
|
+
return { targets, notConfigured, docs: cfg ? docs : DEFAULT_DOCS };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Refuse a write target that leaves the repository, including through a
|
|
97
|
+
// symlinked directory or a (possibly dangling) symlink at the target itself.
|
|
98
|
+
// Lexical checks alone (`isRepoPath`) cannot see a symlink.
|
|
99
|
+
function assertWritableInside(root, rel) {
|
|
100
|
+
const abs = path.resolve(root, rel);
|
|
101
|
+
const inside = path.relative(path.resolve(root), abs);
|
|
102
|
+
if (!inside || inside === ".." || inside.startsWith(".." + path.sep) || path.isAbsolute(inside)) {
|
|
103
|
+
throw new AgentDocError(CODES.PATH_ESCAPE, "path escapes repository: " + rel, { path: rel });
|
|
104
|
+
}
|
|
105
|
+
const realRoot = fs.realpathSync(root);
|
|
106
|
+
let cur = path.resolve(root);
|
|
107
|
+
for (const seg of inside.split(path.sep)) {
|
|
108
|
+
cur = path.join(cur, seg);
|
|
109
|
+
let st;
|
|
110
|
+
try { st = fs.lstatSync(cur); } catch { return; } // nothing further exists yet
|
|
111
|
+
if (!st.isSymbolicLink()) continue;
|
|
112
|
+
let real = null;
|
|
113
|
+
try { real = fs.realpathSync(cur); } catch { /* dangling */ }
|
|
114
|
+
if (real === null || (real !== realRoot && !real.startsWith(realRoot + path.sep))) {
|
|
115
|
+
throw new AgentDocError(CODES.PATH_ESCAPE, "refusing to write through a symlink that leaves the repository: " + rel, { path: rel });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// `cfg` is the loaded configuration (scaffold) or null (init). `docsConfig`
|
|
121
|
+
// overrides the configuration the documentation paths are read from: init
|
|
122
|
+
// writes no component descriptors, but it must still honour an existing
|
|
123
|
+
// configuration's `docs.*` paths.
|
|
124
|
+
export function installTemplates(repo, cfg, { write, root, force = false, eligible = null, docsConfig } = {}) {
|
|
49
125
|
const written = [];
|
|
50
126
|
const skipped = [];
|
|
51
127
|
const targetRoot = root || (repo ? repo.root : process.cwd());
|
|
52
128
|
const r = repo || new Repo(targetRoot, { noGit: false });
|
|
129
|
+
const docsSource = docsConfig === undefined ? cfg : docsConfig;
|
|
130
|
+
const skeleton = docsSkeleton(docsSource);
|
|
53
131
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
59
|
-
fs.writeFileSync(abs, content.endsWith("\n") ? content : content + "\n");
|
|
60
|
-
written.push(rel);
|
|
61
|
-
};
|
|
132
|
+
// Plan every file first and check every target before writing anything, so
|
|
133
|
+
// a refused path leaves the repository exactly as it was.
|
|
134
|
+
const plan = [];
|
|
135
|
+
const put = (rel, content) => { plan.push([rel, content]); };
|
|
62
136
|
|
|
63
137
|
// ── configuration ───────────────────────────────────────────────────────
|
|
64
|
-
const config = buildConfig(scanLayout(r, cfg));
|
|
138
|
+
const config = buildConfig(scanLayout(r, cfg), skeleton.docs);
|
|
65
139
|
put("agentdoc/agentdoc.config.yaml", config);
|
|
66
140
|
put("agentdoc/journeys.yaml", "schemaVersion: agentdoc.dev/journeys/v1\njourneys: []\n");
|
|
67
141
|
for (const f of ["domains.yaml", "systems.yaml", "apis.yaml", "resources.yaml"]) {
|
|
@@ -69,12 +143,10 @@ export function installTemplates(repo, cfg, { write, root, force = false, eligib
|
|
|
69
143
|
}
|
|
70
144
|
put("agentdoc/observations/README.md", OBSERVATIONS_README);
|
|
71
145
|
|
|
72
|
-
// ── documentation hierarchy
|
|
73
|
-
for (const
|
|
74
|
-
|
|
75
|
-
put("docs/" + f, tpl);
|
|
146
|
+
// ── documentation hierarchy (configured docs.* paths) ───────────────────
|
|
147
|
+
for (const t of skeleton.targets) {
|
|
148
|
+
put(t.path, fs.readFileSync(path.join(TEMPLATES_DIR, t.template), "utf8"));
|
|
76
149
|
}
|
|
77
|
-
put("docs/adr/README.md", fs.readFileSync(path.join(TEMPLATES_DIR, "ADR.md"), "utf8"));
|
|
78
150
|
put(".github/workflows/agentdoc.yml", fs.readFileSync(path.join(TEMPLATES_DIR, "ci.yml"), "utf8"));
|
|
79
151
|
|
|
80
152
|
// ── per-unit component descriptors ──────────────────────────────────────
|
|
@@ -88,7 +160,30 @@ export function installTemplates(repo, cfg, { write, root, force = false, eligib
|
|
|
88
160
|
put(u.root + "/" + cfg.discovery.componentDescriptorName, componentDescriptor(u));
|
|
89
161
|
}
|
|
90
162
|
}
|
|
91
|
-
|
|
163
|
+
|
|
164
|
+
const seen = new Set();
|
|
165
|
+
for (const [rel] of plan) assertWritableInside(targetRoot, rel);
|
|
166
|
+
for (const [rel, content] of plan) {
|
|
167
|
+
if (seen.has(rel)) continue; // two roles configured onto one file: first wins
|
|
168
|
+
seen.add(rel);
|
|
169
|
+
const abs = path.join(targetRoot, rel);
|
|
170
|
+
if (fs.existsSync(abs) && !force) { skipped.push(rel); continue; }
|
|
171
|
+
if (!write) { written.push(rel + " (dry run)"); continue; }
|
|
172
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
173
|
+
fs.writeFileSync(abs, content.endsWith("\n") ? content : content + "\n");
|
|
174
|
+
written.push(rel);
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
written,
|
|
178
|
+
skipped,
|
|
179
|
+
...(docsSource && skeleton.notConfigured.length
|
|
180
|
+
? {
|
|
181
|
+
notConfigured: skeleton.notConfigured,
|
|
182
|
+
notConfiguredNote: "the configuration declares no path for these roles, so no skeleton was written for them; add them under docs: to scaffold them",
|
|
183
|
+
}
|
|
184
|
+
: {}),
|
|
185
|
+
note: write ? "files written" : "dry run: pass --write to apply",
|
|
186
|
+
};
|
|
92
187
|
}
|
|
93
188
|
|
|
94
189
|
// Layout inference for CREATE mode: which directories look like unit roots.
|
|
@@ -108,6 +203,7 @@ function scanLayout(repo, cfg) {
|
|
|
108
203
|
for (const e of entries.sort((a, b) => (a.name < b.name ? -1 : 1))) {
|
|
109
204
|
if (e.isSymbolicLink() || skip.has(e.name)) continue;
|
|
110
205
|
const rel = dir ? dir + "/" + e.name : e.name;
|
|
206
|
+
if (repo.isIgnored && repo.isIgnored(rel)) continue;
|
|
111
207
|
if (e.isDirectory()) {
|
|
112
208
|
if (e.name.startsWith(".")) continue;
|
|
113
209
|
walk(rel, depth + 1);
|
|
@@ -269,7 +365,7 @@ const ADAPTER_FOR_MARKER = {
|
|
|
269
365
|
platform: "wrangler",
|
|
270
366
|
};
|
|
271
367
|
|
|
272
|
-
function buildConfig(layout) {
|
|
368
|
+
function buildConfig(layout, docs = DEFAULT_DOCS) {
|
|
273
369
|
const roots = layout.map((l) => l.root);
|
|
274
370
|
const groups = new Map();
|
|
275
371
|
for (const r of roots) {
|
|
@@ -346,15 +442,26 @@ function buildConfig(layout) {
|
|
|
346
442
|
" graph: .agentdoc/graph.json",
|
|
347
443
|
" observations: agentdoc/observations",
|
|
348
444
|
"",
|
|
349
|
-
|
|
350
|
-
" product: docs/PRODUCT.md",
|
|
351
|
-
" architecture: docs/ARCHITECTURE.md",
|
|
352
|
-
" constraints: docs/CONSTRAINTS.md",
|
|
353
|
-
" adrDir: docs/adr",
|
|
445
|
+
...docsSection(docs),
|
|
354
446
|
"",
|
|
355
447
|
].join("\n");
|
|
356
448
|
}
|
|
357
449
|
|
|
450
|
+
// The `docs:` block of a generated configuration. It repeats the configured
|
|
451
|
+
// paths (init --force regenerates the file and must not move documentation
|
|
452
|
+
// elsewhere), or the agentdoc/docs/ defaults when there is no configuration yet.
|
|
453
|
+
function docsSection(docs) {
|
|
454
|
+
const scalar = (v) => (/^[A-Za-z0-9._/-]+$/.test(v) ? v : yamlStr(v));
|
|
455
|
+
const lines = [];
|
|
456
|
+
for (const key of ["product", "architecture", "constraints", "adrDir", "runbookDir"]) {
|
|
457
|
+
if (docs[key]) lines.push(" " + key + ": " + scalar(docs[key]));
|
|
458
|
+
}
|
|
459
|
+
if (Array.isArray(docs.normative) && docs.normative.length) {
|
|
460
|
+
lines.push(" normative:", ...docs.normative.map((g) => " - " + yamlStr(g)));
|
|
461
|
+
}
|
|
462
|
+
return lines.length ? ["docs:", ...lines] : ["# docs: {} # no documentation roles configured"];
|
|
463
|
+
}
|
|
464
|
+
|
|
358
465
|
const OBSERVATIONS_README = `# Runtime observations
|
|
359
466
|
|
|
360
467
|
An ObservationSet records what an authoritative external system actually said,
|
package/core/sourcescan.mjs
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// knows the namespace convention.
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import fs from "node:fs";
|
|
10
|
+
import { joinPath } from "./paths.mjs";
|
|
10
11
|
|
|
11
12
|
const SPECIFIER_RE = /["'`]([A-Za-z@][A-Za-z0-9@._/+-]*(?:\.[A-Za-z0-9@._/+-]+)*(?:\/[A-Za-z0-9@._+*-]+)*)["'`]/g;
|
|
12
13
|
|
|
@@ -66,6 +67,7 @@ export function findMigrationDirs(repo, root, maxDepth = 5) {
|
|
|
66
67
|
const abs = path.join(dirAbs, e.name);
|
|
67
68
|
const rel = path.relative(repo.root, abs).split(path.sep).join("/");
|
|
68
69
|
if (!repo.ownsUnitPath(root, rel)) continue;
|
|
70
|
+
if (repo.isIgnored && repo.isIgnored(rel)) continue;
|
|
69
71
|
if (e.name === "migrations" || e.name === "migration") {
|
|
70
72
|
out.push(rel);
|
|
71
73
|
continue;
|
|
@@ -86,7 +88,8 @@ export function findMigrationDirs(repo, root, maxDepth = 5) {
|
|
|
86
88
|
|
|
87
89
|
export function firstExistingFile(repo, root, names) {
|
|
88
90
|
for (const n of names) {
|
|
89
|
-
|
|
91
|
+
const p = joinPath(root, n);
|
|
92
|
+
if (repo.ownsUnitPath(root, p) && repo.exists(p)) return p;
|
|
90
93
|
}
|
|
91
94
|
return null;
|
|
92
95
|
}
|
package/docs/ADAPTERS.md
CHANGED
|
@@ -220,7 +220,7 @@ Workspace roots come from every `Cargo.toml` that has `[workspace]`, not only
|
|
|
220
220
|
the one at the repository root. The search is bounded to five directories
|
|
221
221
|
down, stays inside the repository, and does not enter `discovery.notUnitRoots`
|
|
222
222
|
or their descendants (the authored-descriptor exception is described below),
|
|
223
|
-
`discovery.generatedDirs`, dot-directories, or `test` / `tests` / `examples` /
|
|
223
|
+
`discovery.generatedDirs`, git-ignored directories, dot-directories, or `test` / `tests` / `examples` /
|
|
224
224
|
`example`. `members` minus `exclude` are still relative to the manifest that
|
|
225
225
|
declares them; `*` matches one path segment, and a member that leaves the
|
|
226
226
|
repository is ignored. `workspace = true` resolves against the nearest
|
package/docs/BENCHMARK.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# BENCHMARK
|
|
2
2
|
|
|
3
|
-
Two measurements
|
|
4
|
-
|
|
3
|
+
Two measurements are reproducible from a checkout. Neither involves a model;
|
|
4
|
+
both depend on the measurement rules described here.
|
|
5
5
|
|
|
6
6
|
## Routing quality: `node evals/run-all.mjs`
|
|
7
7
|
|
|
@@ -9,25 +9,87 @@ The routing evaluation is described in [EVALS.md](EVALS.md). It has 30 scenarios
|
|
|
9
9
|
on six repositories (Node, TypeScript, Go, Rust and Python), with thresholds
|
|
10
10
|
registered on 2026-09-28 and not moved since. For each scenario, it compares
|
|
11
11
|
the context `agentdoc query` + `agentdoc impact` returns with what an agent
|
|
12
|
-
gets
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
12
|
+
gets without routing.
|
|
13
|
+
|
|
14
|
+
The measured systems are catalog routing, a corrected graph-assisted targeted
|
|
15
|
+
reference, and working-tree search. Their current results appear below. The
|
|
16
|
+
pre-registered thresholds and scenario truth are unchanged.
|
|
17
|
+
|
|
18
|
+
### Correction: the fact-recall comparison is withdrawn
|
|
19
|
+
|
|
20
|
+
Until 2026-10-27 this page, the README and the 1.2.0 release notes reported
|
|
21
|
+
that AgentDoc retrieves 100% of the facts a change needs, against 58% for a
|
|
22
|
+
baseline that reads only the touched component, with noise under 9% against
|
|
23
|
+
40%. That comparison does not hold, and it is withdrawn.
|
|
24
|
+
|
|
25
|
+
- The targeted baseline already holds almost every expected fact, under a
|
|
26
|
+
different label from the ground truth. It records `usesResource:<ref>` where
|
|
27
|
+
the truth says `uses:<ref>`, and `buildDependencyOf:<ref>` where the truth
|
|
28
|
+
names the component `<ref>`. Relabelled, it retrieves about 99% of the
|
|
29
|
+
expected facts.
|
|
30
|
+
- The baseline also takes those relations from the compiled graph, which an
|
|
31
|
+
agent without AgentDoc does not have. A fair baseline has to find them with
|
|
32
|
+
search over the repository's files.
|
|
33
|
+
|
|
34
|
+
The search comparison is now measured. The targeted reference's corrected
|
|
35
|
+
micro-recall is 100/101 (99.01%); its scenario-mean recall is 99.17%. The
|
|
36
|
+
100%-versus-58% comparison remains withdrawn.
|
|
37
|
+
|
|
38
|
+
### Search baseline
|
|
39
|
+
|
|
40
|
+
`baseline-search` starts with touched files, searches source/manifests for
|
|
41
|
+
import names and local paths in both directions to a fixed point, and reads
|
|
42
|
+
the reached components' docs and checks. Contracts, endpoint strings, event
|
|
43
|
+
subjects and resource evidence are found in working-tree files. Catalog
|
|
44
|
+
descriptors supply only component identity and path ownership. Retrieval never
|
|
45
|
+
reads the compiled graph or compiler relations. All naming guesses, exclusions
|
|
46
|
+
and check conventions are listed in [EVALS.md](EVALS.md#search-baseline-heuristics).
|
|
47
|
+
|
|
48
|
+
| Corpus (scenarios) | System | Mean recall | Noise | Check selectivity |
|
|
49
|
+
|---|---|---:|---:|---:|
|
|
50
|
+
| Koda (9) | baseline-targeted (corrected) | 100.00% | 41.23% | 100.00% |
|
|
51
|
+
| Koda (9) | baseline-search | 71.90% | 71.55% | 100.00% |
|
|
52
|
+
| Koda (9) | catalog | 100.00% | 4.02% | 28.79% |
|
|
53
|
+
| Node/TS (3) | baseline-targeted (corrected) | 100.00% | 17.86% | 100.00% |
|
|
54
|
+
| Node/TS (3) | baseline-search | 91.67% | 30.64% | 83.33% |
|
|
55
|
+
| Node/TS (3) | catalog | 100.00% | 0.00% | 100.00% |
|
|
56
|
+
| Go (4) | baseline-targeted (corrected) | 93.75% | 28.94% | 100.00% |
|
|
57
|
+
| Go (4) | baseline-search | 83.33% | 26.01% | 100.00% |
|
|
58
|
+
| Go (4) | catalog | 100.00% | 5.88% | 66.67% |
|
|
59
|
+
| Mixed monorepo (4) | baseline-targeted (corrected) | 100.00% | 37.82% | 100.00% |
|
|
60
|
+
| Mixed monorepo (4) | baseline-search | 100.00% | 41.25% | 100.00% |
|
|
61
|
+
| Mixed monorepo (4) | catalog | 100.00% | 5.88% | 54.17% |
|
|
62
|
+
| Rust (4) | baseline-targeted (corrected) | 100.00% | 48.25% | 100.00% |
|
|
63
|
+
| Rust (4) | baseline-search | 100.00% | 32.81% | 83.33% |
|
|
64
|
+
| Rust (4) | catalog | 100.00% | 0.00% | 25.00% |
|
|
65
|
+
| Python (6) | baseline-targeted (corrected) | 100.00% | 46.70% | 100.00% |
|
|
66
|
+
| Python (6) | baseline-search | 100.00% | 23.68% | 41.67% |
|
|
67
|
+
| Python (6) | catalog | 100.00% | 0.00% | 41.67% |
|
|
68
|
+
| Aggregate (30) | baseline-targeted (corrected) | 99.17% | 38.83% | 100.00% |
|
|
69
|
+
| Aggregate (30) | baseline-search | 88.51% | 42.61% | 84.44% |
|
|
70
|
+
| Aggregate (30) | catalog | 100.00% | 2.77% | 46.42% |
|
|
71
|
+
|
|
72
|
+
These are scenario means, aggregated by scenario count. Search micro-recall is
|
|
73
|
+
85/101 (84.16%), compared with 100/101 (99.01%) for corrected targeted and
|
|
74
|
+
101/101 for catalog routing. All three systems retrieve every scenario-required
|
|
75
|
+
verification expectation. Selectivity counts distinct registered check IDs,
|
|
76
|
+
including repository gates, after matching search's discovered scoped commands.
|
|
77
|
+
|
|
78
|
+
Search matches catalog fact recall in 21 of 30 scenarios, including all Rust,
|
|
79
|
+
Python and mixed-monorepo scenarios. It beats catalog recall in none. The
|
|
80
|
+
remaining misses include authored placement, authority conflicts/runtime
|
|
81
|
+
observations, and API identities/roles not recoverable by these lexical rules.
|
|
82
|
+
Its broad closure can also overreach through shared endpoint strings. This is
|
|
83
|
+
a comparison of deterministic retrieval rules on authored corpora, not an agent
|
|
84
|
+
session, a limit on search quality, or evidence of time/token savings. Noise is
|
|
85
|
+
scored against the catalog's one-hop graph closure and can penalize useful
|
|
86
|
+
source facts absent from that graph.
|
|
87
|
+
|
|
88
|
+
The unchanged relative recall gate passes: corrected targeted ties catalog in
|
|
89
|
+
five corpora and trails it in Go. All applicable gates pass. Normalization also
|
|
90
|
+
changes noise and deduplicates fact counts: the full Koda inventory now has 86
|
|
91
|
+
facts, below the unchanged 100-fact size floor. Aggregate retrieval reduction
|
|
92
|
+
is **not measured**, so the former 70% size figure is not a current result.
|
|
31
93
|
|
|
32
94
|
## Fresh setup on public repositories: `node scripts/bench/run.mjs`
|
|
33
95
|
|
package/docs/CI.md
CHANGED
|
@@ -20,7 +20,7 @@ jobs:
|
|
|
20
20
|
with: { fetch-depth: 0 }
|
|
21
21
|
- uses: actions/setup-node@v4
|
|
22
22
|
with: { node-version: '24' }
|
|
23
|
-
# Pin a tag for reproducibility, e.g. github:lbframe/agent-doc-system#v1.
|
|
23
|
+
# Pin a tag for reproducibility, e.g. github:lbframe/agent-doc-system#v1.3.0 —
|
|
24
24
|
# a compiler upgrade intentionally invalidates every committed graph.
|
|
25
25
|
- run: npm install -g github:lbframe/agent-doc-system
|
|
26
26
|
- run: agentdoc validate
|
|
@@ -47,6 +47,12 @@ compare the graph's recorded commit with `HEAD`: committing the graph changes
|
|
|
47
47
|
The commit is recorded as provenance — which checkout produced this graph — not
|
|
48
48
|
as a gate.
|
|
49
49
|
|
|
50
|
+
Files git ignores are not inputs, so a graph compiled after a local build
|
|
51
|
+
(`astro build`, `next build`, anything writing into an ignored directory) is the
|
|
52
|
+
graph CI reproduces from a clean checkout. No `git clean -fdX` is needed before
|
|
53
|
+
`compile`. An untracked file that is *not* ignored still counts, and marks the
|
|
54
|
+
graph dirty: commit it or ignore it.
|
|
55
|
+
|
|
50
56
|
Two things to decide per repository:
|
|
51
57
|
|
|
52
58
|
- **Commit the graph or ignore it.** Both work. Committing it makes the
|