@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/usage.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI's usage text, in its own module.
|
|
3
|
+
*
|
|
4
|
+
* `ak-docs parity` checks that a command this repository documents in public is a command the CLI
|
|
5
|
+
* actually offers, which means something other than the CLI has to be able to read the usage. The
|
|
6
|
+
* text lives here so that check does not import the program and create a cycle through it.
|
|
7
|
+
*/
|
|
8
|
+
export const CLI_COMMAND_USAGE = `ak-docs — human↔agent documentation bridge (@agentskit/doc-bridge)
|
|
9
|
+
|
|
10
|
+
Core (no API key):
|
|
11
|
+
ak-docs init [--demo] [--scaffold-workspaces]
|
|
12
|
+
ak-docs demo [--fixture example|monorepo] [--text] [--in-project]
|
|
13
|
+
ak-docs doctor [--text] [--badge] [--write-badge]
|
|
14
|
+
ak-docs index [--watch]
|
|
15
|
+
ak-docs discover [--text|--json]
|
|
16
|
+
ak-docs benchmark <fixture.json> <observation.json> [--text|--json]
|
|
17
|
+
ak-docs bench retrieval <suite.json> [--index <file>] [--baseline <file>] [--limit <n>] [--text|--json]
|
|
18
|
+
ak-docs bench retrieval <suite.json> --update-baseline --by <name> [--reason <text>]
|
|
19
|
+
ak-docs study protocol <protocol.json> [--text|--json]
|
|
20
|
+
ak-docs study history <registry.json> [--protocol <protocol.json>] [--text|--json]
|
|
21
|
+
ak-docs study tasks <task-suite.json> [--text|--json]
|
|
22
|
+
ak-docs study select <task-suite.json> [--text|--json]
|
|
23
|
+
ak-docs study plan <run-plan.json> [--text|--json]
|
|
24
|
+
ak-docs study providers <provider-cli.json> [--text|--json]
|
|
25
|
+
ak-docs study run <run-plan.json> <task-suite.json> --providers <provider-cli.json> --repositories <repositories.json> --ledger <ledger.json> [--round <id>] [--dry-run] [--text|--json]
|
|
26
|
+
ak-docs study adjudicate <observation-ledger.json> <task-suite.json> --adjudicator <provider-cli.json> --output <ledger.json> [--run-id <id>] [--offset <n>] [--limit <n>] [--text|--json]
|
|
27
|
+
ak-docs study ledger <observation-ledger.json> [--text|--json]
|
|
28
|
+
ak-docs study verification <binding.json> [--text|--json]
|
|
29
|
+
ak-docs study metrics <observation-ledger.json> [--baseline-round <id>] [--current-round <id>] [--baseline-run-id <id>] [--current-run-id <id>] [--allow-regressions] [--text|--json]
|
|
30
|
+
ak-docs scan | reconcile | check | map [--text|--json] [--html] [--report-threshold <bytes>]
|
|
31
|
+
ak-docs check --json --format finding emit diagnostics in the ecosystem Finding shape
|
|
32
|
+
ak-docs bench retrieval <suite> --overlay measure the suite with and without the accepted overlay
|
|
33
|
+
ak-docs enrich --retrieval-delta run the overlay through the golden suite after enriching
|
|
34
|
+
ak-docs study expectations <suite> --expectations <file> check the study's mechanical retrieval expectations
|
|
35
|
+
ak-docs check --enrich run the enrichment stage between reconcile and evaluate
|
|
36
|
+
ak-docs enrich [--json|--text] run the configured Registry roles over context packs
|
|
37
|
+
ak-docs enrich list | approve <proposalId> --by <name> | reject <proposalId> --by <name> [--reason <text>]
|
|
38
|
+
ak-docs fix propose links|normalize <artifact> [--output <file>]
|
|
39
|
+
ak-docs fix approve|apply <proposal.json> [--by <name>]
|
|
40
|
+
ak-docs suggest [--documentation] [--json|--text] run the configured Registry agent
|
|
41
|
+
ak-docs query [package|ownership|intent|change] <id> [--agent] [--text]
|
|
42
|
+
ak-docs search <term> [--agent] [--explain] [--mode=<mode>] [--context-budget=<tokens>] [--text]
|
|
43
|
+
ak-docs list <packages|intents|changes|knowledge> [--text]
|
|
44
|
+
ak-docs ask [question] local consult (no LLM)
|
|
45
|
+
ak-docs gate run [gate-id]
|
|
46
|
+
ak-docs rules run <report.json> [--preset default|recommended|strict] [--severity rule=level] [--ignore rule]
|
|
47
|
+
ak-docs conformance run documentation-standard-v1 [--text|--json]
|
|
48
|
+
ak-docs audit documentation [--text|--json]
|
|
49
|
+
ak-docs parity [--claims <file>] [--json|--text] check public claims against what the repository proves
|
|
50
|
+
ak-docs render <llms.txt|area|ownership|change-digest|overlay-review> [--data <artifact>] [--output <path>] [--print-template] [--json]
|
|
51
|
+
ak-docs mcp
|
|
52
|
+
ak-docs mcp install --cursor | --claude
|
|
53
|
+
ak-docs memory ingest|classify|promote [--pr] [--dry-run]
|
|
54
|
+
ak-docs bootstrap agent-docs
|
|
55
|
+
ak-docs validate-config | validate-handoff <file>
|
|
56
|
+
|
|
57
|
+
Intelligence (optional AgentsKit peers):
|
|
58
|
+
ak-docs rag ingest|search <query>
|
|
59
|
+
ak-docs chat terminal chat (Ink + RAG)
|
|
60
|
+
ak-docs ask <question> --chat one-shot grounded answer
|
|
61
|
+
|
|
62
|
+
Advanced / ecosystem:
|
|
63
|
+
ak-docs retrieve <query>
|
|
64
|
+
ak-docs registry topology
|
|
65
|
+
ak-docs playbook draft | pattern [--text]
|
|
66
|
+
|
|
67
|
+
Global flags:
|
|
68
|
+
-h, --help --version
|
|
69
|
+
--config <path> (project root = config file directory)
|
|
70
|
+
--agent --json --text --chat --demo
|
|
71
|
+
`
|
package/src/config/defaults.ts
CHANGED
|
@@ -57,6 +57,7 @@ export const applyConfigDefaults = (config: DocBridgeConfigV1): DocBridgeConfigV
|
|
|
57
57
|
'memory.classify', 'memory.promoteDraft', 'registry.topology',
|
|
58
58
|
'docbridge.snapshot', 'docbridge.report', 'docbridge.diagnostics',
|
|
59
59
|
'docbridge.relations', 'docbridge.run', 'docbridge.proposals',
|
|
60
|
+
'knowledge.search', 'knowledge.lookup',
|
|
60
61
|
],
|
|
61
62
|
transport: 'stdio',
|
|
62
63
|
...config.surfaces?.mcp,
|
package/src/config/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export {
|
|
|
8
8
|
} from './load-config.js'
|
|
9
9
|
export {
|
|
10
10
|
DocBridgeConfigV1Schema,
|
|
11
|
+
DocumentationAuditConfigSchema,
|
|
11
12
|
ReconciliationConfigSchema,
|
|
12
13
|
AgentCorpusConfigSchema,
|
|
13
14
|
HumanCorpusConfigSchema,
|
|
@@ -16,10 +17,13 @@ export {
|
|
|
16
17
|
EcosystemContractEvidenceSchema,
|
|
17
18
|
ConformanceConfigSchema,
|
|
18
19
|
ReportConfigSchema,
|
|
20
|
+
RetrievalConfigSchema,
|
|
19
21
|
type DocBridgeConfigV1,
|
|
22
|
+
type DocumentationAuditConfig,
|
|
20
23
|
type AgentCorpusConfig,
|
|
21
24
|
type DocumentationStandardRuleId,
|
|
22
25
|
type DocumentationStandardV1Config,
|
|
23
26
|
type ReconciliationConfig,
|
|
24
27
|
type ReportConfig,
|
|
28
|
+
type RetrievalConfig,
|
|
25
29
|
} from './schema.js'
|
|
@@ -79,7 +79,13 @@ const parseConfig = (input: unknown): DocBridgeConfigV1 => {
|
|
|
79
79
|
if (result.success) return result.data
|
|
80
80
|
throw new Error(
|
|
81
81
|
`Invalid doc-bridge config:\n${result.error.issues.map((issue) =>
|
|
82
|
-
` - ${issue.path.join('.') || '(root)'}: ${
|
|
82
|
+
` - ${issue.path.join('.') || '(root)'}: ${
|
|
83
|
+
issue.code === 'invalid_type' && issue.message.endsWith('received undefined')
|
|
84
|
+
? 'Required'
|
|
85
|
+
: issue.code === 'invalid_value' && 'values' in issue
|
|
86
|
+
? 'Invalid enum value'
|
|
87
|
+
: issue.message
|
|
88
|
+
}`,
|
|
83
89
|
).join('\n')}`,
|
|
84
90
|
)
|
|
85
91
|
}
|
package/src/config/schema.ts
CHANGED
|
@@ -171,24 +171,47 @@ export const RuleSeveritySchema = z.enum(['off', 'info', 'warn', 'error'])
|
|
|
171
171
|
export const RulesConfigSchema = z
|
|
172
172
|
.object({
|
|
173
173
|
mode: z.enum(['default', 'recommended', 'strict']).optional(),
|
|
174
|
-
severity: z.
|
|
174
|
+
severity: z.partialRecord(RuleIdSchema, RuleSeveritySchema).optional(),
|
|
175
175
|
ignore: z.array(RuleIdSchema).max(128).optional(),
|
|
176
176
|
criticalEntities: z.array(z.string().min(1).max(256)).max(128).optional(),
|
|
177
177
|
criticalPaths: z.array(z.string().min(1).max(512)).max(128).optional(),
|
|
178
|
-
warningThresholds: z.
|
|
178
|
+
warningThresholds: z.partialRecord(RuleIdSchema, z.number().int().min(1).max(100_000)).optional(),
|
|
179
179
|
})
|
|
180
180
|
.strict()
|
|
181
181
|
|
|
182
182
|
export const ReconciliationConfigSchema = z
|
|
183
183
|
.object({
|
|
184
184
|
/** Semantic comparison level. Raw discovery always keeps file-level relations. */
|
|
185
|
-
scope: z.enum(['file', 'module', 'package']).optional(),
|
|
185
|
+
scope: z.enum(['file', 'module', 'area', 'package']).optional(),
|
|
186
186
|
/** Relation kinds that must have documentation declarations. Omit to require all observed kinds; [] disables this signal. */
|
|
187
187
|
requiredRelationKinds: z.array(z.string().min(1).max(128)).max(128).optional(),
|
|
188
|
+
/** Limit missing-declaration findings to relations whose endpoints are internal project entities. */
|
|
189
|
+
requiredRelationTargets: z.enum(['all', 'internal']).optional(),
|
|
188
190
|
includeOrphanedDocuments: z.boolean().optional(),
|
|
189
191
|
})
|
|
190
192
|
.strict()
|
|
191
193
|
|
|
194
|
+
export const DocumentationAuditConfigSchema = z
|
|
195
|
+
.object({
|
|
196
|
+
/** Glob-like paths that should be compared for structure coverage. */
|
|
197
|
+
criticalPaths: z.array(z.string().min(1).max(512)).max(128).optional(),
|
|
198
|
+
/** Generated docs are checked for presence/freshness evidence only; content quality is skipped. */
|
|
199
|
+
generatedPaths: z.array(z.string().min(1).max(512)).max(128).optional(),
|
|
200
|
+
exclude: z.array(z.string().min(1).max(512)).max(128).optional(),
|
|
201
|
+
minWords: z.number().int().nonnegative().max(100_000).optional(),
|
|
202
|
+
requiredSections: z.array(z.string().min(1).max(128)).max(32).optional(),
|
|
203
|
+
requireExamples: z.boolean().optional(),
|
|
204
|
+
exactDuplicates: z.boolean().optional(),
|
|
205
|
+
defaultTier: z.enum(['tier-0', 'tier-1', 'tier-2']).optional(),
|
|
206
|
+
tierRules: z.array(z.object({
|
|
207
|
+
pattern: z.string().min(1).max(512),
|
|
208
|
+
tier: z.enum(['tier-0', 'tier-1', 'tier-2']),
|
|
209
|
+
critical: z.boolean().optional(),
|
|
210
|
+
}).strict()).max(128).optional(),
|
|
211
|
+
requiredCriticalMetadata: z.array(z.enum(['owner', 'lifecycle', 'sourceOfTruth', 'validationPath'])).max(4).optional(),
|
|
212
|
+
})
|
|
213
|
+
.strict()
|
|
214
|
+
|
|
192
215
|
export const AnalysisConfigSchema = z
|
|
193
216
|
.object({
|
|
194
217
|
plugins: z
|
|
@@ -223,6 +246,18 @@ export const AnalysisConfigSchema = z
|
|
|
223
246
|
})
|
|
224
247
|
.strict()
|
|
225
248
|
.optional(),
|
|
249
|
+
/**
|
|
250
|
+
* How code areas are derived — the unit of architecture between a package and a file.
|
|
251
|
+
* `roots` names directories that contain areas rather than being one (`src` holds
|
|
252
|
+
* `src/query`); `depth` is how many levels below such a root an area sits.
|
|
253
|
+
*/
|
|
254
|
+
areas: z
|
|
255
|
+
.object({
|
|
256
|
+
depth: z.number().int().min(1).max(8).optional(),
|
|
257
|
+
roots: z.array(z.string().min(1).max(128)).max(32).optional(),
|
|
258
|
+
})
|
|
259
|
+
.strict()
|
|
260
|
+
.optional(),
|
|
226
261
|
})
|
|
227
262
|
.strict()
|
|
228
263
|
|
|
@@ -280,9 +315,11 @@ export const SurfacesConfigSchema = z
|
|
|
280
315
|
'docbridge.relations',
|
|
281
316
|
'docbridge.run',
|
|
282
317
|
'docbridge.proposals',
|
|
318
|
+
'knowledge.search',
|
|
319
|
+
'knowledge.lookup',
|
|
283
320
|
]),
|
|
284
321
|
)
|
|
285
|
-
.max(
|
|
322
|
+
.max(18)
|
|
286
323
|
.optional(),
|
|
287
324
|
transport: z.enum(['stdio', 'http']).optional(),
|
|
288
325
|
http: z
|
|
@@ -298,6 +335,16 @@ export const SurfacesConfigSchema = z
|
|
|
298
335
|
})
|
|
299
336
|
.strict()
|
|
300
337
|
|
|
338
|
+
/** One enrichment role: an installed Registry agent and the prompt version it is run with. */
|
|
339
|
+
export const EnrichmentRoleSchema = z
|
|
340
|
+
.object({
|
|
341
|
+
enabled: z.boolean().optional(),
|
|
342
|
+
/** Defaults to `intelligence.registry.agentId`. */
|
|
343
|
+
agentId: z.string().min(1).max(256).optional(),
|
|
344
|
+
promptVersion: z.string().min(1).max(64).optional(),
|
|
345
|
+
})
|
|
346
|
+
.strict()
|
|
347
|
+
|
|
301
348
|
export const IntelligenceConfigSchema = z
|
|
302
349
|
.object({
|
|
303
350
|
enabled: z.boolean().optional(),
|
|
@@ -357,11 +404,34 @@ export const IntelligenceConfigSchema = z
|
|
|
357
404
|
agentId: z.string().min(1).max(256).optional(),
|
|
358
405
|
agentRoot: z.string().min(1).max(512).optional(),
|
|
359
406
|
runnerModule: z.string().min(1).max(512).optional(),
|
|
407
|
+
cli: z
|
|
408
|
+
.object({
|
|
409
|
+
/** Executable name or absolute path. Arguments are passed without a shell. */
|
|
410
|
+
command: z.string().min(1).max(512),
|
|
411
|
+
args: z.array(z.string().max(2_048)).max(64).optional(),
|
|
412
|
+
})
|
|
413
|
+
.strict()
|
|
414
|
+
.optional(),
|
|
360
415
|
deterministic: z.boolean().optional(),
|
|
361
416
|
timeoutMs: z.number().int().positive().max(600_000).optional(),
|
|
417
|
+
maxInputBytes: z.number().int().positive().max(50_000_000).optional(),
|
|
362
418
|
maxTokens: z.number().int().positive().max(1_000_000).optional(),
|
|
363
419
|
maxResponseBytes: z.number().int().positive().max(10_000_000).optional(),
|
|
364
420
|
maxConcurrency: z.number().int().positive().max(64).optional(),
|
|
421
|
+
/** Byte budget of one enrichment context pack. Default 65536. */
|
|
422
|
+
maxPackBytes: z.number().int().min(4_096).max(4_000_000).optional(),
|
|
423
|
+
/**
|
|
424
|
+
* Which installed agent plays which enrichment role. The default is the configured agent
|
|
425
|
+
* as curator only. The adjudicator must be a different identity from both others.
|
|
426
|
+
*/
|
|
427
|
+
roles: z
|
|
428
|
+
.object({
|
|
429
|
+
curator: EnrichmentRoleSchema.optional(),
|
|
430
|
+
reviewer: EnrichmentRoleSchema.optional(),
|
|
431
|
+
adjudicator: EnrichmentRoleSchema.optional(),
|
|
432
|
+
})
|
|
433
|
+
.strict()
|
|
434
|
+
.optional(),
|
|
365
435
|
})
|
|
366
436
|
.strict()
|
|
367
437
|
.optional(),
|
|
@@ -468,12 +538,53 @@ export const DocumentationStandardV1ConfigSchema = z
|
|
|
468
538
|
}
|
|
469
539
|
})
|
|
470
540
|
|
|
541
|
+
/**
|
|
542
|
+
* Retrieval tuning. Weights and parameters are configuration rather than code so a repository can
|
|
543
|
+
* change what its agents find first without a release, and so the change is recorded in the index.
|
|
544
|
+
*/
|
|
545
|
+
export const RetrievalConfigSchema = z
|
|
546
|
+
.object({
|
|
547
|
+
corpus: z
|
|
548
|
+
.object({
|
|
549
|
+
/** Project repository documents and modules into the index. On by default. */
|
|
550
|
+
enabled: z.boolean().optional(),
|
|
551
|
+
})
|
|
552
|
+
.strict()
|
|
553
|
+
.optional(),
|
|
554
|
+
weights: z.record(z.string().min(1).max(64), z.number().min(0).max(1_000)).optional(),
|
|
555
|
+
params: z
|
|
556
|
+
.object({ k1: z.number().min(0).max(100).optional(), b: z.number().min(0).max(1).optional() })
|
|
557
|
+
.strict()
|
|
558
|
+
.optional(),
|
|
559
|
+
benchmark: z
|
|
560
|
+
.object({
|
|
561
|
+
/** The golden retrieval suite the doctor measures hit@3 against. `docs/bench/retrieval-suite-v1.json` by default. */
|
|
562
|
+
suite: z.string().min(1).max(512).optional(),
|
|
563
|
+
})
|
|
564
|
+
.strict()
|
|
565
|
+
.optional(),
|
|
566
|
+
})
|
|
567
|
+
.strict()
|
|
568
|
+
|
|
471
569
|
export const ConformanceConfigSchema = z
|
|
472
570
|
.object({
|
|
473
571
|
documentationStandardV1: DocumentationStandardV1ConfigSchema.optional(),
|
|
474
572
|
})
|
|
475
573
|
.strict()
|
|
476
574
|
|
|
575
|
+
export const RenderTemplateNameSchema = z.enum(['llms.txt', 'area', 'ownership', 'change-digest', 'overlay-review'])
|
|
576
|
+
|
|
577
|
+
export const RenderConfigSchema = z
|
|
578
|
+
.object({
|
|
579
|
+
/**
|
|
580
|
+
* Project templates that replace the bundled ones, by template name, as paths relative to
|
|
581
|
+
* the project root. `ak-docs render <name> --print-template` prints the bundled template to
|
|
582
|
+
* start from.
|
|
583
|
+
*/
|
|
584
|
+
templates: z.partialRecord(RenderTemplateNameSchema, z.string().min(1).max(512)).optional(),
|
|
585
|
+
})
|
|
586
|
+
.strict()
|
|
587
|
+
|
|
477
588
|
export const DocBridgeConfigV1Schema = z
|
|
478
589
|
.object({
|
|
479
590
|
schemaVersion: z.literal(CONFIG_SCHEMA_VERSION),
|
|
@@ -494,6 +605,7 @@ export const DocBridgeConfigV1Schema = z
|
|
|
494
605
|
routing: RoutingConfigSchema.optional(),
|
|
495
606
|
gates: GatesConfigSchema.optional(),
|
|
496
607
|
reconciliation: ReconciliationConfigSchema.optional(),
|
|
608
|
+
audit: z.object({ documentation: DocumentationAuditConfigSchema.optional() }).strict().optional(),
|
|
497
609
|
analysis: AnalysisConfigSchema.optional(),
|
|
498
610
|
rules: RulesConfigSchema.optional(),
|
|
499
611
|
workflow: WorkflowConfigSchema.optional(),
|
|
@@ -503,15 +615,19 @@ export const DocBridgeConfigV1Schema = z
|
|
|
503
615
|
intelligence: IntelligenceConfigSchema.optional(),
|
|
504
616
|
federation: FederationConfigSchema.optional(),
|
|
505
617
|
conformance: ConformanceConfigSchema.optional(),
|
|
618
|
+
retrieval: RetrievalConfigSchema.optional(),
|
|
619
|
+
render: RenderConfigSchema.optional(),
|
|
506
620
|
})
|
|
507
621
|
.strict()
|
|
508
622
|
|
|
509
623
|
export type DocBridgeConfigV1 = z.infer<typeof DocBridgeConfigV1Schema>
|
|
624
|
+
export type RenderConfig = z.infer<typeof RenderConfigSchema>
|
|
510
625
|
export type AgentCorpusConfig = z.infer<typeof AgentCorpusConfigSchema>
|
|
511
626
|
export type HumanCorpusConfig = z.infer<typeof HumanCorpusConfigSchema>
|
|
512
627
|
export type DocumentationStandardV1Config = z.infer<typeof DocumentationStandardV1ConfigSchema>
|
|
513
628
|
export type DocumentationStandardRuleId = z.infer<typeof DocumentationStandardRuleIdSchema>
|
|
514
629
|
export type ReconciliationConfig = z.infer<typeof ReconciliationConfigSchema>
|
|
630
|
+
export type DocumentationAuditConfig = z.infer<typeof DocumentationAuditConfigSchema>
|
|
515
631
|
export type AnalysisConfig = z.infer<typeof AnalysisConfigSchema>
|
|
516
632
|
export type RuleId = z.infer<typeof RuleIdSchema>
|
|
517
633
|
export type RuleSeverity = z.infer<typeof RuleSeveritySchema>
|
|
@@ -519,3 +635,4 @@ export type RulesConfig = z.infer<typeof RulesConfigSchema>
|
|
|
519
635
|
export type WorkflowConfig = z.infer<typeof WorkflowConfigSchema>
|
|
520
636
|
export type RepositorySafetyConfig = z.infer<typeof RepositorySafetyConfigSchema>
|
|
521
637
|
export type ReportConfig = z.infer<typeof ReportConfigSchema>
|
|
638
|
+
export type RetrievalConfig = z.infer<typeof RetrievalConfigSchema>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync, realpathSync
|
|
1
|
+
import { closeSync, existsSync, fstatSync, openSync, readFileSync, realpathSync } from 'node:fs'
|
|
2
2
|
import { isAbsolute, relative, resolve, sep } from 'node:path'
|
|
3
3
|
|
|
4
4
|
import type {
|
|
@@ -11,6 +11,7 @@ import { buildDocBridgeIndex } from '../index-builder/build-index.js'
|
|
|
11
11
|
import { scanHumanDocRecords } from '../index-builder/human-adapters/index.js'
|
|
12
12
|
import { renderLlmsTxt } from '../index-builder/llms-txt.js'
|
|
13
13
|
import { toPosix } from '../lib/paths.js'
|
|
14
|
+
import type { DocBridgeIndexV1 } from '../schemas/doc-bridge-index.js'
|
|
14
15
|
|
|
15
16
|
export const DOCUMENTATION_STANDARD_V1_ID = 'documentation-standard-v1' as const
|
|
16
17
|
export const DOCUMENTATION_STANDARD_V1_STATUS = 'stable' as const
|
|
@@ -95,11 +96,10 @@ const fileEvidence = (
|
|
|
95
96
|
evidence: { path, detail: 'Path escapes the project root.' },
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
|
-
|
|
99
|
-
return { exists: false, content: '', evidence: { path, detail: 'File does not exist.' } }
|
|
100
|
-
}
|
|
99
|
+
let fd: number | undefined
|
|
101
100
|
try {
|
|
102
|
-
|
|
101
|
+
fd = openSync(abs, 'r')
|
|
102
|
+
const stat = fstatSync(fd)
|
|
103
103
|
if (!stat.isFile()) {
|
|
104
104
|
return { exists: false, content: '', evidence: { path, detail: 'Path is not a regular file.' } }
|
|
105
105
|
}
|
|
@@ -120,7 +120,7 @@ const fileEvidence = (
|
|
|
120
120
|
evidence: { path, detail: `Text evidence exceeds ${MAX_TEXT_EVIDENCE_BYTES} bytes.` },
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
-
const content = readFileSync(
|
|
123
|
+
const content = readFileSync(fd, 'utf8')
|
|
124
124
|
return {
|
|
125
125
|
exists: content.trim().length > 0,
|
|
126
126
|
content,
|
|
@@ -129,8 +129,15 @@ const fileEvidence = (
|
|
|
129
129
|
detail: content.trim().length > 0 ? 'File exists and is non-empty.' : 'File is empty.',
|
|
130
130
|
},
|
|
131
131
|
}
|
|
132
|
-
} catch {
|
|
133
|
-
|
|
132
|
+
} catch (error) {
|
|
133
|
+
const code = error && typeof error === 'object' && 'code' in error ? error.code : undefined
|
|
134
|
+
return {
|
|
135
|
+
exists: false,
|
|
136
|
+
content: '',
|
|
137
|
+
evidence: { path, detail: code === 'ENOENT' ? 'File does not exist.' : 'File is not readable text.' },
|
|
138
|
+
}
|
|
139
|
+
} finally {
|
|
140
|
+
if (fd !== undefined) closeSync(fd)
|
|
134
141
|
}
|
|
135
142
|
}
|
|
136
143
|
|
|
@@ -180,6 +187,7 @@ const llmsRule = (
|
|
|
180
187
|
root: string,
|
|
181
188
|
config: DocBridgeConfigV1,
|
|
182
189
|
options: DocumentationStandardV1Config,
|
|
190
|
+
generated: DocBridgeIndexV1,
|
|
183
191
|
): RuleDraft => {
|
|
184
192
|
const llmsPath = config.index?.llmsTxt?.outFile ?? 'llms.txt'
|
|
185
193
|
const llmsKey = safePath(root, llmsPath) ?? resolve(root, llmsPath)
|
|
@@ -190,8 +198,7 @@ const llmsRule = (
|
|
|
190
198
|
}
|
|
191
199
|
const paths = [llmsPath, ...rawSources.values()]
|
|
192
200
|
const evidence = paths.map((path) => fileEvidence(root, path))
|
|
193
|
-
const
|
|
194
|
-
const expectedLlms = renderLlmsTxt(config, generated.knowledge, generated.project?.name ?? 'project')
|
|
201
|
+
const expectedLlms = renderLlmsTxt(config, generated.knowledge, generated.project?.name ?? 'project', { root })
|
|
195
202
|
const llmsIsFresh = evidence[0]?.content === expectedLlms
|
|
196
203
|
if (evidence[0]?.exists) {
|
|
197
204
|
evidence[0] = {
|
|
@@ -278,8 +285,7 @@ const ecosystemContract = (
|
|
|
278
285
|
}
|
|
279
286
|
}
|
|
280
287
|
|
|
281
|
-
const handoffsRule = (
|
|
282
|
-
const index = buildDocBridgeIndex({ root, config, write: false }).index
|
|
288
|
+
const handoffsRule = (index: DocBridgeIndexV1): RuleDraft => {
|
|
283
289
|
const handoffs = Object.values(index.handoffs ?? {})
|
|
284
290
|
const ready = handoffs.filter(
|
|
285
291
|
(handoff) =>
|
|
@@ -450,10 +456,12 @@ export const runDocumentationStandardV1 = (
|
|
|
450
456
|
config: DocBridgeConfigV1,
|
|
451
457
|
): DocumentationConformanceReportV1 => {
|
|
452
458
|
const options = config.conformance?.documentationStandardV1 ?? {}
|
|
459
|
+
// Two rules need the index. Building it projects and hashes the whole repository, so build once.
|
|
460
|
+
const index = buildDocBridgeIndex({ root, config, write: false }).index
|
|
453
461
|
const drafts: RuleDraft[] = [
|
|
454
462
|
humanDocsRule(root, config),
|
|
455
|
-
llmsRule(root, config, options),
|
|
456
|
-
handoffsRule(
|
|
463
|
+
llmsRule(root, config, options, index),
|
|
464
|
+
handoffsRule(index),
|
|
457
465
|
contributionRule(root, options),
|
|
458
466
|
markersRule(root, options, 'metadata', 'required'),
|
|
459
467
|
linksRule(root, options),
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { toPosix } from '../lib/paths.js'
|
|
2
|
+
import { entityId } from './identity.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Code areas: the unit of architecture between a package and a file.
|
|
6
|
+
*
|
|
7
|
+
* Reconciliation could only compare at file, module or package level, and most repositories are a
|
|
8
|
+
* single package — so every internal relation aggregated into one self-loop, which the comparison
|
|
9
|
+
* skips. The result was a repository with a thousand observed relations reporting zero
|
|
10
|
+
* diagnostics, and a health score of 100 out of 100 saying nothing at all.
|
|
11
|
+
*
|
|
12
|
+
* An area is a directory that groups modules. It is derived, not declared: the first directory
|
|
13
|
+
* level under a package's source roots, plus any path an ownership record already names — because
|
|
14
|
+
* a configuration saying `path: "src/mcp"` is a human telling us that directory is a unit, and the
|
|
15
|
+
* graph had no entity for it.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const AREA_ANALYZER_VERSION = '1.0.0'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Directories that contain areas rather than being one.
|
|
22
|
+
*
|
|
23
|
+
* `src` is not an area in any useful sense; `src/query` is. When a package keeps its code under
|
|
24
|
+
* one of these, the area is the level below it.
|
|
25
|
+
*/
|
|
26
|
+
export const DEFAULT_AREA_ROOTS = ['src', 'lib', 'app', 'source', 'server', 'client', 'packages', 'apps'] as const
|
|
27
|
+
|
|
28
|
+
export const DEFAULT_AREA_DEPTH = 1
|
|
29
|
+
|
|
30
|
+
export type AreaModule = {
|
|
31
|
+
readonly moduleId: string
|
|
32
|
+
/** Repository-relative module path. */
|
|
33
|
+
readonly path: string
|
|
34
|
+
readonly packageId: string
|
|
35
|
+
/** Repository-relative package path; `.` for the root package. */
|
|
36
|
+
readonly packagePath: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type AreaOwnership = {
|
|
40
|
+
readonly id: string
|
|
41
|
+
readonly path: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type DerivedArea = {
|
|
45
|
+
readonly id: string
|
|
46
|
+
readonly path: string
|
|
47
|
+
readonly name: string
|
|
48
|
+
readonly packageId: string
|
|
49
|
+
/** The most specific area that encloses this one, when there is one. */
|
|
50
|
+
readonly parentId?: string
|
|
51
|
+
readonly moduleIds: readonly string[]
|
|
52
|
+
/** The ownership record that names exactly this path. */
|
|
53
|
+
readonly ownershipId?: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type DeriveAreasOptions = {
|
|
57
|
+
readonly modules: readonly AreaModule[]
|
|
58
|
+
readonly ownership?: readonly AreaOwnership[]
|
|
59
|
+
readonly depth?: number
|
|
60
|
+
readonly roots?: readonly string[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
* Trailing separators are trimmed with a loop rather than /\/+$/: a quantified group anchored at
|
|
65
|
+
* the end backtracks quadratically on a path that is mostly separators, and a module path comes
|
|
66
|
+
* from the repository being scanned.
|
|
67
|
+
*/
|
|
68
|
+
const normalize = (path: string): string => {
|
|
69
|
+
const posix = toPosix(path).replace(/^\.\//, '')
|
|
70
|
+
let end = posix.length
|
|
71
|
+
while (end > 0 && posix[end - 1] === '/') end -= 1
|
|
72
|
+
return posix.slice(0, end)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const relativeToPackage = (modulePath: string, packagePath: string): string | undefined => {
|
|
76
|
+
if (packagePath === '.' || packagePath === '') return modulePath
|
|
77
|
+
const prefix = `${packagePath}/`
|
|
78
|
+
return modulePath.startsWith(prefix) ? modulePath.slice(prefix.length) : undefined
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const join = (...parts: readonly string[]): string => parts.filter(Boolean).join('/')
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The area a module belongs to by convention: `depth` directory levels, counted below a source
|
|
85
|
+
* root when the module sits under one. A module directly in the package or in a bare source root
|
|
86
|
+
* has no conventional area — there is no directory grouping it with anything.
|
|
87
|
+
*/
|
|
88
|
+
export const conventionalAreaPath = (
|
|
89
|
+
module: AreaModule,
|
|
90
|
+
depth = DEFAULT_AREA_DEPTH,
|
|
91
|
+
roots: readonly string[] = DEFAULT_AREA_ROOTS,
|
|
92
|
+
): string | undefined => {
|
|
93
|
+
const relative = relativeToPackage(normalize(module.path), normalize(module.packagePath))
|
|
94
|
+
if (relative === undefined) return undefined
|
|
95
|
+
const segments = relative.split('/')
|
|
96
|
+
segments.pop()
|
|
97
|
+
if (!segments.length) return undefined
|
|
98
|
+
|
|
99
|
+
const rootPrefix: string[] = []
|
|
100
|
+
let rest = segments
|
|
101
|
+
if (segments[0] && roots.includes(segments[0])) {
|
|
102
|
+
rootPrefix.push(segments[0])
|
|
103
|
+
rest = segments.slice(1)
|
|
104
|
+
}
|
|
105
|
+
const taken = rest.slice(0, Math.max(1, depth))
|
|
106
|
+
if (!taken.length) return undefined
|
|
107
|
+
|
|
108
|
+
const packagePath = normalize(module.packagePath)
|
|
109
|
+
return join(packagePath === '.' ? '' : packagePath, ...rootPrefix, ...taken)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const encloses = (candidate: string, path: string): boolean => path === candidate || path.startsWith(`${candidate}/`)
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Derive the areas of a repository.
|
|
116
|
+
*
|
|
117
|
+
* Every module is assigned to exactly one area — the most specific one containing it — so the
|
|
118
|
+
* containment graph stays a tree and an aggregation at area scope has one answer per module.
|
|
119
|
+
* Nested areas keep their shape: `src` names the area that holds what is directly in `src`, and
|
|
120
|
+
* `src/query` its own, with `src` recorded as the parent.
|
|
121
|
+
*/
|
|
122
|
+
export const deriveAreas = (options: DeriveAreasOptions): readonly DerivedArea[] => {
|
|
123
|
+
const depth = options.depth ?? DEFAULT_AREA_DEPTH
|
|
124
|
+
const roots = options.roots ?? [...DEFAULT_AREA_ROOTS]
|
|
125
|
+
const ownership = (options.ownership ?? []).map((record) => ({ ...record, path: normalize(record.path) }))
|
|
126
|
+
|
|
127
|
+
/** Candidate path to the package it belongs to. */
|
|
128
|
+
const candidates = new Map<string, string>()
|
|
129
|
+
for (const module of options.modules) {
|
|
130
|
+
const path = conventionalAreaPath(module, depth, roots)
|
|
131
|
+
if (path && !candidates.has(path)) candidates.set(path, module.packageId)
|
|
132
|
+
}
|
|
133
|
+
// An ownership record naming a directory that holds observed code is an area by declaration.
|
|
134
|
+
for (const record of ownership) {
|
|
135
|
+
if (!record.path || candidates.has(record.path)) continue
|
|
136
|
+
const owner = options.modules.find((module) => encloses(record.path, normalize(module.path)))
|
|
137
|
+
if (owner) candidates.set(record.path, owner.packageId)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const paths = [...candidates.keys()].sort()
|
|
141
|
+
const byLength = [...paths].sort((a, b) => b.length - a.length || a.localeCompare(b))
|
|
142
|
+
const moduleIds = new Map<string, string[]>(paths.map((path) => [path, []]))
|
|
143
|
+
|
|
144
|
+
for (const module of options.modules) {
|
|
145
|
+
const modulePath = normalize(module.path)
|
|
146
|
+
const area = byLength.find((candidate) => encloses(candidate, modulePath))
|
|
147
|
+
if (area) moduleIds.get(area)?.push(module.moduleId)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const ownershipByPath = new Map(ownership.map((record) => [record.path, record.id]))
|
|
151
|
+
|
|
152
|
+
return paths.map((path) => {
|
|
153
|
+
const parent = byLength.find((candidate) => candidate !== path && encloses(candidate, path))
|
|
154
|
+
const ownershipId = ownershipByPath.get(path)
|
|
155
|
+
return {
|
|
156
|
+
id: entityId('area', path),
|
|
157
|
+
path,
|
|
158
|
+
name: path.split('/').pop() ?? path,
|
|
159
|
+
packageId: candidates.get(path) as string,
|
|
160
|
+
...(parent ? { parentId: entityId('area', parent) } : {}),
|
|
161
|
+
moduleIds: [...(moduleIds.get(path) ?? [])].sort(),
|
|
162
|
+
...(ownershipId ? { ownershipId } : {}),
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Ownership paths no observed entity lives under.
|
|
169
|
+
*
|
|
170
|
+
* A configured path that matches nothing is almost always a rename or a typo, and it is invisible
|
|
171
|
+
* until something looks: the handoff still resolves, it just points at a directory that no longer
|
|
172
|
+
* holds what it claims.
|
|
173
|
+
*/
|
|
174
|
+
export const unobservedOwnershipPaths = (
|
|
175
|
+
ownership: readonly AreaOwnership[],
|
|
176
|
+
observedPaths: readonly string[],
|
|
177
|
+
): readonly AreaOwnership[] => {
|
|
178
|
+
const paths = observedPaths.map(normalize)
|
|
179
|
+
return ownership
|
|
180
|
+
.map((record) => ({ ...record, path: normalize(record.path) }))
|
|
181
|
+
.filter((record) => record.path && !paths.some((path) => encloses(record.path, path)))
|
|
182
|
+
}
|