@agentskit/doc-bridge 1.7.44 → 1.9.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 (223) hide show
  1. package/CHANGELOG.md +471 -0
  2. package/CONTRIBUTING.md +29 -4
  3. package/README.md +87 -40
  4. package/SECURITY.md +7 -0
  5. package/action.yml +1 -1
  6. package/bin/ak-docs.js +2 -2
  7. package/bin/ak-verify.js +13 -7
  8. package/dist/cli/program.d.ts +3 -1
  9. package/dist/cli/program.js +15888 -6061
  10. package/dist/cli/program.js.map +1 -1
  11. package/dist/config/index.d.ts +1 -1
  12. package/dist/config/index.js +91 -9
  13. package/dist/config/index.js.map +1 -1
  14. package/dist/index-Beor6Yhi.d.ts +792 -0
  15. package/dist/index.d.ts +9979 -3257
  16. package/dist/index.js +15954 -5774
  17. package/dist/index.js.map +1 -1
  18. package/docs/MARKETPLACE.md +1 -1
  19. package/docs/PRD-documentation-efficiency-study.md +406 -0
  20. package/docs/PRD-knowledge-retrieval-and-enrichment.md +466 -0
  21. package/docs/RELEASE.md +22 -8
  22. package/docs/adr/0002-documentation-audit-boundary.md +22 -0
  23. package/docs/adr/0003-study-protocol-and-historical-evidence.md +40 -0
  24. package/docs/adr/0004-controlled-study-runner.md +25 -0
  25. package/docs/adr/0005-documentation-quality-and-criticality.md +20 -0
  26. package/docs/adr/0006-registry-semantic-grounding.md +20 -0
  27. package/docs/adr/0007-longitudinal-study-metrics.md +21 -0
  28. package/docs/adr/0008-study-verification-boundary.md +21 -0
  29. package/docs/adr/0009-study-provider-cli-adapter.md +25 -0
  30. package/docs/agent-corpus/INDEX.md +14 -3
  31. package/docs/agent-corpus/OVERVIEW.md +25 -0
  32. package/docs/agent-corpus/chat.md +7 -3
  33. package/docs/agent-corpus/cli.md +18 -2
  34. package/docs/agent-corpus/conformance.md +14 -2
  35. package/docs/agent-corpus/doc-bridge.md +48 -1
  36. package/docs/agent-corpus/doctor.md +10 -2
  37. package/docs/agent-corpus/gates.md +6 -2
  38. package/docs/agent-corpus/mcp.md +15 -2
  39. package/docs/agent-corpus/memory.md +6 -2
  40. package/docs/agent-corpus/query.md +35 -2
  41. package/docs/bench/README.md +122 -0
  42. package/docs/bench/retrieval-baseline-v1.json +28 -0
  43. package/docs/bench/retrieval-suite-v1.json +1033 -0
  44. package/docs/chat-and-rag.md +3 -2
  45. package/docs/for-agents.md +9 -1
  46. package/docs/getting-started.md +4 -11
  47. package/docs/guides/gate-ci.md +11 -1
  48. package/docs/guides/install-and-run.md +9 -65
  49. package/docs/index.md +22 -1
  50. package/docs/knowledge-engine-runbook.md +51 -4
  51. package/docs/landing/assets/context-payload-reduction.svg +21 -0
  52. package/docs/landing/assets/controlled-ab-comparison.svg +30 -0
  53. package/docs/landing/index.html +119 -5
  54. package/docs/loop-workflow.md +117 -0
  55. package/docs/mcp.md +6 -1
  56. package/docs/parity/public-claims-v1.json +145 -0
  57. package/docs/playbook/doc-bridge-pattern.md +1 -1
  58. package/docs/query.md +90 -2
  59. package/docs/recipes/index-pipeline.md +1 -1
  60. package/docs/schemas/agent-handoff-v1.md +15 -0
  61. package/docs/schemas/doc-bridge-index-v1.md +65 -0
  62. package/docs/spec/benchmark-v1.md +39 -1
  63. package/docs/spec/cli.md +30 -10
  64. package/docs/spec/config-v1.md +192 -8
  65. package/docs/spec/documentation-audit-v1.md +61 -0
  66. package/docs/spec/enrichment-overlay-v1.md +241 -0
  67. package/docs/spec/graph-signals-v1.md +92 -0
  68. package/docs/spec/incremental-scan-v1.md +102 -0
  69. package/docs/spec/markdown-analyzer-v1.md +73 -0
  70. package/docs/spec/mcp-knowledge-tools-v1.md +147 -0
  71. package/docs/spec/measured-enrichment-v1.md +229 -0
  72. package/docs/spec/public-parity-v1.md +119 -0
  73. package/docs/spec/registry-agents.md +6 -0
  74. package/docs/spec/render-v1.md +122 -0
  75. package/docs/spec/retrieval-index-v1.md +164 -0
  76. package/docs/spec/study-metrics-v1.md +58 -0
  77. package/docs/spec/study-protocol-v1.md +46 -0
  78. package/docs/spec/study-provider-cli-v1.md +116 -0
  79. package/docs/spec/study-runner-v1.md +35 -0
  80. package/docs/spec/study-task-suite-v1.md +41 -0
  81. package/docs/spec/study-verification-v1.md +40 -0
  82. package/docs/study/README.md +84 -0
  83. package/docs/study/ab-adjudicated-cost-analysis-v1.md +29 -0
  84. package/docs/study/ab-adjudicated-cost-plan-v1.json +33 -0
  85. package/docs/study/ab-adjudicated-cost-plan-v2-v1.json +33 -0
  86. package/docs/study/ab-adjudicated-cost-result-v1.json +80 -0
  87. package/docs/study/ab-baseline-analysis-v1.md +21 -0
  88. package/docs/study/ab-baseline-plan-v1.json +33 -0
  89. package/docs/study/ab-baseline-recovery-plan-v1.json +33 -0
  90. package/docs/study/ab-baseline-result-v1.json +79 -0
  91. package/docs/study/documentation-audit-round-2026-08-31.json +183 -0
  92. package/docs/study/historical-evidence-v1.json +252 -0
  93. package/docs/study/observation-ledger-v1.json +30632 -0
  94. package/docs/study/phase3-task-coverage-v1.json +34 -0
  95. package/docs/study/phase4-public-pilot-ledger-v1.json +1344 -0
  96. package/docs/study/phase4-public-pilot-result-v1.json +52 -0
  97. package/docs/study/phase4-public-pilot-run-plan-v1.json +26 -0
  98. package/docs/study/phase4-public-pilot-task-suite-v1.json +71 -0
  99. package/docs/study/pilot-round-2026-08-31.json +46 -0
  100. package/docs/study/protocol-v1.json +90 -0
  101. package/docs/study/publication-gate-v1.md +45 -0
  102. package/docs/study/quality-scorecard-cycle-plan.md +545 -0
  103. package/docs/study/quality-scorecard-v1.json +38 -0
  104. package/docs/study/round-1-adjudicated-smoke-v1.json +30642 -0
  105. package/docs/study/round-1-instrumentation-plan-v1.md +39 -0
  106. package/docs/study/round-2-expanded-adjudication-v1.json +91 -0
  107. package/docs/study/round-2-expanded-validation-v1.md +58 -0
  108. package/docs/study/round-3-evidence-contract-v1.json +75 -0
  109. package/docs/study/round-3-evidence-contract-v1.md +57 -0
  110. package/docs/study/round-4-confirmation-v1.json +75 -0
  111. package/docs/study/round-4-confirmation-v1.md +55 -0
  112. package/docs/study/run-plan-v1.json +33 -0
  113. package/docs/study/semantic-adjudication-cycle-8.md +20 -0
  114. package/docs/study/task-suite-v1.json +96 -0
  115. package/docs/study/token-efficiency-plan-v1.md +337 -0
  116. package/docs/study/token-efficiency-protocol-v2.json +62 -0
  117. package/docs/study/verification-binding-v1.json +27 -0
  118. package/docs/validation-cycle-plan.md +33 -0
  119. package/docs/verification-harness.md +15 -6
  120. package/ecosystem-claims.json +2 -2
  121. package/ecosystem-upstream.json +2 -2
  122. package/ecosystem.json +4 -4
  123. package/mcpb/manifest.json +9 -1
  124. package/package.json +89 -72
  125. package/scripts/check-ecosystem-upstream.mjs +36 -7
  126. package/scripts/report-visual-check.mjs +20 -3
  127. package/skills/doc-bridge-handoff/fixtures/synthetic-repo/docs/for-agents/packages/payments.md +7 -0
  128. package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
  129. package/src/agents/registry-adapter.ts +192 -24
  130. package/src/audit/documentation.ts +513 -0
  131. package/src/bench/baseline.ts +198 -0
  132. package/src/bench/overlay-delta.ts +139 -0
  133. package/src/bench/retrieval.ts +319 -0
  134. package/src/budget/compile.ts +91 -0
  135. package/src/budget/sections.ts +70 -0
  136. package/src/cli/demo.ts +2 -2
  137. package/src/cli/program.ts +699 -79
  138. package/src/cli/usage.ts +71 -0
  139. package/src/config/defaults.ts +1 -0
  140. package/src/config/index.ts +4 -0
  141. package/src/config/load-config.ts +7 -1
  142. package/src/config/schema.ts +121 -4
  143. package/src/conformance/documentation-standard-v1.ts +22 -14
  144. package/src/discovery/areas.ts +182 -0
  145. package/src/discovery/documentation.ts +255 -23
  146. package/src/discovery/identity.ts +24 -0
  147. package/src/discovery/incremental.ts +314 -0
  148. package/src/discovery/inputs.ts +110 -0
  149. package/src/discovery/markdown.ts +481 -0
  150. package/src/discovery/repository.ts +557 -125
  151. package/src/doctor/run-doctor.ts +246 -27
  152. package/src/enrich/approvals.ts +190 -0
  153. package/src/enrich/cache.ts +93 -0
  154. package/src/enrich/context-pack.ts +272 -0
  155. package/src/enrich/overlay.ts +255 -0
  156. package/src/enrich/review.ts +106 -0
  157. package/src/enrich/stage.ts +374 -0
  158. package/src/enrich/stats.ts +100 -0
  159. package/src/enrich/validate.ts +410 -0
  160. package/src/federation/llms.ts +74 -24
  161. package/src/findings/report.ts +103 -0
  162. package/src/fixes/proposals.ts +4 -3
  163. package/src/graph/build.ts +356 -0
  164. package/src/graph/memory.ts +208 -0
  165. package/src/index-builder/build-handoffs.ts +22 -11
  166. package/src/index-builder/build-index.ts +132 -3
  167. package/src/index-builder/human-adapters/fumadocs.ts +1 -1
  168. package/src/index-builder/llms-txt.ts +48 -8
  169. package/src/index-builder/project-corpus.ts +111 -0
  170. package/src/index-builder/watch-index.ts +1 -1
  171. package/src/index.ts +630 -2
  172. package/src/lib/bounded-text.ts +15 -10
  173. package/src/lib/fuzzy-match.ts +235 -0
  174. package/src/mcp/knowledge.ts +554 -0
  175. package/src/mcp/server.ts +113 -18
  176. package/src/metrics/benchmark.ts +21 -0
  177. package/src/parity/check.ts +309 -0
  178. package/src/parity/claims.ts +259 -0
  179. package/src/parity/resolve.ts +160 -0
  180. package/src/query/handoff.ts +326 -0
  181. package/src/query/load-index.ts +53 -1
  182. package/src/query/query.ts +92 -59
  183. package/src/query/search.ts +289 -92
  184. package/src/query/text.ts +155 -0
  185. package/src/reconciliation/reconcile.ts +148 -15
  186. package/src/render/data.ts +356 -0
  187. package/src/render/engine.ts +398 -0
  188. package/src/render/generated.ts +77 -0
  189. package/src/render/render.ts +209 -0
  190. package/src/render/template-source.ts +52 -0
  191. package/src/render/templates.ts +289 -0
  192. package/src/report/html.ts +23 -17
  193. package/src/retrieval/bm25.ts +161 -0
  194. package/src/retrieval/project.ts +495 -0
  195. package/src/retrieval/rank.ts +383 -0
  196. package/src/retrieval/weights.ts +39 -0
  197. package/src/retriever/doc-bridge-retriever.ts +100 -15
  198. package/src/rules/engine.ts +45 -12
  199. package/src/safety/repository.ts +1 -1
  200. package/src/schemas/agent-handoff.ts +56 -0
  201. package/src/schemas/budget.ts +37 -0
  202. package/src/schemas/doc-bridge-index.ts +53 -2
  203. package/src/schemas/enrichment.ts +369 -0
  204. package/src/schemas/json-schemas.ts +39 -2
  205. package/src/schemas/knowledge.ts +19 -3
  206. package/src/schemas/retrieval-index.ts +152 -0
  207. package/src/shims/graphology.d.ts +91 -0
  208. package/src/study/adjudication.ts +196 -0
  209. package/src/study/execution.ts +350 -0
  210. package/src/study/expectations.ts +219 -0
  211. package/src/study/metrics.ts +467 -0
  212. package/src/study/protocol.ts +271 -0
  213. package/src/study/provider-cli.ts +115 -0
  214. package/src/study/provider-telemetry.ts +47 -0
  215. package/src/study/quality-scorecard.ts +164 -0
  216. package/src/study/runner.ts +461 -0
  217. package/src/study/task-suite.ts +321 -0
  218. package/src/study/verification.ts +134 -0
  219. package/src/validate.ts +8 -5
  220. package/src/version.ts +1 -1
  221. package/src/workflow/engine.ts +36 -11
  222. package/dist/index-C2PCQSrB.d.ts +0 -2251
  223. package/scripts/verification-harness.mjs +0 -483
@@ -0,0 +1,241 @@
1
+ ---
2
+ title: Enrichment overlay v1
3
+ description: Typed agent proposals, the deterministic validators that decide them, the overlay that stores the decisions, and how little of it the deterministic layer is allowed to feel.
4
+ ---
5
+
6
+ # Enrichment overlay v1
7
+
8
+ A Registry agent may enrich the knowledge graph. It may never become an authority over it.
9
+
10
+ `AgentProposalV1` could say "review this finding". It could not say that a document is canonical
11
+ for an area, that an alias should resolve to an entity, or that a relation exists with a given
12
+ confidence — and nothing stored what it said, nothing consumed it, the deterministic cache was an
13
+ in-process `Map`, and `ak-docs suggest` sent the whole redacted snapshot, 740 KB on this
14
+ repository, in one call. This document is the replacement: proposals typed per claim, decided by
15
+ validators that have no judgement to exercise, stored in an overlay bound to the content hash of
16
+ what each entry describes, and projected into retrieval with an influence that is bounded by
17
+ construction.
18
+
19
+ ## Typed proposals
20
+
21
+ `EnrichmentProposalV1` in `src/schemas/enrichment.ts` is a discriminated union on `kind`. The
22
+ envelope is the same for every kind:
23
+
24
+ | Field | Meaning |
25
+ | --- | --- |
26
+ | `proposalId` | The hash of kind, entity, target content hash, agent identity, prompt version — and, for kinds one entity can carry several of, the payload field that tells them apart (`alias`, `phrase`, the relation's `to` and `kind`, `against`, `with`, `area` and `missing`, `directories`). A re-run over unchanged inputs produces the same id; a summary is a slot, an alias is a set. |
27
+ | `entity` | Must exist in the snapshot the proposal was made against. |
28
+ | `targetContentHash` | The entity's content hash at proposal time: the file hash for a file-backed entity, a hash of the entity as recorded for an area. The entry expires when it moves. |
29
+ | `confidence`, `reason` | 0..1, and up to 1 000 characters. |
30
+ | `evidence` | At least one item, and every item present — by source, path and lines — in the snapshot or the report. |
31
+ | `relatedDiagnosticIds` | Optional; every id must exist in the report. |
32
+ | `origin` | `agentId`, `agentVersion`, `promptVersion`, and optionally `model` and `provider`. |
33
+ | `baseSnapshotHash` | The snapshot it was made against. |
34
+ | `payload` | Per kind, below. |
35
+
36
+ | Kind | Payload | Deterministic validator | Policy |
37
+ | --- | --- | --- | --- |
38
+ | `classify-document` | `type`, `audience`, `lifecycle`, `criticality` | enumerated values; the entity is a document | accept by policy |
39
+ | `summarize` | `summary` ≤ 400 chars, `language` | length; redaction scan; differs from the current summary | accept by policy |
40
+ | `add-alias` | `alias` ≤ 64 chars | no collision with an id, name, title, filename stem or alias — exact, or Jaro-Winkler ≥ 0.95 | accept by policy |
41
+ | `add-intent` | `phrase` ≤ 120 chars, `language` | length; language tag present | accept by policy |
42
+ | `mark-canonical` | `scope` | the scope exists; the entity is a document; at most one canonical document per scope after the merge — a second one is a `canonical-conflict` | human approval |
43
+ | `propose-relation` | `from`, `to`, `kind`, `detection` | both endpoints exist; the kind is one of `covers`, `mentions`, `links-to`, `depends-on`, `related-to`, `documents`; not already observed; every evidence item lies inside one endpoint | human approval |
44
+ | `flag-contradiction` | `against`, `claim`, `observed` | both entities exist; evidence inside both | human approval |
45
+ | `flag-redundancy` | `with` | both are documents; not already exact duplicates | human approval |
46
+ | `flag-gap` | `area`, `missing` | the area exists; not already flagged with the same text | accepted as a finding, never as a fact |
47
+ | `rank-hint` | `relevance`: `strong` or `weak` | the entity exists | accept by policy, bounded weight |
48
+ | `suggest-area` | `directories`, `name` | every directory exists; no overlap with an existing area | human approval, then configuration, never an entity |
49
+
50
+ Any other kind is recorded as rejected with reason `invalid-kind`. The full reason list is
51
+ `ENRICHMENT_REJECTION_REASONS`; every rejection carries one of them, so a run's rejections are a
52
+ histogram and not a log.
53
+
54
+ Envelope checks run before the kind's own rule, in a fixed order: `invalid-kind`, `no-evidence`,
55
+ `schema`, `base-snapshot-mismatch`, `unknown-entity`, `stale-target`, `unknown-diagnostic`,
56
+ `evidence-outside-artifacts`, `proposal-id-mismatch`. A proposal that fails the envelope never
57
+ reaches a kind validator.
58
+
59
+ ## The overlay
60
+
61
+ `EnrichmentOverlayV1` lives at `.doc-bridge/enrich/overlay.json`:
62
+
63
+ ```json
64
+ {
65
+ "type": "enrichment-overlay",
66
+ "schemaVersion": 1,
67
+ "contentHash": "…",
68
+ "baseSnapshotHash": "…",
69
+ "accepted": [{ "proposal": { "…": "…" }, "acceptedAt": "2026-09-14T00:00:00.000Z", "acceptedBy": "policy" }],
70
+ "pending": [{ "proposal": { "…": "…" }, "approvalId": "…", "note": "canonical-conflict" }],
71
+ "rejected": [{ "proposalId": "…", "kind": "propose-relation", "entity": "…", "reason": "unknown-endpoint", "detail": "…" }],
72
+ "stats": { "byKind": { "summarize": { "proposed": 4, "accepted": 4, "pending": 0, "rejected": 0 } }, "rejectionReasons": {}, "agentRuns": 1, "cacheHits": 0, "packs": 4, "inputBytes": 0, "outputBytes": 0, "expired": 0 }
73
+ }
74
+ ```
75
+
76
+ `acceptedBy` is `policy` for accept-by-policy kinds and a person's name otherwise; the schema
77
+ refuses `policy` on a human-approval kind and refuses an `acceptedBy` equal to the proposal's
78
+ `origin.agentId`. `contentHash` is over the decisions — every accepted, pending and rejected entry
79
+ and the hashes they bind to — and not over `acceptedAt` or `stats`, because two runs over one
80
+ unchanged repository must produce one overlay hash and a timestamp is not a fact about the
81
+ repository.
82
+
83
+ Reading never writes. `readEnrichmentOverlay` returns nothing for a missing, unreadable, malformed
84
+ or hash-mismatched file, and a reader that got nothing behaves as if there were no overlay.
85
+
86
+ **Staleness is per entry.** At projection time an accepted entry whose `targetContentHash` no
87
+ longer matches its entity — or whose other endpoint no longer exists — is expired in the result
88
+ and excluded from ranking; its siblings for unchanged entities survive. The file is untouched by a
89
+ read; the next `ak-docs enrich` moves the expired entries to `rejected` with reason `expired`.
90
+
91
+ **Reproducible acceptance.** `revalidateEnrichmentOverlay` re-runs the validators over the stored
92
+ proposals and returns the partition they produce; a test asserts it equals the stored one. An
93
+ overlay whose partition does not reproduce was edited by hand or outlived its validators, and
94
+ either way it is no longer evidence of a decision.
95
+
96
+ ## The stage
97
+
98
+ `enrich` is a workflow stage between `reconcile` and `evaluate`, run only by `ak-docs enrich` or
99
+ `ak-docs check --enrich`. A plain `check` leaves its step pending and `evaluate` reads a null
100
+ previous output. A missing, failed, timed-out or stale enrichment never changes a `check` result:
101
+ `check --enrich` reports `enrichment.status: failed` with the error and carries on.
102
+
103
+ ```
104
+ $ ak-docs enrich --text
105
+ Roles: curator=ecosystem-doc-bridge-corpus-scanner
106
+ Packs: 92 (agent calls 3, cache hits 89, re-run 3)
107
+ Accepted: 180 Pending: 4 Rejected: 7 Expired: 3
108
+ summarize: accepted 89, pending 0, rejected 1
109
+ …
110
+ Overlay: 7c1e… (overlay.json)
111
+ ```
112
+
113
+ The stage:
114
+
115
+ 1. builds one context pack per target entity for each configured role and batches them by area;
116
+ 2. answers each pack from the cache or from the agent, caching what the agent said per pack —
117
+ including nothing, so silence is not asked for twice;
118
+ 3. validates everything through the partition against the snapshot, the report and the overlay
119
+ already on disk; policy kinds are accepted, human kinds are requested from the approval gate;
120
+ 4. adjudicates what two roles could not settle, if a third identity is configured;
121
+ 5. merges with the stored overlay — decisions people made survive while their target does — and
122
+ writes it.
123
+
124
+ The step's workflow input names the overlay it attaches (`{ reportHash, overlayHash }`), because
125
+ an approval changes the overlay without changing the report, and the engine refuses a step whose
126
+ input did not move but whose output did.
127
+
128
+ ## Context packs and the cache
129
+
130
+ Agents never receive the snapshot. `buildContextPacks` in `src/enrich/context-pack.ts` builds
131
+ one pack per target entity:
132
+
133
+ | Section | Content | Bound |
134
+ | --- | --- | --- |
135
+ | `target` | id, kind, name, path, content hash, aliases, its own evidence items, bounded metadata | — |
136
+ | `neighbours` | the other end of every relation touching the target, with kind, path, content hash, the relation and its direction; sorted by kind then id | 32 |
137
+ | `diagnostics` | open diagnostics naming the target or citing its file | 16 |
138
+ | `evidence` | the target's own file, redacted, read only if it still hashes to what the snapshot recorded | 12 KB |
139
+ | `budget` | `maxBytes`, `bytes`, `dropped` | 64 KB default, `intelligence.registry.maxPackBytes` |
140
+
141
+ Over budget, sections are dropped in a declared order — excerpt bytes (halved until it fits, then
142
+ dropped), then diagnostics from the end, then neighbours from the end — and the target is never
143
+ dropped. This mirrors `compileBudget` from `@agentskit/core` with a byte counter and the
144
+ `drop-oldest` strategy over sections ordered least-important-first; a test cross-checks the two,
145
+ and the mirror is what runs, because a pack must be the same pack whether or not an optional peer
146
+ is installed. Every string in a pack passes `redactSecrets`, asserted by test.
147
+
148
+ `packHash` covers the target's and every neighbour's content hash and nothing else. The cache key
149
+ is the hash of task, agent identity and version, prompt version and pack hash; entries live under
150
+ `.doc-bridge/enrich/cache/<key>.json`. An unchanged repository therefore makes zero agent calls,
151
+ and a one-document change re-runs only the packs whose hash moved — the changed document's, and
152
+ any pack it was a neighbour of.
153
+
154
+ ## Protocol v2
155
+
156
+ The adapter (`src/agents/registry-adapter.ts`) keeps `run` for `AgentProposalV1` over protocol
157
+ `doc-bridge.registry-agent.v1` — `ak-docs suggest` still works — and gains `enrich(task, packs)`
158
+ over `doc-bridge.registry-agent.v2`:
159
+
160
+ ```json
161
+ { "protocol": "doc-bridge.registry-agent.v2", "task": "curate", "role": "curator", "promptVersion": "1", "packs": [ … ], "capabilities": ["pack.read", "proposal.write"], "network": false, "shell": false, "deterministic": true }
162
+ ```
163
+
164
+ The answer is one JSON object `{ "proposals": [ … ] }`. `task` is `curate`, `review` or
165
+ `adjudicate`; for `adjudicate` the packs are followed by one `adjudication-request` item listing
166
+ the disputed proposals, and `proposals` carries `EnrichmentAdjudicationV1` values. The adapter
167
+ checks transport and budget and returns the raw array; grounding is the validators' job, and
168
+ keeping it there is what makes a stored overlay reproducible from its proposals. The same CLI or
169
+ local runner module serves both protocols and tells them apart by `protocol`.
170
+
171
+ ## Roles
172
+
173
+ | Role | Task | Targets | Kinds it is for |
174
+ | --- | --- | --- | --- |
175
+ | curator | `curate` | documents | classification, summaries, aliases, intents, canonical markers, redundancy, gaps |
176
+ | reviewer | `review` | documents, areas | relations, contradictions, gaps, rank hints, area suggestions |
177
+ | adjudicator | `adjudicate` | the disputed entities | canonical conflicts and disputed contradictions only |
178
+
179
+ Roles are configuration under `intelligence.registry.roles`; the default is the configured agent
180
+ as curator only. The adjudicator must be a different agent identity from both others —
181
+ `resolveEnrichmentRoles` refuses the configuration otherwise — and the validator rejects any
182
+ adjudication whose origin matches a proposal it judges as `self-adjudication`. An adjudication
183
+ judges; it never approves: the losers are rejected as `adjudicated`, the winner stays pending for
184
+ the person the kind's policy requires, and a settled dispute is not reopened by the cache
185
+ replaying the proposals that caused it.
186
+
187
+ ## Approvals
188
+
189
+ Human approval goes through `createApprovalGate` from `@agentskit/core/hitl` over a file-backed
190
+ `ApprovalStore` under `.doc-bridge/approvals/`, one JSON record per approval. The gate contract is
191
+ mirrored in-repo (`src/enrich/approvals.ts`) for the same reason every ecosystem contract is —
192
+ `@agentskit/core` is an optional peer — and the real gate is used through `importPeer` when it is
193
+ installed; a test writes through both over one store and reads the same records.
194
+
195
+ An approval id is the hash of `proposalId` and `targetContentHash`, so an approval given for one
196
+ version of a document cannot be replayed against the next. `ak-docs enrich approve <proposalId>
197
+ --by <name>`, MCP `docbridge.proposals { action: "enrich-approve" }` and a rendered review page
198
+ call the same `decideEnrichment`: the gate decides first, the overlay moves second, and if the
199
+ gate refuses — already decided, unknown — the overlay is untouched. An approver equal to the
200
+ proposal's author is refused; `policy` is not a person. `ak-docs fix approve` records its
201
+ approval through the same gate under `doc-bridge.fix`, bound to the fix proposal id and its
202
+ content hash.
203
+
204
+ ## Projection and bounded influence
205
+
206
+ `projectEnrichmentOverlay(overlay, snapshot)` turns the live accepted set into what
207
+ `projectRetrievalIndex` reads:
208
+
209
+ | Accepted kind | Effect in the projection |
210
+ | --- | --- |
211
+ | `add-alias` | one more alias on the entry |
212
+ | `summarize` | the entry's summary, only when the entity has none of its own |
213
+ | `add-intent` | an `intent` entry with `provenance: proposed` |
214
+ | `mark-canonical` | the `canonical` tag, and 0.8 of the signal |
215
+ | `rank-hint` | 1 (strong) or 0.5 (weak) of the signal |
216
+ | `propose-relation` | one more edge with `confidence: proposed`; a `covers` edge counts toward `coveredBy` and canonicality |
217
+ | `flag-*`, `suggest-area` | nothing: findings and suggestions are for a reviewer, not for ranking |
218
+
219
+ The signal is a per-entry share in 0..1, carried on the entry as `agentSignal`, and the ranker
220
+ multiplies it by `ACCEPTED_SIGNALS_WEIGHT` = 15% of the exact-id boost (30 of 200). It applies to
221
+ lexical hits only, like every other tie-breaker. An accepted signal can therefore reorder near-ties
222
+ and can never lift an entry past one the query named exactly — asserted by test. Without an
223
+ overlay, `ACCEPTED_SIGNALS_WEIGHT × 0` is what it always was, and the projection is byte-identical.
224
+
225
+ The projection's `overlayHash` is the hash of the live accepted set — `EMPTY_OVERLAY_HASH` when
226
+ nothing is live — so it is part of the projection's identity and `IndexStaleError` sees a changed
227
+ overlay. `withAcceptedRelations` merges live proposed relations into a snapshot for the graph, the
228
+ HTML report and the memory view, and `assertObservedSurvive` checks that every observed entity and
229
+ relation is present and unchanged afterwards; the report draws a proposed edge dashed.
230
+
231
+ The index builder consults the overlay only while `intelligence.registry.enabled` is true:
232
+ switching the Registry off restores the deterministic baseline exactly.
233
+
234
+ ## What never happens
235
+
236
+ - No overlay entry deletes or alters observed data. The projection asserts that every observed
237
+ entity and relation survives enrichment.
238
+ - No agent is called from `check`, `index`, `search`, `query`, `render` or MCP. The enrich stage
239
+ runs only on request, and nothing reachable from the query path imports `src/agents`.
240
+ - No agent approves anything — its own output, another agent's, or an adjudication's winner.
241
+ - No read writes. A corrupt overlay is no overlay.
@@ -0,0 +1,92 @@
1
+ ---
2
+ title: Graph signals v1
3
+ description: What Doc Bridge computes from the repository graph, what each signal means, and what it deliberately does not claim.
4
+ ---
5
+
6
+ # Graph signals v1
7
+
8
+ The knowledge snapshot is a graph, and some questions are only answerable as graph questions: which
9
+ document is the entry point, which module every change reaches through, which directories move
10
+ together, where the imports run in a circle.
11
+
12
+ These are computed with [graphology](https://graphology.github.io) from the snapshot, on demand.
13
+ The graph is a working structure: it is never serialised, `DiscoverySnapshotV1` does not change, and
14
+ graphology's own format never reaches disk. Nodes and edges are inserted in sorted order and every
15
+ score is rounded before it is returned, so two runs over the same snapshot agree exactly — including
16
+ after the input order is shuffled.
17
+
18
+ ## The signals
19
+
20
+ | Signal | Computed from | Over |
21
+ | --- | --- | --- |
22
+ | **Canonicality** | PageRank | `links-to` and `covers` |
23
+ | **Centrality** | betweenness, normalized | `imports` and `re-exports` |
24
+ | **Proximity** | bounded shortest path | imports, documentation, `mentions`, `depends-on` |
25
+ | **Import cycles** | depth-first search | `imports` and `re-exports` |
26
+ | **Area suggestions** | seeded Louvain communities | `imports` and `re-exports` |
27
+
28
+ External and unresolved endpoints are left out unless a caller asks for them: a dependency on a
29
+ third-party package is a fact about the repository, not a part of its architecture.
30
+
31
+ **Canonicality** answers where a reader should start. A page many documents link to, or that covers
32
+ many entities, outranks a leaf note nothing points at — however recently the leaf was edited.
33
+
34
+ **Centrality** answers how much of the dependency structure runs through one module. It is a review
35
+ signal: a change there reaches further than its diff suggests. It is *not* a statement about runtime
36
+ availability, and the diagnostic says so, because "single point of failure" is a claim about
37
+ deployment that a static import graph cannot make.
38
+
39
+ **Proximity** answers how many hops apart two entities are, bounded — three by default. Unbounded
40
+ proximity is not useful: at ten hops everything is related to everything. `contains` is excluded on
41
+ purpose. It is hierarchy, and including it puts every module in an area two hops from every other
42
+ one, which is true and tells a reader nothing.
43
+
44
+ **Import cycles** are reported with every edge that forms them. `graphology-dag` answers whether a
45
+ cycle exists; reporting one needs the path, because a diagnostic whose loop a reader cannot trace is
46
+ a claim rather than a finding. The search is bounded in count, and the cycle is rotated so the
47
+ lexicographically smallest node comes first — the same cycle found from two different entry points
48
+ is one finding.
49
+
50
+ ## Area suggestions are suggestions
51
+
52
+ A Louvain community is a hypothesis: these modules move together, so perhaps they are one unit. It
53
+ never becomes an [area](./config-v1.md#analysisareas-optional) on its own. An area is derived from
54
+ the repository's own structure or declared by a human, and a clustering algorithm is neither.
55
+
56
+ Suggestions travel as `coverage` entries with `analyzer: graph`, `scope: area-suggestion:<path>` and
57
+ status `not-analyzed` — the honest status, because the clustering ran but whether the cluster is an
58
+ area is a question nobody has answered. A directory that is already an area produces no suggestion.
59
+ Community detection draws from a seeded generator, so a suggestion is reproducible rather than a
60
+ different guess each run.
61
+
62
+ ## Reading the graph as memory
63
+
64
+ ```ts
65
+ import { createDocBridgeGraphMemory } from '@agentskit/doc-bridge'
66
+
67
+ const graph = createDocBridgeGraphMemory(snapshot)
68
+ await graph.getNode('module:src/mcp/server.ts')
69
+ await graph.findEdges({ label: 'covers', to: 'area:src/mcp' })
70
+ await graph.neighbors('module:src/mcp/server.ts', { depth: 2 })
71
+ ```
72
+
73
+ This satisfies `GraphMemory` from `@agentskit/memory`, so an agent built on AgentsKit walks
74
+ repository structure with the same three calls it uses for its own memory. A second argument layers
75
+ extra entities and relations over the observation, in the snapshot's own vocabulary, so an approved
76
+ enrichment overlay plugs in unchanged.
77
+
78
+ Writes are accepted and kept in process. The snapshot is an observation and a caller cannot edit it,
79
+ so `upsertNode` lands in a working layer above the projection and `deleteNode` masks rather than
80
+ erases — an agent can annotate what it is exploring without any of it being mistaken for something
81
+ the repository said. `clear()` drops the working layer and leaves the projection intact.
82
+
83
+ ## What consumes them
84
+
85
+ `centrality-risk` in the rules engine is betweenness now. It used to count how many
86
+ undocumented-relation findings were attached to an entity, which measures documentation debt and
87
+ calls it architecture: a module every import path runs through scored zero if it happened to be
88
+ documented. Without a graph the rule reports nothing at all, which is better than reporting the
89
+ wrong thing under a name people act on.
90
+
91
+ The rule's threshold reads as a rank when it is 1 or more — `3` means "flag the three most central
92
+ entities" — and as a minimum betweenness when it is below 1.
@@ -0,0 +1,102 @@
1
+ ---
2
+ title: Incremental scan v1
3
+ description: How Doc Bridge hashes each file-backed entity, when a second scan may reuse one, and what it refuses to reuse.
4
+ ---
5
+
6
+ # Incremental scan v1
7
+
8
+ Every `module`, `document` and `package` entity carries the hash of its file in its first evidence
9
+ item. An `external` entity carries none: it is a name in a manifest, not a file on disk.
10
+
11
+ ```json
12
+ {
13
+ "id": "module:src/query/search.ts",
14
+ "kind": "module",
15
+ "evidence": [{ "source": "code", "path": "src/query/search.ts", "contentHash": "…" }]
16
+ }
17
+ ```
18
+
19
+ `EvidenceSchema.contentHash` had existed since the first schema and discovery never filled it, so
20
+ every cache and every overlay could be keyed only on "the whole repository changed" — which is true
21
+ between any two commits and therefore useless. With a hash per file, a consumer can expire one
22
+ entry, and a second scan can skip the expensive part: the TypeScript parse and the Markdown parse,
23
+ where nearly all of discovery's time goes.
24
+
25
+ A document's hash is taken after a leading byte-order mark is stripped. A mark is not content: a
26
+ file that only gained one parses to the same tree, and should not invalidate anything.
27
+
28
+ ## Reuse
29
+
30
+ ```ts
31
+ const cold = discoverRepository({ root, config })
32
+ const fast = discoverRepository({ root, config, previous: cold })
33
+ ```
34
+
35
+ `previous` is an offer, not an instruction. Discovery reuses an entity only when reuse cannot change
36
+ the answer, and two different things can change it:
37
+
38
+ - **An entity's own fields depend on its own bytes.** A hash match is enough.
39
+ - **A relation depends on what else exists.** A module importing `./new.js` resolved to nothing
40
+ before that file was added and resolves to a module after; a document mentioning `rank` points at
41
+ whichever module declares it. So relation reuse also requires that the universe the references
42
+ resolve against is identical.
43
+
44
+ Two fingerprints capture that universe, both derived from the previous snapshot rather than stored
45
+ in it — everything they cover is in the snapshot already, and a stored fingerprint is one more
46
+ thing that can be stale or forged.
47
+
48
+ | Fingerprint | Covers | Gates |
49
+ | --- | --- | --- |
50
+ | Module universe | module paths, packages, compiler options | reuse of a module's relations |
51
+ | Resolution | the module universe, plus document paths, area paths and which module declares each exported symbol | reuse of a document |
52
+
53
+ A reused entity's relations are replayed against the entity set the scan is producing. An edge
54
+ whose internal target is gone is dropped rather than carried: the file it pointed at was renamed or
55
+ deleted, and a graph that keeps the edge is lying about the repository. An external or unresolved
56
+ endpoint is re-added instead, because such an entity is in the snapshot only because something
57
+ referenced it, and the thing that referenced it is exactly what was reused.
58
+
59
+ ## What is refused outright
60
+
61
+ A hash says a file has not changed. It says nothing about whether this code would still read it the
62
+ same way — an analyzer that learns to record a document's headings produces different entities from
63
+ identical bytes, and a configuration change moves area boundaries and runtime-wiring detection. So
64
+ the whole snapshot is refused unless it was produced by this `pipelineVersion`, these
65
+ `analyzerVersions` and this `configurationHash`, and a snapshot that does not declare all three is
66
+ refused as well. Trusting an undeclared input with a repository scan is how a cache becomes a
67
+ source of wrong answers.
68
+
69
+ A cache that is only usually right is worse than no cache. Reuse either produces the snapshot a
70
+ cold scan would produce, or it does not happen.
71
+
72
+ A reused entity also replays the per-file `coverage` its analyzer produced, because the aggregate
73
+ entries are derived from those rather than stored. An aggregate that cannot be rebuilt from what
74
+ the snapshot carries is an aggregate a fast scan gets wrong: a fact that lives only in a local
75
+ variable during a parse is a fact the next scan cannot replay. `dynamic-imports:<path>` and
76
+ `runtime-wiring:<path>` therefore record every observed load and wiring call — `complete` when the
77
+ target is statically known, `not-analyzed` when it is not.
78
+
79
+ ## The run explains itself
80
+
81
+ A run that finishes in a tenth of the time has to be able to say why, or nobody can tell a working
82
+ cache from a broken scan. One `coverage` entry reports it:
83
+
84
+ ```json
85
+ {
86
+ "analyzer": "repository",
87
+ "scope": "reused-entities",
88
+ "status": "complete",
89
+ "reason": "Reused 102 entities and skipped 102 of 102 parse(s). Nothing needed re-parsing."
90
+ }
91
+ ```
92
+
93
+ `status` is `complete` when everything reusable was reused, `partial` when reuse was refused — the
94
+ reason then names what changed — and `not-applicable` when there was no previous snapshot to reuse.
95
+
96
+ This entry is the one part of a snapshot that describes the *run* rather than the repository. The
97
+ entities, the relations and every other coverage entry are byte-identical to a cold scan's, which
98
+ is what the tests assert; the snapshot's own `contentHash` covers this entry too, so a warm scan
99
+ and a cold scan of the same tree hash differently. That is why the CLI does not yet pass a previous
100
+ snapshot: the artifacts it writes are compared across runs, and the caching layer has to decide
101
+ what a report keys on before a fast scan starts feeding it. `contentHash` and `sourceRevision`
102
+ semantics are otherwise untouched.
@@ -0,0 +1,73 @@
1
+ ---
2
+ title: Markdown analyzer v1
3
+ description: What Doc Bridge reads from a Markdown document, and the relations it observes from prose.
4
+ ---
5
+
6
+ # Markdown analyzer v1
7
+
8
+ Documentation is parsed with remark (CommonMark plus GFM), not with regular expressions, so a
9
+ document's own prose becomes evidence. Every relation below is `observed` and carries the file and
10
+ line the claim was made on.
11
+
12
+ | Relation | From → to | Observed from |
13
+ | --- | --- | --- |
14
+ | `links-to` | document → document | a relative link that resolves to a scanned document |
15
+ | `mentions` | document → module or package | inline code or link text equal to a scanned path or a package name |
16
+ | `mentions-symbol` | document → module | inline code equal to an exported name of exactly one module |
17
+ | `covers` | document → anything | a `docbridge` declaration, unchanged |
18
+
19
+ A symbol resolves to the module that **declares** it, never to a barrel that re-exports it. When
20
+ two modules declare the same name the reference resolves to neither: the tokens and their lines
21
+ are reported as a `markdown` coverage note instead, because sending an agent to one of two
22
+ possible definitions is worse than sending it nowhere. The same rule governs near-misses — an
23
+ unresolved path-shaped reference is matched with Jaro-Winkler and accepted only at 0.92 or above
24
+ *and* with a single candidate, recorded as `metadata.confidence: "fuzzy"`.
25
+
26
+ Document entities gain what the parser can see: `title`, headings to depth three with their
27
+ lines, a bounded `summary`, `wordCount`, the frontmatter subset (`type`, `audience`, `owner`,
28
+ `lifecycle`, `tier`), any generated regions, and the file's `contentHash` on its evidence. A
29
+ document declaring `audience` overrides the path heuristic that classifies it; `type` overrides it
30
+ only when it names an audience, since in practice `type` names a document kind.
31
+
32
+ A document referencing more than 64 entities records `evidenceTruncated` and a coverage note. An
33
+ index page's sixty-fifth link is not knowledge, and an unbounded list is not evidence.
34
+
35
+ ## Generated regions
36
+
37
+ ```markdown
38
+ <!-- doc-bridge:generated hash=8f79947 -->
39
+ …generator output…
40
+ <!-- /doc-bridge:generated -->
41
+ ```
42
+
43
+ Mentions inside a generated region are ignored, so Doc Bridge never reads its own output back in
44
+ as evidence about the repository. An unclosed marker owns the rest of the file. The regions are
45
+ recorded on the document entity, which is what lets the audit report a manual edit inside one.
46
+
47
+ ## The `docbridge` block
48
+
49
+ The block is real YAML validated by a schema, so quoted lists, flow mappings, anchors and
50
+ multi-line strings work as they do in every other tool. Schema violations report the field:
51
+ `docbridge.relations.0: Unrecognized key: note`. Each `DOCBRIDGE_*` code is preserved — a
52
+ repository failing its build on one keeps failing on the same one — and a block YAML cannot read
53
+ at all falls back to the line-oriented scanner, because on a mangled block a diagnostic per line
54
+ helps the author more than a single parser error.
55
+
56
+ ## The cost of a near-miss
57
+
58
+ Near-miss resolution is the analyzer's only super-linear step: every unresolved path-shaped
59
+ reference is a query against every document, module and area path in the repository. On a monorepo
60
+ of 4 100 documents and 9 240 modules the naive form of that — rebuilding the candidate list per
61
+ document, then computing Jaro-Winkler against each candidate — did not finish.
62
+
63
+ Two bounds make it cheap, and both are bounds on Jaro's match count `m` rather than heuristics.
64
+ `m` cannot exceed the shorter string, so `jaro ≤ (m/|a| + m/|b| + 1) / 3`; with the prefix bonus
65
+ bounded by `jw ≤ 0.6·jaro + 0.4`, a threshold of 0.92 admits only candidates whose length is
66
+ within 0.6× to 1.67× the query's. And `m` cannot exceed the number of characters the two strings
67
+ share, which one pass over a 128-slot count vector answers, where a similarity computation costs a
68
+ pass over one string per character of the other.
69
+
70
+ Both only ever over-estimate `m`, so a candidate they skip could not have matched: the index is a
71
+ speed-up with no effect on results, tied scores included. `MarkdownResolution.pathIndex` carries it,
72
+ built once per run; an analyzer called without one builds its own and produces the same output more
73
+ slowly.
@@ -0,0 +1,147 @@
1
+ ---
2
+ title: MCP knowledge tools v1
3
+ description: One search and one lookup that answer inside a declared token budget, findings in the shape the ecosystem consumes, and a doctor that measures what retrieval can see.
4
+ ---
5
+
6
+ # MCP knowledge tools v1
7
+
8
+ An agent about to edit `src/mcp` used to need four calls — a search, a handoff, the diagnostics,
9
+ the relations — and got back payloads whose size it could only estimate as `contextBytes / 4`.
10
+ Two tools now answer that question in one bounded response, the existing tools stay as they are,
11
+ and the doctor stops reporting a health it did not measure.
12
+
13
+ ## `knowledge.search`
14
+
15
+ ```json
16
+ { "query": "where do I add a new MCP tool", "kinds": ["document", "area"], "limit": 5, "explain": true, "budgetTokens": 600 }
17
+ ```
18
+
19
+ Ranks the retrieval projection with the same `searchIndex` the CLI uses, so with no `kinds` the
20
+ `results` are what `ak-docs search <query> --json` prints for the same query and index — a test
21
+ compares the two rather than assuming it. Each result adds the entry's `title` and an `excerpt`,
22
+ the opening of the projected body. `kinds` filters after ranking, from a deeper ranking, so the
23
+ order never changes and a filtered call still returns up to `limit`. `explain` attaches the
24
+ matched terms and every score component, as on the CLI.
25
+
26
+ ## `knowledge.lookup`
27
+
28
+ ```json
29
+ { "id": "area:src/mcp", "depth": 1, "budgetTokens": 900 }
30
+ ```
31
+
32
+ Accepts an entity id, an ownership id, an alias or a `path`, through the same resolution
33
+ `handoff.resolve` uses. One response carries:
34
+
35
+ | Section | What it is |
36
+ | --- | --- |
37
+ | `entity` | id, kind, path, title, summary, aliases, symbols, tags, provenance, confidence, content hash, ownership, area, package, PageRank |
38
+ | `neighbours` | every entry within `depth` hops, grouped by relation kind (`covers`, `mentions`, `links-to`, `imports`, `contains`, …), each with direction, confidence, distance and the entry it was reached through |
39
+ | `documents` | the documents that `cover` the entity and those that `mention` it, most canonical first |
40
+ | `handoff` | exactly what `handoff.resolve` returns for the entity, `related` included |
41
+ | `diagnostics` | the open diagnostics of the latest reconciliation report that name the entity or point at its file, with the report hash — or `reportHash: null` when no workflow run exists |
42
+ | `evidence` | the entity's path and content hash, then those of the documents about it, each with an `excerpt` |
43
+
44
+ `contains` is not a projection edge — it is hierarchy — but an agent asking about an area wants its
45
+ modules listed, so the lookup synthesises it from `areaId` and `packageId`. Neighbours are
46
+ bounded per relation kind and visited in sorted order, so two lookups over the same projection
47
+ produce the same response. `depth` is at most 3.
48
+
49
+ `format: "text"` on either tool renders the same payload as prose through
50
+ `formatRetrievedDocuments`, for clients that prefer it.
51
+
52
+ ## Budgets
53
+
54
+ When `budgetTokens` is present, both tools and `handoff.resolve` trim through `compileBudget`:
55
+ each droppable section becomes a message, oldest first in the order it may be dropped, the payload
56
+ with every section removed becomes the last message, and `drop-oldest` with `keepRecent: 1` sheds
57
+ sections from the front until the rest fits. The declared order is
58
+
59
+ ```
60
+ evidenceExcerpts → related → neighbours → summaries
61
+ ```
62
+
63
+ and nothing else is ever dropped: the entity, the evidence paths and hashes, the handoff fields an
64
+ agent acts on and the open diagnostics survive every budget. A payload whose undroppable core
65
+ still exceeds the budget reports `fits: false` with every section dropped, rather than truncating
66
+ what it may not drop. A section a payload does not have — search has no `related`, a handoff has
67
+ no `neighbours` — is absent from the report, never reported dropped.
68
+
69
+ ```json
70
+ {
71
+ "budget": {
72
+ "budgetTokens": 900,
73
+ "tokens": { "total": 790, "budget": 900, "core": 684, "sections": { "evidenceExcerpts": 59, "related": 290, "neighbours": 229, "summaries": 106 } },
74
+ "fits": true,
75
+ "order": ["evidenceExcerpts", "related", "neighbours", "summaries"],
76
+ "kept": ["summaries"],
77
+ "dropped": ["evidenceExcerpts", "related", "neighbours"],
78
+ "tokenMethod": "approximate"
79
+ }
80
+ }
81
+ ```
82
+
83
+ Tokens are counted with `approximateCounter` — four characters per token plus two per message,
84
+ over the serialised sections — and reported as `tokenMethod: "approximate"`. `compileBudget` and
85
+ `approximateCounter` are mirrored in `src/budget/compile.ts` because `@agentskit/core` is an
86
+ optional peer and every query surface answers with no peer installed; a test runs the real
87
+ `compileBudget` over the same messages and asserts the two agree on every token count, every
88
+ dropped message and `fits`. On a handoff the droppable sections are `related` and the note that
89
+ repeats the target's summary; `budget` is an optional field of `AgentHandoffV1`, so a budgeted
90
+ handoff is still a valid handoff.
91
+
92
+ ## Every existing tool keeps working
93
+
94
+ `handoff.resolve`, `doc.search`, `doc.get`, `gate.status`, `retriever.query`, `memory.*`,
95
+ `registry.topology` and `docbridge.*` keep their names, arguments and payloads. `handoff.resolve`
96
+ gains an optional `budgetTokens`; `docbridge.diagnostics` gains an optional `format`. The new tools
97
+ are appended to the advertised list and to the configuration's `surfaces.mcp.tools` default, so a
98
+ configuration that names its tools explicitly is unchanged until it names them.
99
+
100
+ ## Canonical findings
101
+
102
+ ```
103
+ ak-docs check --json --format finding
104
+ docbridge.diagnostics { "format": "finding" }
105
+ ```
106
+
107
+ Both emit every reconciliation diagnostic as a `Finding` from `@agentskit/core/finding`, with
108
+ severities drawn from `SEVERITY_ORDER`, so Code Review, AKOS and dashboards read Doc Bridge with no
109
+ parser of their own. Internal severities map `error → high`, `warn → medium`, `info → low`,
110
+ `off → info`; nothing is `critical`, because a documentation finding never takes a system down.
111
+ `title` is the code as words, `detail` the message, `category` the status, `location` the first
112
+ evidence path and line, `ref` the code, and `metadata` carries the internal code, status,
113
+ severity, evidence and entity ids, so nothing is lost. `confidence` follows the status: an
114
+ observed relation is certain, a stale declaration less so, a coverage gap least of all. Findings
115
+ are ordered most severe first and by id within a severity.
116
+
117
+ This is a reporter, not a migration: `KnowledgeDiagnostic`, `RuleFinding` and
118
+ `DocumentationAuditFinding` keep their shapes, and the rule verdict still decides the exit code
119
+ of `check`. A test imports the real package and asserts assignability and the severity order.
120
+
121
+ ## The measured doctor
122
+
123
+ Three dimensions join the score and can lower the grade:
124
+
125
+ | Dimension | Measures | Points |
126
+ | --- | --- | --- |
127
+ | Reachability | the share of the snapshot's document entities present in the retrieval projection | 15 |
128
+ | Connectivity | the mean of: areas with at least one covering or mentioning document; documents with at least one edge into code | 15 |
129
+ | Benchmark | hit@3 over the golden suite at `retrieval.benchmark.suite` (`docs/bench/retrieval-suite-v1.json` by default) | 10 |
130
+
131
+ The existing dimensions — index present and fresh, agent docs, human guides, gates — make up the
132
+ other sixty. A repository with no golden suite reports the benchmark as `not-analyzed`, scores
133
+ nothing for it and says so in the issues; it is never silently omitted.
134
+
135
+ An A requires all three: reachability at 100 percent, connectivity at 80 percent or more, and a
136
+ measured hit@3 of 80 percent or more. A score of 90 that misses one of them is a B, and the text
137
+ report says which. Reachability must be complete because a document retrieval cannot find is a
138
+ document the product does not deliver; the other two have a floor rather than a ceiling.
139
+
140
+ On this repository every document is in the projection and the benchmark is at 88.3 percent, but
141
+ 20 of 39 areas have no document about them and 55 of 100 documents do not point at code —
142
+ connectivity 47 percent, grade B. That is the honest state. A test proves the other direction on
143
+ the same repository: one document out of the projection is enough to lose the A, and an index
144
+ with no projection at all — what the builder produced before the corpus projection — is at zero.
145
+
146
+ The doctor's `ok` is unchanged: it is still "no error-severity issue and the gates pass", so a
147
+ CI step that runs `ak-docs doctor --text` fails on a stale index, not on a B.