@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
@@ -3,7 +3,7 @@
3
3
  import { spawnSync } from 'node:child_process'
4
4
  import { isAbsolute } from 'node:path'
5
5
 
6
- const VERSION = '1.7.44'
6
+ const VERSION = '1.9.0'
7
7
  const kinds = new Set(['package', 'ownership'])
8
8
  const args = process.argv.slice(2)
9
9
  const id = args[0]
@@ -1,9 +1,11 @@
1
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'
2
+ import { spawn } from 'node:child_process'
2
3
  import { join, resolve } from 'node:path'
3
4
  import { pathToFileURL } from 'node:url'
4
5
  import { z } from 'zod'
5
6
 
6
7
  import type { DocBridgeConfigV1 } from '../config/schema.js'
8
+ import type { DocumentationAuditReportV1 } from '../audit/documentation.js'
7
9
  import { AgentProposalV1Schema, type AgentProposalV1, type DiscoverySnapshotV1, type ReconciliationReportV1 } from '../schemas/knowledge.js'
8
10
  import { contentHashForArtifactV1, sha256NormalizedV1 } from '../index-builder/content-hash.js'
9
11
  import { containedPath, redactValue } from '../safety/repository.js'
@@ -24,6 +26,7 @@ export type RegistryAgentContext = {
24
26
  readonly snapshot: DiscoverySnapshotV1
25
27
  readonly report: ReconciliationReportV1
26
28
  readonly evidence: readonly ReconciliationReportV1['diagnostics'][number]['evidence'][number][]
29
+ readonly documentation?: Pick<DocumentationAuditReportV1, 'contentHash' | 'sourceRevision' | 'status' | 'findings' | 'documentAssessments' | 'metrics' | 'limitations'>
27
30
  readonly capabilities: readonly ['snapshot.read', 'evidence.read', 'proposal.write']
28
31
  readonly network: false
29
32
  readonly shell: false
@@ -32,9 +35,35 @@ export type RegistryAgentContext = {
32
35
 
33
36
  export type RegistryAgentRunner = (context: RegistryAgentContext) => Promise<unknown> | unknown
34
37
 
38
+ type RegistryCliConfig = NonNullable<NonNullable<NonNullable<DocBridgeConfigV1['intelligence']>['registry']>['cli']>
39
+
40
+ /**
41
+ * The v2 protocol: a task and a bounded set of context packs, never the snapshot.
42
+ *
43
+ * `curate` and `review` return `proposals` — `EnrichmentProposalV1` values, validated by the
44
+ * enrich stage, never here; `adjudicate` returns adjudications under the same key. The adapter
45
+ * checks transport and budget and returns the raw array: grounding is the validators' job, and
46
+ * keeping it there is what makes a stored overlay reproducible from its proposals.
47
+ */
48
+ export const REGISTRY_AGENT_PROTOCOL_V2 = 'doc-bridge.registry-agent.v2'
49
+
50
+ export type RegistryEnrichmentContext = {
51
+ readonly protocol: typeof REGISTRY_AGENT_PROTOCOL_V2
52
+ readonly task: 'curate' | 'review' | 'adjudicate'
53
+ readonly role: string
54
+ readonly promptVersion: string
55
+ readonly packs: readonly unknown[]
56
+ readonly capabilities: readonly ['pack.read', 'proposal.write']
57
+ readonly network: false
58
+ readonly shell: false
59
+ readonly deterministic: boolean
60
+ }
61
+
35
62
  export type RegistryAgentAdapter = {
36
63
  readonly metadata: RegistryAgentMetadata
37
- readonly run: (snapshot: DiscoverySnapshotV1, report: ReconciliationReportV1, evidence?: readonly RegistryAgentContext['evidence'][number][]) => Promise<AgentProposalV1>
64
+ readonly run: (snapshot: DiscoverySnapshotV1, report: ReconciliationReportV1, evidence?: readonly RegistryAgentContext['evidence'][number][], documentation?: DocumentationAuditReportV1) => Promise<AgentProposalV1>
65
+ /** Send packs for a task and return the raw `proposals` array. Throws on transport, budget or shape failure. */
66
+ readonly enrich: (task: RegistryEnrichmentContext['task'], packs: readonly unknown[], options?: { readonly role?: string; readonly promptVersion?: string }) => Promise<readonly unknown[]>
38
67
  }
39
68
 
40
69
  const deepFreeze = <T>(value: T): T => {
@@ -47,6 +76,91 @@ const deepFreeze = <T>(value: T): T => {
47
76
 
48
77
  const registryConfig = (config: DocBridgeConfigV1) => config.intelligence?.registry
49
78
 
79
+ const evidenceKey = (item: RegistryAgentContext['evidence'][number]): string => `${item.source}:${item.path}:${item.lineStart ?? ''}:${item.lineEnd ?? ''}`
80
+
81
+ const validateGrounding = (proposal: AgentProposalV1, snapshot: DiscoverySnapshotV1, report: ReconciliationReportV1, documentation?: DocumentationAuditReportV1): void => {
82
+ const diagnosticIds = new Set(report.diagnostics.map((diagnostic) => diagnostic.id))
83
+ for (const finding of documentation?.findings ?? []) diagnosticIds.add(finding.id)
84
+ const evidence = [
85
+ ...report.diagnostics.flatMap((diagnostic) => diagnostic.evidence),
86
+ ...snapshot.entities.flatMap((entity) => entity.evidence),
87
+ ...snapshot.relations.flatMap((relation) => relation.evidence),
88
+ ...(documentation?.findings.flatMap((finding) => finding.evidence) ?? []),
89
+ ]
90
+ const evidenceKeys = new Set(evidence.map(evidenceKey))
91
+ if (!proposal.evidence.length) throw new Error('Registry agent proposal must contain at least one evidence reference.')
92
+ if (proposal.relatedDiagnosticIds.some((id) => !diagnosticIds.has(id))) throw new Error('Registry agent proposal references an unknown diagnostic.')
93
+ if (proposal.evidence.some((item) => !evidenceKeys.has(evidenceKey(item)))) throw new Error('Registry agent proposal contains evidence outside the supplied snapshot/report.')
94
+ }
95
+
96
+ const runCli = (root: string, cli: RegistryCliConfig, context: RegistryAgentContext | RegistryEnrichmentContext, timeoutMs: number, maxInputBytes: number, maxResponseBytes: number): Promise<unknown> => new Promise((resolve, reject) => {
97
+ const v2 = 'protocol' in context
98
+ const input = JSON.stringify(
99
+ v2
100
+ ? { protocol: REGISTRY_AGENT_PROTOCOL_V2, response: 'Return exactly one JSON object { "proposals": [...] } on stdout. Do not emit markdown or logs on stdout.', context }
101
+ : { protocol: 'doc-bridge.registry-agent.v1', response: 'Return exactly one AgentProposalV1 JSON object on stdout. Do not emit markdown or logs on stdout.', context },
102
+ )
103
+ if (Buffer.byteLength(input, 'utf8') > maxInputBytes) {
104
+ reject(new Error(`Registry agent CLI input limit ${maxInputBytes} bytes exceeded.`))
105
+ return
106
+ }
107
+ const child = spawn(cli.command, cli.args ?? [], {
108
+ cwd: root,
109
+ shell: false,
110
+ env: process.env,
111
+ stdio: ['pipe', 'pipe', 'pipe'],
112
+ })
113
+ let stdout = ''
114
+ let stderr = ''
115
+ let settled = false
116
+ const finish = (callback: () => void): void => {
117
+ if (settled) return
118
+ settled = true
119
+ callback()
120
+ }
121
+ const timer = setTimeout(() => {
122
+ child.kill('SIGTERM')
123
+ finish(() => reject(new Error(`Registry agent CLI timed out after ${timeoutMs}ms.`)))
124
+ }, timeoutMs)
125
+ child.stdout.on('data', (chunk: Buffer) => {
126
+ stdout += chunk.toString('utf8')
127
+ if (Buffer.byteLength(stdout, 'utf8') > maxResponseBytes) {
128
+ clearTimeout(timer)
129
+ child.kill('SIGTERM')
130
+ finish(() => reject(new Error(`Registry agent CLI response limit ${maxResponseBytes} bytes exceeded.`)))
131
+ }
132
+ })
133
+ child.stderr.on('data', (chunk: Buffer) => {
134
+ stderr += chunk.toString('utf8')
135
+ if (Buffer.byteLength(stderr, 'utf8') > maxResponseBytes) stderr = stderr.slice(-maxResponseBytes)
136
+ })
137
+ child.once('error', (error) => {
138
+ clearTimeout(timer)
139
+ finish(() => reject(new Error(`Registry agent CLI failed to start: ${error.message}`)))
140
+ })
141
+ child.once('close', (code, signal) => {
142
+ clearTimeout(timer)
143
+ finish(() => {
144
+ if (code !== 0) {
145
+ const detail = stderr.trim() ? `: ${stderr.trim()}` : signal ? ` (${signal})` : ''
146
+ reject(new Error(`Registry agent CLI exited with code ${code ?? 'unknown'}${detail}`))
147
+ return
148
+ }
149
+ try {
150
+ resolve(JSON.parse(stdout) as unknown)
151
+ } catch (error) {
152
+ reject(new Error(`Registry agent CLI must return one JSON object on stdout: ${error instanceof Error ? error.message : String(error)}`))
153
+ }
154
+ })
155
+ })
156
+ child.stdin.once('error', (error) => {
157
+ clearTimeout(timer)
158
+ child.kill('SIGTERM')
159
+ finish(() => reject(new Error(`Registry agent CLI stdin failed: ${error.message}`)))
160
+ })
161
+ child.stdin.end(input)
162
+ })
163
+
50
164
  export const loadRegistryAgentRunner = async (root: string, config: DocBridgeConfigV1): Promise<RegistryAgentRunner> => {
51
165
  const metadata = loadRegistryAgentMetadata(root, config)
52
166
  const configured = registryConfig(config)?.runnerModule
@@ -71,41 +185,95 @@ export const loadRegistryAgentMetadata = (root: string, config: DocBridgeConfigV
71
185
  return { ...metadata, root: agentPath }
72
186
  }
73
187
 
74
- export const createRegistryAgentAdapter = (root: string, config: DocBridgeConfigV1, runner: RegistryAgentRunner): RegistryAgentAdapter => {
188
+ export const createRegistryAgentAdapter = (root: string, config: DocBridgeConfigV1, runner?: RegistryAgentRunner): RegistryAgentAdapter => {
75
189
  if (!registryConfig(config)?.enabled) throw new Error('Registry agents are disabled. Set intelligence.registry.enabled: true to run an assisted workflow.')
76
190
  const metadata = loadRegistryAgentMetadata(resolve(root), config)
77
191
  const settings = registryConfig(config) ?? {}
78
192
  const timeoutMs = settings.timeoutMs ?? 120_000
193
+ const maxInputBytes = settings.maxInputBytes ?? 8_000_000
79
194
  const maxResponseBytes = settings.maxResponseBytes ?? 256_000
80
195
  const maxTokens = settings.maxTokens ?? Math.ceil(maxResponseBytes / 4)
81
196
  const maxConcurrency = settings.maxConcurrency ?? 1
82
197
  let active = 0
83
198
  const deterministicCache = new Map<string, AgentProposalV1>()
199
+ /** One transport for both protocols: the configured CLI, or the local runner, under the same limits. */
200
+ const transport = async (context: RegistryAgentContext | RegistryEnrichmentContext): Promise<unknown> => {
201
+ if (active >= maxConcurrency) throw new Error(`Registry agent concurrency limit ${maxConcurrency} exceeded.`)
202
+ if (!settings.cli && !runner) throw new Error('Registry agent requires either intelligence.registry.cli or a local runner module.')
203
+ active += 1
204
+ let timer: ReturnType<typeof setTimeout> | undefined
205
+ try {
206
+ let raw: unknown
207
+ if (settings.cli) {
208
+ raw = await runCli(resolve(root), settings.cli, context, timeoutMs, maxInputBytes, maxResponseBytes)
209
+ } else {
210
+ const localRunner = runner as RegistryAgentRunner
211
+ const timeout = new Promise<never>((_, reject) => { timer = setTimeout(() => reject(new Error(`Registry agent timed out after ${timeoutMs}ms.`)), timeoutMs) })
212
+ raw = await Promise.race([Promise.resolve(localRunner(context as RegistryAgentContext)), timeout])
213
+ }
214
+ const responseBytes = Buffer.byteLength(JSON.stringify(raw) ?? '')
215
+ if (responseBytes > maxResponseBytes) throw new Error(`Registry agent response limit ${maxResponseBytes} bytes exceeded.`)
216
+ if (Math.ceil(responseBytes / 4) > maxTokens) throw new Error(`Registry agent token budget ${maxTokens} exceeded.`)
217
+ return raw
218
+ } finally {
219
+ if (timer) clearTimeout(timer)
220
+ active -= 1
221
+ }
222
+ }
84
223
  return {
85
224
  metadata,
86
- run: async (snapshot, report, evidence = report.diagnostics.flatMap((diagnostic) => diagnostic.evidence).slice(0, 64)) => {
87
- if (active >= maxConcurrency) throw new Error(`Registry agent concurrency limit ${maxConcurrency} exceeded.`)
88
- const cacheKey = sha256NormalizedV1({ snapshotHash: snapshot.contentHash, reportHash: report.contentHash, agentId: metadata.id, agentVersion: metadata.version, evidence })
225
+ enrich: async (task, packs, options = {}) => {
226
+ const input = JSON.stringify(packs)
227
+ if (Buffer.byteLength(input, 'utf8') > maxInputBytes) throw new Error(`Registry agent input limit ${maxInputBytes} bytes exceeded.`)
228
+ const context = deepFreeze({
229
+ protocol: REGISTRY_AGENT_PROTOCOL_V2,
230
+ task,
231
+ role: options.role ?? task,
232
+ promptVersion: options.promptVersion ?? '1',
233
+ packs: redactValue(packs),
234
+ capabilities: ['pack.read', 'proposal.write'] as const,
235
+ network: false as const,
236
+ shell: false as const,
237
+ deterministic: settings.deterministic ?? true,
238
+ }) as RegistryEnrichmentContext
239
+ const raw = await transport(context)
240
+ const proposals = raw && typeof raw === 'object' && 'proposals' in raw ? (raw as { proposals: unknown }).proposals : undefined
241
+ if (!Array.isArray(proposals)) throw new Error('Registry agent must return one JSON object with a "proposals" array.')
242
+ if (proposals.length > 1_024) throw new Error('Registry agent returned more than 1024 proposals.')
243
+ return proposals
244
+ },
245
+ run: async (snapshot, report, evidence = report.diagnostics.flatMap((diagnostic) => diagnostic.evidence).slice(0, 64), documentation) => {
246
+ const cacheKey = sha256NormalizedV1({ snapshotHash: snapshot.contentHash, reportHash: report.contentHash, documentationAuditHash: documentation?.contentHash ?? null, agentId: metadata.id, agentVersion: metadata.version, cli: settings.cli ?? null, maxInputBytes, evidence })
89
247
  if (settings.deterministic && deterministicCache.has(cacheKey)) return deterministicCache.get(cacheKey) as AgentProposalV1
90
- active += 1
91
- let timer: ReturnType<typeof setTimeout> | undefined
92
- try {
93
- const context = deepFreeze({ snapshot: redactValue(snapshot), report: redactValue(report), evidence: redactValue(evidence), capabilities: ['snapshot.read', 'evidence.read', 'proposal.write'] as const, network: false as const, shell: false as const, deterministic: settings.deterministic ?? true }) as RegistryAgentContext
94
- const timeout = new Promise<never>((_, reject) => { timer = setTimeout(() => reject(new Error(`Registry agent timed out after ${timeoutMs}ms.`)), timeoutMs) })
95
- const raw = await Promise.race([Promise.resolve(runner(context)), timeout])
96
- const responseBytes = Buffer.byteLength(JSON.stringify(raw))
97
- if (responseBytes > maxResponseBytes) throw new Error(`Registry agent response limit ${maxResponseBytes} bytes exceeded.`)
98
- if (Math.ceil(responseBytes / 4) > maxTokens) throw new Error(`Registry agent token budget ${maxTokens} exceeded.`)
99
- const proposal = AgentProposalV1Schema.parse(raw)
100
- if (proposal.contentHash !== contentHashForArtifactV1(proposal)) throw new Error('Registry agent proposal contentHash does not match its canonical contents.')
101
- if (proposal.baseSnapshotHash !== snapshot.contentHash || proposal.baseReportHash !== report.contentHash) throw new Error('Registry agent proposal is not based on the supplied snapshot/report hashes.')
102
- if (proposal.origin.kind !== 'registry-agent' || proposal.origin.id !== metadata.id) throw new Error(`Registry agent proposal origin must be ${metadata.id}.`)
103
- if (settings.deterministic) deterministicCache.set(cacheKey, proposal)
104
- return proposal
105
- } finally {
106
- if (timer) clearTimeout(timer)
107
- active -= 1
108
- }
248
+ const context = deepFreeze({
249
+ snapshot: redactValue(snapshot),
250
+ report: redactValue(report),
251
+ evidence: redactValue(evidence),
252
+ ...(documentation ? {
253
+ documentation: redactValue({
254
+ contentHash: documentation.contentHash,
255
+ sourceRevision: documentation.sourceRevision,
256
+ status: documentation.status,
257
+ findings: documentation.findings.slice(0, 64),
258
+ documentAssessments: documentation.documentAssessments.slice(0, 128),
259
+ metrics: documentation.metrics,
260
+ limitations: documentation.limitations,
261
+ }),
262
+ } : {}),
263
+ capabilities: ['snapshot.read', 'evidence.read', 'proposal.write'] as const,
264
+ network: false as const,
265
+ shell: false as const,
266
+ deterministic: settings.deterministic ?? true,
267
+ }) as RegistryAgentContext
268
+ const raw = await transport(context)
269
+ const proposal = AgentProposalV1Schema.parse(raw)
270
+ if (proposal.contentHash !== contentHashForArtifactV1(proposal)) throw new Error('Registry agent proposal contentHash does not match its canonical contents.')
271
+ if (proposal.baseSnapshotHash !== snapshot.contentHash || proposal.baseReportHash !== report.contentHash) throw new Error('Registry agent proposal is not based on the supplied snapshot/report hashes.')
272
+ if (documentation && proposal.baseDocumentationAuditHash !== documentation.contentHash) throw new Error('Registry agent proposal is not based on the supplied documentation audit hash.')
273
+ if (proposal.origin.kind !== 'registry-agent' || proposal.origin.id !== metadata.id || proposal.origin.version !== metadata.version) throw new Error(`Registry agent proposal origin must be ${metadata.id}@${metadata.version}.`)
274
+ validateGrounding(proposal, snapshot, report, documentation)
275
+ if (settings.deterministic) deterministicCache.set(cacheKey, proposal)
276
+ return proposal
109
277
  },
110
278
  }
111
279
  }