agent-doc-system 1.0.0 → 1.2.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 +269 -0
- package/README.md +97 -112
- package/adapters/ansible.mjs +306 -0
- package/adapters/cache.mjs +8 -2
- package/adapters/contract-consumers.mjs +1 -1
- package/adapters/database.mjs +18 -4
- package/adapters/events.mjs +1 -1
- package/adapters/github-actions.mjs +244 -85
- package/adapters/go.mjs +1 -1
- package/adapters/index.mjs +5 -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 +987 -0
- package/adapters/typescript.mjs +1 -1
- package/bin/agentdoc.mjs +60 -44
- package/bin/commands.mjs +169 -0
- package/bin/usage.txt +5 -3
- package/core/audit.mjs +55 -4
- package/core/authority/engine.mjs +27 -4
- package/core/codes.mjs +11 -0
- package/core/compile.mjs +263 -13
- package/core/dependents.mjs +42 -0
- package/core/descriptors.mjs +5 -2
- package/core/discover.mjs +60 -9
- package/core/fsx.mjs +27 -4
- package/core/graph.mjs +1 -1
- package/core/impact.mjs +55 -44
- package/core/normative.mjs +171 -0
- package/core/query.mjs +18 -8
- package/core/scaffold.mjs +87 -22
- package/core/sourcescan.mjs +11 -2
- package/core/toml.mjs +463 -140
- package/core/yaml.mjs +5 -3
- package/docs/ADAPTERS.md +232 -7
- package/docs/AUTHORITY.md +10 -1
- package/docs/BENCHMARK.md +68 -0
- package/docs/CI.md +3 -1
- package/docs/EVALS.md +33 -2
- package/docs/MIGRATION.md +76 -6
- package/docs/QUERY.md +53 -4
- package/docs/SPEC.md +22 -0
- package/docs/assets/readme/agentdoc-hero.webp +0 -0
- package/docs/assets/readme/agentdoc-in-action.webp +0 -0
- package/docs/assets/readme/how-agentdoc-works.webp +0 -0
- package/docs/assets/readme/why-agentdoc.webp +0 -0
- package/docs/ci/github-actions.yml +1 -1
- package/evals/harness.mjs +32 -6
- 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 +2 -1
- package/schemas/common.schema.json +40 -0
- package/schemas/component.schema.json +5 -1
- package/schemas/config.schema.json +117 -1
- package/skills/agent-doc-system/SKILL.md +5 -1
- package/skills/agent-doc-system/references/cli.md +12 -4
- package/skills/agent-doc-system/references/create-migrate.md +28 -6
- package/templates/ci.yml +1 -1
package/docs/SPEC.md
CHANGED
|
@@ -237,6 +237,28 @@ The core has no knowledge of folder names. A directory is a unit when it holds
|
|
|
237
237
|
a component descriptor, when an adapter recognises it from a workspace file, or
|
|
238
238
|
when it is listed in `discovery.supplementalRoots`.
|
|
239
239
|
|
|
240
|
+
`init` configures the recursive component descriptor glob `*/**/agentdoc.yaml`,
|
|
241
|
+
so descriptors for adapter-discovered units and units added later are loaded
|
|
242
|
+
without regenerating a path list. The scaffold layout scan supplements its
|
|
243
|
+
shallow marker heuristics with adapter discovery: declared workspace members
|
|
244
|
+
under `examples`, `example`, `test`, or `tests`, or beyond the scan depth, remain
|
|
245
|
+
units. Those names alone do not create units. Scaffold selects artifact types
|
|
246
|
+
from adapter shape: deployable (including both shapes) → `service`, importable
|
|
247
|
+
only → `library`, neither → `other`. Placement remains a human choice.
|
|
248
|
+
|
|
249
|
+
`discovery.notUnitRoots` excludes each listed root and its whole subtree from
|
|
250
|
+
inferred units (adapter markers, adapter `roots()`, and supplemental roots) and
|
|
251
|
+
from scaffold layout scans and descriptor proposals, in both dry run and
|
|
252
|
+
`--write`. The defaults are `agentdoc`, `docs`, `scripts`, `node_modules`, and
|
|
253
|
+
`.github`; a configured list replaces them, and `[]` disables these exclusions.
|
|
254
|
+
An existing authored component descriptor strictly below an excluded root still
|
|
255
|
+
claims that directory as a unit. Its own root must supply the usual adapter
|
|
256
|
+
evidence for its artifact type; the exclusion does not waive corroboration.
|
|
257
|
+
Scaffold preserves that descriptor and never proposes or writes one there.
|
|
258
|
+
A descriptor exactly at an excluded root is refused with
|
|
259
|
+
`AGENTDOC_COMPONENT_UNCORROBORATED` naming the exclusion: remove that root from
|
|
260
|
+
`notUnitRoots` or move the descriptor.
|
|
261
|
+
|
|
240
262
|
- Every discovered unit must have exactly one descriptor at its root, or
|
|
241
263
|
`AGENTDOC_COVERAGE_ZERO`.
|
|
242
264
|
- Two descriptors at one root, or a descriptor no adapter can corroborate, is an
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -21,7 +21,7 @@ jobs:
|
|
|
21
21
|
with: { node-version: '24' }
|
|
22
22
|
- name: Install the agentdoc CLI
|
|
23
23
|
# Unpinned tracks main. For CI reproducibility pin a tag or commit:
|
|
24
|
-
# github:lbframe/agent-doc-system#v1.
|
|
24
|
+
# github:lbframe/agent-doc-system#v1.2.0
|
|
25
25
|
# A compiler upgrade intentionally invalidates committed graphs.
|
|
26
26
|
run: npm install -g github:lbframe/agent-doc-system
|
|
27
27
|
- name: Validate and compile
|
package/evals/harness.mjs
CHANGED
|
@@ -35,6 +35,7 @@ const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
|
35
35
|
export function factsFromContext(ctx) {
|
|
36
36
|
const out = new Set();
|
|
37
37
|
out.add(ctx.entity.ref);
|
|
38
|
+
for (const n of ctx.neighbours) out.add(n.ref);
|
|
38
39
|
for (const r of ctx.relations.items) out.add(r.type + ":" + r.other);
|
|
39
40
|
for (const p of ctx.journeyPeers || []) out.add(p);
|
|
40
41
|
for (const s2 of ctx.placement.systems) out.add(s2);
|
|
@@ -76,7 +77,10 @@ export function criticalFromContext(ctx) {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
export function verificationFromContext(ctx) {
|
|
79
|
-
|
|
80
|
+
// Commands such as `go test ./...` recur in multiple modules. Scenarios can
|
|
81
|
+
// require a scoped check ID as well as a command to measure dependent checks
|
|
82
|
+
// without letting the library's identical command satisfy the expectation.
|
|
83
|
+
return new Set(ctx.verification.items.flatMap((v) => ["verify:" + v.command, "verify-id:" + v.id]));
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
// ── baseline: unassisted repository exploration ────────────────────────
|
|
@@ -154,7 +158,20 @@ export function fullRepositoryContext(graph) {
|
|
|
154
158
|
// Baseline verification: without routing, an agent runs the union of every
|
|
155
159
|
// check in the repository, because nothing narrows it.
|
|
156
160
|
function baselineVerification(graph) {
|
|
157
|
-
return new Set(graph.verification.
|
|
161
|
+
return new Set([...graph.verification, ...graph.gates].flatMap((v) => ["verify:" + v.command, "verify-id:" + v.id]));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function verificationRecall(retrieved, expected) {
|
|
165
|
+
const { hit, total } = score(retrieved, expected);
|
|
166
|
+
return total ? hit / total : 1;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Count scoped check IDs, not commands: identical commands in different
|
|
170
|
+
// components are separate checks. Gates are repository-scoped checks too.
|
|
171
|
+
export function checkSelectivity(graph, recommendedIds) {
|
|
172
|
+
const all = new Set([...graph.verification, ...graph.gates].map((v) => v.id));
|
|
173
|
+
const recommended = [...all].filter((id) => recommendedIds.has(id)).length;
|
|
174
|
+
return all.size ? recommended / all.size : 1;
|
|
158
175
|
}
|
|
159
176
|
|
|
160
177
|
function score(retrieved, truth) {
|
|
@@ -271,6 +288,10 @@ export function runRoutingEval(root, { json = false } = {}) {
|
|
|
271
288
|
const retrieved = new Set();
|
|
272
289
|
const critical = new Set();
|
|
273
290
|
const verif = new Set();
|
|
291
|
+
const recommendedIds = new Set();
|
|
292
|
+
const collectChecks = (ctx) => {
|
|
293
|
+
for (const v of [...ctx.verification.items, ...ctx.globalGates]) recommendedIds.add(v.id);
|
|
294
|
+
};
|
|
274
295
|
let unresolved = 0;
|
|
275
296
|
for (const t of s.touch) {
|
|
276
297
|
let ctx;
|
|
@@ -285,13 +306,16 @@ export function runRoutingEval(root, { json = false } = {}) {
|
|
|
285
306
|
for (const x of factsFromContext(ctx)) retrieved.add(x);
|
|
286
307
|
for (const x of criticalFromContext(ctx)) critical.add(x);
|
|
287
308
|
for (const x of verificationFromContext(ctx)) verif.add(x);
|
|
309
|
+
collectChecks(ctx);
|
|
288
310
|
}
|
|
289
311
|
// Change impact is part of routing: it is what tells the agent which other
|
|
290
312
|
// components a change reaches. Its output is legitimately part of context.
|
|
291
313
|
const im = impact(graph, s.touch);
|
|
314
|
+
for (const v of im.recommendedVerification) recommendedIds.add(v.id);
|
|
292
315
|
for (const c of im.affectedComponents) {
|
|
293
316
|
const ctx = queryContext(graph, c.ref, {});
|
|
294
317
|
for (const x of factsFromContext(ctx)) retrieved.add(x);
|
|
318
|
+
collectChecks(ctx);
|
|
295
319
|
}
|
|
296
320
|
|
|
297
321
|
const base = baselineContext(graph, s.touch);
|
|
@@ -303,8 +327,6 @@ export function runRoutingEval(root, { json = false } = {}) {
|
|
|
303
327
|
const all = score(retrieved, new Set(s.truth));
|
|
304
328
|
const baseAll = score(base, new Set(s.truth));
|
|
305
329
|
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
330
|
const noise = irrelevanceRatio(retrieved, closure);
|
|
309
331
|
const baseNoise = irrelevanceRatio(base, baseClosure);
|
|
310
332
|
|
|
@@ -314,12 +336,14 @@ export function runRoutingEval(root, { json = false } = {}) {
|
|
|
314
336
|
recall: all.hit / (all.total || 1),
|
|
315
337
|
precision: 1 - noise,
|
|
316
338
|
criticalRecall: crit.total ? crit.hit / crit.total : 1,
|
|
317
|
-
verificationRecall:
|
|
339
|
+
verificationRecall: verificationRecall(verif, new Set(s.verification)),
|
|
340
|
+
checkSelectivity: checkSelectivity(graph, recommendedIds),
|
|
318
341
|
noise,
|
|
319
342
|
baseline: {
|
|
320
343
|
recall: baseAll.hit / (baseAll.total || 1),
|
|
321
344
|
noise: baseNoise,
|
|
322
|
-
verificationRecall:
|
|
345
|
+
verificationRecall: verificationRecall(baseV, new Set(s.verification)),
|
|
346
|
+
checkSelectivity: 1, // The baseline runs every verification entry and gate.
|
|
323
347
|
},
|
|
324
348
|
missed: all.missed.slice(0, 8),
|
|
325
349
|
offClosure: [...retrieved].filter((r) => !closure.has(r)).slice(0, 8),
|
|
@@ -340,12 +364,14 @@ export function runRoutingEval(root, { json = false } = {}) {
|
|
|
340
364
|
precision: round(mean("precision")),
|
|
341
365
|
criticalRecall: round(mean("criticalRecall")),
|
|
342
366
|
verificationRecall: round(mean("verificationRecall")),
|
|
367
|
+
checkSelectivity: round(mean("checkSelectivity")),
|
|
343
368
|
noise: round(mean("noise")),
|
|
344
369
|
retrievalReduction: 0,
|
|
345
370
|
baseline: {
|
|
346
371
|
recall: round(meanB("recall")),
|
|
347
372
|
noise: round(meanB("noise")),
|
|
348
373
|
verificationRecall: round(meanB("verificationRecall")),
|
|
374
|
+
checkSelectivity: round(meanB("checkSelectivity")),
|
|
349
375
|
},
|
|
350
376
|
};
|
|
351
377
|
// 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.2.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": [
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"schemas",
|
|
43
43
|
"skills",
|
|
44
44
|
"templates",
|
|
45
|
+
"CHANGELOG.md",
|
|
45
46
|
"INSTALL_FOR_AGENTS.md"
|
|
46
47
|
],
|
|
47
48
|
"license": "MIT"
|
|
@@ -66,6 +66,46 @@
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
},
|
|
69
|
+
"normativeCitation": {
|
|
70
|
+
"description": "One section of a configured normative document, pinned by the digest it had when it was reviewed. `agentdoc audit` lists every section with its current digest.",
|
|
71
|
+
"type": "object",
|
|
72
|
+
"additionalProperties": false,
|
|
73
|
+
"required": ["path", "section", "sha256"],
|
|
74
|
+
"properties": {
|
|
75
|
+
"path": { "$ref": "#/$defs/repoPath" },
|
|
76
|
+
"section": { "type": "string", "minLength": 1, "maxLength": 200, "pattern": "^\\S+$" },
|
|
77
|
+
"sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"requirementKey": { "type": "string", "maxLength": 80, "pattern": "^requirement\\.[a-z0-9]+(?:[.-][a-z0-9]+)*$" },
|
|
81
|
+
"requirementValue": { "type": "string", "maxLength": 80, "pattern": "^[a-z0-9]+(?:[.-][a-z0-9]+)*$" },
|
|
82
|
+
"requirement": {
|
|
83
|
+
"description": "An implementation obligation a normative document places on this component. It is an AUTHORED fact that must be corroborated by implementation evidence on the same key; it never stands on its own word.",
|
|
84
|
+
"type": "object",
|
|
85
|
+
"additionalProperties": false,
|
|
86
|
+
"required": ["id", "key", "value", "source"],
|
|
87
|
+
"properties": {
|
|
88
|
+
"id": { "$ref": "#/$defs/name" },
|
|
89
|
+
"key": { "$ref": "#/$defs/requirementKey" },
|
|
90
|
+
"value": { "$ref": "#/$defs/requirementValue" },
|
|
91
|
+
"source": { "$ref": "#/$defs/normativeCitation" },
|
|
92
|
+
"satisfiedBy": {
|
|
93
|
+
"description": "Adapters able to derive the implementation side of this requirement. Named so a missing extractor is reported as such instead of as silence.",
|
|
94
|
+
"type": "array", "minItems": 1, "maxItems": 12, "uniqueItems": true,
|
|
95
|
+
"items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" }
|
|
96
|
+
},
|
|
97
|
+
"expects": {
|
|
98
|
+
"description": "What an adapter in satisfiedBy must find for the requirement to be provided: a host path installed, or a service managed. Without it an adapter has nothing deterministic to compare.",
|
|
99
|
+
"type": "object",
|
|
100
|
+
"additionalProperties": false,
|
|
101
|
+
"oneOf": [{ "required": ["installs"] }, { "required": ["service"] }],
|
|
102
|
+
"properties": {
|
|
103
|
+
"installs": { "type": "string", "maxLength": 400, "pattern": "^/[^\\s{}]*[^\\s{}/]$" },
|
|
104
|
+
"service": { "type": "string", "maxLength": 200, "pattern": "^[A-Za-z0-9@._-]+$" }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
69
109
|
"warningAcceptance": {
|
|
70
110
|
"type": "object",
|
|
71
111
|
"additionalProperties": false,
|
|
@@ -27,7 +27,11 @@
|
|
|
27
27
|
"description": "Published artifacts or remotes this component is consumed through.",
|
|
28
28
|
"items": { "$ref": "common.json#/$defs/repoPath" }
|
|
29
29
|
},
|
|
30
|
-
"context": { "$ref": "common.json#/$defs/context" }
|
|
30
|
+
"context": { "$ref": "common.json#/$defs/context" },
|
|
31
|
+
"requirements": {
|
|
32
|
+
"type": "array", "minItems": 1, "maxItems": 64,
|
|
33
|
+
"items": { "$ref": "common.json#/$defs/requirement" }
|
|
34
|
+
}
|
|
31
35
|
},
|
|
32
36
|
"oneOf": [
|
|
33
37
|
{ "required": ["system"], "not": { "anyOf": [{ "required": ["domain"] }, { "required": ["placementRationale"] }] } },
|
|
@@ -358,7 +358,8 @@
|
|
|
358
358
|
"capability",
|
|
359
359
|
"component-placement",
|
|
360
360
|
"event-producer",
|
|
361
|
-
"event-consumer"
|
|
361
|
+
"event-consumer",
|
|
362
|
+
"requirement-satisfaction"
|
|
362
363
|
]
|
|
363
364
|
},
|
|
364
365
|
"subject": {
|
|
@@ -416,6 +417,12 @@
|
|
|
416
417
|
"minLength": 1,
|
|
417
418
|
"maxLength": 200
|
|
418
419
|
},
|
|
420
|
+
"key": {
|
|
421
|
+
"$ref": "common.json#/$defs/requirementKey"
|
|
422
|
+
},
|
|
423
|
+
"value": {
|
|
424
|
+
"$ref": "common.json#/$defs/requirementValue"
|
|
425
|
+
},
|
|
419
426
|
"reason": {
|
|
420
427
|
"$ref": "common.json#/$defs/rationale"
|
|
421
428
|
},
|
|
@@ -633,6 +640,59 @@
|
|
|
633
640
|
]
|
|
634
641
|
}
|
|
635
642
|
}
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
"if": {
|
|
646
|
+
"properties": {
|
|
647
|
+
"fact": {
|
|
648
|
+
"const": "requirement-satisfaction"
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
"required": [
|
|
652
|
+
"fact"
|
|
653
|
+
]
|
|
654
|
+
},
|
|
655
|
+
"then": {
|
|
656
|
+
"required": [
|
|
657
|
+
"key",
|
|
658
|
+
"value"
|
|
659
|
+
],
|
|
660
|
+
"not": {
|
|
661
|
+
"anyOf": [
|
|
662
|
+
{
|
|
663
|
+
"required": [
|
|
664
|
+
"target"
|
|
665
|
+
]
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
"required": [
|
|
669
|
+
"transport"
|
|
670
|
+
]
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
"required": [
|
|
674
|
+
"role"
|
|
675
|
+
]
|
|
676
|
+
}
|
|
677
|
+
]
|
|
678
|
+
}
|
|
679
|
+
},
|
|
680
|
+
"else": {
|
|
681
|
+
"not": {
|
|
682
|
+
"anyOf": [
|
|
683
|
+
{
|
|
684
|
+
"required": [
|
|
685
|
+
"key"
|
|
686
|
+
]
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
"required": [
|
|
690
|
+
"value"
|
|
691
|
+
]
|
|
692
|
+
}
|
|
693
|
+
]
|
|
694
|
+
}
|
|
695
|
+
}
|
|
636
696
|
}
|
|
637
697
|
]
|
|
638
698
|
}
|
|
@@ -644,6 +704,51 @@
|
|
|
644
704
|
"$ref": "common.json#/$defs/warningAcceptance"
|
|
645
705
|
}
|
|
646
706
|
},
|
|
707
|
+
"normativeCoverage": {
|
|
708
|
+
"description": "Reviewed attestations that a normative section carries no implementation obligation. Pinned by the section digest; a changed section invalidates its attestation and forces a fresh review.",
|
|
709
|
+
"type": "array",
|
|
710
|
+
"maxItems": 2048,
|
|
711
|
+
"items": {
|
|
712
|
+
"type": "object",
|
|
713
|
+
"additionalProperties": false,
|
|
714
|
+
"required": [
|
|
715
|
+
"path",
|
|
716
|
+
"section",
|
|
717
|
+
"sha256",
|
|
718
|
+
"disposition",
|
|
719
|
+
"reason",
|
|
720
|
+
"reviewWhen"
|
|
721
|
+
],
|
|
722
|
+
"properties": {
|
|
723
|
+
"path": {
|
|
724
|
+
"$ref": "common.json#/$defs/repoPath"
|
|
725
|
+
},
|
|
726
|
+
"section": {
|
|
727
|
+
"type": "string",
|
|
728
|
+
"minLength": 1,
|
|
729
|
+
"maxLength": 200,
|
|
730
|
+
"pattern": "^\\S+$"
|
|
731
|
+
},
|
|
732
|
+
"sha256": {
|
|
733
|
+
"type": "string",
|
|
734
|
+
"pattern": "^[0-9a-f]{64}$"
|
|
735
|
+
},
|
|
736
|
+
"disposition": {
|
|
737
|
+
"enum": [
|
|
738
|
+
"no-implementation-obligation",
|
|
739
|
+
"informative"
|
|
740
|
+
]
|
|
741
|
+
},
|
|
742
|
+
"reason": {
|
|
743
|
+
"$ref": "common.json#/$defs/rationale"
|
|
744
|
+
},
|
|
745
|
+
"reviewWhen": {
|
|
746
|
+
"type": "string",
|
|
747
|
+
"minLength": 12
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
},
|
|
647
752
|
"output": {
|
|
648
753
|
"type": "object",
|
|
649
754
|
"additionalProperties": false,
|
|
@@ -678,6 +783,17 @@
|
|
|
678
783
|
},
|
|
679
784
|
"runbookDir": {
|
|
680
785
|
"$ref": "common.json#/$defs/repoPath"
|
|
786
|
+
},
|
|
787
|
+
"normative": {
|
|
788
|
+
"description": "Globs of normative documents (specifications, technical profiles). Every level-2 section of a matched Markdown document must be cited by a component requirement or attested in normativeCoverage; otherwise AGENTDOC_NORMATIVE_UNCOVERED.",
|
|
789
|
+
"type": "array",
|
|
790
|
+
"minItems": 1,
|
|
791
|
+
"maxItems": 64,
|
|
792
|
+
"uniqueItems": true,
|
|
793
|
+
"items": {
|
|
794
|
+
"type": "string",
|
|
795
|
+
"minLength": 1
|
|
796
|
+
}
|
|
681
797
|
}
|
|
682
798
|
}
|
|
683
799
|
}
|
|
@@ -96,7 +96,11 @@ not work around it: run `agentdoc compile`, review the diff, commit the graph.
|
|
|
96
96
|
- **Never hand-edit `.agentdoc/graph.json`.** The gate rejects it, and it would
|
|
97
97
|
be wrong anyway.
|
|
98
98
|
- **Do not trust existing documentation.** In MIGRATE it is evidence of what
|
|
99
|
-
someone once believed
|
|
99
|
+
someone once believed. The audit flags only a narrow set of contradictions
|
|
100
|
+
automatically (negated components, port claims); it does not read prose for
|
|
101
|
+
meaning. Obligations in specifications are checked only once declared:
|
|
102
|
+
configure `docs.normative`, turn every section into a component requirement
|
|
103
|
+
or a reviewed attestation, and finish with `agentdoc check --strict`.
|
|
100
104
|
|
|
101
105
|
## Deeper references
|
|
102
106
|
|
|
@@ -5,13 +5,18 @@ operates on a catalog can be run from any subdirectory of the repository: the
|
|
|
5
5
|
configuration is found by walking up to `agentdoc/agentdoc.config.yaml`. No
|
|
6
6
|
command ever needs the agentdoc source tree or a path to it.
|
|
7
7
|
|
|
8
|
-
Exit codes: `0` ok, `1` failure, `2` usage.
|
|
8
|
+
Exit codes: `0` ok, `1` failure, `2` usage. Flags are declared per command:
|
|
9
|
+
an unknown or misspelled flag is a usage error (`2`), with a suggestion when
|
|
10
|
+
one is close — it is never silently ignored, so `check --strcit` cannot pass
|
|
11
|
+
for `check --strict`. `agentdoc <command> --help` lists exactly what a command
|
|
12
|
+
accepts.
|
|
9
13
|
|
|
10
14
|
## Meta
|
|
11
15
|
|
|
12
16
|
```bash
|
|
13
17
|
agentdoc --version # installed version
|
|
14
|
-
agentdoc --help # command list
|
|
18
|
+
agentdoc --help # command list
|
|
19
|
+
agentdoc <command> --help # every flag of one command (also: agentdoc help <command>)
|
|
15
20
|
agentdoc doctor # machine check: is this CLI install healthy
|
|
16
21
|
agentdoc doctor --project # the same, plus the current project's catalog health
|
|
17
22
|
```
|
|
@@ -42,13 +47,16 @@ failures as part of its output.
|
|
|
42
47
|
```bash
|
|
43
48
|
agentdoc validate # sources + schemas + semantic checks
|
|
44
49
|
agentdoc compile [--require-clean] # deterministic graph build, atomic write
|
|
45
|
-
agentdoc check [--require-clean] [--allow-stale-observations]
|
|
50
|
+
agentdoc check [--strict] [--require-clean] [--allow-stale-observations]
|
|
46
51
|
```
|
|
47
52
|
|
|
48
53
|
`compile` writes `.agentdoc/graph.json` (or the path in `output.graph`).
|
|
49
54
|
`check` is the gate: it fails on a stale or hand-edited graph, a compiler or
|
|
50
55
|
schema version mismatch, an observation past its declared `maxAgeDays`, and —
|
|
51
|
-
with `--require-clean` — catalog inputs that differ from `HEAD`.
|
|
56
|
+
with `--require-clean` — catalog inputs that differ from `HEAD`. With
|
|
57
|
+
`--strict` it also fails on every warning not accepted in `warningAcceptances`
|
|
58
|
+
— the gate that ends a MIGRATE (unreviewed normative sections, unproven
|
|
59
|
+
requirements, orphan documents).
|
|
52
60
|
|
|
53
61
|
## Query and impact
|
|
54
62
|
|
|
@@ -40,9 +40,19 @@ never copies the agentdoc implementation into the repository.
|
|
|
40
40
|
In `agentdoc.config.yaml`:
|
|
41
41
|
|
|
42
42
|
- Set `namespace`, fix `discovery.componentDescriptors` to match the real
|
|
43
|
-
layout, and enable the adapters for the technologies present. `
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
layout, and enable the adapters for the technologies present. `init`
|
|
44
|
+
pre-selects the language and deployment adapters whose manifests it finds:
|
|
45
|
+
`node`, `go`, `rust` and `python`, plus `dockerfile` and `wrangler`. It also
|
|
46
|
+
adds `database`, `openapi`, `events` and `github-actions`. Add the rest
|
|
47
|
+
(`pnpm`, `turborepo`, `typescript`, `protobuf`, `graphql`, `ansible`, …) when
|
|
48
|
+
the repository uses them. `adapters: []` is a valid but useless starting
|
|
49
|
+
point: with no adapter, a component descriptor is an unsupported claim and
|
|
50
|
+
compilation fails on purpose.
|
|
51
|
+
- An existing catalog does not gain new adapters on upgrade. After upgrading
|
|
52
|
+
the CLI, compare `adapters:` with the technologies present, for example a
|
|
53
|
+
repository with `Cargo.toml` or `pyproject.toml` units and no `rust` /
|
|
54
|
+
`python` entry. Then run `agentdoc audit`: newly visible units appear as
|
|
55
|
+
`AGENTDOC_COVERAGE_ZERO` until they have a descriptor.
|
|
46
56
|
- Three example authority rules are written **commented out**. Uncomment only
|
|
47
57
|
the ones you can justify, with the rationale in your own words. An authority
|
|
48
58
|
rule is a governance decision; the scaffolder does not make it for you.
|
|
@@ -81,8 +91,14 @@ agentdoc audit > migration-report.json
|
|
|
81
91
|
|
|
82
92
|
Then, in order:
|
|
83
93
|
|
|
84
|
-
1. **Source-of-truth mapping.**
|
|
85
|
-
|
|
94
|
+
1. **Source-of-truth mapping.** Set `docs.normative` to the specifications and
|
|
95
|
+
profiles, then empty `normative.uncovered`: each section becomes a
|
|
96
|
+
`spec.requirements` entry on the component that must fulfil it, or a
|
|
97
|
+
`normativeCoverage` attestation that it obliges nothing. Prove each
|
|
98
|
+
requirement (adapter evidence or a `requirement-satisfaction` override);
|
|
99
|
+
an unproven one stays `REQUIREMENT_UNVERIFIED`. For other major claims,
|
|
100
|
+
record which inventory supports them; anything supported by none goes to
|
|
101
|
+
`reviewedAmbiguity`.
|
|
86
102
|
2. **Contradictions.** Work `contradictions` and `documentationContradictions`.
|
|
87
103
|
Each needs a human decision: fix the docs, fix the code, or add an authority
|
|
88
104
|
rule saying which class wins for that key.
|
|
@@ -91,7 +107,13 @@ Then, in order:
|
|
|
91
107
|
5. **Stale contracts.** Point the API descriptor at the current contract file.
|
|
92
108
|
6. **Ownership.** Placement from code where unambiguous, from a human elsewhere.
|
|
93
109
|
7. **Catalog.** `scaffold --write`, review every generated file.
|
|
94
|
-
8. **Gate.** `validate` → `compile` → `check`, then install CI.
|
|
110
|
+
8. **Gate.** `validate` → `compile` → `check --strict`, then install CI. Strict
|
|
111
|
+
fails on any unaccepted warning: an unreviewed section or an unproven
|
|
112
|
+
requirement cannot ship in a "complete" migration.
|
|
113
|
+
|
|
114
|
+
**The audit does not read prose for meaning.** It checks only negated
|
|
115
|
+
components and port claims automatically; every other documented obligation is
|
|
116
|
+
caught only if it is declared as a requirement.
|
|
95
117
|
|
|
96
118
|
**Do not trust existing documentation.** The single most common migration
|
|
97
119
|
failure is copying existing prose into descriptions. If you cannot write a
|