@agentskit/doc-bridge 1.7.45 → 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 (213) hide show
  1. package/CHANGELOG.md +465 -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 +15861 -6223
  10. package/dist/cli/program.js.map +1 -1
  11. package/dist/config/index.d.ts +1 -1
  12. package/dist/config/index.js +86 -6
  13. package/dist/config/index.js.map +1 -1
  14. package/dist/{index-BUL0q7s8.d.ts → index-Beor6Yhi.d.ts} +133 -1
  15. package/dist/index.d.ts +9871 -1062
  16. package/dist/index.js +16126 -6125
  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 +147 -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 +25 -6
  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 +117 -2
  139. package/src/conformance/documentation-standard-v1.ts +8 -6
  140. package/src/discovery/areas.ts +182 -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 +484 -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,18 @@ 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.
253
+ */
254
+ areas: z
255
+ .object({
256
+ depth: z.number().int().min(1).max(8).optional(),
257
+ roots: z.array(z.string().min(1).max(128)).max(32).optional(),
258
+ })
259
+ .strict()
260
+ .optional(),
228
261
  })
229
262
  .strict()
230
263
 
@@ -282,9 +315,11 @@ export const SurfacesConfigSchema = z
282
315
  'docbridge.relations',
283
316
  'docbridge.run',
284
317
  'docbridge.proposals',
318
+ 'knowledge.search',
319
+ 'knowledge.lookup',
285
320
  ]),
286
321
  )
287
- .max(16)
322
+ .max(18)
288
323
  .optional(),
289
324
  transport: z.enum(['stdio', 'http']).optional(),
290
325
  http: z
@@ -300,6 +335,16 @@ export const SurfacesConfigSchema = z
300
335
  })
301
336
  .strict()
302
337
 
338
+ /** One enrichment role: an installed Registry agent and the prompt version it is run with. */
339
+ export const EnrichmentRoleSchema = z
340
+ .object({
341
+ enabled: z.boolean().optional(),
342
+ /** Defaults to `intelligence.registry.agentId`. */
343
+ agentId: z.string().min(1).max(256).optional(),
344
+ promptVersion: z.string().min(1).max(64).optional(),
345
+ })
346
+ .strict()
347
+
303
348
  export const IntelligenceConfigSchema = z
304
349
  .object({
305
350
  enabled: z.boolean().optional(),
@@ -359,11 +404,34 @@ export const IntelligenceConfigSchema = z
359
404
  agentId: z.string().min(1).max(256).optional(),
360
405
  agentRoot: z.string().min(1).max(512).optional(),
361
406
  runnerModule: z.string().min(1).max(512).optional(),
407
+ cli: z
408
+ .object({
409
+ /** Executable name or absolute path. Arguments are passed without a shell. */
410
+ command: z.string().min(1).max(512),
411
+ args: z.array(z.string().max(2_048)).max(64).optional(),
412
+ })
413
+ .strict()
414
+ .optional(),
362
415
  deterministic: z.boolean().optional(),
363
416
  timeoutMs: z.number().int().positive().max(600_000).optional(),
417
+ maxInputBytes: z.number().int().positive().max(50_000_000).optional(),
364
418
  maxTokens: z.number().int().positive().max(1_000_000).optional(),
365
419
  maxResponseBytes: z.number().int().positive().max(10_000_000).optional(),
366
420
  maxConcurrency: z.number().int().positive().max(64).optional(),
421
+ /** Byte budget of one enrichment context pack. Default 65536. */
422
+ maxPackBytes: z.number().int().min(4_096).max(4_000_000).optional(),
423
+ /**
424
+ * Which installed agent plays which enrichment role. The default is the configured agent
425
+ * as curator only. The adjudicator must be a different identity from both others.
426
+ */
427
+ roles: z
428
+ .object({
429
+ curator: EnrichmentRoleSchema.optional(),
430
+ reviewer: EnrichmentRoleSchema.optional(),
431
+ adjudicator: EnrichmentRoleSchema.optional(),
432
+ })
433
+ .strict()
434
+ .optional(),
367
435
  })
368
436
  .strict()
369
437
  .optional(),
@@ -470,12 +538,53 @@ export const DocumentationStandardV1ConfigSchema = z
470
538
  }
471
539
  })
472
540
 
541
+ /**
542
+ * Retrieval tuning. Weights and parameters are configuration rather than code so a repository can
543
+ * change what its agents find first without a release, and so the change is recorded in the index.
544
+ */
545
+ export const RetrievalConfigSchema = z
546
+ .object({
547
+ corpus: z
548
+ .object({
549
+ /** Project repository documents and modules into the index. On by default. */
550
+ enabled: z.boolean().optional(),
551
+ })
552
+ .strict()
553
+ .optional(),
554
+ weights: z.record(z.string().min(1).max(64), z.number().min(0).max(1_000)).optional(),
555
+ params: z
556
+ .object({ k1: z.number().min(0).max(100).optional(), b: z.number().min(0).max(1).optional() })
557
+ .strict()
558
+ .optional(),
559
+ benchmark: z
560
+ .object({
561
+ /** The golden retrieval suite the doctor measures hit@3 against. `docs/bench/retrieval-suite-v1.json` by default. */
562
+ suite: z.string().min(1).max(512).optional(),
563
+ })
564
+ .strict()
565
+ .optional(),
566
+ })
567
+ .strict()
568
+
473
569
  export const ConformanceConfigSchema = z
474
570
  .object({
475
571
  documentationStandardV1: DocumentationStandardV1ConfigSchema.optional(),
476
572
  })
477
573
  .strict()
478
574
 
575
+ export const RenderTemplateNameSchema = z.enum(['llms.txt', 'area', 'ownership', 'change-digest', 'overlay-review'])
576
+
577
+ export const RenderConfigSchema = z
578
+ .object({
579
+ /**
580
+ * Project templates that replace the bundled ones, by template name, as paths relative to
581
+ * the project root. `ak-docs render <name> --print-template` prints the bundled template to
582
+ * start from.
583
+ */
584
+ templates: z.partialRecord(RenderTemplateNameSchema, z.string().min(1).max(512)).optional(),
585
+ })
586
+ .strict()
587
+
479
588
  export const DocBridgeConfigV1Schema = z
480
589
  .object({
481
590
  schemaVersion: z.literal(CONFIG_SCHEMA_VERSION),
@@ -496,6 +605,7 @@ export const DocBridgeConfigV1Schema = z
496
605
  routing: RoutingConfigSchema.optional(),
497
606
  gates: GatesConfigSchema.optional(),
498
607
  reconciliation: ReconciliationConfigSchema.optional(),
608
+ audit: z.object({ documentation: DocumentationAuditConfigSchema.optional() }).strict().optional(),
499
609
  analysis: AnalysisConfigSchema.optional(),
500
610
  rules: RulesConfigSchema.optional(),
501
611
  workflow: WorkflowConfigSchema.optional(),
@@ -505,15 +615,19 @@ export const DocBridgeConfigV1Schema = z
505
615
  intelligence: IntelligenceConfigSchema.optional(),
506
616
  federation: FederationConfigSchema.optional(),
507
617
  conformance: ConformanceConfigSchema.optional(),
618
+ retrieval: RetrievalConfigSchema.optional(),
619
+ render: RenderConfigSchema.optional(),
508
620
  })
509
621
  .strict()
510
622
 
511
623
  export type DocBridgeConfigV1 = z.infer<typeof DocBridgeConfigV1Schema>
624
+ export type RenderConfig = z.infer<typeof RenderConfigSchema>
512
625
  export type AgentCorpusConfig = z.infer<typeof AgentCorpusConfigSchema>
513
626
  export type HumanCorpusConfig = z.infer<typeof HumanCorpusConfigSchema>
514
627
  export type DocumentationStandardV1Config = z.infer<typeof DocumentationStandardV1ConfigSchema>
515
628
  export type DocumentationStandardRuleId = z.infer<typeof DocumentationStandardRuleIdSchema>
516
629
  export type ReconciliationConfig = z.infer<typeof ReconciliationConfigSchema>
630
+ export type DocumentationAuditConfig = z.infer<typeof DocumentationAuditConfigSchema>
517
631
  export type AnalysisConfig = z.infer<typeof AnalysisConfigSchema>
518
632
  export type RuleId = z.infer<typeof RuleIdSchema>
519
633
  export type RuleSeverity = z.infer<typeof RuleSeveritySchema>
@@ -521,3 +635,4 @@ export type RulesConfig = z.infer<typeof RulesConfigSchema>
521
635
  export type WorkflowConfig = z.infer<typeof WorkflowConfigSchema>
522
636
  export type RepositorySafetyConfig = z.infer<typeof RepositorySafetyConfigSchema>
523
637
  export type ReportConfig = z.infer<typeof ReportConfigSchema>
638
+ 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,182 @@
1
+ import { toPosix } from '../lib/paths.js'
2
+ import { entityId } from './identity.js'
3
+
4
+ /**
5
+ * Code areas: the unit of architecture between a package and a file.
6
+ *
7
+ * Reconciliation could only compare at file, module or package level, and most repositories are a
8
+ * single package — so every internal relation aggregated into one self-loop, which the comparison
9
+ * skips. The result was a repository with a thousand observed relations reporting zero
10
+ * diagnostics, and a health score of 100 out of 100 saying nothing at all.
11
+ *
12
+ * An area is a directory that groups modules. It is derived, not declared: the first directory
13
+ * level under a package's source roots, plus any path an ownership record already names — because
14
+ * a configuration saying `path: "src/mcp"` is a human telling us that directory is a unit, and the
15
+ * graph had no entity for it.
16
+ */
17
+
18
+ export const AREA_ANALYZER_VERSION = '1.0.0'
19
+
20
+ /**
21
+ * Directories that contain areas rather than being one.
22
+ *
23
+ * `src` is not an area in any useful sense; `src/query` is. When a package keeps its code under
24
+ * one of these, the area is the level below it.
25
+ */
26
+ export const DEFAULT_AREA_ROOTS = ['src', 'lib', 'app', 'source', 'server', 'client', 'packages', 'apps'] as const
27
+
28
+ export const DEFAULT_AREA_DEPTH = 1
29
+
30
+ export type AreaModule = {
31
+ readonly moduleId: string
32
+ /** Repository-relative module path. */
33
+ readonly path: string
34
+ readonly packageId: string
35
+ /** Repository-relative package path; `.` for the root package. */
36
+ readonly packagePath: string
37
+ }
38
+
39
+ export type AreaOwnership = {
40
+ readonly id: string
41
+ readonly path: string
42
+ }
43
+
44
+ export type DerivedArea = {
45
+ readonly id: string
46
+ readonly path: string
47
+ readonly name: string
48
+ readonly packageId: string
49
+ /** The most specific area that encloses this one, when there is one. */
50
+ readonly parentId?: string
51
+ readonly moduleIds: readonly string[]
52
+ /** The ownership record that names exactly this path. */
53
+ readonly ownershipId?: string
54
+ }
55
+
56
+ export type DeriveAreasOptions = {
57
+ readonly modules: readonly AreaModule[]
58
+ readonly ownership?: readonly AreaOwnership[]
59
+ readonly depth?: number
60
+ readonly roots?: readonly string[]
61
+ }
62
+
63
+ /*
64
+ * Trailing separators are trimmed with a loop rather than /\/+$/: a quantified group anchored at
65
+ * the end backtracks quadratically on a path that is mostly separators, and a module path comes
66
+ * from the repository being scanned.
67
+ */
68
+ const normalize = (path: string): string => {
69
+ const posix = toPosix(path).replace(/^\.\//, '')
70
+ let end = posix.length
71
+ while (end > 0 && posix[end - 1] === '/') end -= 1
72
+ return posix.slice(0, end)
73
+ }
74
+
75
+ const relativeToPackage = (modulePath: string, packagePath: string): string | undefined => {
76
+ if (packagePath === '.' || packagePath === '') return modulePath
77
+ const prefix = `${packagePath}/`
78
+ return modulePath.startsWith(prefix) ? modulePath.slice(prefix.length) : undefined
79
+ }
80
+
81
+ const join = (...parts: readonly string[]): string => parts.filter(Boolean).join('/')
82
+
83
+ /**
84
+ * The area a module belongs to by convention: `depth` directory levels, counted below a source
85
+ * root when the module sits under one. A module directly in the package or in a bare source root
86
+ * has no conventional area — there is no directory grouping it with anything.
87
+ */
88
+ export const conventionalAreaPath = (
89
+ module: AreaModule,
90
+ depth = DEFAULT_AREA_DEPTH,
91
+ roots: readonly string[] = DEFAULT_AREA_ROOTS,
92
+ ): string | undefined => {
93
+ const relative = relativeToPackage(normalize(module.path), normalize(module.packagePath))
94
+ if (relative === undefined) return undefined
95
+ const segments = relative.split('/')
96
+ segments.pop()
97
+ if (!segments.length) return undefined
98
+
99
+ const rootPrefix: string[] = []
100
+ let rest = segments
101
+ if (segments[0] && roots.includes(segments[0])) {
102
+ rootPrefix.push(segments[0])
103
+ rest = segments.slice(1)
104
+ }
105
+ const taken = rest.slice(0, Math.max(1, depth))
106
+ if (!taken.length) return undefined
107
+
108
+ const packagePath = normalize(module.packagePath)
109
+ return join(packagePath === '.' ? '' : packagePath, ...rootPrefix, ...taken)
110
+ }
111
+
112
+ const encloses = (candidate: string, path: string): boolean => path === candidate || path.startsWith(`${candidate}/`)
113
+
114
+ /**
115
+ * Derive the areas of a repository.
116
+ *
117
+ * Every module is assigned to exactly one area — the most specific one containing it — so the
118
+ * containment graph stays a tree and an aggregation at area scope has one answer per module.
119
+ * Nested areas keep their shape: `src` names the area that holds what is directly in `src`, and
120
+ * `src/query` its own, with `src` recorded as the parent.
121
+ */
122
+ export const deriveAreas = (options: DeriveAreasOptions): readonly DerivedArea[] => {
123
+ const depth = options.depth ?? DEFAULT_AREA_DEPTH
124
+ const roots = options.roots ?? [...DEFAULT_AREA_ROOTS]
125
+ const ownership = (options.ownership ?? []).map((record) => ({ ...record, path: normalize(record.path) }))
126
+
127
+ /** Candidate path to the package it belongs to. */
128
+ const candidates = new Map<string, string>()
129
+ for (const module of options.modules) {
130
+ const path = conventionalAreaPath(module, depth, roots)
131
+ if (path && !candidates.has(path)) candidates.set(path, module.packageId)
132
+ }
133
+ // An ownership record naming a directory that holds observed code is an area by declaration.
134
+ for (const record of ownership) {
135
+ if (!record.path || candidates.has(record.path)) continue
136
+ const owner = options.modules.find((module) => encloses(record.path, normalize(module.path)))
137
+ if (owner) candidates.set(record.path, owner.packageId)
138
+ }
139
+
140
+ const paths = [...candidates.keys()].sort()
141
+ const byLength = [...paths].sort((a, b) => b.length - a.length || a.localeCompare(b))
142
+ const moduleIds = new Map<string, string[]>(paths.map((path) => [path, []]))
143
+
144
+ for (const module of options.modules) {
145
+ const modulePath = normalize(module.path)
146
+ const area = byLength.find((candidate) => encloses(candidate, modulePath))
147
+ if (area) moduleIds.get(area)?.push(module.moduleId)
148
+ }
149
+
150
+ const ownershipByPath = new Map(ownership.map((record) => [record.path, record.id]))
151
+
152
+ return paths.map((path) => {
153
+ const parent = byLength.find((candidate) => candidate !== path && encloses(candidate, path))
154
+ const ownershipId = ownershipByPath.get(path)
155
+ return {
156
+ id: entityId('area', path),
157
+ path,
158
+ name: path.split('/').pop() ?? path,
159
+ packageId: candidates.get(path) as string,
160
+ ...(parent ? { parentId: entityId('area', parent) } : {}),
161
+ moduleIds: [...(moduleIds.get(path) ?? [])].sort(),
162
+ ...(ownershipId ? { ownershipId } : {}),
163
+ }
164
+ })
165
+ }
166
+
167
+ /**
168
+ * Ownership paths no observed entity lives under.
169
+ *
170
+ * A configured path that matches nothing is almost always a rename or a typo, and it is invisible
171
+ * until something looks: the handoff still resolves, it just points at a directory that no longer
172
+ * holds what it claims.
173
+ */
174
+ export const unobservedOwnershipPaths = (
175
+ ownership: readonly AreaOwnership[],
176
+ observedPaths: readonly string[],
177
+ ): readonly AreaOwnership[] => {
178
+ const paths = observedPaths.map(normalize)
179
+ return ownership
180
+ .map((record) => ({ ...record, path: normalize(record.path) }))
181
+ .filter((record) => record.path && !paths.some((path) => encloses(record.path, path)))
182
+ }