@agentskit/doc-bridge 1.4.3 → 1.5.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.
@@ -2,18 +2,21 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
2
2
  import { dirname, resolve } from 'node:path'
3
3
  import { createInterface } from 'node:readline/promises'
4
4
 
5
- import { loadConfig, projectRootFromConfigPath } from '../config/load-config.js'
6
- import type { DocBridgeConfigV1 } from '../config/schema.js'
5
+ import { ConfigNotFoundError, loadConfig, projectRootFromConfigPath } from '../config/load-config.js'
6
+ import type { DocBridgeConfigV1, RuleId, RuleSeverity } from '../config/schema.js'
7
7
  import {
8
8
  DOCUMENTATION_STANDARD_V1_ID,
9
9
  formatDocumentationStandardText,
10
10
  runDocumentationStandardV1,
11
11
  } from '../conformance/documentation-standard-v1.js'
12
12
  import { buildDocBridgeIndex } from '../index-builder/build-index.js'
13
+ import { applyDocumentationDeclarations } from '../discovery/documentation.js'
14
+ import { discoverRepository } from '../discovery/repository.js'
13
15
  import { discoverPnpmPackages } from '../index-builder/plugins/pnpm-monorepo.js'
14
16
  import { scanHumanDocRecords } from '../index-builder/human-adapters/index.js'
15
17
  import { retrieveHybridChunks } from '../federation/llms.js'
16
18
  import { runGates, type GateId } from '../gates/run-gates.js'
19
+ import { evaluateRules, parseRuleId, parseRuleSeverity, type RuleMode } from '../rules/engine.js'
17
20
  import { runChatOnce, startInkChat } from '../intelligence/chat.js'
18
21
  import { PeerMissingError, layer1InstallHint } from '../intelligence/peers.js'
19
22
  import { createDocBridgeRag } from '../intelligence/rag.js'
@@ -22,6 +25,7 @@ import { ingestMemoryCandidates } from '../memory/ingest.js'
22
25
  import { classifyMemoryCandidates, draftMemoryPromotion } from '../memory/pipeline.js'
23
26
  import { promoteMemoryToGithubPr } from '../memory/github-pr.js'
24
27
  import { watchDocBridgeIndex } from '../index-builder/watch-index.js'
28
+ import { loadWorkflowManifest, loadWorkflowStepOutput, runWorkflow, type WorkflowExecutionResult } from '../workflow/engine.js'
25
29
  import {
26
30
  formatDoctorBadgeJson,
27
31
  formatDoctorBadgeMarkdown,
@@ -35,7 +39,14 @@ import { IndexNotFoundError, loadDocBridgeIndex } from '../query/load-index.js'
35
39
  import { runQuery, type QueryKind } from '../query/query.js'
36
40
  import { searchIndex } from '../query/search.js'
37
41
  import type { DocBridgeIndexV1 } from '../schemas/doc-bridge-index.js'
38
- import { parseAgentHandoff, parseDocBridgeConfig } from '../validate.js'
42
+ import { parseAgentHandoff, parseDocBridgeConfig, parseReconciliationReport } from '../validate.js'
43
+ import { parseDiscoverySnapshot } from '../validate.js'
44
+ import { reconcileKnowledge } from '../reconciliation/reconcile.js'
45
+ import type { DiscoverySnapshotV1, ReconciliationReportV1 } from '../schemas/knowledge.js'
46
+ import { sha256NormalizedV1 } from '../index-builder/content-hash.js'
47
+ import { applyFixProposal, approveFixProposal, createArtifactNormalizationProposal, createMarkdownLinkFixProposal } from '../fixes/proposals.js'
48
+ import { createRegistryAgentAdapter, loadRegistryAgentRunner, persistRegistryAgentProposal } from '../agents/registry-adapter.js'
49
+ import { renderOfflineReport } from '../report/html.js'
39
50
  import { PACKAGE_VERSION } from '../version.js'
40
51
 
41
52
  type Command =
@@ -48,8 +59,16 @@ type Command =
48
59
  | 'memory'
49
60
  | 'playbook'
50
61
  | 'registry'
62
+ | 'discover'
63
+ | 'scan'
64
+ | 'reconcile'
65
+ | 'check'
66
+ | 'map'
67
+ | 'fix'
68
+ | 'suggest'
51
69
  | 'index'
52
70
  | 'gate'
71
+ | 'rules'
53
72
  | 'mcp'
54
73
  | 'doctor'
55
74
  | 'demo'
@@ -69,11 +88,17 @@ Core (no API key):
69
88
  ak-docs demo [--fixture example|monorepo] [--text] [--in-project]
70
89
  ak-docs doctor [--text] [--badge] [--write-badge]
71
90
  ak-docs index [--watch]
91
+ ak-docs discover [--text|--json]
92
+ ak-docs scan | reconcile | check | map [--text|--json] [--html]
93
+ ak-docs fix propose links|normalize <artifact> [--output <file>]
94
+ ak-docs fix approve|apply <proposal.json> [--by <name>]
95
+ ak-docs suggest [--json|--text] run the configured local Registry agent
72
96
  ak-docs query [package|ownership|intent|change] <id> [--agent] [--text]
73
97
  ak-docs search <term> [--agent] [--text]
74
98
  ak-docs list <packages|intents|changes|knowledge> [--text]
75
99
  ak-docs ask [question] local consult (no LLM)
76
100
  ak-docs gate run [gate-id]
101
+ ak-docs rules run <report.json> [--preset default|recommended|strict] [--severity rule=level] [--ignore rule]
77
102
  ak-docs conformance run documentation-standard-v1 [--text|--json]
78
103
  ak-docs mcp
79
104
  ak-docs mcp install --cursor | --claude
@@ -137,8 +162,16 @@ const parseArgs = (argv: readonly string[]) => {
137
162
  else if (positional[0] === 'memory') command = 'memory'
138
163
  else if (positional[0] === 'playbook') command = 'playbook'
139
164
  else if (positional[0] === 'registry') command = 'registry'
165
+ else if (positional[0] === 'discover') command = 'discover'
166
+ else if (positional[0] === 'scan') command = 'scan'
167
+ else if (positional[0] === 'reconcile') command = 'reconcile'
168
+ else if (positional[0] === 'check') command = 'check'
169
+ else if (positional[0] === 'map') command = 'map'
170
+ else if (positional[0] === 'fix') command = 'fix'
171
+ else if (positional[0] === 'suggest') command = 'suggest'
140
172
  else if (positional[0] === 'index') command = 'index'
141
173
  else if (positional[0] === 'gate') command = 'gate'
174
+ else if (positional[0] === 'rules') command = 'rules'
142
175
  else if (positional[0] === 'mcp') command = 'mcp'
143
176
  else if (positional[0] === 'doctor') command = 'doctor'
144
177
  else if (positional[0] === 'demo') command = 'demo'
@@ -154,6 +187,30 @@ const parseArgs = (argv: readonly string[]) => {
154
187
  return { command, flags, configPath, positional }
155
188
  }
156
189
 
190
+ const optionValues = (argv: readonly string[], name: string): string[] => {
191
+ const values: string[] = []
192
+ for (let index = 0; index < argv.length; index += 1) {
193
+ const arg = argv[index]
194
+ if (arg?.startsWith(`${name}=`)) values.push(arg.slice(name.length + 1))
195
+ else if (arg === name && argv[index + 1] && !argv[index + 1]?.startsWith('-')) {
196
+ values.push(argv[index + 1] as string)
197
+ index += 1
198
+ }
199
+ }
200
+ return values
201
+ }
202
+
203
+ const parseRuleAssignments = <T>(values: readonly string[], parseValue: (value: string) => T): Record<string, T> => {
204
+ const result: Record<string, T> = {}
205
+ for (const value of values) {
206
+ const separator = value.indexOf('=')
207
+ if (separator <= 0 || separator === value.length - 1) throw new Error(`Invalid rule assignment "${value}". Use rule=value.`)
208
+ const key = value.slice(0, separator)
209
+ result[key] = parseValue(value.slice(separator + 1))
210
+ }
211
+ return result
212
+ }
213
+
157
214
  const writeJson = (payload: unknown): void => {
158
215
  process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`)
159
216
  }
@@ -400,6 +457,198 @@ const diagnosticNextCommands = (
400
457
  ]
401
458
  }
402
459
 
460
+ const workflowOptions = (
461
+ root: string,
462
+ config: DocBridgeConfigV1,
463
+ sourceRevision: string,
464
+ stage: 'collect' | 'normalize' | 'reconcile' | 'evaluate' | 'report',
465
+ handlers: Parameters<typeof runWorkflow>[0]['handlers'],
466
+ ): Parameters<typeof runWorkflow>[0] => ({
467
+ root,
468
+ ...(config.workflow?.stateDir ? { stateDir: config.workflow.stateDir } : {}),
469
+ sourceRevision,
470
+ configurationHash: sha256NormalizedV1(config),
471
+ stage,
472
+ handlers,
473
+ })
474
+
475
+ const scanWorkflow = (root: string, config: DocBridgeConfigV1): WorkflowExecutionResult => {
476
+ const discovered = discoverRepository({ root, config })
477
+ runWorkflow(workflowOptions(root, config, discovered.sourceRevision, 'collect', { collect: () => discovered }))
478
+ return runWorkflow(workflowOptions(root, config, discovered.sourceRevision, 'normalize', { normalize: ({ input }) => input }))
479
+ }
480
+
481
+ const documentationInputs = (root: string, snapshot: DiscoverySnapshotV1) => snapshot.entities
482
+ .filter((entity) => entity.kind === 'document' && entity.path)
483
+ .map((entity) => ({ path: entity.path as string, content: readFileSync(resolve(root, entity.path as string), 'utf8') }))
484
+
485
+ const reconcileWorkflow = (root: string, config: DocBridgeConfigV1): WorkflowExecutionResult => {
486
+ const scanned = scanWorkflow(root, config)
487
+ const snapshot = parseDiscoverySnapshot(loadWorkflowStepOutput(scanned.stateDir, 'normalize'))
488
+ const declared = applyDocumentationDeclarations(snapshot, documentationInputs(root, snapshot)).snapshot
489
+ const report = reconcileKnowledge(snapshot, declared)
490
+ return runWorkflow(workflowOptions(root, config, snapshot.sourceRevision, 'reconcile', { reconcile: () => report }))
491
+ }
492
+
493
+ const checkWorkflow = (root: string, config: DocBridgeConfigV1): WorkflowExecutionResult => {
494
+ const reconciled = reconcileWorkflow(root, config)
495
+ const report = parseReconciliationReport(loadWorkflowStepOutput(reconciled.stateDir, 'reconcile'))
496
+ const evaluated = runWorkflow(workflowOptions(root, config, report.sourceRevision, 'evaluate', { evaluate: () => evaluateRules(report, { ...(config.rules ? { config: config.rules } : {}) }) }))
497
+ return runWorkflow(workflowOptions(root, config, report.sourceRevision, 'report', { report: ({ input }) => input }))
498
+ }
499
+
500
+ const workflowOutput = (result: WorkflowExecutionResult): Record<string, unknown> => {
501
+ const snapshot = (() => { try { return parseDiscoverySnapshot(loadWorkflowStepOutput(result.stateDir, 'normalize')) } catch { return undefined } })()
502
+ const report = (() => { try { return parseReconciliationReport(loadWorkflowStepOutput(result.stateDir, 'reconcile')) } catch { return undefined } })()
503
+ const rules = (() => {
504
+ try {
505
+ const value = loadWorkflowStepOutput(result.stateDir, 'evaluate')
506
+ return value && typeof value === 'object' ? value : undefined
507
+ } catch { return undefined }
508
+ })()
509
+ return {
510
+ ok: result.run.state !== 'failed' && result.run.state !== 'stale',
511
+ runId: result.run.runId,
512
+ state: result.run.state,
513
+ stateDir: result.stateDir,
514
+ artifactRefs: result.run.artifactRefs,
515
+ steps: result.run.steps,
516
+ reusedStages: result.reusedStages,
517
+ ...(snapshot ? { snapshotHash: snapshot.contentHash, sourceRevision: snapshot.sourceRevision, configurationHash: snapshot.configurationHash, coverage: snapshot.coverage } : {}),
518
+ ...(report ? { reportHash: report.contentHash, diagnostics: report.diagnostics } : {}),
519
+ ...(rules ? { rules } : {}),
520
+ }
521
+ }
522
+
523
+ const runWorkflowCommand = (
524
+ command: 'scan' | 'reconcile' | 'check' | 'map',
525
+ flags: ReadonlySet<string>,
526
+ configPath: string | undefined,
527
+ argv: readonly string[],
528
+ ): number => {
529
+ try {
530
+ const { config, root } = loadProject(configPath)
531
+ const result = command === 'scan' ? scanWorkflow(root, config) : command === 'reconcile' ? reconcileWorkflow(root, config) : checkWorkflow(root, config)
532
+ const output = workflowOutput(result)
533
+ if (command === 'map') output.kind = 'architecture-map'
534
+ if (command === 'map' && flags.has('--html')) {
535
+ const snapshot = parseDiscoverySnapshot(loadWorkflowStepOutput(result.stateDir, 'normalize'))
536
+ const report = parseReconciliationReport(loadWorkflowStepOutput(result.stateDir, 'reconcile'))
537
+ const outputPath = optionValues(argv, '--output')[0] ?? '.doc-bridge/report.html'
538
+ const htmlPath = resolve(root, outputPath)
539
+ mkdirSync(dirname(htmlPath), { recursive: true })
540
+ writeFileSync(htmlPath, renderOfflineReport({ snapshot, report }), 'utf8')
541
+ output.htmlPath = htmlPath
542
+ }
543
+ if (wantsTextOutput(flags, config)) {
544
+ writeLines([`Run: ${String(output.runId)}`, `State: ${String(output.state)}`, ...(output.snapshotHash ? [`Snapshot: ${String(output.snapshotHash)}`] : []), ...(output.reportHash ? [`Report: ${String(output.reportHash)}`] : []), `Artifacts: ${String((output.artifactRefs as unknown[]).length)}`])
545
+ } else writeJson(output)
546
+ const ruleExitCode = output.rules && typeof output.rules === 'object' && 'exitCode' in output.rules && (output.rules as { exitCode?: unknown }).exitCode === 1 ? 1 : 0
547
+ return result.run.state === 'failed' ? 1 : ruleExitCode
548
+ } catch (error) {
549
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
550
+ return 2
551
+ }
552
+ }
553
+
554
+ const runRulesCommand = (
555
+ argv: readonly string[],
556
+ flags: ReadonlySet<string>,
557
+ positional: readonly string[],
558
+ configPath: string | undefined,
559
+ ): number => {
560
+ const action = positional[1]
561
+ const reportPath = positional[2]
562
+ if (action !== 'run' || !reportPath) {
563
+ process.stderr.write('Usage: ak-docs rules run <report.json> [--preset default|recommended|strict] [--severity rule=level] [--ignore rule]\n')
564
+ return 2
565
+ }
566
+
567
+ try {
568
+ const { config } = loadProject(configPath)
569
+ const report = parseReconciliationReport(JSON.parse(readFileSync(resolve(reportPath), 'utf8')) as unknown)
570
+ const presetValue = optionValues(argv, '--preset')[0]
571
+ const preset = presetValue === undefined
572
+ ? undefined
573
+ : (['default', 'recommended', 'strict'].includes(presetValue) ? presetValue as RuleMode : (() => { throw new Error(`Invalid rules preset "${presetValue}".`) })())
574
+ const severity: Partial<Record<RuleId, RuleSeverity>> = {}
575
+ for (const [rule, level] of Object.entries(parseRuleAssignments(optionValues(argv, '--severity'), parseRuleSeverity))) {
576
+ severity[parseRuleId(rule)] = level
577
+ }
578
+ const ignore = optionValues(argv, '--ignore').map(parseRuleId)
579
+ const result = evaluateRules(report, {
580
+ ...(config.rules ? { config: config.rules } : {}),
581
+ ...(preset ? { preset } : {}),
582
+ ...(Object.keys(severity).length ? { severity } : {}),
583
+ ...(ignore.length ? { ignore } : {}),
584
+ ...(optionValues(argv, '--critical-entity').length ? { criticalEntities: optionValues(argv, '--critical-entity') } : {}),
585
+ ...(optionValues(argv, '--critical-path').length ? { criticalPaths: optionValues(argv, '--critical-path') } : {}),
586
+ })
587
+ if (wantsTextOutput(flags, config)) {
588
+ writeLines([
589
+ `Rules: ${result.mode}`,
590
+ `Findings: ${result.findings.length}`,
591
+ ...(result.findings.length ? result.findings.map((finding) => ` [${finding.severity}] ${finding.code}: ${finding.message}`) : [' (none)']),
592
+ `Exit code: ${result.exitCode}`,
593
+ ])
594
+ } else {
595
+ writeJson({ ok: result.exitCode === 0, reportHash: report.contentHash, ...result })
596
+ }
597
+ return result.exitCode
598
+ } catch (error) {
599
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
600
+ return 2
601
+ }
602
+ }
603
+
604
+ const runFixCommand = (argv: readonly string[], positional: readonly string[], configPath: string | undefined): number => {
605
+ try {
606
+ const { config, root } = loadProject(configPath)
607
+ const action = positional[1]
608
+ const proposalPath = positional[2]
609
+ const sourceRevision = discoverRepository({ root, config }).sourceRevision
610
+ const fixOptions = { baseRevision: sourceRevision, configurationHash: sha256NormalizedV1(config), ...(config.project?.name ? { projectName: config.project.name } : {}) }
611
+ if (action === 'propose') {
612
+ const proposal = positional[2] === 'links'
613
+ ? createMarkdownLinkFixProposal(root, fixOptions)
614
+ : positional[2] === 'normalize' && positional[3] ? createArtifactNormalizationProposal(root, positional[3], fixOptions) : undefined
615
+ if (!proposal) { writeJson({ ok: true, proposal: null }); return 0 }
616
+ const outputPath = optionValues(argv, '--output')[0]
617
+ if (outputPath) { mkdirSync(dirname(resolve(root, outputPath)), { recursive: true }); writeFileSync(resolve(root, outputPath), `${JSON.stringify(proposal, null, 2)}\n`, 'utf8') }
618
+ writeJson({ ok: true, proposal, ...(outputPath ? { proposalPath: resolve(root, outputPath) } : {}) })
619
+ return 0
620
+ }
621
+ if (!proposalPath || !['approve', 'apply'].includes(action ?? '')) throw new Error('Usage: ak-docs fix propose links|normalize <artifact> [--output <file>] | fix approve|apply <proposal.json> [--by <name>]')
622
+ const file = resolve(root, proposalPath)
623
+ const proposal = JSON.parse(readFileSync(file, 'utf8')) as unknown
624
+ const result = action === 'approve' ? approveFixProposal(proposal, optionValues(argv, '--by')[0] ?? 'human') : applyFixProposal(root, proposal, { currentRevision: sourceRevision })
625
+ writeFileSync(file, `${JSON.stringify(result, null, 2)}\n`, 'utf8')
626
+ writeJson({ ok: true, proposal: result, proposalPath: file })
627
+ return 0
628
+ } catch (error) {
629
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
630
+ return 2
631
+ }
632
+ }
633
+
634
+ const runSuggestCommand = async (flags: ReadonlySet<string>, configPath: string | undefined): Promise<number> => {
635
+ try {
636
+ const { config, root } = loadProject(configPath)
637
+ const stateDir = resolve(root, config.workflow?.stateDir ?? '.doc-bridge/workflow')
638
+ const snapshot = parseDiscoverySnapshot(loadWorkflowStepOutput(stateDir, 'normalize'))
639
+ const report = parseReconciliationReport(loadWorkflowStepOutput(stateDir, 'reconcile'))
640
+ const adapter = createRegistryAgentAdapter(root, config, await loadRegistryAgentRunner(root, config))
641
+ const proposal = await adapter.run(snapshot, report)
642
+ const proposalPath = persistRegistryAgentProposal(stateDir, proposal)
643
+ if (flags.has('--text')) writeLines([`Agent: ${adapter.metadata.id}`, `Proposal: ${proposal.proposalId}`, `Hash: ${proposal.contentHash}`, `Saved: ${proposalPath}`])
644
+ else writeJson({ ok: true, proposal, proposalPath })
645
+ return 0
646
+ } catch (error) {
647
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
648
+ return 2
649
+ }
650
+ }
651
+
403
652
  const writeIfMissing = (path: string, contents: string): boolean => {
404
653
  if (existsSync(path)) return false
405
654
  mkdirSync(dirname(path), { recursive: true })
@@ -617,6 +866,37 @@ export const runCli = (argv: readonly string[]): number | undefined | Promise<nu
617
866
  }
618
867
  }
619
868
 
869
+ if (command === 'discover') {
870
+ try {
871
+ let config: DocBridgeConfigV1 | undefined
872
+ let root = process.cwd()
873
+ try {
874
+ const loaded = loadProject(configPath)
875
+ config = loaded.config
876
+ root = loaded.root
877
+ } catch (error) {
878
+ if (!(error instanceof ConfigNotFoundError) || configPath) throw error
879
+ }
880
+ const snapshot = discoverRepository({ root, ...(config ? { config } : {}) })
881
+ if (wantsTextOutput(flags, config ?? { surfaces: { cli: { defaultFormat: 'json' } } } as DocBridgeConfigV1)) {
882
+ writeLines([`Project: ${snapshot.project.name}`, `Entities: ${snapshot.entities.length}`, `Relations: ${snapshot.relations.length}`, `Source revision: ${snapshot.sourceRevision}`, ...(config ? [] : ['No configuration found; discovery used safe defaults.'])])
883
+ } else {
884
+ writeJson({ ok: true, snapshot, ...(config ? {} : { proposedConfig: { schemaVersion: 1, corpus: { agent: { root: 'docs/for-agents' } } } }) })
885
+ }
886
+ return 0
887
+ } catch (error) {
888
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
889
+ return 2
890
+ }
891
+ }
892
+
893
+ if (command === 'scan' || command === 'reconcile' || command === 'check' || command === 'map') {
894
+ return runWorkflowCommand(command, flags, configPath, argv)
895
+ }
896
+
897
+ if (command === 'fix') return runFixCommand(argv, positional, configPath)
898
+ if (command === 'suggest') return runSuggestCommand(flags, configPath)
899
+
620
900
  if (command === 'init') {
621
901
  const root = process.cwd()
622
902
  const withDemo = flags.has('--demo') || !flags.has('--no-demo')
@@ -816,6 +1096,8 @@ export const runCli = (argv: readonly string[]): number | undefined | Promise<nu
816
1096
  }
817
1097
  }
818
1098
 
1099
+ if (command === 'rules') return runRulesCommand(argv, flags, positional, configPath)
1100
+
819
1101
  if (command === 'conformance') {
820
1102
  const action = positional[1]
821
1103
  const profile = positional[2]
@@ -36,6 +36,14 @@ export const applyConfigDefaults = (config: DocBridgeConfigV1): DocBridgeConfigV
36
36
  preset: 'minimal',
37
37
  ...config.gates,
38
38
  },
39
+ rules: {
40
+ mode: 'default',
41
+ ...config.rules,
42
+ },
43
+ safety: {
44
+ redactSecrets: true,
45
+ ...config.safety,
46
+ },
39
47
  surfaces: {
40
48
  cli: {
41
49
  bin: 'ak-docs',
@@ -44,7 +52,12 @@ export const applyConfigDefaults = (config: DocBridgeConfigV1): DocBridgeConfigV
44
52
  },
45
53
  mcp: {
46
54
  enabled: true,
47
- tools: ['handoff.resolve', 'doc.search', 'doc.get', 'gate.status'],
55
+ tools: [
56
+ 'handoff.resolve', 'doc.search', 'doc.get', 'gate.status', 'retriever.query',
57
+ 'memory.classify', 'memory.promoteDraft', 'registry.topology',
58
+ 'docbridge.snapshot', 'docbridge.report', 'docbridge.diagnostics',
59
+ 'docbridge.relations', 'docbridge.run', 'docbridge.proposals',
60
+ ],
48
61
  transport: 'stdio',
49
62
  ...config.surfaces?.mcp,
50
63
  },
@@ -152,6 +152,50 @@ export const GatesConfigSchema = z
152
152
  })
153
153
  .strict()
154
154
 
155
+ export const RuleIdSchema = z.enum([
156
+ 'documentation-quality',
157
+ 'graph-undocumented-relation',
158
+ 'declared-unobserved-relation',
159
+ 'unresolved-reference',
160
+ 'conflicting-declaration',
161
+ 'not-analyzed-coverage',
162
+ 'stale-documentation',
163
+ 'centrality-risk',
164
+ 'critical-path-risk',
165
+ 'freshness',
166
+ 'ownership',
167
+ ])
168
+
169
+ export const RuleSeveritySchema = z.enum(['off', 'info', 'warn', 'error'])
170
+
171
+ export const RulesConfigSchema = z
172
+ .object({
173
+ mode: z.enum(['default', 'recommended', 'strict']).optional(),
174
+ severity: z.record(RuleIdSchema, RuleSeveritySchema).optional(),
175
+ ignore: z.array(RuleIdSchema).max(128).optional(),
176
+ criticalEntities: z.array(z.string().min(1).max(256)).max(128).optional(),
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(),
179
+ })
180
+ .strict()
181
+
182
+ export const WorkflowConfigSchema = z
183
+ .object({
184
+ stateDir: z.string().min(1).max(512).optional(),
185
+ })
186
+ .strict()
187
+
188
+ export const RepositorySafetyConfigSchema = z
189
+ .object({
190
+ exclude: z.array(z.string().min(1).max(512)).max(128).optional(),
191
+ maxFiles: z.number().int().positive().max(1_000_000).optional(),
192
+ maxBytes: z.number().int().positive().max(10_000_000_000).optional(),
193
+ maxTimeMs: z.number().int().positive().max(86_400_000).optional(),
194
+ maxMemoryMb: z.number().int().positive().max(1_048_576).optional(),
195
+ redactSecrets: z.boolean().optional(),
196
+ })
197
+ .strict()
198
+
155
199
  export const SurfacesConfigSchema = z
156
200
  .object({
157
201
  cli: z
@@ -176,6 +220,12 @@ export const SurfacesConfigSchema = z
176
220
  'memory.classify',
177
221
  'memory.promoteDraft',
178
222
  'registry.topology',
223
+ 'docbridge.snapshot',
224
+ 'docbridge.report',
225
+ 'docbridge.diagnostics',
226
+ 'docbridge.relations',
227
+ 'docbridge.run',
228
+ 'docbridge.proposals',
179
229
  ]),
180
230
  )
181
231
  .max(16)
@@ -247,6 +297,15 @@ export const IntelligenceConfigSchema = z
247
297
  .optional(),
248
298
  runtime: z.enum(['agentskit', 'custom']).optional(),
249
299
  runtimeModule: z.string().min(1).max(512).optional(),
300
+ registry: z
301
+ .object({
302
+ enabled: z.boolean().optional(),
303
+ agentId: z.string().min(1).max(256).optional(),
304
+ agentRoot: z.string().min(1).max(512).optional(),
305
+ runnerModule: z.string().min(1).max(512).optional(),
306
+ })
307
+ .strict()
308
+ .optional(),
250
309
  })
251
310
  .strict()
252
311
 
@@ -375,6 +434,9 @@ export const DocBridgeConfigV1Schema = z
375
434
  index: IndexConfigSchema.optional(),
376
435
  routing: RoutingConfigSchema.optional(),
377
436
  gates: GatesConfigSchema.optional(),
437
+ rules: RulesConfigSchema.optional(),
438
+ workflow: WorkflowConfigSchema.optional(),
439
+ safety: RepositorySafetyConfigSchema.optional(),
378
440
  surfaces: SurfacesConfigSchema.optional(),
379
441
  intelligence: IntelligenceConfigSchema.optional(),
380
442
  federation: FederationConfigSchema.optional(),
@@ -387,3 +449,8 @@ export type AgentCorpusConfig = z.infer<typeof AgentCorpusConfigSchema>
387
449
  export type HumanCorpusConfig = z.infer<typeof HumanCorpusConfigSchema>
388
450
  export type DocumentationStandardV1Config = z.infer<typeof DocumentationStandardV1ConfigSchema>
389
451
  export type DocumentationStandardRuleId = z.infer<typeof DocumentationStandardRuleIdSchema>
452
+ export type RuleId = z.infer<typeof RuleIdSchema>
453
+ export type RuleSeverity = z.infer<typeof RuleSeveritySchema>
454
+ export type RulesConfig = z.infer<typeof RulesConfigSchema>
455
+ export type WorkflowConfig = z.infer<typeof WorkflowConfigSchema>
456
+ export type RepositorySafetyConfig = z.infer<typeof RepositorySafetyConfigSchema>