@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.
- package/CHANGELOG.md +471 -0
- package/CONTRIBUTING.md +29 -4
- package/README.md +87 -40
- package/SECURITY.md +7 -0
- package/action.yml +1 -1
- package/bin/ak-docs.js +2 -2
- package/bin/ak-verify.js +13 -7
- package/dist/cli/program.d.ts +3 -1
- package/dist/cli/program.js +15888 -6061
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +91 -9
- package/dist/config/index.js.map +1 -1
- package/dist/index-Beor6Yhi.d.ts +792 -0
- package/dist/index.d.ts +9979 -3257
- package/dist/index.js +15954 -5774
- package/dist/index.js.map +1 -1
- package/docs/MARKETPLACE.md +1 -1
- package/docs/PRD-documentation-efficiency-study.md +406 -0
- package/docs/PRD-knowledge-retrieval-and-enrichment.md +466 -0
- package/docs/RELEASE.md +22 -8
- package/docs/adr/0002-documentation-audit-boundary.md +22 -0
- package/docs/adr/0003-study-protocol-and-historical-evidence.md +40 -0
- package/docs/adr/0004-controlled-study-runner.md +25 -0
- package/docs/adr/0005-documentation-quality-and-criticality.md +20 -0
- package/docs/adr/0006-registry-semantic-grounding.md +20 -0
- package/docs/adr/0007-longitudinal-study-metrics.md +21 -0
- package/docs/adr/0008-study-verification-boundary.md +21 -0
- package/docs/adr/0009-study-provider-cli-adapter.md +25 -0
- package/docs/agent-corpus/INDEX.md +14 -3
- package/docs/agent-corpus/OVERVIEW.md +25 -0
- package/docs/agent-corpus/chat.md +7 -3
- package/docs/agent-corpus/cli.md +18 -2
- package/docs/agent-corpus/conformance.md +14 -2
- package/docs/agent-corpus/doc-bridge.md +48 -1
- package/docs/agent-corpus/doctor.md +10 -2
- package/docs/agent-corpus/gates.md +6 -2
- package/docs/agent-corpus/mcp.md +15 -2
- package/docs/agent-corpus/memory.md +6 -2
- package/docs/agent-corpus/query.md +35 -2
- package/docs/bench/README.md +122 -0
- package/docs/bench/retrieval-baseline-v1.json +28 -0
- package/docs/bench/retrieval-suite-v1.json +1033 -0
- package/docs/chat-and-rag.md +3 -2
- package/docs/for-agents.md +9 -1
- package/docs/getting-started.md +4 -11
- package/docs/guides/gate-ci.md +11 -1
- package/docs/guides/install-and-run.md +9 -65
- package/docs/index.md +22 -1
- package/docs/knowledge-engine-runbook.md +51 -4
- package/docs/landing/assets/context-payload-reduction.svg +21 -0
- package/docs/landing/assets/controlled-ab-comparison.svg +30 -0
- package/docs/landing/index.html +119 -5
- package/docs/loop-workflow.md +117 -0
- package/docs/mcp.md +6 -1
- package/docs/parity/public-claims-v1.json +145 -0
- package/docs/playbook/doc-bridge-pattern.md +1 -1
- package/docs/query.md +90 -2
- package/docs/recipes/index-pipeline.md +1 -1
- package/docs/schemas/agent-handoff-v1.md +15 -0
- package/docs/schemas/doc-bridge-index-v1.md +65 -0
- package/docs/spec/benchmark-v1.md +39 -1
- package/docs/spec/cli.md +30 -10
- package/docs/spec/config-v1.md +192 -8
- package/docs/spec/documentation-audit-v1.md +61 -0
- package/docs/spec/enrichment-overlay-v1.md +241 -0
- package/docs/spec/graph-signals-v1.md +92 -0
- package/docs/spec/incremental-scan-v1.md +102 -0
- package/docs/spec/markdown-analyzer-v1.md +73 -0
- package/docs/spec/mcp-knowledge-tools-v1.md +147 -0
- package/docs/spec/measured-enrichment-v1.md +229 -0
- package/docs/spec/public-parity-v1.md +119 -0
- package/docs/spec/registry-agents.md +6 -0
- package/docs/spec/render-v1.md +122 -0
- package/docs/spec/retrieval-index-v1.md +164 -0
- package/docs/spec/study-metrics-v1.md +58 -0
- package/docs/spec/study-protocol-v1.md +46 -0
- package/docs/spec/study-provider-cli-v1.md +116 -0
- package/docs/spec/study-runner-v1.md +35 -0
- package/docs/spec/study-task-suite-v1.md +41 -0
- package/docs/spec/study-verification-v1.md +40 -0
- package/docs/study/README.md +84 -0
- package/docs/study/ab-adjudicated-cost-analysis-v1.md +29 -0
- package/docs/study/ab-adjudicated-cost-plan-v1.json +33 -0
- package/docs/study/ab-adjudicated-cost-plan-v2-v1.json +33 -0
- package/docs/study/ab-adjudicated-cost-result-v1.json +80 -0
- package/docs/study/ab-baseline-analysis-v1.md +21 -0
- package/docs/study/ab-baseline-plan-v1.json +33 -0
- package/docs/study/ab-baseline-recovery-plan-v1.json +33 -0
- package/docs/study/ab-baseline-result-v1.json +79 -0
- package/docs/study/documentation-audit-round-2026-08-31.json +183 -0
- package/docs/study/historical-evidence-v1.json +252 -0
- package/docs/study/observation-ledger-v1.json +30632 -0
- package/docs/study/phase3-task-coverage-v1.json +34 -0
- package/docs/study/phase4-public-pilot-ledger-v1.json +1344 -0
- package/docs/study/phase4-public-pilot-result-v1.json +52 -0
- package/docs/study/phase4-public-pilot-run-plan-v1.json +26 -0
- package/docs/study/phase4-public-pilot-task-suite-v1.json +71 -0
- package/docs/study/pilot-round-2026-08-31.json +46 -0
- package/docs/study/protocol-v1.json +90 -0
- package/docs/study/publication-gate-v1.md +45 -0
- package/docs/study/quality-scorecard-cycle-plan.md +545 -0
- package/docs/study/quality-scorecard-v1.json +38 -0
- package/docs/study/round-1-adjudicated-smoke-v1.json +30642 -0
- package/docs/study/round-1-instrumentation-plan-v1.md +39 -0
- package/docs/study/round-2-expanded-adjudication-v1.json +91 -0
- package/docs/study/round-2-expanded-validation-v1.md +58 -0
- package/docs/study/round-3-evidence-contract-v1.json +75 -0
- package/docs/study/round-3-evidence-contract-v1.md +57 -0
- package/docs/study/round-4-confirmation-v1.json +75 -0
- package/docs/study/round-4-confirmation-v1.md +55 -0
- package/docs/study/run-plan-v1.json +33 -0
- package/docs/study/semantic-adjudication-cycle-8.md +20 -0
- package/docs/study/task-suite-v1.json +96 -0
- package/docs/study/token-efficiency-plan-v1.md +337 -0
- package/docs/study/token-efficiency-protocol-v2.json +62 -0
- package/docs/study/verification-binding-v1.json +27 -0
- package/docs/validation-cycle-plan.md +33 -0
- package/docs/verification-harness.md +15 -6
- package/ecosystem-claims.json +2 -2
- package/ecosystem-upstream.json +2 -2
- package/ecosystem.json +4 -4
- package/mcpb/manifest.json +9 -1
- package/package.json +89 -72
- package/scripts/check-ecosystem-upstream.mjs +36 -7
- package/scripts/report-visual-check.mjs +20 -3
- package/skills/doc-bridge-handoff/fixtures/synthetic-repo/docs/for-agents/packages/payments.md +7 -0
- package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
- package/src/agents/registry-adapter.ts +192 -24
- package/src/audit/documentation.ts +513 -0
- package/src/bench/baseline.ts +198 -0
- package/src/bench/overlay-delta.ts +139 -0
- package/src/bench/retrieval.ts +319 -0
- package/src/budget/compile.ts +91 -0
- package/src/budget/sections.ts +70 -0
- package/src/cli/demo.ts +2 -2
- package/src/cli/program.ts +699 -79
- package/src/cli/usage.ts +71 -0
- package/src/config/defaults.ts +1 -0
- package/src/config/index.ts +4 -0
- package/src/config/load-config.ts +7 -1
- package/src/config/schema.ts +121 -4
- package/src/conformance/documentation-standard-v1.ts +22 -14
- package/src/discovery/areas.ts +182 -0
- package/src/discovery/documentation.ts +255 -23
- package/src/discovery/identity.ts +24 -0
- package/src/discovery/incremental.ts +314 -0
- package/src/discovery/inputs.ts +110 -0
- package/src/discovery/markdown.ts +481 -0
- package/src/discovery/repository.ts +557 -125
- package/src/doctor/run-doctor.ts +246 -27
- package/src/enrich/approvals.ts +190 -0
- package/src/enrich/cache.ts +93 -0
- package/src/enrich/context-pack.ts +272 -0
- package/src/enrich/overlay.ts +255 -0
- package/src/enrich/review.ts +106 -0
- package/src/enrich/stage.ts +374 -0
- package/src/enrich/stats.ts +100 -0
- package/src/enrich/validate.ts +410 -0
- package/src/federation/llms.ts +74 -24
- package/src/findings/report.ts +103 -0
- package/src/fixes/proposals.ts +4 -3
- package/src/graph/build.ts +356 -0
- package/src/graph/memory.ts +208 -0
- package/src/index-builder/build-handoffs.ts +22 -11
- package/src/index-builder/build-index.ts +132 -3
- package/src/index-builder/human-adapters/fumadocs.ts +1 -1
- package/src/index-builder/llms-txt.ts +48 -8
- package/src/index-builder/project-corpus.ts +111 -0
- package/src/index-builder/watch-index.ts +1 -1
- package/src/index.ts +630 -2
- package/src/lib/bounded-text.ts +15 -10
- package/src/lib/fuzzy-match.ts +235 -0
- package/src/mcp/knowledge.ts +554 -0
- package/src/mcp/server.ts +113 -18
- package/src/metrics/benchmark.ts +21 -0
- package/src/parity/check.ts +309 -0
- package/src/parity/claims.ts +259 -0
- package/src/parity/resolve.ts +160 -0
- package/src/query/handoff.ts +326 -0
- package/src/query/load-index.ts +53 -1
- package/src/query/query.ts +92 -59
- package/src/query/search.ts +289 -92
- package/src/query/text.ts +155 -0
- package/src/reconciliation/reconcile.ts +148 -15
- package/src/render/data.ts +356 -0
- package/src/render/engine.ts +398 -0
- package/src/render/generated.ts +77 -0
- package/src/render/render.ts +209 -0
- package/src/render/template-source.ts +52 -0
- package/src/render/templates.ts +289 -0
- package/src/report/html.ts +23 -17
- package/src/retrieval/bm25.ts +161 -0
- package/src/retrieval/project.ts +495 -0
- package/src/retrieval/rank.ts +383 -0
- package/src/retrieval/weights.ts +39 -0
- package/src/retriever/doc-bridge-retriever.ts +100 -15
- package/src/rules/engine.ts +45 -12
- package/src/safety/repository.ts +1 -1
- package/src/schemas/agent-handoff.ts +56 -0
- package/src/schemas/budget.ts +37 -0
- package/src/schemas/doc-bridge-index.ts +53 -2
- package/src/schemas/enrichment.ts +369 -0
- package/src/schemas/json-schemas.ts +39 -2
- package/src/schemas/knowledge.ts +19 -3
- package/src/schemas/retrieval-index.ts +152 -0
- package/src/shims/graphology.d.ts +91 -0
- package/src/study/adjudication.ts +196 -0
- package/src/study/execution.ts +350 -0
- package/src/study/expectations.ts +219 -0
- package/src/study/metrics.ts +467 -0
- package/src/study/protocol.ts +271 -0
- package/src/study/provider-cli.ts +115 -0
- package/src/study/provider-telemetry.ts +47 -0
- package/src/study/quality-scorecard.ts +164 -0
- package/src/study/runner.ts +461 -0
- package/src/study/task-suite.ts +321 -0
- package/src/study/verification.ts +134 -0
- package/src/validate.ts +8 -5
- package/src/version.ts +1 -1
- package/src/workflow/engine.ts +36 -11
- package/dist/index-C2PCQSrB.d.ts +0 -2251
- package/scripts/verification-harness.mjs +0 -483
package/src/cli/program.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
|
|
1
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { dirname, relative, resolve } from 'node:path'
|
|
3
3
|
import { createInterface } from 'node:readline/promises'
|
|
4
4
|
|
|
@@ -17,6 +17,7 @@ import { scanHumanDocRecords } from '../index-builder/human-adapters/index.js'
|
|
|
17
17
|
import { retrieveHybridChunks } from '../federation/llms.js'
|
|
18
18
|
import { runGates, type GateId } from '../gates/run-gates.js'
|
|
19
19
|
import { evaluateRules, parseRuleId, parseRuleSeverity, type RuleMode } from '../rules/engine.js'
|
|
20
|
+
import { centrality } from '../graph/build.js'
|
|
20
21
|
import { runChatOnce, startInkChat } from '../intelligence/chat.js'
|
|
21
22
|
import { PeerMissingError, layer1InstallHint } from '../intelligence/peers.js'
|
|
22
23
|
import { createDocBridgeRag } from '../intelligence/rag.js'
|
|
@@ -25,30 +26,79 @@ import { ingestMemoryCandidates } from '../memory/ingest.js'
|
|
|
25
26
|
import { classifyMemoryCandidates, draftMemoryPromotion } from '../memory/pipeline.js'
|
|
26
27
|
import { promoteMemoryToGithubPr } from '../memory/github-pr.js'
|
|
27
28
|
import { watchDocBridgeIndex } from '../index-builder/watch-index.js'
|
|
28
|
-
import {
|
|
29
|
+
import { loadWorkflowStepOutput, runWorkflow, type WorkflowExecutionResult } from '../workflow/engine.js'
|
|
29
30
|
import {
|
|
30
31
|
formatDoctorBadgeJson,
|
|
31
32
|
formatDoctorBadgeMarkdown,
|
|
32
33
|
} from '../doctor/badge.js'
|
|
33
34
|
import { docBridgePatternMarkdown, docBridgePatternPayload } from '../playbook/doc-bridge-pattern.js'
|
|
34
35
|
import { formatDemoText, runDemo, withDemoWorkspace, type DemoFixture } from './demo.js'
|
|
35
|
-
import { formatDoctorText, runDoctor } from '../doctor/run-doctor.js'
|
|
36
|
+
import { DEFAULT_RETRIEVAL_SUITE, formatDoctorText, runDoctor } from '../doctor/run-doctor.js'
|
|
36
37
|
import { installMcpConfig, mcpSnippet } from '../mcp/install.js'
|
|
37
38
|
import { startMcpStdioServer } from '../mcp/server.js'
|
|
38
|
-
import {
|
|
39
|
+
import { measureOverlayRetrievalDelta, formatOverlayRetrievalDeltaText } from '../bench/overlay-delta.js'
|
|
40
|
+
import { enrichmentCost } from '../enrich/stats.js'
|
|
41
|
+
import { checkStudyExpectations, formatStudyExpectationsText, parseStudyExpectations } from '../study/expectations.js'
|
|
42
|
+
import { IndexNotFoundError, loadFreshDocBridgeIndex } from '../query/load-index.js'
|
|
39
43
|
import { runQuery, type QueryKind } from '../query/query.js'
|
|
40
44
|
import { searchIndex } from '../query/search.js'
|
|
41
45
|
import type { DocBridgeIndexV1 } from '../schemas/doc-bridge-index.js'
|
|
42
|
-
import { parseAgentHandoff, parseDocBridgeConfig, parseReconciliationReport } from '../validate.js'
|
|
46
|
+
import { parseAgentHandoff, parseDocBridgeConfig, parseDocBridgeIndex, parseReconciliationReport } from '../validate.js'
|
|
43
47
|
import { parseDiscoverySnapshot } from '../validate.js'
|
|
48
|
+
import { findingsFromDiagnostics } from '../findings/report.js'
|
|
49
|
+
import {
|
|
50
|
+
formatRetrievalBenchText,
|
|
51
|
+
parseRetrievalSuite,
|
|
52
|
+
runRetrievalBench,
|
|
53
|
+
type RetrievalBenchResultV1,
|
|
54
|
+
} from '../bench/retrieval.js'
|
|
55
|
+
import {
|
|
56
|
+
compareRetrievalBaseline,
|
|
57
|
+
createRetrievalBaseline,
|
|
58
|
+
formatRetrievalComparisonText,
|
|
59
|
+
parseRetrievalBaseline,
|
|
60
|
+
} from '../bench/baseline.js'
|
|
44
61
|
import { reconcileKnowledge } from '../reconciliation/reconcile.js'
|
|
45
62
|
import type { DiscoverySnapshotV1, ReconciliationReportV1 } from '../schemas/knowledge.js'
|
|
46
63
|
import { sha256NormalizedV1 } from '../index-builder/content-hash.js'
|
|
47
64
|
import { applyFixProposal, approveFixProposal, createArtifactNormalizationProposal, createMarkdownLinkFixProposal } from '../fixes/proposals.js'
|
|
48
65
|
import { createRegistryAgentAdapter, loadRegistryAgentRunner, persistRegistryAgentProposal } from '../agents/registry-adapter.js'
|
|
66
|
+
import { fixApprovalId, recordApproval, FIX_APPROVAL_GATE } from '../enrich/approvals.js'
|
|
67
|
+
import { readEnrichmentOverlay, withAcceptedRelations } from '../enrich/overlay.js'
|
|
68
|
+
import { decideEnrichment, listEnrichment } from '../enrich/review.js'
|
|
69
|
+
import { formatEnrichmentText, runEnrichment, type EnrichmentRunResult } from '../enrich/stage.js'
|
|
49
70
|
import { renderOfflineReportArtifact } from '../report/html.js'
|
|
50
71
|
import { benchmarkFixture, formatBenchmarkText, measureBenchmark } from '../metrics/benchmark.js'
|
|
51
72
|
import { PACKAGE_VERSION } from '../version.js'
|
|
73
|
+
import { auditDocumentation, formatDocumentationAuditText } from '../audit/documentation.js'
|
|
74
|
+
import { renderArtifact, writeRenderedPages } from '../render/render.js'
|
|
75
|
+
import { CLI_COMMAND_USAGE } from './usage.js'
|
|
76
|
+
import { checkPublicParity, formatPublicParityText } from '../parity/check.js'
|
|
77
|
+
import { parsePublicClaims } from '../parity/claims.js'
|
|
78
|
+
import { resolveTemplateSource } from '../render/template-source.js'
|
|
79
|
+
import { RENDER_TEMPLATE_NAMES, RENDER_TEMPLATES, isRenderTemplateName } from '../render/templates.js'
|
|
80
|
+
import {
|
|
81
|
+
formatHistoricalEvidenceText,
|
|
82
|
+
formatStudyProtocolText,
|
|
83
|
+
parseHistoricalEvidenceRegistry,
|
|
84
|
+
parseStudyProtocol,
|
|
85
|
+
validateHistoricalEvidenceRegistry,
|
|
86
|
+
} from '../study/protocol.js'
|
|
87
|
+
import {
|
|
88
|
+
formatStudyTaskSuiteText,
|
|
89
|
+
parseStudyTaskSuite,
|
|
90
|
+
selectTaskExecutions,
|
|
91
|
+
} from '../study/task-suite.js'
|
|
92
|
+
import {
|
|
93
|
+
formatControlledStudyRunPlanText,
|
|
94
|
+
parseControlledStudyLedger,
|
|
95
|
+
parseControlledStudyRunPlan,
|
|
96
|
+
} from '../study/runner.js'
|
|
97
|
+
import { formatControlledStudyRunText, parseStudyRepositoryConfig, runControlledStudy } from '../study/execution.js'
|
|
98
|
+
import { independentlyAdjudicateStudyLedger, persistIndependentlyAdjudicatedLedger } from '../study/adjudication.js'
|
|
99
|
+
import { formatStudyProviderCliText, parseStudyProviderCliConfig } from '../study/provider-cli.js'
|
|
100
|
+
import { calculateStudyMetrics, formatStudyMetricsText } from '../study/metrics.js'
|
|
101
|
+
import { formatStudyVerificationText, parseStudyVerificationBinding } from '../study/verification.js'
|
|
52
102
|
|
|
53
103
|
type Command =
|
|
54
104
|
| 'help'
|
|
@@ -62,12 +112,15 @@ type Command =
|
|
|
62
112
|
| 'registry'
|
|
63
113
|
| 'discover'
|
|
64
114
|
| 'benchmark'
|
|
115
|
+
| 'bench'
|
|
116
|
+
| 'study'
|
|
65
117
|
| 'scan'
|
|
66
118
|
| 'reconcile'
|
|
67
119
|
| 'check'
|
|
68
120
|
| 'map'
|
|
69
121
|
| 'fix'
|
|
70
122
|
| 'suggest'
|
|
123
|
+
| 'enrich'
|
|
71
124
|
| 'index'
|
|
72
125
|
| 'gate'
|
|
73
126
|
| 'rules'
|
|
@@ -82,48 +135,11 @@ type Command =
|
|
|
82
135
|
| 'rag'
|
|
83
136
|
| 'list'
|
|
84
137
|
| 'conformance'
|
|
138
|
+
| 'audit'
|
|
139
|
+
| 'parity'
|
|
140
|
+
| 'render'
|
|
85
141
|
|
|
86
|
-
const usage =
|
|
87
|
-
|
|
88
|
-
Core (no API key):
|
|
89
|
-
ak-docs init [--demo] [--scaffold-workspaces]
|
|
90
|
-
ak-docs demo [--fixture example|monorepo] [--text] [--in-project]
|
|
91
|
-
ak-docs doctor [--text] [--badge] [--write-badge]
|
|
92
|
-
ak-docs index [--watch]
|
|
93
|
-
ak-docs discover [--text|--json]
|
|
94
|
-
ak-docs benchmark <fixture.json> <observation.json> [--text|--json]
|
|
95
|
-
ak-docs scan | reconcile | check | map [--text|--json] [--html] [--report-threshold <bytes>]
|
|
96
|
-
ak-docs fix propose links|normalize <artifact> [--output <file>]
|
|
97
|
-
ak-docs fix approve|apply <proposal.json> [--by <name>]
|
|
98
|
-
ak-docs suggest [--json|--text] run the configured local Registry agent
|
|
99
|
-
ak-docs query [package|ownership|intent|change] <id> [--agent] [--text]
|
|
100
|
-
ak-docs search <term> [--agent] [--text]
|
|
101
|
-
ak-docs list <packages|intents|changes|knowledge> [--text]
|
|
102
|
-
ak-docs ask [question] local consult (no LLM)
|
|
103
|
-
ak-docs gate run [gate-id]
|
|
104
|
-
ak-docs rules run <report.json> [--preset default|recommended|strict] [--severity rule=level] [--ignore rule]
|
|
105
|
-
ak-docs conformance run documentation-standard-v1 [--text|--json]
|
|
106
|
-
ak-docs mcp
|
|
107
|
-
ak-docs mcp install --cursor | --claude
|
|
108
|
-
ak-docs memory ingest|classify|promote [--pr] [--dry-run]
|
|
109
|
-
ak-docs bootstrap agent-docs
|
|
110
|
-
ak-docs validate-config | validate-handoff <file>
|
|
111
|
-
|
|
112
|
-
Intelligence (optional AgentsKit peers):
|
|
113
|
-
ak-docs rag ingest|search <query>
|
|
114
|
-
ak-docs chat terminal chat (Ink + RAG)
|
|
115
|
-
ak-docs ask <question> --chat one-shot grounded answer
|
|
116
|
-
|
|
117
|
-
Advanced / ecosystem:
|
|
118
|
-
ak-docs retrieve <query>
|
|
119
|
-
ak-docs registry topology
|
|
120
|
-
ak-docs playbook draft | pattern [--text]
|
|
121
|
-
|
|
122
|
-
Global flags:
|
|
123
|
-
-h, --help --version
|
|
124
|
-
--config <path> (project root = config file directory)
|
|
125
|
-
--agent --json --text --chat --demo
|
|
126
|
-
`
|
|
142
|
+
const usage = CLI_COMMAND_USAGE
|
|
127
143
|
|
|
128
144
|
const QUERY_KINDS = new Set<QueryKind>(['package', 'ownership', 'intent', 'change', 'search'])
|
|
129
145
|
const LIST_KINDS = new Set(['packages', 'intents', 'changes', 'knowledge'])
|
|
@@ -148,6 +164,12 @@ const parseArgs = (argv: readonly string[]) => {
|
|
|
148
164
|
i += 1
|
|
149
165
|
continue
|
|
150
166
|
}
|
|
167
|
+
if (arg === '--mode' || arg === '--context-budget') {
|
|
168
|
+
const value = argv[i + 1]
|
|
169
|
+
if (value !== undefined && !value.startsWith('-')) flags.add(`${arg}=${value}`)
|
|
170
|
+
i += 1
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
151
173
|
if (arg.startsWith('-')) {
|
|
152
174
|
flags.add(arg)
|
|
153
175
|
continue
|
|
@@ -167,12 +189,15 @@ const parseArgs = (argv: readonly string[]) => {
|
|
|
167
189
|
else if (positional[0] === 'registry') command = 'registry'
|
|
168
190
|
else if (positional[0] === 'discover') command = 'discover'
|
|
169
191
|
else if (positional[0] === 'benchmark') command = 'benchmark'
|
|
192
|
+
else if (positional[0] === 'bench') command = 'bench'
|
|
193
|
+
else if (positional[0] === 'study') command = 'study'
|
|
170
194
|
else if (positional[0] === 'scan') command = 'scan'
|
|
171
195
|
else if (positional[0] === 'reconcile') command = 'reconcile'
|
|
172
196
|
else if (positional[0] === 'check') command = 'check'
|
|
173
197
|
else if (positional[0] === 'map') command = 'map'
|
|
174
198
|
else if (positional[0] === 'fix') command = 'fix'
|
|
175
199
|
else if (positional[0] === 'suggest') command = 'suggest'
|
|
200
|
+
else if (positional[0] === 'enrich') command = 'enrich'
|
|
176
201
|
else if (positional[0] === 'index') command = 'index'
|
|
177
202
|
else if (positional[0] === 'gate') command = 'gate'
|
|
178
203
|
else if (positional[0] === 'rules') command = 'rules'
|
|
@@ -187,6 +212,9 @@ const parseArgs = (argv: readonly string[]) => {
|
|
|
187
212
|
else if (positional[0] === 'rag') command = 'rag'
|
|
188
213
|
else if (positional[0] === 'list') command = 'list'
|
|
189
214
|
else if (positional[0] === 'conformance') command = 'conformance'
|
|
215
|
+
else if (positional[0] === 'audit') command = 'audit'
|
|
216
|
+
else if (positional[0] === 'parity') command = 'parity'
|
|
217
|
+
else if (positional[0] === 'render') command = 'render'
|
|
190
218
|
|
|
191
219
|
return { command, flags, configPath, positional }
|
|
192
220
|
}
|
|
@@ -219,6 +247,10 @@ const writeJson = (payload: unknown): void => {
|
|
|
219
247
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`)
|
|
220
248
|
}
|
|
221
249
|
|
|
250
|
+
const writeAgentJson = (payload: unknown): void => {
|
|
251
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`)
|
|
252
|
+
}
|
|
253
|
+
|
|
222
254
|
const writeLines = (lines: readonly string[]): void => {
|
|
223
255
|
process.stdout.write(lines.length ? `${lines.join('\n')}\n` : '')
|
|
224
256
|
}
|
|
@@ -270,10 +302,23 @@ const formatSearchMatch = (match: {
|
|
|
270
302
|
readonly path: string
|
|
271
303
|
readonly summary?: string
|
|
272
304
|
readonly score?: number
|
|
305
|
+
readonly confidence?: string
|
|
306
|
+
readonly explain?: { readonly matched: Readonly<Record<string, readonly string[]>>; readonly components: Readonly<Record<string, number>>; readonly surfacedBy?: { readonly kind: string; readonly id: string } }
|
|
273
307
|
}): string => {
|
|
274
308
|
const summary = match.summary ? match.summary.replace(/\s+/g, ' ').slice(0, 100) : ''
|
|
275
309
|
const score = typeof match.score === 'number' ? ` score=${match.score}` : ''
|
|
276
|
-
|
|
310
|
+
const confidence = match.confidence ? ` confidence=${match.confidence}` : ''
|
|
311
|
+
const lines = [` [${match.type}] ${match.id}${score}${confidence}`, ` ${match.path}`, ...(summary ? [` ${summary}`] : [])]
|
|
312
|
+
if (match.explain) {
|
|
313
|
+
// Every component with a contribution, so a wrong ranking is reportable with numbers.
|
|
314
|
+
const parts = Object.entries(match.explain.components).filter(([name, value]) => value !== 0 && name !== 'prior').map(([name, value]) => `${name}=${value}`)
|
|
315
|
+
if (match.explain.components.prior !== undefined && match.explain.components.prior !== 1) parts.push(`prior=×${match.explain.components.prior}`)
|
|
316
|
+
const matched = Object.entries(match.explain.matched).map(([field, terms]) => `${field}: ${terms.join(', ')}`)
|
|
317
|
+
lines.push(` why: ${parts.join(' ') || 'no scoring component'}`)
|
|
318
|
+
if (matched.length) lines.push(` matched: ${matched.join(' | ')}`)
|
|
319
|
+
if (match.explain.surfacedBy) lines.push(` via: ${match.explain.surfacedBy.kind} from ${match.explain.surfacedBy.id}`)
|
|
320
|
+
}
|
|
321
|
+
return lines.join('\n')
|
|
277
322
|
}
|
|
278
323
|
|
|
279
324
|
const writeTextSearch = (
|
|
@@ -367,7 +412,7 @@ const writeAsk = (
|
|
|
367
412
|
}
|
|
368
413
|
|
|
369
414
|
const readIndexedDoc = (root: string, config: DocBridgeConfigV1, idOrPath: string): string => {
|
|
370
|
-
const index =
|
|
415
|
+
const index = loadFreshDocBridgeIndex(root, config)
|
|
371
416
|
const entry = index.knowledge.find((doc) => doc.id === idOrPath || doc.path === idOrPath)
|
|
372
417
|
if (!entry) throw new Error(`Unknown indexed doc "${idOrPath}". Try: search ${idOrPath}`)
|
|
373
418
|
|
|
@@ -380,7 +425,7 @@ const readIndexedDoc = (root: string, config: DocBridgeConfigV1, idOrPath: strin
|
|
|
380
425
|
}
|
|
381
426
|
|
|
382
427
|
const runAskRepl = async (root: string, config: DocBridgeConfigV1): Promise<number> => {
|
|
383
|
-
const index =
|
|
428
|
+
const index = loadFreshDocBridgeIndex(root, config)
|
|
384
429
|
const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true })
|
|
385
430
|
try {
|
|
386
431
|
for (;;) {
|
|
@@ -465,7 +510,7 @@ const workflowOptions = (
|
|
|
465
510
|
root: string,
|
|
466
511
|
config: DocBridgeConfigV1,
|
|
467
512
|
sourceRevision: string,
|
|
468
|
-
stage: 'collect' | 'normalize' | 'reconcile' | 'evaluate' | 'report',
|
|
513
|
+
stage: 'collect' | 'normalize' | 'reconcile' | 'enrich' | 'evaluate' | 'report',
|
|
469
514
|
handlers: Parameters<typeof runWorkflow>[0]['handlers'],
|
|
470
515
|
versions?: Pick<Parameters<typeof runWorkflow>[0], 'pipelineVersion' | 'analyzerVersions'>,
|
|
471
516
|
): Parameters<typeof runWorkflow>[0] => ({
|
|
@@ -491,6 +536,12 @@ const documentationInputs = (root: string, snapshot: DiscoverySnapshotV1) => sna
|
|
|
491
536
|
.filter((entity) => entity.kind === 'document' && entity.path)
|
|
492
537
|
.map((entity) => ({ path: entity.path as string, content: readFileSync(resolve(root, entity.path as string), 'utf8') }))
|
|
493
538
|
|
|
539
|
+
/** Ownership records as reconciliation sees them, so a path matching nothing gets reported. */
|
|
540
|
+
const ownershipOptions = (config: DocBridgeConfigV1): { readonly ownership?: readonly { readonly id: string; readonly path: string }[] } => {
|
|
541
|
+
const ownership = Object.entries(config.routing?.options?.ownership ?? {}).map(([id, record]) => ({ id, path: record.path }))
|
|
542
|
+
return ownership.length ? { ownership } : {}
|
|
543
|
+
}
|
|
544
|
+
|
|
494
545
|
const reconcileWorkflow = (root: string, config: DocBridgeConfigV1): WorkflowExecutionResult => {
|
|
495
546
|
const scanned = scanWorkflow(root, config)
|
|
496
547
|
const snapshot = parseDiscoverySnapshot(loadWorkflowStepOutput(scanned.stateDir, 'normalize'))
|
|
@@ -500,16 +551,53 @@ const reconcileWorkflow = (root: string, config: DocBridgeConfigV1): WorkflowExe
|
|
|
500
551
|
const report = reconcileKnowledge(snapshot, declared, {
|
|
501
552
|
...(config.reconciliation?.scope === undefined ? {} : { scope: config.reconciliation.scope }),
|
|
502
553
|
...(config.reconciliation?.requiredRelationKinds === undefined ? {} : { requiredRelationKinds: config.reconciliation.requiredRelationKinds }),
|
|
554
|
+
...(config.reconciliation?.requiredRelationTargets === undefined ? {} : { requiredRelationTargets: config.reconciliation.requiredRelationTargets }),
|
|
503
555
|
...(config.reconciliation?.includeOrphanedDocuments === undefined ? {} : { includeOrphanedDocuments: config.reconciliation.includeOrphanedDocuments }),
|
|
556
|
+
...ownershipOptions(config),
|
|
504
557
|
})
|
|
505
558
|
return runWorkflow(workflowOptions(root, config, snapshot.sourceRevision, 'reconcile', { reconcile: () => report }, { pipelineVersion: snapshot.pipelineVersion, analyzerVersions: snapshot.analyzerVersions }))
|
|
506
559
|
}
|
|
507
560
|
|
|
508
|
-
|
|
509
|
-
|
|
561
|
+
/**
|
|
562
|
+
* Record the overlay as the enrich step of the current run.
|
|
563
|
+
*
|
|
564
|
+
* The step's input names the overlay it attaches, not only the report it was made from: a person
|
|
565
|
+
* approving a proposal changes the overlay without changing the report, and the engine refuses a
|
|
566
|
+
* step whose input has not moved but whose output has. Keying on the overlay's own hash keeps that
|
|
567
|
+
* rule honest — same overlay, same artifact; a new overlay, a new one.
|
|
568
|
+
*/
|
|
569
|
+
const attachEnrichmentStage = (root: string, config: DocBridgeConfigV1, report: ReconciliationReportV1, result: EnrichmentRunResult): WorkflowExecutionResult =>
|
|
570
|
+
runWorkflow({
|
|
571
|
+
...workflowOptions(root, config, report.sourceRevision, 'enrich', { enrich: () => result.overlay }, { pipelineVersion: report.pipelineVersion, analyzerVersions: report.analyzerVersions }),
|
|
572
|
+
inputs: { enrich: { reportHash: report.contentHash, overlayHash: result.overlay.contentHash } },
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* The enrich stage, run only on request. Its failure is reported, never propagated: a `check`
|
|
577
|
+
* that asked for enrichment and did not get it is still a `check`, with the same result.
|
|
578
|
+
*/
|
|
579
|
+
const enrichWorkflow = async (root: string, config: DocBridgeConfigV1, reconciled: WorkflowExecutionResult): Promise<{ readonly status: 'ok' | 'failed'; readonly result?: EnrichmentRunResult; readonly error?: string }> => {
|
|
580
|
+
try {
|
|
581
|
+
const snapshot = parseDiscoverySnapshot(loadWorkflowStepOutput(reconciled.stateDir, 'normalize'))
|
|
582
|
+
const report = parseReconciliationReport(loadWorkflowStepOutput(reconciled.stateDir, 'reconcile'))
|
|
583
|
+
const result = await runEnrichment({ root, config, snapshot, report })
|
|
584
|
+
attachEnrichmentStage(root, config, report, result)
|
|
585
|
+
return { status: 'ok', result }
|
|
586
|
+
} catch (error) {
|
|
587
|
+
return { status: 'failed', error: error instanceof Error ? error.message : String(error) }
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const checkWorkflow = (root: string, config: DocBridgeConfigV1, reconciled = reconcileWorkflow(root, config)): WorkflowExecutionResult => {
|
|
510
592
|
const report = parseReconciliationReport(loadWorkflowStepOutput(reconciled.stateDir, 'reconcile'))
|
|
511
593
|
const versions = { pipelineVersion: report.pipelineVersion, analyzerVersions: report.analyzerVersions }
|
|
512
|
-
|
|
594
|
+
// `centrality-risk` needs betweenness over the import graph, which only the snapshot can give.
|
|
595
|
+
const snapshot = parseDiscoverySnapshot(loadWorkflowStepOutput(reconciled.stateDir, 'normalize'))
|
|
596
|
+
const evaluate = () => evaluateRules(report, {
|
|
597
|
+
...(config.rules ? { config: config.rules } : {}),
|
|
598
|
+
centrality: centrality(snapshot),
|
|
599
|
+
})
|
|
600
|
+
runWorkflow(workflowOptions(root, config, report.sourceRevision, 'evaluate', { evaluate }, versions))
|
|
513
601
|
return runWorkflow(workflowOptions(root, config, report.sourceRevision, 'report', { report: ({ input }) => input }, versions))
|
|
514
602
|
}
|
|
515
603
|
|
|
@@ -591,14 +679,85 @@ const runWorkflowCommand = (
|
|
|
591
679
|
flags: ReadonlySet<string>,
|
|
592
680
|
configPath: string | undefined,
|
|
593
681
|
argv: readonly string[],
|
|
594
|
-
): number => {
|
|
682
|
+
): number | Promise<number> => {
|
|
683
|
+
/*
|
|
684
|
+
* Validated before anything runs, and for every path: `--enrich` returns early, and a run that
|
|
685
|
+
* ends up printing findings must have been allowed to ask for them.
|
|
686
|
+
*/
|
|
687
|
+
const format = optionValues(argv, '--format')[0]
|
|
688
|
+
if (format !== undefined && format !== 'json' && format !== 'finding') {
|
|
689
|
+
process.stderr.write('--format must be json or finding.\n')
|
|
690
|
+
return 2
|
|
691
|
+
}
|
|
692
|
+
if (format === 'finding' && command !== 'check') {
|
|
693
|
+
process.stderr.write('--format finding is only available for ak-docs check.\n')
|
|
694
|
+
return 2
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if (command === 'check' && flags.has('--enrich')) {
|
|
698
|
+
return (async () => {
|
|
699
|
+
try {
|
|
700
|
+
const { config, root } = loadProject(configPath)
|
|
701
|
+
const reconciled = reconcileWorkflow(root, config)
|
|
702
|
+
const enrichment = await enrichWorkflow(root, config, reconciled)
|
|
703
|
+
return finishWorkflowCommand(command, flags, argv, config, root, checkWorkflow(root, config, reconciled), {
|
|
704
|
+
enrichment: enrichment.status === 'ok' && enrichment.result
|
|
705
|
+
? { status: 'ok', overlayHash: enrichment.result.overlay.contentHash, agentCalls: enrichment.result.agentCalls, cacheHits: enrichment.result.cacheHits, accepted: enrichment.result.overlay.accepted.length, pending: enrichment.result.overlay.pending.length, rejected: enrichment.result.overlay.rejected.length }
|
|
706
|
+
: { status: 'failed', error: enrichment.error },
|
|
707
|
+
})
|
|
708
|
+
} catch (error) {
|
|
709
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
710
|
+
return 2
|
|
711
|
+
}
|
|
712
|
+
})()
|
|
713
|
+
}
|
|
595
714
|
try {
|
|
596
715
|
const { config, root } = loadProject(configPath)
|
|
597
716
|
const result = command === 'scan' ? scanWorkflow(root, config) : command === 'reconcile' ? reconcileWorkflow(root, config) : checkWorkflow(root, config)
|
|
598
|
-
|
|
717
|
+
return finishWorkflowCommand(command, flags, argv, config, root, result, {})
|
|
718
|
+
} catch (error) {
|
|
719
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
720
|
+
return 2
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
const finishWorkflowCommand = (
|
|
725
|
+
command: 'scan' | 'reconcile' | 'check' | 'map',
|
|
726
|
+
flags: ReadonlySet<string>,
|
|
727
|
+
argv: readonly string[],
|
|
728
|
+
config: DocBridgeConfigV1,
|
|
729
|
+
root: string,
|
|
730
|
+
result: WorkflowExecutionResult,
|
|
731
|
+
extra: Record<string, unknown>,
|
|
732
|
+
): number => {
|
|
733
|
+
try {
|
|
734
|
+
const output = { ...workflowOutput(result), ...extra }
|
|
599
735
|
if (command === 'map') output.kind = 'architecture-map'
|
|
736
|
+
// Read from argv rather than taken as an argument: every caller already passes argv, and the
|
|
737
|
+
// flag is validated where the command is dispatched.
|
|
738
|
+
if (optionValues(argv, '--format')[0] === 'finding') {
|
|
739
|
+
/*
|
|
740
|
+
* The canonical shape the ecosystem consumes: every reconciliation diagnostic as a `Finding`
|
|
741
|
+
* with a severity from `SEVERITY_ORDER`. The rule verdict still decides the exit code, so a
|
|
742
|
+
* dashboard reading findings and a CI job reading the exit code agree on what failed.
|
|
743
|
+
*/
|
|
744
|
+
const diagnostics = Array.isArray(output.diagnostics) ? (output.diagnostics as Parameters<typeof findingsFromDiagnostics>[0]) : []
|
|
745
|
+
writeJson({
|
|
746
|
+
ok: output.ok,
|
|
747
|
+
runId: output.runId,
|
|
748
|
+
state: output.state,
|
|
749
|
+
...(output.snapshotHash ? { snapshotHash: output.snapshotHash } : {}),
|
|
750
|
+
...(output.reportHash ? { reportHash: output.reportHash } : {}),
|
|
751
|
+
format: 'finding',
|
|
752
|
+
findings: findingsFromDiagnostics(diagnostics),
|
|
753
|
+
})
|
|
754
|
+
const exitCode = output.rules && typeof output.rules === 'object' && 'exitCode' in output.rules && (output.rules as { exitCode?: unknown }).exitCode === 1 ? 1 : 0
|
|
755
|
+
return result.run.state === 'failed' ? 1 : exitCode
|
|
756
|
+
}
|
|
600
757
|
if (command === 'map' && flags.has('--html')) {
|
|
601
|
-
const
|
|
758
|
+
const observed = parseDiscoverySnapshot(loadWorkflowStepOutput(result.stateDir, 'normalize'))
|
|
759
|
+
// Accepted proposed relations render as dashed edges; nothing observed is removed or redrawn.
|
|
760
|
+
const snapshot = config.intelligence?.registry?.enabled ? withAcceptedRelations(observed, readEnrichmentOverlay(root)) : observed
|
|
602
761
|
const report = parseReconciliationReport(loadWorkflowStepOutput(result.stateDir, 'reconcile'))
|
|
603
762
|
const outputPath = optionValues(argv, '--output')[0] ?? '.doc-bridge/report.html'
|
|
604
763
|
const htmlPath = resolve(root, outputPath)
|
|
@@ -621,6 +780,187 @@ const runWorkflowCommand = (
|
|
|
621
780
|
}
|
|
622
781
|
}
|
|
623
782
|
|
|
783
|
+
const buildDocumentationAuditReport = (root: string, config: DocBridgeConfigV1) => {
|
|
784
|
+
const scanned = scanWorkflow(root, config)
|
|
785
|
+
const snapshot = parseDiscoverySnapshot(loadWorkflowStepOutput(scanned.stateDir, 'normalize'))
|
|
786
|
+
const analysis = applyDocumentationDeclarations(snapshot, documentationInputs(root, snapshot), { agentRoot: config.corpus.agent.root })
|
|
787
|
+
const reconciliation = reconcileKnowledge(snapshot, analysis.snapshot, {
|
|
788
|
+
...(config.reconciliation?.scope === undefined ? {} : { scope: config.reconciliation.scope }),
|
|
789
|
+
...(config.reconciliation?.requiredRelationKinds === undefined ? {} : { requiredRelationKinds: config.reconciliation.requiredRelationKinds }),
|
|
790
|
+
...(config.reconciliation?.requiredRelationTargets === undefined ? {} : { requiredRelationTargets: config.reconciliation.requiredRelationTargets }),
|
|
791
|
+
includeOrphanedDocuments: config.reconciliation?.includeOrphanedDocuments ?? true,
|
|
792
|
+
...ownershipOptions(config),
|
|
793
|
+
})
|
|
794
|
+
return auditDocumentation({
|
|
795
|
+
root,
|
|
796
|
+
snapshot,
|
|
797
|
+
declared: analysis.snapshot,
|
|
798
|
+
reconciliation,
|
|
799
|
+
declarationDiagnostics: analysis.diagnostics,
|
|
800
|
+
...(config.audit?.documentation ? { config: config.audit.documentation } : {}),
|
|
801
|
+
})
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const BENCH_USAGE = [
|
|
805
|
+
'Usage: ak-docs bench retrieval <suite.json> [--index <file>] [--baseline <file>] [--limit <n>] [--text|--json]',
|
|
806
|
+
' ak-docs bench retrieval <suite.json> --baseline <file> --update-baseline --by <name> [--reason <text>]',
|
|
807
|
+
].join('\n')
|
|
808
|
+
|
|
809
|
+
const runBenchCommand = (
|
|
810
|
+
flags: ReadonlySet<string>,
|
|
811
|
+
positional: readonly string[],
|
|
812
|
+
configPath: string | undefined,
|
|
813
|
+
argv: readonly string[],
|
|
814
|
+
): number => {
|
|
815
|
+
if (positional[1] !== 'retrieval' || !positional[2]) {
|
|
816
|
+
process.stderr.write(`${BENCH_USAGE}\n`)
|
|
817
|
+
return 1
|
|
818
|
+
}
|
|
819
|
+
try {
|
|
820
|
+
const { config, root } = loadProject(configPath)
|
|
821
|
+
const suite = parseRetrievalSuite(JSON.parse(readFileSync(resolve(root, positional[2]), 'utf8')) as unknown)
|
|
822
|
+
|
|
823
|
+
const limitOption = optionValues(argv, '--limit')[0]
|
|
824
|
+
const limit = limitOption === undefined ? undefined : Number(limitOption)
|
|
825
|
+
if (limit !== undefined && (!Number.isInteger(limit) || limit <= 0)) {
|
|
826
|
+
throw new Error('--limit must be a positive integer.')
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (flags.has('--overlay')) {
|
|
830
|
+
/*
|
|
831
|
+
* Does the accepted overlay earn its cost? The same suite over the same snapshot, once with
|
|
832
|
+
* the overlay projected and once without. A drop in hit@3 exits non-zero: an overlay that
|
|
833
|
+
* makes retrieval worse is a finding about the agent, not a new baseline.
|
|
834
|
+
*
|
|
835
|
+
* Both indexes are projected here, from the snapshot, so this answer needs no index on disk.
|
|
836
|
+
*/
|
|
837
|
+
const overlay = readEnrichmentOverlay(root)
|
|
838
|
+
if (!overlay) throw new Error('No enrichment overlay at .doc-bridge/enrich/overlay.json. Run: ak-docs enrich')
|
|
839
|
+
const stateDir = resolve(root, config.workflow?.stateDir ?? '.doc-bridge/workflow')
|
|
840
|
+
const snapshot = (() => {
|
|
841
|
+
try { return parseDiscoverySnapshot(loadWorkflowStepOutput(stateDir, 'normalize')) }
|
|
842
|
+
catch { return discoverRepository({ root, config }) }
|
|
843
|
+
})()
|
|
844
|
+
const delta = measureOverlayRetrievalDelta({ root, config, snapshot, overlay, suite, ...(limit === undefined ? {} : { limit }) })
|
|
845
|
+
if (wantsTextOutput(flags, config)) writeLines(formatOverlayRetrievalDeltaText(delta))
|
|
846
|
+
else writeJson({ ok: !delta.regression, overlayDelta: delta })
|
|
847
|
+
return delta.regression ? 1 : 0
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const indexOption = optionValues(argv, '--index')[0]
|
|
851
|
+
const index = indexOption
|
|
852
|
+
? parseDocBridgeIndex(JSON.parse(readFileSync(resolve(root, indexOption), 'utf8')) as unknown)
|
|
853
|
+
: loadFreshDocBridgeIndex(root, config)
|
|
854
|
+
|
|
855
|
+
const result: RetrievalBenchResultV1 = runRetrievalBench({
|
|
856
|
+
index,
|
|
857
|
+
suite,
|
|
858
|
+
...(limit === undefined ? {} : { limit }),
|
|
859
|
+
})
|
|
860
|
+
|
|
861
|
+
const baselineOption = optionValues(argv, '--baseline')[0]
|
|
862
|
+
const baselinePath = baselineOption ? resolve(root, baselineOption) : undefined
|
|
863
|
+
|
|
864
|
+
if (flags.has('--update-baseline')) {
|
|
865
|
+
const approvedBy = optionValues(argv, '--by')[0]
|
|
866
|
+
if (!baselinePath) throw new Error('--update-baseline requires --baseline <file>.')
|
|
867
|
+
if (!approvedBy) throw new Error('--update-baseline requires --by <name>: a baseline is an approved figure, not a side effect of a run.')
|
|
868
|
+
const reason = optionValues(argv, '--reason')[0]
|
|
869
|
+
const baseline = createRetrievalBaseline({ result, approvedBy, ...(reason ? { reason } : {}) })
|
|
870
|
+
mkdirSync(dirname(baselinePath), { recursive: true })
|
|
871
|
+
writeFileSync(baselinePath, `${JSON.stringify(baseline, null, 2)}\n`, 'utf8')
|
|
872
|
+
if (wantsTextOutput(flags, config)) {
|
|
873
|
+
writeLines([...formatRetrievalBenchText(result), `Baseline written: ${relative(root, baselinePath)} (approved by ${approvedBy})`])
|
|
874
|
+
} else writeJson({ ok: true, result, baseline, baselinePath })
|
|
875
|
+
return 0
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if (baselinePath && !existsSync(baselinePath)) {
|
|
879
|
+
throw new Error(
|
|
880
|
+
`No baseline at ${relative(root, baselinePath)}. Record the current figures with: ak-docs bench retrieval ${positional[2]} --baseline ${baselineOption} --update-baseline --by <name>`,
|
|
881
|
+
)
|
|
882
|
+
}
|
|
883
|
+
const comparison = baselinePath
|
|
884
|
+
? compareRetrievalBaseline(result, parseRetrievalBaseline(JSON.parse(readFileSync(baselinePath, 'utf8')) as unknown))
|
|
885
|
+
: undefined
|
|
886
|
+
|
|
887
|
+
if (wantsTextOutput(flags, config)) {
|
|
888
|
+
writeLines([...formatRetrievalBenchText(result), ...(comparison ? formatRetrievalComparisonText(comparison) : [])])
|
|
889
|
+
} else {
|
|
890
|
+
writeJson({ ok: !comparison?.blocking, result, ...(comparison ? { comparison } : {}) })
|
|
891
|
+
}
|
|
892
|
+
return comparison?.blocking ? 1 : 0
|
|
893
|
+
} catch (error) {
|
|
894
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
895
|
+
return 2
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
const runDocumentationAuditCommand = (
|
|
900
|
+
flags: ReadonlySet<string>,
|
|
901
|
+
positional: readonly string[],
|
|
902
|
+
configPath: string | undefined,
|
|
903
|
+
): number => {
|
|
904
|
+
if (positional[1] !== 'documentation') {
|
|
905
|
+
process.stderr.write('Usage: ak-docs audit documentation [--text|--json]\n')
|
|
906
|
+
return 1
|
|
907
|
+
}
|
|
908
|
+
try {
|
|
909
|
+
const { config, root } = loadProject(configPath)
|
|
910
|
+
const report = buildDocumentationAuditReport(root, config)
|
|
911
|
+
if (wantsTextOutput(flags, config)) writeLines(formatDocumentationAuditText(report))
|
|
912
|
+
else writeJson({ ok: report.status !== 'blocked', report })
|
|
913
|
+
return report.status === 'blocked' ? 1 : 0
|
|
914
|
+
} catch (error) {
|
|
915
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
916
|
+
return 2
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const RENDER_USAGE = [
|
|
921
|
+
`Usage: ak-docs render <${RENDER_TEMPLATE_NAMES.join('|')}> [--data <artifact>] [--output <path>] [--print-template] [--json]`,
|
|
922
|
+
...RENDER_TEMPLATE_NAMES.map((name) => ` ${name.padEnd(16)} ${RENDER_TEMPLATES[name].description} (--data: ${RENDER_TEMPLATES[name].data})`),
|
|
923
|
+
].join('\n')
|
|
924
|
+
|
|
925
|
+
const runRenderCommand = (
|
|
926
|
+
flags: ReadonlySet<string>,
|
|
927
|
+
positional: readonly string[],
|
|
928
|
+
configPath: string | undefined,
|
|
929
|
+
argv: readonly string[],
|
|
930
|
+
): number => {
|
|
931
|
+
// parseArgs files option values as positionals; the template name is the first one no option consumed.
|
|
932
|
+
const dataPath = optionValues(argv, '--data')[0]
|
|
933
|
+
const outputPath = optionValues(argv, '--output')[0]
|
|
934
|
+
const consumed = new Set([dataPath, outputPath].filter(Boolean))
|
|
935
|
+
const name = positional.slice(1).find((value) => !consumed.has(value))
|
|
936
|
+
if (!name || !isRenderTemplateName(name)) {
|
|
937
|
+
process.stderr.write(`${name ? `Unknown template "${name}".\n` : ''}${RENDER_USAGE}\n`)
|
|
938
|
+
return 1
|
|
939
|
+
}
|
|
940
|
+
try {
|
|
941
|
+
const { config, root } = loadProject(configPath)
|
|
942
|
+
if (flags.has('--print-template')) {
|
|
943
|
+
process.stdout.write(resolveTemplateSource(name, config, root).source)
|
|
944
|
+
return 0
|
|
945
|
+
}
|
|
946
|
+
const result = renderArtifact({ root, config, template: name, ...(dataPath ? { dataPath } : {}) })
|
|
947
|
+
if (outputPath) {
|
|
948
|
+
const written = writeRenderedPages(result, resolve(root, outputPath), root)
|
|
949
|
+
writeJson({ ok: true, template: name, source: result.origin, written })
|
|
950
|
+
return 0
|
|
951
|
+
}
|
|
952
|
+
if (flags.has('--json')) {
|
|
953
|
+
writeJson({ ok: true, template: name, source: result.origin, pages: result.pages })
|
|
954
|
+
return 0
|
|
955
|
+
}
|
|
956
|
+
process.stdout.write(result.pages.map((page) => page.content).join('\n'))
|
|
957
|
+
return 0
|
|
958
|
+
} catch (error) {
|
|
959
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
960
|
+
return 2
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
624
964
|
const runRulesCommand = (
|
|
625
965
|
argv: readonly string[],
|
|
626
966
|
flags: ReadonlySet<string>,
|
|
@@ -671,7 +1011,7 @@ const runRulesCommand = (
|
|
|
671
1011
|
}
|
|
672
1012
|
}
|
|
673
1013
|
|
|
674
|
-
const runFixCommand = (argv: readonly string[], positional: readonly string[], configPath: string | undefined): number => {
|
|
1014
|
+
const runFixCommand = async (argv: readonly string[], positional: readonly string[], configPath: string | undefined): Promise<number> => {
|
|
675
1015
|
try {
|
|
676
1016
|
const { config, root } = loadProject(configPath)
|
|
677
1017
|
const action = positional[1]
|
|
@@ -692,8 +1032,12 @@ const runFixCommand = (argv: readonly string[], positional: readonly string[], c
|
|
|
692
1032
|
const file = resolve(root, proposalPath)
|
|
693
1033
|
const proposal = JSON.parse(readFileSync(file, 'utf8')) as unknown
|
|
694
1034
|
const result = action === 'approve' ? approveFixProposal(proposal, optionValues(argv, '--by')[0] ?? 'human') : applyFixProposal(root, proposal, { currentRevision: sourceRevision })
|
|
1035
|
+
// An approval is recorded through the ecosystem gate too, bound to the proposal and its exact content hash.
|
|
1036
|
+
const recorded = action === 'approve' && result.approval
|
|
1037
|
+
? await recordApproval(root, { id: fixApprovalId(result.proposalId, result.approval.proposalHash), name: FIX_APPROVAL_GATE, payload: { proposalId: result.proposalId, proposalHash: result.approval.proposalHash }, decision: 'approved', by: result.approval.approvedBy })
|
|
1038
|
+
: undefined
|
|
695
1039
|
writeFileSync(file, `${JSON.stringify(result, null, 2)}\n`, 'utf8')
|
|
696
|
-
writeJson({ ok: true, proposal: result, proposalPath: file })
|
|
1040
|
+
writeJson({ ok: true, proposal: result, proposalPath: file, ...(recorded ? { approvalId: recorded.approval.id } : {}) })
|
|
697
1041
|
return 0
|
|
698
1042
|
} catch (error) {
|
|
699
1043
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
@@ -701,17 +1045,128 @@ const runFixCommand = (argv: readonly string[], positional: readonly string[], c
|
|
|
701
1045
|
}
|
|
702
1046
|
}
|
|
703
1047
|
|
|
1048
|
+
const runEnrichCommand = async (flags: ReadonlySet<string>, positional: readonly string[], argv: readonly string[], configPath: string | undefined): Promise<number> => {
|
|
1049
|
+
try {
|
|
1050
|
+
const { config, root } = loadProject(configPath)
|
|
1051
|
+
const action = positional[1]
|
|
1052
|
+
if (action === 'list') {
|
|
1053
|
+
const review = listEnrichment(root)
|
|
1054
|
+
if (wantsTextOutput(flags, config)) {
|
|
1055
|
+
writeLines(review
|
|
1056
|
+
? [`Overlay: ${review.overlayHash}`, `Accepted: ${review.accepted.length} Pending: ${review.pending.length} Rejected: ${review.rejected.length}`, ...review.pending.map((entry) => ` pending ${entry.proposal.proposalId.slice(0, 16)} ${entry.proposal.kind} ${entry.proposal.entity}${entry.note ? ` (${entry.note})` : ''}`)]
|
|
1057
|
+
: ['No enrichment overlay. Run `ak-docs enrich` first.'])
|
|
1058
|
+
} else writeJson({ ok: true, enrichment: review ?? null })
|
|
1059
|
+
return 0
|
|
1060
|
+
}
|
|
1061
|
+
if (action === 'approve' || action === 'reject') {
|
|
1062
|
+
const proposalId = positional[2]
|
|
1063
|
+
const by = optionValues(argv, '--by')[0]
|
|
1064
|
+
if (!proposalId || !by) throw new Error('Usage: ak-docs enrich approve|reject <proposalId> --by <name> [--reason <text>]')
|
|
1065
|
+
const stateDir = resolve(root, config.workflow?.stateDir ?? '.doc-bridge/workflow')
|
|
1066
|
+
const snapshot = (() => { try { return parseDiscoverySnapshot(loadWorkflowStepOutput(stateDir, 'normalize')) } catch { return undefined } })()
|
|
1067
|
+
const reason = optionValues(argv, '--reason')[0]
|
|
1068
|
+
const decided = await decideEnrichment({ root, proposalId, decision: action === 'approve' ? 'approved' : 'rejected', by, ...(reason ? { reason } : {}), ...(snapshot ? { snapshot } : {}) })
|
|
1069
|
+
const decidedId = 'proposal' in decided.entry ? decided.entry.proposal.proposalId : decided.entry.proposalId
|
|
1070
|
+
if (wantsTextOutput(flags, config)) writeLines([`${action === 'approve' ? 'Approved' : 'Rejected'}: ${decidedId}`, `Approval: ${decided.approvalId} (${decided.gateSource})`, `Overlay: ${decided.overlay.contentHash}`])
|
|
1071
|
+
else writeJson({ ok: true, approvalId: decided.approvalId, gate: decided.gateSource, entry: decided.entry, overlayHash: decided.overlay.contentHash })
|
|
1072
|
+
return 0
|
|
1073
|
+
}
|
|
1074
|
+
if (action !== undefined) throw new Error('Usage: ak-docs enrich [list | approve <proposalId> --by <name> | reject <proposalId> --by <name>] [--json|--text]')
|
|
1075
|
+
const reconciled = reconcileWorkflow(root, config)
|
|
1076
|
+
const snapshot = parseDiscoverySnapshot(loadWorkflowStepOutput(reconciled.stateDir, 'normalize'))
|
|
1077
|
+
const report = parseReconciliationReport(loadWorkflowStepOutput(reconciled.stateDir, 'reconcile'))
|
|
1078
|
+
const result = await runEnrichment({ root, config, snapshot, report })
|
|
1079
|
+
attachEnrichmentStage(root, config, report, result)
|
|
1080
|
+
/*
|
|
1081
|
+
* The retrieval delta is opt-in: it runs the golden suite twice, which is the right cost for
|
|
1082
|
+
* an answer about whether the overlay helped and the wrong cost for every routine run.
|
|
1083
|
+
*/
|
|
1084
|
+
const delta = flags.has('--retrieval-delta')
|
|
1085
|
+
? measureOverlayRetrievalDelta({
|
|
1086
|
+
root,
|
|
1087
|
+
config,
|
|
1088
|
+
snapshot,
|
|
1089
|
+
overlay: result.overlay,
|
|
1090
|
+
suite: parseRetrievalSuite(JSON.parse(readFileSync(resolve(root, config.retrieval?.benchmark?.suite ?? DEFAULT_RETRIEVAL_SUITE), 'utf8')) as unknown),
|
|
1091
|
+
})
|
|
1092
|
+
: undefined
|
|
1093
|
+
if (wantsTextOutput(flags, config)) writeLines([...formatEnrichmentText(result), ...(delta ? formatOverlayRetrievalDeltaText(delta) : [])])
|
|
1094
|
+
else writeJson({
|
|
1095
|
+
ok: !delta?.regression,
|
|
1096
|
+
overlayPath: result.overlayPath,
|
|
1097
|
+
overlayHash: result.overlay.contentHash,
|
|
1098
|
+
baseSnapshotHash: result.overlay.baseSnapshotHash,
|
|
1099
|
+
roles: result.roles,
|
|
1100
|
+
packs: result.packs,
|
|
1101
|
+
agentCalls: result.agentCalls,
|
|
1102
|
+
cacheHits: result.cacheHits,
|
|
1103
|
+
rerun: result.rerun,
|
|
1104
|
+
expired: result.expired,
|
|
1105
|
+
stats: result.overlay.stats,
|
|
1106
|
+
cost: enrichmentCost(result.overlay.stats),
|
|
1107
|
+
stability: result.stability,
|
|
1108
|
+
accepted: result.overlay.accepted.length,
|
|
1109
|
+
pending: result.overlay.pending.length,
|
|
1110
|
+
rejected: result.overlay.rejected.length,
|
|
1111
|
+
...(delta ? { retrievalDelta: delta } : {}),
|
|
1112
|
+
})
|
|
1113
|
+
return delta?.regression ? 1 : 0
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
1116
|
+
return 2
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
/** Where the claim registry lives unless a caller points somewhere else. */
|
|
1121
|
+
export const DEFAULT_PUBLIC_CLAIMS = 'docs/parity/public-claims-v1.json'
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* `ak-docs parity`: what this repository says in public, against what it can prove.
|
|
1125
|
+
*
|
|
1126
|
+
* The gate fails on a blocking finding — a stale or contradictory claim nobody accepted — and
|
|
1127
|
+
* leaves warnings and unresolved claims visible without failing, so a repository can adopt the
|
|
1128
|
+
* registry one claim at a time. The doctor runs only when a claim asks for one of its figures:
|
|
1129
|
+
* measuring it costs an index and a benchmark, and most registries never need it.
|
|
1130
|
+
*/
|
|
1131
|
+
const runParityCommand = (flags: ReadonlySet<string>, configPath: string | undefined, argv: readonly string[]): number => {
|
|
1132
|
+
try {
|
|
1133
|
+
const { config, root } = loadProject(configPath)
|
|
1134
|
+
const claimsPath = optionValues(argv, '--claims')[0] ?? DEFAULT_PUBLIC_CLAIMS
|
|
1135
|
+
const registry = parsePublicClaims(JSON.parse(readFileSync(resolve(root, claimsPath), 'utf8')) as unknown)
|
|
1136
|
+
const snapshot = discoverRepository({ root, config })
|
|
1137
|
+
const doctor = registry.claims.some((claim) => claim.evidence.kind === 'doctor-metric') ? runDoctor(root, config) : undefined
|
|
1138
|
+
const report = checkPublicParity({
|
|
1139
|
+
root,
|
|
1140
|
+
config,
|
|
1141
|
+
registry,
|
|
1142
|
+
snapshot,
|
|
1143
|
+
...(doctor ? { doctor } : {}),
|
|
1144
|
+
project: { name: snapshot.project.name },
|
|
1145
|
+
sourceRevision: snapshot.sourceRevision,
|
|
1146
|
+
sourceRevisionKind: snapshot.sourceRevisionKind,
|
|
1147
|
+
})
|
|
1148
|
+
if (wantsTextOutput(flags, config)) writeLines(formatPublicParityText(report))
|
|
1149
|
+
else writeJson({ ok: report.metrics.blocking === 0, parity: report })
|
|
1150
|
+
return report.metrics.blocking ? 1 : 0
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
1153
|
+
return 2
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
704
1157
|
const runSuggestCommand = async (flags: ReadonlySet<string>, configPath: string | undefined): Promise<number> => {
|
|
705
1158
|
try {
|
|
706
1159
|
const { config, root } = loadProject(configPath)
|
|
707
1160
|
const stateDir = resolve(root, config.workflow?.stateDir ?? '.doc-bridge/workflow')
|
|
708
1161
|
const snapshot = parseDiscoverySnapshot(loadWorkflowStepOutput(stateDir, 'normalize'))
|
|
709
1162
|
const report = parseReconciliationReport(loadWorkflowStepOutput(stateDir, 'reconcile'))
|
|
710
|
-
const
|
|
711
|
-
const
|
|
1163
|
+
const runner = config.intelligence?.registry?.cli ? undefined : await loadRegistryAgentRunner(root, config)
|
|
1164
|
+
const adapter = createRegistryAgentAdapter(root, config, runner)
|
|
1165
|
+
const documentation = flags.has('--documentation') ? buildDocumentationAuditReport(root, config) : undefined
|
|
1166
|
+
const proposal = await adapter.run(snapshot, report, undefined, documentation)
|
|
712
1167
|
const proposalPath = persistRegistryAgentProposal(stateDir, proposal)
|
|
713
|
-
if (flags.has('--text')) writeLines([`Agent: ${adapter.metadata.id}`, `Proposal: ${proposal.proposalId}`, `Hash: ${proposal.contentHash}`, `Saved: ${proposalPath}`])
|
|
714
|
-
else writeJson({ ok: true, proposal, proposalPath })
|
|
1168
|
+
if (flags.has('--text')) writeLines([`Agent: ${adapter.metadata.id}`, ...(documentation ? [`Documentation audit: ${documentation.contentHash}`] : []), `Proposal: ${proposal.proposalId}`, `Hash: ${proposal.contentHash}`, `Saved: ${proposalPath}`])
|
|
1169
|
+
else writeJson({ ok: true, ...(documentation ? { documentationAuditHash: documentation.contentHash } : {}), proposal, proposalPath })
|
|
715
1170
|
return 0
|
|
716
1171
|
} catch (error) {
|
|
717
1172
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
@@ -720,10 +1175,19 @@ const runSuggestCommand = async (flags: ReadonlySet<string>, configPath: string
|
|
|
720
1175
|
}
|
|
721
1176
|
|
|
722
1177
|
const writeIfMissing = (path: string, contents: string): boolean => {
|
|
723
|
-
if (existsSync(path)) return false
|
|
724
1178
|
mkdirSync(dirname(path), { recursive: true })
|
|
725
|
-
|
|
726
|
-
|
|
1179
|
+
try {
|
|
1180
|
+
const fd = openSync(path, 'wx')
|
|
1181
|
+
try {
|
|
1182
|
+
writeFileSync(fd, contents, 'utf8')
|
|
1183
|
+
return true
|
|
1184
|
+
} finally {
|
|
1185
|
+
closeSync(fd)
|
|
1186
|
+
}
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
if ((error as NodeJS.ErrnoException).code === 'EEXIST') return false
|
|
1189
|
+
throw error
|
|
1190
|
+
}
|
|
727
1191
|
}
|
|
728
1192
|
|
|
729
1193
|
const demoOwnership = {
|
|
@@ -891,6 +1355,143 @@ const registryTopology = () => ({
|
|
|
891
1355
|
mergePolicy: { autoMerge: false, requiresHuman: true },
|
|
892
1356
|
})
|
|
893
1357
|
|
|
1358
|
+
const runStudyCommand = async (flags: ReadonlySet<string>, positional: readonly string[], argv: readonly string[], configPath: string | undefined): Promise<number> => {
|
|
1359
|
+
const action = positional[1]
|
|
1360
|
+
const inputPath = positional[2]
|
|
1361
|
+
if (!inputPath || !['protocol', 'history', 'tasks', 'select', 'plan', 'providers', 'run', 'adjudicate', 'ledger', 'metrics', 'verification', 'expectations'].includes(action ?? '')) {
|
|
1362
|
+
process.stderr.write('Usage: ak-docs study protocol|history|tasks|select|plan|providers|run|adjudicate|ledger|metrics|verification|expectations <artifact.json> [--protocol <protocol.json>] [--providers <provider-cli.json>] [--adjudicator <provider-cli.json>] [--repositories <repositories.json>] [--ledger <ledger.json>] [--output <ledger.json>] [--run-id <id>] [--limit <n>] [--round <id>] [--dry-run] [--baseline-round <id>] [--current-round <id>] [--expectations <expectations.json>] [--index <index.json>] [--repository <id>] [--text|--json]\n')
|
|
1363
|
+
return 1
|
|
1364
|
+
}
|
|
1365
|
+
try {
|
|
1366
|
+
if (action === 'expectations') {
|
|
1367
|
+
/*
|
|
1368
|
+
* The mechanical half of the study, checked by the same benchmark that gates this
|
|
1369
|
+
* repository's retrieval. The suite carries opaque references; the local expectations file
|
|
1370
|
+
* resolves them to entities and documents, which is why it is never published.
|
|
1371
|
+
*/
|
|
1372
|
+
const expectationsPath = optionValues(argv, '--expectations')[0]
|
|
1373
|
+
if (!expectationsPath) throw new Error('Study expectations require --expectations <expectations.json>.')
|
|
1374
|
+
const suite = parseStudyTaskSuite(JSON.parse(readFileSync(resolve(inputPath), 'utf8')) as unknown)
|
|
1375
|
+
const expectations = parseStudyExpectations(JSON.parse(readFileSync(resolve(expectationsPath), 'utf8')) as unknown)
|
|
1376
|
+
const repositoryId = optionValues(argv, '--repository')[0]
|
|
1377
|
+
const indexOption = optionValues(argv, '--index')[0]
|
|
1378
|
+
const { config, root } = loadProject(configPath)
|
|
1379
|
+
const index = indexOption
|
|
1380
|
+
? parseDocBridgeIndex(JSON.parse(readFileSync(resolve(root, indexOption), 'utf8')) as unknown)
|
|
1381
|
+
: loadFreshDocBridgeIndex(root, config)
|
|
1382
|
+
const check = checkStudyExpectations({ taskSuite: suite, expectations, index, ...(repositoryId ? { repositoryId } : {}) })
|
|
1383
|
+
if (flags.has('--text')) writeLines(formatStudyExpectationsText(check))
|
|
1384
|
+
else writeJson({ ok: check.ok, expectations: check })
|
|
1385
|
+
return check.ok ? 0 : 1
|
|
1386
|
+
}
|
|
1387
|
+
if (action === 'run') {
|
|
1388
|
+
const taskSuitePath = positional[3]
|
|
1389
|
+
const providersPath = optionValues(argv, '--providers')[0]
|
|
1390
|
+
const repositoriesPath = optionValues(argv, '--repositories')[0]
|
|
1391
|
+
const ledgerPath = optionValues(argv, '--ledger')[0]
|
|
1392
|
+
if (!taskSuitePath || !providersPath || !repositoriesPath || !ledgerPath) throw new Error('Study run requires a task suite, --providers, --repositories, and --ledger.')
|
|
1393
|
+
const summary = await runControlledStudy({
|
|
1394
|
+
plan: parseControlledStudyRunPlan(JSON.parse(readFileSync(resolve(inputPath), 'utf8')) as unknown),
|
|
1395
|
+
suite: parseStudyTaskSuite(JSON.parse(readFileSync(resolve(taskSuitePath), 'utf8')) as unknown),
|
|
1396
|
+
providers: parseStudyProviderCliConfig(JSON.parse(readFileSync(resolve(providersPath), 'utf8')) as unknown),
|
|
1397
|
+
repositories: parseStudyRepositoryConfig(JSON.parse(readFileSync(resolve(repositoriesPath), 'utf8')) as unknown),
|
|
1398
|
+
ledgerPath: resolve(ledgerPath),
|
|
1399
|
+
...(optionValues(argv, '--round')[0] === undefined ? {} : { round: optionValues(argv, '--round')[0] }),
|
|
1400
|
+
...(flags.has('--dry-run') ? { dryRun: true } : {}),
|
|
1401
|
+
})
|
|
1402
|
+
if (flags.has('--text')) writeLines(formatControlledStudyRunText(summary))
|
|
1403
|
+
else writeJson({ ok: summary.status === 'dry-run' || summary.status === 'completed', summary })
|
|
1404
|
+
return 0
|
|
1405
|
+
}
|
|
1406
|
+
if (action === 'adjudicate') {
|
|
1407
|
+
const taskSuitePath = positional[3]
|
|
1408
|
+
const adjudicatorConfigPath = optionValues(argv, '--adjudicator')[0]
|
|
1409
|
+
const outputPath = optionValues(argv, '--output')[0]
|
|
1410
|
+
if (!taskSuitePath || !adjudicatorConfigPath || !outputPath) throw new Error('Study adjudication requires a task suite, --adjudicator with an adjudicator, and --output.')
|
|
1411
|
+
const config = parseStudyProviderCliConfig(JSON.parse(readFileSync(resolve(adjudicatorConfigPath), 'utf8')) as unknown)
|
|
1412
|
+
if (!config.adjudicator) throw new Error('Study provider config must declare an adjudicator.')
|
|
1413
|
+
const ledger = parseControlledStudyLedger(JSON.parse(readFileSync(resolve(inputPath), 'utf8')) as unknown)
|
|
1414
|
+
const suite = parseStudyTaskSuite(JSON.parse(readFileSync(resolve(taskSuitePath), 'utf8')) as unknown)
|
|
1415
|
+
const limitValue = optionValues(argv, '--limit')[0]
|
|
1416
|
+
const limit = limitValue === undefined ? undefined : Number(limitValue)
|
|
1417
|
+
const offsetValue = optionValues(argv, '--offset')[0]
|
|
1418
|
+
const offset = offsetValue === undefined ? undefined : Number(offsetValue)
|
|
1419
|
+
const result = await independentlyAdjudicateStudyLedger({ ledger, taskSuite: suite, config: config.adjudicator, configurationHash: config.contentHash, cwd: process.cwd(), maxRuntimeMs: suite.maxRuntimeMsPerTask, ...(optionValues(argv, '--run-id')[0] === undefined ? {} : { runId: optionValues(argv, '--run-id')[0] }), ...(offset === undefined ? {} : { offset }), ...(limit === undefined ? {} : { limit }) })
|
|
1420
|
+
persistIndependentlyAdjudicatedLedger(outputPath, result)
|
|
1421
|
+
if (flags.has('--text')) writeLines([`Adjudicated observations: ${result.observations.length}`, `Ledger: ${resolve(outputPath)}`, `Content hash: ${result.contentHash}`])
|
|
1422
|
+
else writeJson({ ok: true, ledger: result })
|
|
1423
|
+
return 0
|
|
1424
|
+
}
|
|
1425
|
+
const input = JSON.parse(readFileSync(resolve(inputPath), 'utf8')) as unknown
|
|
1426
|
+
if (action === 'protocol') {
|
|
1427
|
+
const protocol = parseStudyProtocol(input)
|
|
1428
|
+
if (flags.has('--text')) writeLines(formatStudyProtocolText(protocol))
|
|
1429
|
+
else writeJson({ ok: true, protocol })
|
|
1430
|
+
return 0
|
|
1431
|
+
}
|
|
1432
|
+
if (action === 'tasks') {
|
|
1433
|
+
const suite = parseStudyTaskSuite(input)
|
|
1434
|
+
if (flags.has('--text')) writeLines(formatStudyTaskSuiteText(suite))
|
|
1435
|
+
else writeJson({ ok: true, suite })
|
|
1436
|
+
return 0
|
|
1437
|
+
}
|
|
1438
|
+
if (action === 'select') {
|
|
1439
|
+
const suite = parseStudyTaskSuite(input)
|
|
1440
|
+
const executions = selectTaskExecutions(suite)
|
|
1441
|
+
if (flags.has('--text')) writeLines([`Selected executions: ${executions.length}`, `First execution: ${executions[0]?.taskId ?? 'none'} / ${executions[0]?.variantId ?? 'none'}`])
|
|
1442
|
+
else writeJson({ ok: true, executions })
|
|
1443
|
+
return 0
|
|
1444
|
+
}
|
|
1445
|
+
if (action === 'plan') {
|
|
1446
|
+
const plan = parseControlledStudyRunPlan(input)
|
|
1447
|
+
if (flags.has('--text')) writeLines(formatControlledStudyRunPlanText(plan))
|
|
1448
|
+
else writeJson({ ok: true, plan })
|
|
1449
|
+
return 0
|
|
1450
|
+
}
|
|
1451
|
+
if (action === 'providers') {
|
|
1452
|
+
const providers = parseStudyProviderCliConfig(input)
|
|
1453
|
+
if (flags.has('--text')) writeLines(formatStudyProviderCliText(providers))
|
|
1454
|
+
else writeJson({ ok: true, providers })
|
|
1455
|
+
return 0
|
|
1456
|
+
}
|
|
1457
|
+
if (action === 'ledger') {
|
|
1458
|
+
const ledger = parseControlledStudyLedger(input)
|
|
1459
|
+
if (flags.has('--text')) writeLines([`Ledger: ${ledger.ledgerVersion}`, `Observations: ${ledger.observations.length}`, `Content hash: ${ledger.contentHash}`])
|
|
1460
|
+
else writeJson({ ok: true, ledger })
|
|
1461
|
+
return 0
|
|
1462
|
+
}
|
|
1463
|
+
if (action === 'metrics') {
|
|
1464
|
+
const ledger = parseControlledStudyLedger(input)
|
|
1465
|
+
const baselineRound = optionValues(argv, '--baseline-round')[0]
|
|
1466
|
+
const currentRound = optionValues(argv, '--current-round')[0]
|
|
1467
|
+
const baselineRunId = optionValues(argv, '--baseline-run-id')[0]
|
|
1468
|
+
const currentRunId = optionValues(argv, '--current-run-id')[0]
|
|
1469
|
+
const report = calculateStudyMetrics(ledger.observations, { ...(baselineRound === undefined ? {} : { baselineRound }), ...(currentRound === undefined ? {} : { currentRound }), ...(baselineRunId === undefined ? {} : { baselineRunId }), ...(currentRunId === undefined ? {} : { currentRunId }) })
|
|
1470
|
+
if (flags.has('--text')) writeLines(formatStudyMetricsText(report))
|
|
1471
|
+
else writeJson({ ok: true, report })
|
|
1472
|
+
return report.comparisons.some((comparison) => comparison.status === 'regressed') && !flags.has('--allow-regressions') ? 1 : 0
|
|
1473
|
+
}
|
|
1474
|
+
if (action === 'verification') {
|
|
1475
|
+
const binding = parseStudyVerificationBinding(input)
|
|
1476
|
+
if (flags.has('--text')) writeLines(formatStudyVerificationText(binding))
|
|
1477
|
+
else writeJson({ ok: true, binding })
|
|
1478
|
+
return 0
|
|
1479
|
+
}
|
|
1480
|
+
const registry = parseHistoricalEvidenceRegistry(input)
|
|
1481
|
+
const protocolPath = optionValues(argv, '--protocol')[0]
|
|
1482
|
+
if (protocolPath) {
|
|
1483
|
+
const protocol = parseStudyProtocol(JSON.parse(readFileSync(resolve(protocolPath), 'utf8')) as unknown)
|
|
1484
|
+
validateHistoricalEvidenceRegistry(registry, protocol)
|
|
1485
|
+
}
|
|
1486
|
+
if (flags.has('--text')) writeLines(formatHistoricalEvidenceText(registry))
|
|
1487
|
+
else writeJson({ ok: true, registry })
|
|
1488
|
+
return 0
|
|
1489
|
+
} catch (error) {
|
|
1490
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
1491
|
+
return 2
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
894
1495
|
export const runCli = (argv: readonly string[]): number | undefined | Promise<number> => {
|
|
895
1496
|
const { command, flags, configPath, positional } = parseArgs(argv)
|
|
896
1497
|
|
|
@@ -980,12 +1581,19 @@ export const runCli = (argv: readonly string[]): number | undefined | Promise<nu
|
|
|
980
1581
|
}
|
|
981
1582
|
}
|
|
982
1583
|
|
|
1584
|
+
if (command === 'study') return runStudyCommand(flags, positional, argv, configPath)
|
|
1585
|
+
|
|
983
1586
|
if (command === 'scan' || command === 'reconcile' || command === 'check' || command === 'map') {
|
|
984
1587
|
return runWorkflowCommand(command, flags, configPath, argv)
|
|
985
1588
|
}
|
|
1589
|
+
if (command === 'audit') return runDocumentationAuditCommand(flags, positional, configPath)
|
|
1590
|
+
if (command === 'parity') return runParityCommand(flags, configPath, argv)
|
|
1591
|
+
if (command === 'render') return runRenderCommand(flags, positional, configPath, argv)
|
|
1592
|
+
if (command === 'bench') return runBenchCommand(flags, positional, configPath, argv)
|
|
986
1593
|
|
|
987
1594
|
if (command === 'fix') return runFixCommand(argv, positional, configPath)
|
|
988
1595
|
if (command === 'suggest') return runSuggestCommand(flags, configPath)
|
|
1596
|
+
if (command === 'enrich') return runEnrichCommand(flags, positional, argv, configPath)
|
|
989
1597
|
|
|
990
1598
|
if (command === 'init') {
|
|
991
1599
|
const root = process.cwd()
|
|
@@ -1056,7 +1664,7 @@ export const runCli = (argv: readonly string[]): number | undefined | Promise<nu
|
|
|
1056
1664
|
writeJson({ ok: true, count: candidates.length, candidates })
|
|
1057
1665
|
return 0
|
|
1058
1666
|
}
|
|
1059
|
-
const index =
|
|
1667
|
+
const index = loadFreshDocBridgeIndex(root, config)
|
|
1060
1668
|
const classifications = classifyMemoryCandidates(candidates, index)
|
|
1061
1669
|
if (positional[1] === 'classify') {
|
|
1062
1670
|
writeJson({ ok: true, count: classifications.length, classifications })
|
|
@@ -1114,7 +1722,7 @@ export const runCli = (argv: readonly string[]): number | undefined | Promise<nu
|
|
|
1114
1722
|
}
|
|
1115
1723
|
try {
|
|
1116
1724
|
const { config, root } = loadProject(configPath)
|
|
1117
|
-
const index =
|
|
1725
|
+
const index = loadFreshDocBridgeIndex(root, config)
|
|
1118
1726
|
const draft = draftMemoryPromotion(classifyMemoryCandidates(ingestMemoryCandidates(root), index))
|
|
1119
1727
|
writeJson({
|
|
1120
1728
|
...draft,
|
|
@@ -1319,9 +1927,10 @@ export const runCli = (argv: readonly string[]): number | undefined | Promise<nu
|
|
|
1319
1927
|
}
|
|
1320
1928
|
try {
|
|
1321
1929
|
const { config, root } = loadProject(configPath)
|
|
1322
|
-
const index =
|
|
1323
|
-
const result = runQuery(index, config, { kind, id, agent: flags.has('--agent') })
|
|
1930
|
+
const index = loadFreshDocBridgeIndex(root, config)
|
|
1931
|
+
const result = runQuery(index, config, { kind, id, agent: flags.has('--agent') }, { root })
|
|
1324
1932
|
if (wantsTextOutput(flags, config)) writeTextQuery(result)
|
|
1933
|
+
else if (flags.has('--agent')) writeAgentJson(result)
|
|
1325
1934
|
else writeJson(result)
|
|
1326
1935
|
return 0
|
|
1327
1936
|
} catch (error) {
|
|
@@ -1338,12 +1947,23 @@ export const runCli = (argv: readonly string[]): number | undefined | Promise<nu
|
|
|
1338
1947
|
}
|
|
1339
1948
|
try {
|
|
1340
1949
|
const { config, root } = loadProject(configPath)
|
|
1341
|
-
|
|
1950
|
+
const index = loadFreshDocBridgeIndex(root, config)
|
|
1951
|
+
const explain = flags.has('--explain')
|
|
1342
1952
|
if (flags.has('--agent')) {
|
|
1343
|
-
const
|
|
1344
|
-
|
|
1953
|
+
const mode = optionValues(argv, '--mode')[0]
|
|
1954
|
+
const budgetValue = optionValues(argv, '--context-budget')[0]
|
|
1955
|
+
const contextBudgetTokens = budgetValue === undefined ? undefined : Number(budgetValue)
|
|
1956
|
+
const result = runQuery(index, config, {
|
|
1957
|
+
kind: 'search',
|
|
1958
|
+
term,
|
|
1959
|
+
agent: true,
|
|
1960
|
+
...(explain ? { explain: true } : {}),
|
|
1961
|
+
...(mode === undefined ? {} : { mode: mode as 'discovery' | 'editing' | 'debugging' | 'documentation' }),
|
|
1962
|
+
...(contextBudgetTokens === undefined ? {} : { contextBudgetTokens }),
|
|
1963
|
+
})
|
|
1964
|
+
writeAgentJson(result)
|
|
1345
1965
|
} else {
|
|
1346
|
-
const matches = searchIndex(index, term)
|
|
1966
|
+
const matches = searchIndex(index, term, 20, explain ? { explain: true } : {})
|
|
1347
1967
|
if (wantsTextOutput(flags, config)) writeTextSearch(term, matches)
|
|
1348
1968
|
else writeJson({ term, count: matches.length, matches })
|
|
1349
1969
|
}
|
|
@@ -1367,7 +1987,7 @@ export const runCli = (argv: readonly string[]): number | undefined | Promise<nu
|
|
|
1367
1987
|
return (async () => {
|
|
1368
1988
|
try {
|
|
1369
1989
|
const { config, root } = loadProject(configPath)
|
|
1370
|
-
const index =
|
|
1990
|
+
const index = loadFreshDocBridgeIndex(root, config)
|
|
1371
1991
|
writeJson({ query, chunks: await retrieveHybridChunks(root, config, index, query) })
|
|
1372
1992
|
return 0
|
|
1373
1993
|
} catch (error) {
|
|
@@ -1386,7 +2006,7 @@ export const runCli = (argv: readonly string[]): number | undefined | Promise<nu
|
|
|
1386
2006
|
return (async () => {
|
|
1387
2007
|
try {
|
|
1388
2008
|
const { config, root } = loadProject(configPath)
|
|
1389
|
-
const index =
|
|
2009
|
+
const index = loadFreshDocBridgeIndex(root, config)
|
|
1390
2010
|
const rag = await createDocBridgeRag(root, config, index)
|
|
1391
2011
|
if (action === 'ingest') {
|
|
1392
2012
|
const result = await rag.ingest()
|
|
@@ -1423,7 +2043,7 @@ export const runCli = (argv: readonly string[]): number | undefined | Promise<nu
|
|
|
1423
2043
|
)
|
|
1424
2044
|
return 1
|
|
1425
2045
|
}
|
|
1426
|
-
const index =
|
|
2046
|
+
const index = loadFreshDocBridgeIndex(root, config)
|
|
1427
2047
|
await startInkChat(root, config, index)
|
|
1428
2048
|
return 0
|
|
1429
2049
|
} catch (error) {
|
|
@@ -1455,7 +2075,7 @@ export const runCli = (argv: readonly string[]): number | undefined | Promise<nu
|
|
|
1455
2075
|
}
|
|
1456
2076
|
return (async () => {
|
|
1457
2077
|
try {
|
|
1458
|
-
const index =
|
|
2078
|
+
const index = loadFreshDocBridgeIndex(root, config)
|
|
1459
2079
|
const result = await runChatOnce(root, config, index, question)
|
|
1460
2080
|
writeLines([result.content])
|
|
1461
2081
|
return 0
|
|
@@ -1474,7 +2094,7 @@ export const runCli = (argv: readonly string[]): number | undefined | Promise<nu
|
|
|
1474
2094
|
process.stderr.write('Usage: ak-docs ask <question>, or run ak-docs ask in an interactive terminal.\n')
|
|
1475
2095
|
return 1
|
|
1476
2096
|
}
|
|
1477
|
-
const index =
|
|
2097
|
+
const index = loadFreshDocBridgeIndex(root, config)
|
|
1478
2098
|
writeAsk(question, searchIndex(index, question, 8), index, config)
|
|
1479
2099
|
return 0
|
|
1480
2100
|
} catch (error) {
|
|
@@ -1491,7 +2111,7 @@ export const runCli = (argv: readonly string[]): number | undefined | Promise<nu
|
|
|
1491
2111
|
}
|
|
1492
2112
|
try {
|
|
1493
2113
|
const { config, root } = loadProject(configPath)
|
|
1494
|
-
const index =
|
|
2114
|
+
const index = loadFreshDocBridgeIndex(root, config)
|
|
1495
2115
|
|
|
1496
2116
|
if (kind === 'packages') {
|
|
1497
2117
|
const items = index.lookup?.packages ?? []
|