@agentskit/doc-bridge 1.7.45 → 1.10.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 (213) hide show
  1. package/CHANGELOG.md +488 -0
  2. package/CONTRIBUTING.md +23 -0
  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 +15871 -6218
  10. package/dist/cli/program.js.map +1 -1
  11. package/dist/config/index.d.ts +1 -1
  12. package/dist/config/index.js +95 -6
  13. package/dist/config/index.js.map +1 -1
  14. package/dist/{index-BUL0q7s8.d.ts → index-7wYGbllW.d.ts} +134 -1
  15. package/dist/index.d.ts +9880 -1062
  16. package/dist/index.js +16143 -6127
  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/adr/0002-documentation-audit-boundary.md +22 -0
  22. package/docs/adr/0003-study-protocol-and-historical-evidence.md +40 -0
  23. package/docs/adr/0004-controlled-study-runner.md +25 -0
  24. package/docs/adr/0005-documentation-quality-and-criticality.md +20 -0
  25. package/docs/adr/0006-registry-semantic-grounding.md +20 -0
  26. package/docs/adr/0007-longitudinal-study-metrics.md +21 -0
  27. package/docs/adr/0008-study-verification-boundary.md +21 -0
  28. package/docs/adr/0009-study-provider-cli-adapter.md +25 -0
  29. package/docs/agent-corpus/INDEX.md +12 -1
  30. package/docs/agent-corpus/OVERVIEW.md +25 -0
  31. package/docs/agent-corpus/chat.md +5 -1
  32. package/docs/agent-corpus/cli.md +16 -0
  33. package/docs/agent-corpus/conformance.md +12 -0
  34. package/docs/agent-corpus/doc-bridge.md +47 -0
  35. package/docs/agent-corpus/doctor.md +8 -0
  36. package/docs/agent-corpus/gates.md +4 -0
  37. package/docs/agent-corpus/mcp.md +13 -0
  38. package/docs/agent-corpus/memory.md +4 -0
  39. package/docs/agent-corpus/query.md +33 -0
  40. package/docs/bench/README.md +122 -0
  41. package/docs/bench/retrieval-baseline-v1.json +28 -0
  42. package/docs/bench/retrieval-suite-v1.json +1033 -0
  43. package/docs/chat-and-rag.md +3 -2
  44. package/docs/for-agents.md +9 -1
  45. package/docs/getting-started.md +4 -11
  46. package/docs/guides/gate-ci.md +11 -1
  47. package/docs/guides/install-and-run.md +9 -65
  48. package/docs/index.md +22 -1
  49. package/docs/knowledge-engine-runbook.md +38 -3
  50. package/docs/landing/assets/context-payload-reduction.svg +21 -0
  51. package/docs/landing/assets/controlled-ab-comparison.svg +30 -0
  52. package/docs/landing/index.html +119 -5
  53. package/docs/loop-workflow.md +117 -0
  54. package/docs/mcp.md +6 -1
  55. package/docs/parity/public-claims-v1.json +145 -0
  56. package/docs/playbook/doc-bridge-pattern.md +1 -1
  57. package/docs/query.md +90 -2
  58. package/docs/recipes/index-pipeline.md +1 -1
  59. package/docs/schemas/agent-handoff-v1.md +15 -0
  60. package/docs/schemas/doc-bridge-index-v1.md +65 -0
  61. package/docs/spec/benchmark-v1.md +33 -1
  62. package/docs/spec/cli.md +30 -10
  63. package/docs/spec/config-v1.md +157 -8
  64. package/docs/spec/documentation-audit-v1.md +61 -0
  65. package/docs/spec/enrichment-overlay-v1.md +241 -0
  66. package/docs/spec/graph-signals-v1.md +92 -0
  67. package/docs/spec/incremental-scan-v1.md +102 -0
  68. package/docs/spec/markdown-analyzer-v1.md +73 -0
  69. package/docs/spec/mcp-knowledge-tools-v1.md +147 -0
  70. package/docs/spec/measured-enrichment-v1.md +229 -0
  71. package/docs/spec/public-parity-v1.md +119 -0
  72. package/docs/spec/registry-agents.md +6 -0
  73. package/docs/spec/render-v1.md +122 -0
  74. package/docs/spec/retrieval-index-v1.md +164 -0
  75. package/docs/spec/study-metrics-v1.md +58 -0
  76. package/docs/spec/study-protocol-v1.md +46 -0
  77. package/docs/spec/study-provider-cli-v1.md +116 -0
  78. package/docs/spec/study-runner-v1.md +35 -0
  79. package/docs/spec/study-task-suite-v1.md +41 -0
  80. package/docs/spec/study-verification-v1.md +40 -0
  81. package/docs/study/README.md +84 -0
  82. package/docs/study/ab-adjudicated-cost-analysis-v1.md +29 -0
  83. package/docs/study/ab-adjudicated-cost-plan-v1.json +33 -0
  84. package/docs/study/ab-adjudicated-cost-plan-v2-v1.json +33 -0
  85. package/docs/study/ab-adjudicated-cost-result-v1.json +80 -0
  86. package/docs/study/ab-baseline-analysis-v1.md +21 -0
  87. package/docs/study/ab-baseline-plan-v1.json +33 -0
  88. package/docs/study/ab-baseline-recovery-plan-v1.json +33 -0
  89. package/docs/study/ab-baseline-result-v1.json +79 -0
  90. package/docs/study/documentation-audit-round-2026-08-31.json +183 -0
  91. package/docs/study/historical-evidence-v1.json +252 -0
  92. package/docs/study/observation-ledger-v1.json +30632 -0
  93. package/docs/study/phase3-task-coverage-v1.json +34 -0
  94. package/docs/study/phase4-public-pilot-ledger-v1.json +1344 -0
  95. package/docs/study/phase4-public-pilot-result-v1.json +52 -0
  96. package/docs/study/phase4-public-pilot-run-plan-v1.json +26 -0
  97. package/docs/study/phase4-public-pilot-task-suite-v1.json +71 -0
  98. package/docs/study/pilot-round-2026-08-31.json +46 -0
  99. package/docs/study/protocol-v1.json +90 -0
  100. package/docs/study/publication-gate-v1.md +45 -0
  101. package/docs/study/quality-scorecard-cycle-plan.md +545 -0
  102. package/docs/study/quality-scorecard-v1.json +38 -0
  103. package/docs/study/round-1-adjudicated-smoke-v1.json +30642 -0
  104. package/docs/study/round-1-instrumentation-plan-v1.md +39 -0
  105. package/docs/study/round-2-expanded-adjudication-v1.json +91 -0
  106. package/docs/study/round-2-expanded-validation-v1.md +58 -0
  107. package/docs/study/round-3-evidence-contract-v1.json +75 -0
  108. package/docs/study/round-3-evidence-contract-v1.md +57 -0
  109. package/docs/study/round-4-confirmation-v1.json +75 -0
  110. package/docs/study/round-4-confirmation-v1.md +55 -0
  111. package/docs/study/run-plan-v1.json +33 -0
  112. package/docs/study/semantic-adjudication-cycle-8.md +20 -0
  113. package/docs/study/task-suite-v1.json +96 -0
  114. package/docs/study/token-efficiency-plan-v1.md +337 -0
  115. package/docs/study/token-efficiency-protocol-v2.json +62 -0
  116. package/docs/study/verification-binding-v1.json +27 -0
  117. package/docs/validation-cycle-plan.md +14 -0
  118. package/docs/verification-harness.md +11 -6
  119. package/ecosystem-claims.json +2 -2
  120. package/ecosystem-upstream.json +2 -2
  121. package/ecosystem.json +4 -4
  122. package/mcpb/manifest.json +9 -1
  123. package/package.json +34 -15
  124. package/scripts/check-ecosystem-upstream.mjs +34 -6
  125. package/skills/doc-bridge-handoff/fixtures/synthetic-repo/docs/for-agents/packages/payments.md +7 -0
  126. package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
  127. package/src/agents/registry-adapter.ts +192 -24
  128. package/src/audit/documentation.ts +513 -0
  129. package/src/bench/baseline.ts +198 -0
  130. package/src/bench/overlay-delta.ts +139 -0
  131. package/src/bench/retrieval.ts +319 -0
  132. package/src/budget/compile.ts +91 -0
  133. package/src/budget/sections.ts +70 -0
  134. package/src/cli/program.ts +684 -74
  135. package/src/cli/usage.ts +71 -0
  136. package/src/config/defaults.ts +1 -0
  137. package/src/config/index.ts +4 -0
  138. package/src/config/schema.ts +126 -2
  139. package/src/conformance/documentation-standard-v1.ts +8 -6
  140. package/src/discovery/areas.ts +197 -0
  141. package/src/discovery/documentation.ts +211 -5
  142. package/src/discovery/identity.ts +24 -0
  143. package/src/discovery/incremental.ts +314 -0
  144. package/src/discovery/inputs.ts +110 -0
  145. package/src/discovery/markdown.ts +481 -0
  146. package/src/discovery/repository.ts +485 -101
  147. package/src/doctor/run-doctor.ts +244 -12
  148. package/src/enrich/approvals.ts +190 -0
  149. package/src/enrich/cache.ts +93 -0
  150. package/src/enrich/context-pack.ts +272 -0
  151. package/src/enrich/overlay.ts +255 -0
  152. package/src/enrich/review.ts +106 -0
  153. package/src/enrich/stage.ts +374 -0
  154. package/src/enrich/stats.ts +100 -0
  155. package/src/enrich/validate.ts +410 -0
  156. package/src/federation/llms.ts +2 -4
  157. package/src/findings/report.ts +103 -0
  158. package/src/graph/build.ts +356 -0
  159. package/src/graph/memory.ts +208 -0
  160. package/src/index-builder/build-handoffs.ts +22 -11
  161. package/src/index-builder/build-index.ts +132 -3
  162. package/src/index-builder/llms-txt.ts +48 -8
  163. package/src/index-builder/project-corpus.ts +111 -0
  164. package/src/index.ts +630 -2
  165. package/src/lib/fuzzy-match.ts +235 -0
  166. package/src/mcp/knowledge.ts +554 -0
  167. package/src/mcp/server.ts +113 -18
  168. package/src/metrics/benchmark.ts +21 -0
  169. package/src/parity/check.ts +309 -0
  170. package/src/parity/claims.ts +259 -0
  171. package/src/parity/resolve.ts +160 -0
  172. package/src/query/handoff.ts +326 -0
  173. package/src/query/load-index.ts +53 -1
  174. package/src/query/query.ts +92 -59
  175. package/src/query/search.ts +289 -92
  176. package/src/query/text.ts +155 -0
  177. package/src/reconciliation/reconcile.ts +101 -10
  178. package/src/render/data.ts +356 -0
  179. package/src/render/engine.ts +398 -0
  180. package/src/render/generated.ts +77 -0
  181. package/src/render/render.ts +209 -0
  182. package/src/render/template-source.ts +52 -0
  183. package/src/render/templates.ts +289 -0
  184. package/src/report/html.ts +2 -2
  185. package/src/retrieval/bm25.ts +161 -0
  186. package/src/retrieval/project.ts +495 -0
  187. package/src/retrieval/rank.ts +383 -0
  188. package/src/retrieval/weights.ts +39 -0
  189. package/src/retriever/doc-bridge-retriever.ts +100 -15
  190. package/src/rules/engine.ts +30 -10
  191. package/src/schemas/agent-handoff.ts +56 -0
  192. package/src/schemas/budget.ts +37 -0
  193. package/src/schemas/doc-bridge-index.ts +53 -2
  194. package/src/schemas/enrichment.ts +369 -0
  195. package/src/schemas/json-schemas.ts +39 -2
  196. package/src/schemas/knowledge.ts +14 -1
  197. package/src/schemas/retrieval-index.ts +152 -0
  198. package/src/shims/graphology.d.ts +91 -0
  199. package/src/study/adjudication.ts +196 -0
  200. package/src/study/execution.ts +350 -0
  201. package/src/study/expectations.ts +219 -0
  202. package/src/study/metrics.ts +467 -0
  203. package/src/study/protocol.ts +271 -0
  204. package/src/study/provider-cli.ts +115 -0
  205. package/src/study/provider-telemetry.ts +47 -0
  206. package/src/study/quality-scorecard.ts +164 -0
  207. package/src/study/runner.ts +461 -0
  208. package/src/study/task-suite.ts +321 -0
  209. package/src/study/verification.ts +134 -0
  210. package/src/validate.ts +1 -4
  211. package/src/version.ts +1 -1
  212. package/src/workflow/engine.ts +36 -11
  213. package/scripts/verification-harness.mjs +0 -482
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The CLI's usage text, in its own module.
3
+ *
4
+ * `ak-docs parity` checks that a command this repository documents in public is a command the CLI
5
+ * actually offers, which means something other than the CLI has to be able to read the usage. The
6
+ * text lives here so that check does not import the program and create a cycle through it.
7
+ */
8
+ export const CLI_COMMAND_USAGE = `ak-docs — human↔agent documentation bridge (@agentskit/doc-bridge)
9
+
10
+ Core (no API key):
11
+ ak-docs init [--demo] [--scaffold-workspaces]
12
+ ak-docs demo [--fixture example|monorepo] [--text] [--in-project]
13
+ ak-docs doctor [--text] [--badge] [--write-badge]
14
+ ak-docs index [--watch]
15
+ ak-docs discover [--text|--json]
16
+ ak-docs benchmark <fixture.json> <observation.json> [--text|--json]
17
+ ak-docs bench retrieval <suite.json> [--index <file>] [--baseline <file>] [--limit <n>] [--text|--json]
18
+ ak-docs bench retrieval <suite.json> --update-baseline --by <name> [--reason <text>]
19
+ ak-docs study protocol <protocol.json> [--text|--json]
20
+ ak-docs study history <registry.json> [--protocol <protocol.json>] [--text|--json]
21
+ ak-docs study tasks <task-suite.json> [--text|--json]
22
+ ak-docs study select <task-suite.json> [--text|--json]
23
+ ak-docs study plan <run-plan.json> [--text|--json]
24
+ ak-docs study providers <provider-cli.json> [--text|--json]
25
+ ak-docs study run <run-plan.json> <task-suite.json> --providers <provider-cli.json> --repositories <repositories.json> --ledger <ledger.json> [--round <id>] [--dry-run] [--text|--json]
26
+ ak-docs study adjudicate <observation-ledger.json> <task-suite.json> --adjudicator <provider-cli.json> --output <ledger.json> [--run-id <id>] [--offset <n>] [--limit <n>] [--text|--json]
27
+ ak-docs study ledger <observation-ledger.json> [--text|--json]
28
+ ak-docs study verification <binding.json> [--text|--json]
29
+ ak-docs study metrics <observation-ledger.json> [--baseline-round <id>] [--current-round <id>] [--baseline-run-id <id>] [--current-run-id <id>] [--allow-regressions] [--text|--json]
30
+ ak-docs scan | reconcile | check | map [--text|--json] [--html] [--report-threshold <bytes>]
31
+ ak-docs check --json --format finding emit diagnostics in the ecosystem Finding shape
32
+ ak-docs bench retrieval <suite> --overlay measure the suite with and without the accepted overlay
33
+ ak-docs enrich --retrieval-delta run the overlay through the golden suite after enriching
34
+ ak-docs study expectations <suite> --expectations <file> check the study's mechanical retrieval expectations
35
+ ak-docs check --enrich run the enrichment stage between reconcile and evaluate
36
+ ak-docs enrich [--json|--text] run the configured Registry roles over context packs
37
+ ak-docs enrich list | approve <proposalId> --by <name> | reject <proposalId> --by <name> [--reason <text>]
38
+ ak-docs fix propose links|normalize <artifact> [--output <file>]
39
+ ak-docs fix approve|apply <proposal.json> [--by <name>]
40
+ ak-docs suggest [--documentation] [--json|--text] run the configured Registry agent
41
+ ak-docs query [package|ownership|intent|change] <id> [--agent] [--text]
42
+ ak-docs search <term> [--agent] [--explain] [--mode=<mode>] [--context-budget=<tokens>] [--text]
43
+ ak-docs list <packages|intents|changes|knowledge> [--text]
44
+ ak-docs ask [question] local consult (no LLM)
45
+ ak-docs gate run [gate-id]
46
+ ak-docs rules run <report.json> [--preset default|recommended|strict] [--severity rule=level] [--ignore rule]
47
+ ak-docs conformance run documentation-standard-v1 [--text|--json]
48
+ ak-docs audit documentation [--text|--json]
49
+ ak-docs parity [--claims <file>] [--json|--text] check public claims against what the repository proves
50
+ ak-docs render <llms.txt|area|ownership|change-digest|overlay-review> [--data <artifact>] [--output <path>] [--print-template] [--json]
51
+ ak-docs mcp
52
+ ak-docs mcp install --cursor | --claude
53
+ ak-docs memory ingest|classify|promote [--pr] [--dry-run]
54
+ ak-docs bootstrap agent-docs
55
+ ak-docs validate-config | validate-handoff <file>
56
+
57
+ Intelligence (optional AgentsKit peers):
58
+ ak-docs rag ingest|search <query>
59
+ ak-docs chat terminal chat (Ink + RAG)
60
+ ak-docs ask <question> --chat one-shot grounded answer
61
+
62
+ Advanced / ecosystem:
63
+ ak-docs retrieve <query>
64
+ ak-docs registry topology
65
+ ak-docs playbook draft | pattern [--text]
66
+
67
+ Global flags:
68
+ -h, --help --version
69
+ --config <path> (project root = config file directory)
70
+ --agent --json --text --chat --demo
71
+ `
@@ -57,6 +57,7 @@ export const applyConfigDefaults = (config: DocBridgeConfigV1): DocBridgeConfigV
57
57
  'memory.classify', 'memory.promoteDraft', 'registry.topology',
58
58
  'docbridge.snapshot', 'docbridge.report', 'docbridge.diagnostics',
59
59
  'docbridge.relations', 'docbridge.run', 'docbridge.proposals',
60
+ 'knowledge.search', 'knowledge.lookup',
60
61
  ],
61
62
  transport: 'stdio',
62
63
  ...config.surfaces?.mcp,
@@ -8,6 +8,7 @@ export {
8
8
  } from './load-config.js'
9
9
  export {
10
10
  DocBridgeConfigV1Schema,
11
+ DocumentationAuditConfigSchema,
11
12
  ReconciliationConfigSchema,
12
13
  AgentCorpusConfigSchema,
13
14
  HumanCorpusConfigSchema,
@@ -16,10 +17,13 @@ export {
16
17
  EcosystemContractEvidenceSchema,
17
18
  ConformanceConfigSchema,
18
19
  ReportConfigSchema,
20
+ RetrievalConfigSchema,
19
21
  type DocBridgeConfigV1,
22
+ type DocumentationAuditConfig,
20
23
  type AgentCorpusConfig,
21
24
  type DocumentationStandardRuleId,
22
25
  type DocumentationStandardV1Config,
23
26
  type ReconciliationConfig,
24
27
  type ReportConfig,
28
+ type RetrievalConfig,
25
29
  } from './schema.js'
@@ -182,7 +182,7 @@ export const RulesConfigSchema = z
182
182
  export const ReconciliationConfigSchema = z
183
183
  .object({
184
184
  /** Semantic comparison level. Raw discovery always keeps file-level relations. */
185
- scope: z.enum(['file', 'module', 'package']).optional(),
185
+ scope: z.enum(['file', 'module', 'area', 'package']).optional(),
186
186
  /** Relation kinds that must have documentation declarations. Omit to require all observed kinds; [] disables this signal. */
187
187
  requiredRelationKinds: z.array(z.string().min(1).max(128)).max(128).optional(),
188
188
  /** Limit missing-declaration findings to relations whose endpoints are internal project entities. */
@@ -191,6 +191,27 @@ export const ReconciliationConfigSchema = z
191
191
  })
192
192
  .strict()
193
193
 
194
+ export const DocumentationAuditConfigSchema = z
195
+ .object({
196
+ /** Glob-like paths that should be compared for structure coverage. */
197
+ criticalPaths: z.array(z.string().min(1).max(512)).max(128).optional(),
198
+ /** Generated docs are checked for presence/freshness evidence only; content quality is skipped. */
199
+ generatedPaths: z.array(z.string().min(1).max(512)).max(128).optional(),
200
+ exclude: z.array(z.string().min(1).max(512)).max(128).optional(),
201
+ minWords: z.number().int().nonnegative().max(100_000).optional(),
202
+ requiredSections: z.array(z.string().min(1).max(128)).max(32).optional(),
203
+ requireExamples: z.boolean().optional(),
204
+ exactDuplicates: z.boolean().optional(),
205
+ defaultTier: z.enum(['tier-0', 'tier-1', 'tier-2']).optional(),
206
+ tierRules: z.array(z.object({
207
+ pattern: z.string().min(1).max(512),
208
+ tier: z.enum(['tier-0', 'tier-1', 'tier-2']),
209
+ critical: z.boolean().optional(),
210
+ }).strict()).max(128).optional(),
211
+ requiredCriticalMetadata: z.array(z.enum(['owner', 'lifecycle', 'sourceOfTruth', 'validationPath'])).max(4).optional(),
212
+ })
213
+ .strict()
214
+
194
215
  export const AnalysisConfigSchema = z
195
216
  .object({
196
217
  plugins: z
@@ -225,6 +246,27 @@ export const AnalysisConfigSchema = z
225
246
  })
226
247
  .strict()
227
248
  .optional(),
249
+ /**
250
+ * How code areas are derived — the unit of architecture between a package and a file.
251
+ * `roots` names directories that contain areas rather than being one (`src` holds
252
+ * `src/query`); `depth` is how many levels below such a root an area sits; `exclude`
253
+ * names directories that hold code without being a unit of architecture.
254
+ */
255
+ areas: z
256
+ .object({
257
+ depth: z.number().int().min(1).max(8).optional(),
258
+ roots: z.array(z.string().min(1).max(128)).max(32).optional(),
259
+ /**
260
+ * Glob patterns for directories that are not areas. A monorepo where every package
261
+ * keeps `tests/` and `fixtures/` beside `src/` derives one area per directory, and
262
+ * then connectivity asks for a document about a folder of test data. An ownership
263
+ * record naming an excluded path still makes it an area: a person saying a directory
264
+ * is a unit outranks a pattern saying it is not.
265
+ */
266
+ exclude: z.array(z.string().min(1).max(256)).max(64).optional(),
267
+ })
268
+ .strict()
269
+ .optional(),
228
270
  })
229
271
  .strict()
230
272
 
@@ -282,9 +324,11 @@ export const SurfacesConfigSchema = z
282
324
  'docbridge.relations',
283
325
  'docbridge.run',
284
326
  'docbridge.proposals',
327
+ 'knowledge.search',
328
+ 'knowledge.lookup',
285
329
  ]),
286
330
  )
287
- .max(16)
331
+ .max(18)
288
332
  .optional(),
289
333
  transport: z.enum(['stdio', 'http']).optional(),
290
334
  http: z
@@ -300,6 +344,16 @@ export const SurfacesConfigSchema = z
300
344
  })
301
345
  .strict()
302
346
 
347
+ /** One enrichment role: an installed Registry agent and the prompt version it is run with. */
348
+ export const EnrichmentRoleSchema = z
349
+ .object({
350
+ enabled: z.boolean().optional(),
351
+ /** Defaults to `intelligence.registry.agentId`. */
352
+ agentId: z.string().min(1).max(256).optional(),
353
+ promptVersion: z.string().min(1).max(64).optional(),
354
+ })
355
+ .strict()
356
+
303
357
  export const IntelligenceConfigSchema = z
304
358
  .object({
305
359
  enabled: z.boolean().optional(),
@@ -359,11 +413,34 @@ export const IntelligenceConfigSchema = z
359
413
  agentId: z.string().min(1).max(256).optional(),
360
414
  agentRoot: z.string().min(1).max(512).optional(),
361
415
  runnerModule: z.string().min(1).max(512).optional(),
416
+ cli: z
417
+ .object({
418
+ /** Executable name or absolute path. Arguments are passed without a shell. */
419
+ command: z.string().min(1).max(512),
420
+ args: z.array(z.string().max(2_048)).max(64).optional(),
421
+ })
422
+ .strict()
423
+ .optional(),
362
424
  deterministic: z.boolean().optional(),
363
425
  timeoutMs: z.number().int().positive().max(600_000).optional(),
426
+ maxInputBytes: z.number().int().positive().max(50_000_000).optional(),
364
427
  maxTokens: z.number().int().positive().max(1_000_000).optional(),
365
428
  maxResponseBytes: z.number().int().positive().max(10_000_000).optional(),
366
429
  maxConcurrency: z.number().int().positive().max(64).optional(),
430
+ /** Byte budget of one enrichment context pack. Default 65536. */
431
+ maxPackBytes: z.number().int().min(4_096).max(4_000_000).optional(),
432
+ /**
433
+ * Which installed agent plays which enrichment role. The default is the configured agent
434
+ * as curator only. The adjudicator must be a different identity from both others.
435
+ */
436
+ roles: z
437
+ .object({
438
+ curator: EnrichmentRoleSchema.optional(),
439
+ reviewer: EnrichmentRoleSchema.optional(),
440
+ adjudicator: EnrichmentRoleSchema.optional(),
441
+ })
442
+ .strict()
443
+ .optional(),
367
444
  })
368
445
  .strict()
369
446
  .optional(),
@@ -470,12 +547,53 @@ export const DocumentationStandardV1ConfigSchema = z
470
547
  }
471
548
  })
472
549
 
550
+ /**
551
+ * Retrieval tuning. Weights and parameters are configuration rather than code so a repository can
552
+ * change what its agents find first without a release, and so the change is recorded in the index.
553
+ */
554
+ export const RetrievalConfigSchema = z
555
+ .object({
556
+ corpus: z
557
+ .object({
558
+ /** Project repository documents and modules into the index. On by default. */
559
+ enabled: z.boolean().optional(),
560
+ })
561
+ .strict()
562
+ .optional(),
563
+ weights: z.record(z.string().min(1).max(64), z.number().min(0).max(1_000)).optional(),
564
+ params: z
565
+ .object({ k1: z.number().min(0).max(100).optional(), b: z.number().min(0).max(1).optional() })
566
+ .strict()
567
+ .optional(),
568
+ benchmark: z
569
+ .object({
570
+ /** The golden retrieval suite the doctor measures hit@3 against. `docs/bench/retrieval-suite-v1.json` by default. */
571
+ suite: z.string().min(1).max(512).optional(),
572
+ })
573
+ .strict()
574
+ .optional(),
575
+ })
576
+ .strict()
577
+
473
578
  export const ConformanceConfigSchema = z
474
579
  .object({
475
580
  documentationStandardV1: DocumentationStandardV1ConfigSchema.optional(),
476
581
  })
477
582
  .strict()
478
583
 
584
+ export const RenderTemplateNameSchema = z.enum(['llms.txt', 'area', 'ownership', 'change-digest', 'overlay-review'])
585
+
586
+ export const RenderConfigSchema = z
587
+ .object({
588
+ /**
589
+ * Project templates that replace the bundled ones, by template name, as paths relative to
590
+ * the project root. `ak-docs render <name> --print-template` prints the bundled template to
591
+ * start from.
592
+ */
593
+ templates: z.partialRecord(RenderTemplateNameSchema, z.string().min(1).max(512)).optional(),
594
+ })
595
+ .strict()
596
+
479
597
  export const DocBridgeConfigV1Schema = z
480
598
  .object({
481
599
  schemaVersion: z.literal(CONFIG_SCHEMA_VERSION),
@@ -496,6 +614,7 @@ export const DocBridgeConfigV1Schema = z
496
614
  routing: RoutingConfigSchema.optional(),
497
615
  gates: GatesConfigSchema.optional(),
498
616
  reconciliation: ReconciliationConfigSchema.optional(),
617
+ audit: z.object({ documentation: DocumentationAuditConfigSchema.optional() }).strict().optional(),
499
618
  analysis: AnalysisConfigSchema.optional(),
500
619
  rules: RulesConfigSchema.optional(),
501
620
  workflow: WorkflowConfigSchema.optional(),
@@ -505,15 +624,19 @@ export const DocBridgeConfigV1Schema = z
505
624
  intelligence: IntelligenceConfigSchema.optional(),
506
625
  federation: FederationConfigSchema.optional(),
507
626
  conformance: ConformanceConfigSchema.optional(),
627
+ retrieval: RetrievalConfigSchema.optional(),
628
+ render: RenderConfigSchema.optional(),
508
629
  })
509
630
  .strict()
510
631
 
511
632
  export type DocBridgeConfigV1 = z.infer<typeof DocBridgeConfigV1Schema>
633
+ export type RenderConfig = z.infer<typeof RenderConfigSchema>
512
634
  export type AgentCorpusConfig = z.infer<typeof AgentCorpusConfigSchema>
513
635
  export type HumanCorpusConfig = z.infer<typeof HumanCorpusConfigSchema>
514
636
  export type DocumentationStandardV1Config = z.infer<typeof DocumentationStandardV1ConfigSchema>
515
637
  export type DocumentationStandardRuleId = z.infer<typeof DocumentationStandardRuleIdSchema>
516
638
  export type ReconciliationConfig = z.infer<typeof ReconciliationConfigSchema>
639
+ export type DocumentationAuditConfig = z.infer<typeof DocumentationAuditConfigSchema>
517
640
  export type AnalysisConfig = z.infer<typeof AnalysisConfigSchema>
518
641
  export type RuleId = z.infer<typeof RuleIdSchema>
519
642
  export type RuleSeverity = z.infer<typeof RuleSeveritySchema>
@@ -521,3 +644,4 @@ export type RulesConfig = z.infer<typeof RulesConfigSchema>
521
644
  export type WorkflowConfig = z.infer<typeof WorkflowConfigSchema>
522
645
  export type RepositorySafetyConfig = z.infer<typeof RepositorySafetyConfigSchema>
523
646
  export type ReportConfig = z.infer<typeof ReportConfigSchema>
647
+ export type RetrievalConfig = z.infer<typeof RetrievalConfigSchema>
@@ -11,6 +11,7 @@ import { buildDocBridgeIndex } from '../index-builder/build-index.js'
11
11
  import { scanHumanDocRecords } from '../index-builder/human-adapters/index.js'
12
12
  import { renderLlmsTxt } from '../index-builder/llms-txt.js'
13
13
  import { toPosix } from '../lib/paths.js'
14
+ import type { DocBridgeIndexV1 } from '../schemas/doc-bridge-index.js'
14
15
 
15
16
  export const DOCUMENTATION_STANDARD_V1_ID = 'documentation-standard-v1' as const
16
17
  export const DOCUMENTATION_STANDARD_V1_STATUS = 'stable' as const
@@ -186,6 +187,7 @@ const llmsRule = (
186
187
  root: string,
187
188
  config: DocBridgeConfigV1,
188
189
  options: DocumentationStandardV1Config,
190
+ generated: DocBridgeIndexV1,
189
191
  ): RuleDraft => {
190
192
  const llmsPath = config.index?.llmsTxt?.outFile ?? 'llms.txt'
191
193
  const llmsKey = safePath(root, llmsPath) ?? resolve(root, llmsPath)
@@ -196,8 +198,7 @@ const llmsRule = (
196
198
  }
197
199
  const paths = [llmsPath, ...rawSources.values()]
198
200
  const evidence = paths.map((path) => fileEvidence(root, path))
199
- const generated = buildDocBridgeIndex({ root, config, write: false }).index
200
- const expectedLlms = renderLlmsTxt(config, generated.knowledge, generated.project?.name ?? 'project')
201
+ const expectedLlms = renderLlmsTxt(config, generated.knowledge, generated.project?.name ?? 'project', { root })
201
202
  const llmsIsFresh = evidence[0]?.content === expectedLlms
202
203
  if (evidence[0]?.exists) {
203
204
  evidence[0] = {
@@ -284,8 +285,7 @@ const ecosystemContract = (
284
285
  }
285
286
  }
286
287
 
287
- const handoffsRule = (root: string, config: DocBridgeConfigV1): RuleDraft => {
288
- const index = buildDocBridgeIndex({ root, config, write: false }).index
288
+ const handoffsRule = (index: DocBridgeIndexV1): RuleDraft => {
289
289
  const handoffs = Object.values(index.handoffs ?? {})
290
290
  const ready = handoffs.filter(
291
291
  (handoff) =>
@@ -456,10 +456,12 @@ export const runDocumentationStandardV1 = (
456
456
  config: DocBridgeConfigV1,
457
457
  ): DocumentationConformanceReportV1 => {
458
458
  const options = config.conformance?.documentationStandardV1 ?? {}
459
+ // Two rules need the index. Building it projects and hashes the whole repository, so build once.
460
+ const index = buildDocBridgeIndex({ root, config, write: false }).index
459
461
  const drafts: RuleDraft[] = [
460
462
  humanDocsRule(root, config),
461
- llmsRule(root, config, options),
462
- handoffsRule(root, config),
463
+ llmsRule(root, config, options, index),
464
+ handoffsRule(index),
463
465
  contributionRule(root, options),
464
466
  markersRule(root, options, 'metadata', 'required'),
465
467
  linksRule(root, options),
@@ -0,0 +1,197 @@
1
+ import { minimatch } from 'minimatch'
2
+
3
+ import { toPosix } from '../lib/paths.js'
4
+ import { entityId } from './identity.js'
5
+
6
+ /**
7
+ * Code areas: the unit of architecture between a package and a file.
8
+ *
9
+ * Reconciliation could only compare at file, module or package level, and most repositories are a
10
+ * single package — so every internal relation aggregated into one self-loop, which the comparison
11
+ * skips. The result was a repository with a thousand observed relations reporting zero
12
+ * diagnostics, and a health score of 100 out of 100 saying nothing at all.
13
+ *
14
+ * An area is a directory that groups modules. It is derived, not declared: the first directory
15
+ * level under a package's source roots, plus any path an ownership record already names — because
16
+ * a configuration saying `path: "src/mcp"` is a human telling us that directory is a unit, and the
17
+ * graph had no entity for it.
18
+ */
19
+
20
+ export const AREA_ANALYZER_VERSION = '1.0.0'
21
+
22
+ /**
23
+ * Directories that contain areas rather than being one.
24
+ *
25
+ * `src` is not an area in any useful sense; `src/query` is. When a package keeps its code under
26
+ * one of these, the area is the level below it.
27
+ */
28
+ export const DEFAULT_AREA_ROOTS = ['src', 'lib', 'app', 'source', 'server', 'client', 'packages', 'apps'] as const
29
+
30
+ export const DEFAULT_AREA_DEPTH = 1
31
+
32
+ export type AreaModule = {
33
+ readonly moduleId: string
34
+ /** Repository-relative module path. */
35
+ readonly path: string
36
+ readonly packageId: string
37
+ /** Repository-relative package path; `.` for the root package. */
38
+ readonly packagePath: string
39
+ }
40
+
41
+ export type AreaOwnership = {
42
+ readonly id: string
43
+ readonly path: string
44
+ }
45
+
46
+ export type DerivedArea = {
47
+ readonly id: string
48
+ readonly path: string
49
+ readonly name: string
50
+ readonly packageId: string
51
+ /** The most specific area that encloses this one, when there is one. */
52
+ readonly parentId?: string
53
+ readonly moduleIds: readonly string[]
54
+ /** The ownership record that names exactly this path. */
55
+ readonly ownershipId?: string
56
+ }
57
+
58
+ export type DeriveAreasOptions = {
59
+ readonly modules: readonly AreaModule[]
60
+ readonly ownership?: readonly AreaOwnership[]
61
+ readonly depth?: number
62
+ readonly roots?: readonly string[]
63
+ /**
64
+ * Glob patterns for directories that hold code without being a unit of architecture.
65
+ *
66
+ * A candidate that matches is not derived as an area, and its modules fall to the most
67
+ * specific area that still encloses them — or to none, which is the honest answer for a
68
+ * folder of fixtures. An ownership record naming the same path still wins: a person saying
69
+ * a directory is a unit outranks a pattern saying it is not.
70
+ */
71
+ readonly exclude?: readonly string[]
72
+ }
73
+
74
+ /*
75
+ * Trailing separators are trimmed with a loop rather than /\/+$/: a quantified group anchored at
76
+ * the end backtracks quadratically on a path that is mostly separators, and a module path comes
77
+ * from the repository being scanned.
78
+ */
79
+ const normalize = (path: string): string => {
80
+ const posix = toPosix(path).replace(/^\.\//, '')
81
+ let end = posix.length
82
+ while (end > 0 && posix[end - 1] === '/') end -= 1
83
+ return posix.slice(0, end)
84
+ }
85
+
86
+ const relativeToPackage = (modulePath: string, packagePath: string): string | undefined => {
87
+ if (packagePath === '.' || packagePath === '') return modulePath
88
+ const prefix = `${packagePath}/`
89
+ return modulePath.startsWith(prefix) ? modulePath.slice(prefix.length) : undefined
90
+ }
91
+
92
+ const join = (...parts: readonly string[]): string => parts.filter(Boolean).join('/')
93
+
94
+ /**
95
+ * The area a module belongs to by convention: `depth` directory levels, counted below a source
96
+ * root when the module sits under one. A module directly in the package or in a bare source root
97
+ * has no conventional area — there is no directory grouping it with anything.
98
+ */
99
+ export const conventionalAreaPath = (
100
+ module: AreaModule,
101
+ depth = DEFAULT_AREA_DEPTH,
102
+ roots: readonly string[] = DEFAULT_AREA_ROOTS,
103
+ ): string | undefined => {
104
+ const relative = relativeToPackage(normalize(module.path), normalize(module.packagePath))
105
+ if (relative === undefined) return undefined
106
+ const segments = relative.split('/')
107
+ segments.pop()
108
+ if (!segments.length) return undefined
109
+
110
+ const rootPrefix: string[] = []
111
+ let rest = segments
112
+ if (segments[0] && roots.includes(segments[0])) {
113
+ rootPrefix.push(segments[0])
114
+ rest = segments.slice(1)
115
+ }
116
+ const taken = rest.slice(0, Math.max(1, depth))
117
+ if (!taken.length) return undefined
118
+
119
+ const packagePath = normalize(module.packagePath)
120
+ return join(packagePath === '.' ? '' : packagePath, ...rootPrefix, ...taken)
121
+ }
122
+
123
+ const encloses = (candidate: string, path: string): boolean => path === candidate || path.startsWith(`${candidate}/`)
124
+
125
+ /**
126
+ * Derive the areas of a repository.
127
+ *
128
+ * Every module is assigned to exactly one area — the most specific one containing it — so the
129
+ * containment graph stays a tree and an aggregation at area scope has one answer per module.
130
+ * Nested areas keep their shape: `src` names the area that holds what is directly in `src`, and
131
+ * `src/query` its own, with `src` recorded as the parent.
132
+ */
133
+ export const deriveAreas = (options: DeriveAreasOptions): readonly DerivedArea[] => {
134
+ const depth = options.depth ?? DEFAULT_AREA_DEPTH
135
+ const roots = options.roots ?? [...DEFAULT_AREA_ROOTS]
136
+ const ownership = (options.ownership ?? []).map((record) => ({ ...record, path: normalize(record.path) }))
137
+
138
+ const excluded = options.exclude ?? []
139
+ const isExcluded = (path: string): boolean =>
140
+ excluded.some((pattern) => minimatch(path, pattern, { dot: true }))
141
+
142
+ /** Candidate path to the package it belongs to. */
143
+ const candidates = new Map<string, string>()
144
+ for (const module of options.modules) {
145
+ const path = conventionalAreaPath(module, depth, roots)
146
+ if (path && !candidates.has(path) && !isExcluded(path)) candidates.set(path, module.packageId)
147
+ }
148
+ // An ownership record naming a directory that holds observed code is an area by declaration.
149
+ for (const record of ownership) {
150
+ if (!record.path || candidates.has(record.path)) continue
151
+ const owner = options.modules.find((module) => encloses(record.path, normalize(module.path)))
152
+ if (owner) candidates.set(record.path, owner.packageId)
153
+ }
154
+
155
+ const paths = [...candidates.keys()].sort()
156
+ const byLength = [...paths].sort((a, b) => b.length - a.length || a.localeCompare(b))
157
+ const moduleIds = new Map<string, string[]>(paths.map((path) => [path, []]))
158
+
159
+ for (const module of options.modules) {
160
+ const modulePath = normalize(module.path)
161
+ const area = byLength.find((candidate) => encloses(candidate, modulePath))
162
+ if (area) moduleIds.get(area)?.push(module.moduleId)
163
+ }
164
+
165
+ const ownershipByPath = new Map(ownership.map((record) => [record.path, record.id]))
166
+
167
+ return paths.map((path) => {
168
+ const parent = byLength.find((candidate) => candidate !== path && encloses(candidate, path))
169
+ const ownershipId = ownershipByPath.get(path)
170
+ return {
171
+ id: entityId('area', path),
172
+ path,
173
+ name: path.split('/').pop() ?? path,
174
+ packageId: candidates.get(path) as string,
175
+ ...(parent ? { parentId: entityId('area', parent) } : {}),
176
+ moduleIds: [...(moduleIds.get(path) ?? [])].sort(),
177
+ ...(ownershipId ? { ownershipId } : {}),
178
+ }
179
+ })
180
+ }
181
+
182
+ /**
183
+ * Ownership paths no observed entity lives under.
184
+ *
185
+ * A configured path that matches nothing is almost always a rename or a typo, and it is invisible
186
+ * until something looks: the handoff still resolves, it just points at a directory that no longer
187
+ * holds what it claims.
188
+ */
189
+ export const unobservedOwnershipPaths = (
190
+ ownership: readonly AreaOwnership[],
191
+ observedPaths: readonly string[],
192
+ ): readonly AreaOwnership[] => {
193
+ const paths = observedPaths.map(normalize)
194
+ return ownership
195
+ .map((record) => ({ ...record, path: normalize(record.path) }))
196
+ .filter((record) => record.path && !paths.some((path) => encloses(record.path, path)))
197
+ }