@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,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'
@@ -79,7 +79,13 @@ const parseConfig = (input: unknown): DocBridgeConfigV1 => {
79
79
  if (result.success) return result.data
80
80
  throw new Error(
81
81
  `Invalid doc-bridge config:\n${result.error.issues.map((issue) =>
82
- ` - ${issue.path.join('.') || '(root)'}: ${issue.message}`,
82
+ ` - ${issue.path.join('.') || '(root)'}: ${
83
+ issue.code === 'invalid_type' && issue.message.endsWith('received undefined')
84
+ ? 'Required'
85
+ : issue.code === 'invalid_value' && 'values' in issue
86
+ ? 'Invalid enum value'
87
+ : issue.message
88
+ }`,
83
89
  ).join('\n')}`,
84
90
  )
85
91
  }
@@ -171,24 +171,47 @@ export const RuleSeveritySchema = z.enum(['off', 'info', 'warn', 'error'])
171
171
  export const RulesConfigSchema = z
172
172
  .object({
173
173
  mode: z.enum(['default', 'recommended', 'strict']).optional(),
174
- severity: z.record(RuleIdSchema, RuleSeveritySchema).optional(),
174
+ severity: z.partialRecord(RuleIdSchema, RuleSeveritySchema).optional(),
175
175
  ignore: z.array(RuleIdSchema).max(128).optional(),
176
176
  criticalEntities: z.array(z.string().min(1).max(256)).max(128).optional(),
177
177
  criticalPaths: z.array(z.string().min(1).max(512)).max(128).optional(),
178
- warningThresholds: z.record(RuleIdSchema, z.number().int().min(1).max(100_000)).optional(),
178
+ warningThresholds: z.partialRecord(RuleIdSchema, z.number().int().min(1).max(100_000)).optional(),
179
179
  })
180
180
  .strict()
181
181
 
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
+ /** Limit missing-declaration findings to relations whose endpoints are internal project entities. */
189
+ requiredRelationTargets: z.enum(['all', 'internal']).optional(),
188
190
  includeOrphanedDocuments: z.boolean().optional(),
189
191
  })
190
192
  .strict()
191
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
+
192
215
  export const AnalysisConfigSchema = z
193
216
  .object({
194
217
  plugins: z
@@ -223,6 +246,18 @@ export const AnalysisConfigSchema = z
223
246
  })
224
247
  .strict()
225
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(),
226
261
  })
227
262
  .strict()
228
263
 
@@ -280,9 +315,11 @@ export const SurfacesConfigSchema = z
280
315
  'docbridge.relations',
281
316
  'docbridge.run',
282
317
  'docbridge.proposals',
318
+ 'knowledge.search',
319
+ 'knowledge.lookup',
283
320
  ]),
284
321
  )
285
- .max(16)
322
+ .max(18)
286
323
  .optional(),
287
324
  transport: z.enum(['stdio', 'http']).optional(),
288
325
  http: z
@@ -298,6 +335,16 @@ export const SurfacesConfigSchema = z
298
335
  })
299
336
  .strict()
300
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
+
301
348
  export const IntelligenceConfigSchema = z
302
349
  .object({
303
350
  enabled: z.boolean().optional(),
@@ -357,11 +404,34 @@ export const IntelligenceConfigSchema = z
357
404
  agentId: z.string().min(1).max(256).optional(),
358
405
  agentRoot: z.string().min(1).max(512).optional(),
359
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(),
360
415
  deterministic: z.boolean().optional(),
361
416
  timeoutMs: z.number().int().positive().max(600_000).optional(),
417
+ maxInputBytes: z.number().int().positive().max(50_000_000).optional(),
362
418
  maxTokens: z.number().int().positive().max(1_000_000).optional(),
363
419
  maxResponseBytes: z.number().int().positive().max(10_000_000).optional(),
364
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(),
365
435
  })
366
436
  .strict()
367
437
  .optional(),
@@ -468,12 +538,53 @@ export const DocumentationStandardV1ConfigSchema = z
468
538
  }
469
539
  })
470
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
+
471
569
  export const ConformanceConfigSchema = z
472
570
  .object({
473
571
  documentationStandardV1: DocumentationStandardV1ConfigSchema.optional(),
474
572
  })
475
573
  .strict()
476
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
+
477
588
  export const DocBridgeConfigV1Schema = z
478
589
  .object({
479
590
  schemaVersion: z.literal(CONFIG_SCHEMA_VERSION),
@@ -494,6 +605,7 @@ export const DocBridgeConfigV1Schema = z
494
605
  routing: RoutingConfigSchema.optional(),
495
606
  gates: GatesConfigSchema.optional(),
496
607
  reconciliation: ReconciliationConfigSchema.optional(),
608
+ audit: z.object({ documentation: DocumentationAuditConfigSchema.optional() }).strict().optional(),
497
609
  analysis: AnalysisConfigSchema.optional(),
498
610
  rules: RulesConfigSchema.optional(),
499
611
  workflow: WorkflowConfigSchema.optional(),
@@ -503,15 +615,19 @@ export const DocBridgeConfigV1Schema = z
503
615
  intelligence: IntelligenceConfigSchema.optional(),
504
616
  federation: FederationConfigSchema.optional(),
505
617
  conformance: ConformanceConfigSchema.optional(),
618
+ retrieval: RetrievalConfigSchema.optional(),
619
+ render: RenderConfigSchema.optional(),
506
620
  })
507
621
  .strict()
508
622
 
509
623
  export type DocBridgeConfigV1 = z.infer<typeof DocBridgeConfigV1Schema>
624
+ export type RenderConfig = z.infer<typeof RenderConfigSchema>
510
625
  export type AgentCorpusConfig = z.infer<typeof AgentCorpusConfigSchema>
511
626
  export type HumanCorpusConfig = z.infer<typeof HumanCorpusConfigSchema>
512
627
  export type DocumentationStandardV1Config = z.infer<typeof DocumentationStandardV1ConfigSchema>
513
628
  export type DocumentationStandardRuleId = z.infer<typeof DocumentationStandardRuleIdSchema>
514
629
  export type ReconciliationConfig = z.infer<typeof ReconciliationConfigSchema>
630
+ export type DocumentationAuditConfig = z.infer<typeof DocumentationAuditConfigSchema>
515
631
  export type AnalysisConfig = z.infer<typeof AnalysisConfigSchema>
516
632
  export type RuleId = z.infer<typeof RuleIdSchema>
517
633
  export type RuleSeverity = z.infer<typeof RuleSeveritySchema>
@@ -519,3 +635,4 @@ export type RulesConfig = z.infer<typeof RulesConfigSchema>
519
635
  export type WorkflowConfig = z.infer<typeof WorkflowConfigSchema>
520
636
  export type RepositorySafetyConfig = z.infer<typeof RepositorySafetyConfigSchema>
521
637
  export type ReportConfig = z.infer<typeof ReportConfigSchema>
638
+ export type RetrievalConfig = z.infer<typeof RetrievalConfigSchema>
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs'
1
+ import { closeSync, existsSync, fstatSync, openSync, readFileSync, realpathSync } from 'node:fs'
2
2
  import { isAbsolute, relative, resolve, sep } from 'node:path'
3
3
 
4
4
  import type {
@@ -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
@@ -95,11 +96,10 @@ const fileEvidence = (
95
96
  evidence: { path, detail: 'Path escapes the project root.' },
96
97
  }
97
98
  }
98
- if (!existsSync(abs)) {
99
- return { exists: false, content: '', evidence: { path, detail: 'File does not exist.' } }
100
- }
99
+ let fd: number | undefined
101
100
  try {
102
- const stat = statSync(abs)
101
+ fd = openSync(abs, 'r')
102
+ const stat = fstatSync(fd)
103
103
  if (!stat.isFile()) {
104
104
  return { exists: false, content: '', evidence: { path, detail: 'Path is not a regular file.' } }
105
105
  }
@@ -120,7 +120,7 @@ const fileEvidence = (
120
120
  evidence: { path, detail: `Text evidence exceeds ${MAX_TEXT_EVIDENCE_BYTES} bytes.` },
121
121
  }
122
122
  }
123
- const content = readFileSync(abs, 'utf8')
123
+ const content = readFileSync(fd, 'utf8')
124
124
  return {
125
125
  exists: content.trim().length > 0,
126
126
  content,
@@ -129,8 +129,15 @@ const fileEvidence = (
129
129
  detail: content.trim().length > 0 ? 'File exists and is non-empty.' : 'File is empty.',
130
130
  },
131
131
  }
132
- } catch {
133
- return { exists: false, content: '', evidence: { path, detail: 'File is not readable text.' } }
132
+ } catch (error) {
133
+ const code = error && typeof error === 'object' && 'code' in error ? error.code : undefined
134
+ return {
135
+ exists: false,
136
+ content: '',
137
+ evidence: { path, detail: code === 'ENOENT' ? 'File does not exist.' : 'File is not readable text.' },
138
+ }
139
+ } finally {
140
+ if (fd !== undefined) closeSync(fd)
134
141
  }
135
142
  }
136
143
 
@@ -180,6 +187,7 @@ const llmsRule = (
180
187
  root: string,
181
188
  config: DocBridgeConfigV1,
182
189
  options: DocumentationStandardV1Config,
190
+ generated: DocBridgeIndexV1,
183
191
  ): RuleDraft => {
184
192
  const llmsPath = config.index?.llmsTxt?.outFile ?? 'llms.txt'
185
193
  const llmsKey = safePath(root, llmsPath) ?? resolve(root, llmsPath)
@@ -190,8 +198,7 @@ const llmsRule = (
190
198
  }
191
199
  const paths = [llmsPath, ...rawSources.values()]
192
200
  const evidence = paths.map((path) => fileEvidence(root, path))
193
- const generated = buildDocBridgeIndex({ root, config, write: false }).index
194
- const expectedLlms = renderLlmsTxt(config, generated.knowledge, generated.project?.name ?? 'project')
201
+ const expectedLlms = renderLlmsTxt(config, generated.knowledge, generated.project?.name ?? 'project', { root })
195
202
  const llmsIsFresh = evidence[0]?.content === expectedLlms
196
203
  if (evidence[0]?.exists) {
197
204
  evidence[0] = {
@@ -278,8 +285,7 @@ const ecosystemContract = (
278
285
  }
279
286
  }
280
287
 
281
- const handoffsRule = (root: string, config: DocBridgeConfigV1): RuleDraft => {
282
- const index = buildDocBridgeIndex({ root, config, write: false }).index
288
+ const handoffsRule = (index: DocBridgeIndexV1): RuleDraft => {
283
289
  const handoffs = Object.values(index.handoffs ?? {})
284
290
  const ready = handoffs.filter(
285
291
  (handoff) =>
@@ -450,10 +456,12 @@ export const runDocumentationStandardV1 = (
450
456
  config: DocBridgeConfigV1,
451
457
  ): DocumentationConformanceReportV1 => {
452
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
453
461
  const drafts: RuleDraft[] = [
454
462
  humanDocsRule(root, config),
455
- llmsRule(root, config, options),
456
- handoffsRule(root, config),
463
+ llmsRule(root, config, options, index),
464
+ handoffsRule(index),
457
465
  contributionRule(root, options),
458
466
  markersRule(root, options, 'metadata', 'required'),
459
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
+ }