@agentskit/doc-bridge 1.7.45 → 1.10.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 +488 -0
- package/CONTRIBUTING.md +23 -0
- 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 +15871 -6218
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +95 -6
- package/dist/config/index.js.map +1 -1
- package/dist/{index-BUL0q7s8.d.ts → index-7wYGbllW.d.ts} +134 -1
- package/dist/index.d.ts +9880 -1062
- package/dist/index.js +16143 -6127
- 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/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 +12 -1
- package/docs/agent-corpus/OVERVIEW.md +25 -0
- package/docs/agent-corpus/chat.md +5 -1
- package/docs/agent-corpus/cli.md +16 -0
- package/docs/agent-corpus/conformance.md +12 -0
- package/docs/agent-corpus/doc-bridge.md +47 -0
- package/docs/agent-corpus/doctor.md +8 -0
- package/docs/agent-corpus/gates.md +4 -0
- package/docs/agent-corpus/mcp.md +13 -0
- package/docs/agent-corpus/memory.md +4 -0
- package/docs/agent-corpus/query.md +33 -0
- 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 +38 -3
- 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 +33 -1
- package/docs/spec/cli.md +30 -10
- package/docs/spec/config-v1.md +157 -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 +14 -0
- package/docs/verification-harness.md +11 -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 +34 -15
- package/scripts/check-ecosystem-upstream.mjs +34 -6
- 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/program.ts +684 -74
- package/src/cli/usage.ts +71 -0
- package/src/config/defaults.ts +1 -0
- package/src/config/index.ts +4 -0
- package/src/config/schema.ts +126 -2
- package/src/conformance/documentation-standard-v1.ts +8 -6
- package/src/discovery/areas.ts +197 -0
- package/src/discovery/documentation.ts +211 -5
- 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 +485 -101
- package/src/doctor/run-doctor.ts +244 -12
- 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 +2 -4
- package/src/findings/report.ts +103 -0
- 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/llms-txt.ts +48 -8
- package/src/index-builder/project-corpus.ts +111 -0
- package/src/index.ts +630 -2
- 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 +101 -10
- 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 +2 -2
- 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 +30 -10
- 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 +14 -1
- 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 +1 -4
- package/src/version.ts +1 -1
- package/src/workflow/engine.ts +36 -11
- package/scripts/verification-harness.mjs +0 -482
|
@@ -2,8 +2,14 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
|
2
2
|
import { dirname, join } from 'node:path'
|
|
3
3
|
|
|
4
4
|
import type { DocBridgeConfigV1 } from '../config/schema.js'
|
|
5
|
+
import { applyDocumentationDeclarations } from '../discovery/documentation.js'
|
|
6
|
+
import { discoverRepository } from '../discovery/repository.js'
|
|
7
|
+
import { readBoundedText, type TextReadBudget } from '../lib/bounded-text.js'
|
|
5
8
|
import { toPosix } from '../lib/paths.js'
|
|
6
|
-
import type { DocBridgeIndexV1 } from '../schemas/doc-bridge-index.js'
|
|
9
|
+
import type { DocBridgeIndexV1, KnowledgeEntry } from '../schemas/doc-bridge-index.js'
|
|
10
|
+
import { RETRIEVAL_MAX_ENTRIES } from '../schemas/retrieval-index.js'
|
|
11
|
+
import type { DiscoverySnapshotV1 } from '../schemas/knowledge.js'
|
|
12
|
+
import type { RetrievalIndexV1 } from '../schemas/retrieval-index.js'
|
|
7
13
|
import { buildLookup, collectPackages } from './build-handoffs.js'
|
|
8
14
|
import { renderCapabilitiesJson } from './capabilities.js'
|
|
9
15
|
import { sha256NormalizedV1 } from './content-hash.js'
|
|
@@ -11,12 +17,33 @@ import { renderLlmsTxt } from './llms-txt.js'
|
|
|
11
17
|
import { scanHumanDocs } from './human-adapters/index.js'
|
|
12
18
|
import { discoverNxProjects } from './plugins/nx.js'
|
|
13
19
|
import { discoverPnpmPackages } from './plugins/pnpm-monorepo.js'
|
|
20
|
+
import { repositoryInputs } from './project-corpus.js'
|
|
14
21
|
import { scanAgentCorpus } from './scan-corpus.js'
|
|
22
|
+
import { SEARCH_LEXICON_VERSION } from '../query/text.js'
|
|
23
|
+
import { projectRetrievalIndex, toKnowledgeEntry } from '../retrieval/project.js'
|
|
24
|
+
import { resolveSearchParams, resolveSearchWeights } from '../retrieval/weights.js'
|
|
25
|
+
import { projectEnrichmentOverlay, readEnrichmentOverlay } from '../enrich/overlay.js'
|
|
26
|
+
import type { EnrichmentOverlayV1 } from '../schemas/enrichment.js'
|
|
15
27
|
|
|
16
28
|
export type BuildIndexOptions = {
|
|
17
29
|
readonly root?: string
|
|
18
30
|
readonly config: DocBridgeConfigV1
|
|
19
31
|
readonly write?: boolean
|
|
32
|
+
/**
|
|
33
|
+
* A snapshot to project instead of scanning. The build scans when none is given; a caller that
|
|
34
|
+
* already holds the snapshot — a workflow stage, a test — passes it so the index and the
|
|
35
|
+
* artifacts it sits next to describe the same observation.
|
|
36
|
+
*/
|
|
37
|
+
readonly snapshot?: DiscoverySnapshotV1
|
|
38
|
+
/**
|
|
39
|
+
* Which enrichment overlay the projection reads.
|
|
40
|
+
*
|
|
41
|
+
* Omitted, the builder reads the one on disk while the Registry is enabled — the normal path.
|
|
42
|
+
* `'ignore'` builds the deterministic baseline even then, and an overlay object projects that
|
|
43
|
+
* one instead. Both exist so the retrieval delta can measure the same snapshot twice, with the
|
|
44
|
+
* overlay and without it, rather than comparing two different repositories.
|
|
45
|
+
*/
|
|
46
|
+
readonly overlay?: EnrichmentOverlayV1 | 'ignore'
|
|
20
47
|
}
|
|
21
48
|
|
|
22
49
|
export type BuildIndexResult = {
|
|
@@ -37,6 +64,58 @@ const projectName = (root: string, config: DocBridgeConfigV1): string => {
|
|
|
37
64
|
return toPosix(root.split('/').pop() ?? 'project')
|
|
38
65
|
}
|
|
39
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Observe, declare, project.
|
|
69
|
+
*
|
|
70
|
+
* The snapshot is the observation; documentation declarations (`covers`, frontmatter relations)
|
|
71
|
+
* are applied on top of it so a document's declared coverage ranks and routes like an observed
|
|
72
|
+
* edge; the projection is computed from the result. Document bodies are read once, bounded, and
|
|
73
|
+
* handed to both steps by path.
|
|
74
|
+
*/
|
|
75
|
+
const projectFromSnapshot = (
|
|
76
|
+
root: string,
|
|
77
|
+
config: DocBridgeConfigV1,
|
|
78
|
+
given: DiscoverySnapshotV1 | undefined,
|
|
79
|
+
lookup: ReturnType<typeof buildLookup>['lookup'],
|
|
80
|
+
curated: readonly KnowledgeEntry[],
|
|
81
|
+
requested: BuildIndexOptions['overlay'],
|
|
82
|
+
): { readonly projection: RetrievalIndexV1 } => {
|
|
83
|
+
const observed = given ?? discoverRepository({ root, config })
|
|
84
|
+
const budget: TextReadBudget = { used: 0 }
|
|
85
|
+
const contents = new Map<string, string>()
|
|
86
|
+
for (const entity of observed.entities) {
|
|
87
|
+
if (entity.kind !== 'document' || !entity.path) continue
|
|
88
|
+
try {
|
|
89
|
+
contents.set(entity.path, readBoundedText(join(root, entity.path), budget))
|
|
90
|
+
} catch {
|
|
91
|
+
// Unreadable now: the entity still projects from what the snapshot recorded about it.
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const declared = applyDocumentationDeclarations(
|
|
95
|
+
observed,
|
|
96
|
+
[...contents.entries()].map(([path, content]) => ({ path, content })),
|
|
97
|
+
{ agentRoot: config.corpus.agent.root },
|
|
98
|
+
).snapshot
|
|
99
|
+
/*
|
|
100
|
+
* The accepted enrichment overlay is consulted only while the Registry is enabled: switching it
|
|
101
|
+
* off restores the deterministic baseline exactly. The read never writes, and an entry whose
|
|
102
|
+
* target moved since it was accepted is expired here rather than ranked.
|
|
103
|
+
*/
|
|
104
|
+
const accepted = requested === 'ignore'
|
|
105
|
+
? undefined
|
|
106
|
+
: (requested ?? (config.intelligence?.registry?.enabled ? readEnrichmentOverlay(root) : undefined))
|
|
107
|
+
const overlay = accepted ? projectEnrichmentOverlay(accepted, declared) : undefined
|
|
108
|
+
const projection = projectRetrievalIndex({
|
|
109
|
+
snapshot: declared,
|
|
110
|
+
config,
|
|
111
|
+
routes: lookup,
|
|
112
|
+
curated: curated.map((entry) => ({ id: entry.id, path: entry.path, title: entry.title, ...(entry.description ? { description: entry.description } : {}) })),
|
|
113
|
+
...(overlay ? { overlay } : {}),
|
|
114
|
+
readDocument: (path) => contents.get(path),
|
|
115
|
+
})
|
|
116
|
+
return { projection }
|
|
117
|
+
}
|
|
118
|
+
|
|
40
119
|
const existingGeneratedAt = (indexPath: string, contentHash: string): string | undefined => {
|
|
41
120
|
try {
|
|
42
121
|
const index = JSON.parse(readFileSync(indexPath, 'utf8')) as {
|
|
@@ -59,7 +138,12 @@ export const buildDocBridgeIndex = (opts: BuildIndexOptions): BuildIndexResult =
|
|
|
59
138
|
const indexPath = join(root, outFile)
|
|
60
139
|
|
|
61
140
|
const corpus = scanAgentCorpus(root, config)
|
|
62
|
-
const
|
|
141
|
+
const curated = corpus.map(({ absPath: _a, relPath: _r, frontmatter: _f, ...entry }) => entry)
|
|
142
|
+
const retrieval = {
|
|
143
|
+
lexiconVersion: SEARCH_LEXICON_VERSION,
|
|
144
|
+
weights: resolveSearchWeights(config.retrieval?.weights),
|
|
145
|
+
params: resolveSearchParams(config.retrieval?.params),
|
|
146
|
+
}
|
|
63
147
|
|
|
64
148
|
const shouldDiscover =
|
|
65
149
|
config.routing?.plugin === 'pnpm-monorepo' ||
|
|
@@ -78,11 +162,39 @@ export const buildDocBridgeIndex = (opts: BuildIndexOptions): BuildIndexResult =
|
|
|
78
162
|
|
|
79
163
|
const { lookup, handoffs } = buildLookup(config, packages, corpus, outFile, humanDocs, root)
|
|
80
164
|
|
|
165
|
+
/*
|
|
166
|
+
* Retrieval reads this index, so whatever is missing here is invisible to an agent however well
|
|
167
|
+
* it is ranked. The projection puts every entity the snapshot observed in it — documents,
|
|
168
|
+
* modules, areas, packages — next to the routes the configuration declares. It is a function of
|
|
169
|
+
* the snapshot: the index has no scanner of its own, so a record retrieval can find is an entity
|
|
170
|
+
* discovery observed, with the same id and the same content hash.
|
|
171
|
+
*/
|
|
172
|
+
const projected = config.retrieval?.corpus?.enabled === false ? undefined : projectFromSnapshot(root, config, opts.snapshot, lookup, curated, opts.overlay)
|
|
173
|
+
const projection = projected?.projection
|
|
174
|
+
const inputs = projected ? repositoryInputs(root, config) : undefined
|
|
175
|
+
const curatedPaths = new Set(curated.map((entry) => entry.path))
|
|
176
|
+
/*
|
|
177
|
+
* `knowledge[]` keeps every reader that predates the projection working: the curated sidecars
|
|
178
|
+
* first, in reading order, then every projected document and module. Body text lives once, in
|
|
179
|
+
* the projection, so a projected index carries no bodies here.
|
|
180
|
+
*/
|
|
181
|
+
const knowledge: KnowledgeEntry[] = projection
|
|
182
|
+
? [
|
|
183
|
+
...curated.map(({ body: _body, ...entry }) => entry),
|
|
184
|
+
...projection.entries
|
|
185
|
+
.filter((entry) => (entry.kind === 'document' || entry.kind === 'module') && !curatedPaths.has(entry.path))
|
|
186
|
+
.map(toKnowledgeEntry),
|
|
187
|
+
]
|
|
188
|
+
: curated
|
|
189
|
+
|
|
81
190
|
const hashPayload = {
|
|
82
191
|
schemaVersion: 1,
|
|
83
192
|
knowledge,
|
|
84
193
|
handoffs,
|
|
85
194
|
lookup,
|
|
195
|
+
retrieval,
|
|
196
|
+
...(inputs ? { inputs } : {}),
|
|
197
|
+
...(projection ? { projection: projection.contentHash } : {}),
|
|
86
198
|
}
|
|
87
199
|
|
|
88
200
|
const contentHash = sha256NormalizedV1(hashPayload)
|
|
@@ -95,6 +207,23 @@ export const buildDocBridgeIndex = (opts: BuildIndexOptions): BuildIndexResult =
|
|
|
95
207
|
knowledge,
|
|
96
208
|
handoffs,
|
|
97
209
|
lookup,
|
|
210
|
+
...(inputs ? { inputs } : {}),
|
|
211
|
+
retrieval,
|
|
212
|
+
...(projection ? { projection } : {}),
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/*
|
|
216
|
+
* An artifact its own parser refuses is not an artifact. The bound on `knowledge[]` used to be
|
|
217
|
+
* lower than the bound on the projection it mirrors, so a large repository got an index that
|
|
218
|
+
* `index` reported writing and every reader — `doctor`, `search`, the MCP server — then rejected,
|
|
219
|
+
* with a schema dump naming an array instead of a corpus. The bounds are one constant now; this
|
|
220
|
+
* says so at the point of production, where the numbers and the remedy are both known.
|
|
221
|
+
*/
|
|
222
|
+
if (knowledge.length > RETRIEVAL_MAX_ENTRIES) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`The index would carry ${knowledge.length} knowledge entries, above the ${RETRIEVAL_MAX_ENTRIES} an index may hold. ` +
|
|
225
|
+
'Narrow the corpus with `corpus.*.include` or `corpus.*.exclude`, or split the repository into more than one index.',
|
|
226
|
+
)
|
|
98
227
|
}
|
|
99
228
|
|
|
100
229
|
if (write) {
|
|
@@ -111,7 +240,7 @@ export const buildDocBridgeIndex = (opts: BuildIndexOptions): BuildIndexResult =
|
|
|
111
240
|
if (write) {
|
|
112
241
|
writeFileSync(
|
|
113
242
|
llmsTxtPath,
|
|
114
|
-
renderLlmsTxt(config, knowledge, index.project?.name ?? 'project'),
|
|
243
|
+
renderLlmsTxt(config, knowledge, index.project?.name ?? 'project', { root }),
|
|
115
244
|
'utf8',
|
|
116
245
|
)
|
|
117
246
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { DocBridgeConfigV1 } from '../config/schema.js'
|
|
2
|
+
import { renderNamedTemplate } from '../render/template-source.js'
|
|
2
3
|
import type { KnowledgeEntry } from '../schemas/doc-bridge-index.js'
|
|
4
|
+
import { isProjectedEntry } from './project-corpus.js'
|
|
3
5
|
|
|
4
6
|
const routePath = (path: string, pathPrefix: string | undefined): string => {
|
|
5
7
|
const normalizedPath = path.replaceAll('\\', '/').replace(/\.(?:md|mdx)$/, '')
|
|
@@ -20,20 +22,58 @@ export const knowledgeUrl = (
|
|
|
20
22
|
return new URL(relativePath, base).toString()
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
export
|
|
25
|
+
export type LlmsTxtEntry = {
|
|
26
|
+
readonly title: string
|
|
27
|
+
readonly url: string
|
|
28
|
+
readonly description?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type LlmsTxtVariables = {
|
|
32
|
+
readonly project: string
|
|
33
|
+
readonly preamble: string
|
|
34
|
+
readonly entries: readonly LlmsTxtEntry[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** What the `llms.txt` template sees. Exported so a project overriding the template knows the variables. */
|
|
38
|
+
export const llmsTxtVariables = (
|
|
24
39
|
config: DocBridgeConfigV1,
|
|
25
40
|
knowledge: readonly KnowledgeEntry[],
|
|
26
41
|
projectName: string,
|
|
27
|
-
):
|
|
42
|
+
): LlmsTxtVariables => {
|
|
28
43
|
const preamble =
|
|
29
44
|
config.index?.llmsTxt?.preamble ??
|
|
30
45
|
`# ${projectName}\n\n> Agent-readable documentation index generated by ak-docs (@agentskit/doc-bridge).\n`
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
/*
|
|
48
|
+
* llms.txt is a reading order, not an inventory. The index also carries every document and
|
|
49
|
+
* module the repository projects into it for retrieval; listing those here would bury the
|
|
50
|
+
* curated entry points an agent is meant to start from. The filter lives in the renderer so
|
|
51
|
+
* every caller — the builder and the conformance profile that re-renders to check freshness —
|
|
52
|
+
* agrees on what the file contains.
|
|
53
|
+
*/
|
|
54
|
+
const entries = knowledge
|
|
55
|
+
.filter((entry) => !isProjectedEntry(entry))
|
|
56
|
+
.slice(0, 500)
|
|
57
|
+
.map((entry) => ({
|
|
58
|
+
title: entry.title,
|
|
59
|
+
url: knowledgeUrl(entry.path, config.index?.llmsTxt),
|
|
60
|
+
...(entry.description ? { description: entry.description } : {}),
|
|
61
|
+
}))
|
|
37
62
|
|
|
38
|
-
return
|
|
63
|
+
return { project: projectName, preamble: preamble.trim(), entries }
|
|
39
64
|
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Render `llms.txt` from the bundled template, or the project's under `render.templates`.
|
|
68
|
+
*
|
|
69
|
+
* `root` resolves a project template path; the builder and the conformance profile both pass
|
|
70
|
+
* theirs, so the file the builder writes is the file the profile expects. The bundled template
|
|
71
|
+
* reproduces the previous concatenation byte for byte, which the llms and federation tests hold
|
|
72
|
+
* it to.
|
|
73
|
+
*/
|
|
74
|
+
export const renderLlmsTxt = (
|
|
75
|
+
config: DocBridgeConfigV1,
|
|
76
|
+
knowledge: readonly KnowledgeEntry[],
|
|
77
|
+
projectName: string,
|
|
78
|
+
options: { readonly root?: string } = {},
|
|
79
|
+
): string => renderNamedTemplate('llms.txt', llmsTxtVariables(config, knowledge, projectName), config, options.root)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { basename, extname, relative, resolve, sep } from 'node:path'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
|
|
4
|
+
import type { DocBridgeConfigV1 } from '../config/schema.js'
|
|
5
|
+
import { DOCUMENT_EXTENSIONS, SOURCE_EXTENSIONS, safeWalkOptions } from '../discovery/inputs.js'
|
|
6
|
+
import { toPosix } from '../lib/paths.js'
|
|
7
|
+
import { safeWalkFiles } from '../safety/repository.js'
|
|
8
|
+
import type { KnowledgeEntry } from '../schemas/doc-bridge-index.js'
|
|
9
|
+
import { sha256NormalizedV1 } from './content-hash.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The freshness fingerprint of the index.
|
|
13
|
+
*
|
|
14
|
+
* This file used to be retrieval's own scanner: it walked the repository, parsed every module and
|
|
15
|
+
* document a second time, and projected them into index records that shared nothing with the
|
|
16
|
+
* snapshot but a path. That projection is now a function of the snapshot (`src/retrieval/project.ts`),
|
|
17
|
+
* and what remains here is the one thing a query needs on every call: a cheap way to tell whether
|
|
18
|
+
* the index on disk still describes the files on disk. One walk, one hash per file, no parsing.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Bumped when what the index derives from its inputs changes shape, so an index built by older
|
|
23
|
+
* code is stale by version even when every file matches.
|
|
24
|
+
*/
|
|
25
|
+
export const CORPUS_PROJECTION_VERSION = 2 as const
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Entry types the projection produces into `knowledge[]`. Curated corpus entries are always
|
|
29
|
+
* `agent-doc`, so this is what distinguishes a record retrieval discovered from one a human wrote
|
|
30
|
+
* a sidecar for — which surfaces such as llms.txt need, since a reading order is curated, not
|
|
31
|
+
* enumerated.
|
|
32
|
+
*/
|
|
33
|
+
export const PROJECTED_ENTRY_TYPES = ['document', 'module'] as const
|
|
34
|
+
|
|
35
|
+
export const isProjectedEntry = (entry: Pick<KnowledgeEntry, 'type'>): boolean =>
|
|
36
|
+
(PROJECTED_ENTRY_TYPES as readonly string[]).includes(entry.type)
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Configuration files the index is derived from. Narrow on purpose: any `.json` would make an
|
|
40
|
+
* unrelated data file mark the index stale, and a generated artifact could then invalidate the
|
|
41
|
+
* artifact generated from it.
|
|
42
|
+
*/
|
|
43
|
+
const CONFIG_INPUT_PATTERN =
|
|
44
|
+
/(?:^|\/)(?:package\.json|pnpm-workspace\.ya?ml|tsconfig(?:\.[\w.-]+)?\.json|jsconfig\.json|meta\.json|doc-bridge\.config\.(?:json|ya?ml|js|ts|mjs|cjs))$/
|
|
45
|
+
|
|
46
|
+
const INPUT_EXTENSIONS = [...new Set([...SOURCE_EXTENSIONS, ...DOCUMENT_EXTENSIONS, '.json', '.yaml', '.yml'])]
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Configuration sections the index is derived from.
|
|
50
|
+
*
|
|
51
|
+
* The same files under a different configuration project a different index, so the configuration
|
|
52
|
+
* belongs in the fingerprint — but only the part of it that can change the artifact. Hashing the
|
|
53
|
+
* whole configuration would report a stale index when an unrelated section changed (a gate
|
|
54
|
+
* preset, a report option), which is a false alarm that teaches people to ignore the check.
|
|
55
|
+
*/
|
|
56
|
+
const INDEX_CONFIGURATION_KEYS = ['project', 'corpus', 'index', 'routing', 'safety', 'retrieval', 'analysis'] as const
|
|
57
|
+
|
|
58
|
+
export const indexConfigurationHash = (config: DocBridgeConfigV1 | undefined): string =>
|
|
59
|
+
sha256NormalizedV1(
|
|
60
|
+
Object.fromEntries(
|
|
61
|
+
INDEX_CONFIGURATION_KEYS.filter((key) => config?.[key] !== undefined).map((key) => [key, config?.[key]]),
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
export type RepositoryInputsV1 = {
|
|
66
|
+
/** Hash of every input path and its content. Equal hashes mean an equal projection. */
|
|
67
|
+
readonly hash: string
|
|
68
|
+
readonly fileCount: number
|
|
69
|
+
readonly projectionVersion: number
|
|
70
|
+
/** True when a safety limit stopped the walk, so the file set is not the whole repository. */
|
|
71
|
+
readonly incomplete?: boolean
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const isInput = (path: string, name: string): boolean => {
|
|
75
|
+
const extension = extname(name)
|
|
76
|
+
if ((DOCUMENT_EXTENSIONS as readonly string[]).includes(extension)) return true
|
|
77
|
+
if ((SOURCE_EXTENSIONS as readonly string[]).includes(extension)) return true
|
|
78
|
+
return CONFIG_INPUT_PATTERN.test(path)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Walk the repository once and hash every input the index derives from: sources, documents and
|
|
83
|
+
* the configuration files that decide how they resolve.
|
|
84
|
+
*/
|
|
85
|
+
export const repositoryInputs = (root: string, config: DocBridgeConfigV1 | undefined): RepositoryInputsV1 => {
|
|
86
|
+
const projectRoot = resolve(root)
|
|
87
|
+
const walk = safeWalkFiles(projectRoot, { extensions: INPUT_EXTENSIONS, ...safeWalkOptions(config) })
|
|
88
|
+
const fingerprints: [string, string][] = []
|
|
89
|
+
|
|
90
|
+
for (const absPath of walk.files) {
|
|
91
|
+
const path = toPosix(relative(projectRoot, absPath).split(sep).join('/'))
|
|
92
|
+
if (!isInput(path, basename(absPath))) continue
|
|
93
|
+
try {
|
|
94
|
+
fingerprints.push([path, sha256NormalizedV1(readFileSync(absPath, 'utf8'))])
|
|
95
|
+
} catch {
|
|
96
|
+
// An unreadable input cannot be projected, and must not silently change the hash either.
|
|
97
|
+
fingerprints.push([path, 'unreadable'])
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
hash: sha256NormalizedV1({
|
|
103
|
+
projectionVersion: CORPUS_PROJECTION_VERSION,
|
|
104
|
+
configurationHash: indexConfigurationHash(config),
|
|
105
|
+
files: fingerprints,
|
|
106
|
+
}),
|
|
107
|
+
fileCount: fingerprints.length,
|
|
108
|
+
projectionVersion: CORPUS_PROJECTION_VERSION,
|
|
109
|
+
...(walk.incomplete ? { incomplete: true } : {}),
|
|
110
|
+
}
|
|
111
|
+
}
|