@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.
- package/CHANGELOG.md +6 -0
- package/action.yml +1 -1
- package/dist/cli/program.js +2412 -268
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +74 -3
- package/dist/config/index.js.map +1 -1
- package/dist/{index-DhoAG9Ar.d.ts → index-Di7PkJuf.d.ts} +195 -8
- package/dist/index.d.ts +1942 -4
- package/dist/index.js +2143 -189
- package/dist/index.js.map +1 -1
- package/docs/PRD-doc-bridge-knowledge-engine.md +338 -0
- package/docs/knowledge-engine-runbook.md +44 -0
- package/ecosystem-claims.json +16 -16
- package/ecosystem-upstream.json +2 -2
- package/ecosystem.json +84 -127
- package/mcpb/manifest.json +25 -1
- package/package.json +2 -2
- package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
- package/src/agents/registry-adapter.ts +97 -0
- package/src/cli/program.ts +285 -3
- package/src/config/defaults.ts +14 -1
- package/src/config/schema.ts +67 -0
- package/src/discovery/documentation.ts +320 -0
- package/src/discovery/repository.ts +514 -0
- package/src/fixes/proposals.ts +165 -0
- package/src/index-builder/content-hash.ts +9 -2
- package/src/index.ts +99 -2
- package/src/mcp/server.ts +178 -8
- package/src/reconciliation/reconcile.ts +227 -0
- package/src/report/html.ts +74 -0
- package/src/rules/engine.ts +180 -0
- package/src/safety/repository.ts +84 -0
- package/src/schemas/knowledge.ts +315 -0
- package/src/validate.ts +24 -0
- package/src/version.ts +1 -1
- package/src/workflow/engine.ts +238 -0
- package/tsup.config.ts +2 -1
package/src/cli/program.ts
CHANGED
|
@@ -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]
|
package/src/config/defaults.ts
CHANGED
|
@@ -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: [
|
|
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
|
},
|
package/src/config/schema.ts
CHANGED
|
@@ -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>
|