@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
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import { applyBudget, type BudgetedSection } from '../budget/sections.js'
|
|
2
|
+
import type { DocBridgeConfigV1 } from '../config/schema.js'
|
|
3
|
+
import { handoffForEntity, resolveHandoffEntry } from '../query/handoff.js'
|
|
4
|
+
import { runQuery } from '../query/query.js'
|
|
5
|
+
import { searchIndex, type SearchMatch, type SearchOptions } from '../query/search.js'
|
|
6
|
+
import { formatRetrievedDocuments, type RetrievedDocument } from '../retriever/doc-bridge-retriever.js'
|
|
7
|
+
import type { AgentHandoffV1 } from '../schemas/agent-handoff.js'
|
|
8
|
+
import type { BudgetReport } from '../schemas/budget.js'
|
|
9
|
+
import type { DocBridgeIndexV1 } from '../schemas/doc-bridge-index.js'
|
|
10
|
+
import type { Evidence, KnowledgeDiagnostic, ReconciliationReportV1 } from '../schemas/knowledge.js'
|
|
11
|
+
import type { Confidence, RetrievalEntry, RetrievalIndexV1, RetrievalKind } from '../schemas/retrieval-index.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The two calls an agent makes before editing: search, and one lookup that answers "tell me
|
|
15
|
+
* about this" with the entity, its neighbourhood, the documents about it, its handoff, its open
|
|
16
|
+
* diagnostics and its evidence — bounded by a declared token budget when the caller has one.
|
|
17
|
+
*
|
|
18
|
+
* Both read the retrieval projection the CLI reads and rank with the same `searchIndex`, so a
|
|
19
|
+
* result here is the result `ak-docs search` prints for the same query and index; a test compares
|
|
20
|
+
* the two rather than assuming it. Neither imports anything under `src/agents`: this is the
|
|
21
|
+
* deterministic layer, and a lookup costs milliseconds with no model in the loop.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export const KNOWLEDGE_TOOLS_SCHEMA_VERSION = 1 as const
|
|
25
|
+
|
|
26
|
+
/** Characters of body text an evidence excerpt carries: enough to recognise the passage, not to read it. */
|
|
27
|
+
const EXCERPT_CHARS = 240
|
|
28
|
+
const MAX_NEIGHBOURS_PER_KIND = 32
|
|
29
|
+
const MAX_DIAGNOSTICS = 32
|
|
30
|
+
const MAX_EVIDENCE = 8
|
|
31
|
+
export const MAX_LOOKUP_DEPTH = 3
|
|
32
|
+
|
|
33
|
+
export type KnowledgeSearchRequest = {
|
|
34
|
+
readonly query: string
|
|
35
|
+
/** Keep only these entry kinds. Filtering happens after ranking, so it never changes an order. */
|
|
36
|
+
readonly kinds?: readonly RetrievalKind[] | undefined
|
|
37
|
+
readonly limit?: number | undefined
|
|
38
|
+
readonly explain?: boolean | undefined
|
|
39
|
+
readonly budgetTokens?: number | undefined
|
|
40
|
+
/** The `--agent` prior. */
|
|
41
|
+
readonly agent?: boolean | undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type KnowledgeSearchResult = SearchMatch & {
|
|
45
|
+
readonly title?: string
|
|
46
|
+
/** The opening of the projected body: the evidence excerpt a budget sheds first. */
|
|
47
|
+
readonly excerpt?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type KnowledgeSearchResponse = {
|
|
51
|
+
readonly type: 'knowledge-search'
|
|
52
|
+
readonly schemaVersion: typeof KNOWLEDGE_TOOLS_SCHEMA_VERSION
|
|
53
|
+
readonly source: string
|
|
54
|
+
readonly query: string
|
|
55
|
+
readonly kinds?: readonly RetrievalKind[]
|
|
56
|
+
readonly limit: number
|
|
57
|
+
readonly count: number
|
|
58
|
+
readonly results: readonly KnowledgeSearchResult[]
|
|
59
|
+
readonly budget?: BudgetReport
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type KnowledgeLookupRequest = {
|
|
63
|
+
readonly id?: string | undefined
|
|
64
|
+
readonly path?: string | undefined
|
|
65
|
+
/** How many hops of the graph to include as neighbours. 1 by default, at most `MAX_LOOKUP_DEPTH`. */
|
|
66
|
+
readonly depth?: number | undefined
|
|
67
|
+
readonly budgetTokens?: number | undefined
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type KnowledgeLookupEntity = {
|
|
71
|
+
readonly id: string
|
|
72
|
+
readonly kind: RetrievalKind
|
|
73
|
+
readonly path: string
|
|
74
|
+
readonly title: string
|
|
75
|
+
readonly summary?: string
|
|
76
|
+
readonly audience?: RetrievalEntry['audience']
|
|
77
|
+
readonly aliases: readonly string[]
|
|
78
|
+
readonly symbols?: readonly string[]
|
|
79
|
+
readonly tags: readonly string[]
|
|
80
|
+
readonly provenance: RetrievalEntry['provenance']
|
|
81
|
+
readonly confidence: Confidence
|
|
82
|
+
readonly contentHash: string
|
|
83
|
+
readonly ownershipId?: string
|
|
84
|
+
readonly areaId?: string
|
|
85
|
+
readonly packageId?: string
|
|
86
|
+
readonly pagerank: number
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type KnowledgeNeighbour = {
|
|
90
|
+
readonly id: string
|
|
91
|
+
readonly kind: RetrievalKind
|
|
92
|
+
readonly path: string
|
|
93
|
+
readonly title: string
|
|
94
|
+
readonly summary?: string
|
|
95
|
+
/** Whether the edge points at the entity or away from it. */
|
|
96
|
+
readonly direction: 'inbound' | 'outbound'
|
|
97
|
+
readonly confidence: Confidence
|
|
98
|
+
/** Hops from the entity. */
|
|
99
|
+
readonly distance: number
|
|
100
|
+
/** The entry this one was reached through, when it is more than one hop away. */
|
|
101
|
+
readonly via?: string
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type KnowledgeDocumentRef = {
|
|
105
|
+
readonly id: string
|
|
106
|
+
readonly path: string
|
|
107
|
+
readonly title: string
|
|
108
|
+
readonly summary?: string
|
|
109
|
+
readonly audience?: RetrievalEntry['audience']
|
|
110
|
+
readonly pagerank: number
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type KnowledgeEvidence = Evidence & {
|
|
114
|
+
readonly excerpt?: string
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type KnowledgeLookupResponse = {
|
|
118
|
+
readonly type: 'knowledge-lookup'
|
|
119
|
+
readonly schemaVersion: typeof KNOWLEDGE_TOOLS_SCHEMA_VERSION
|
|
120
|
+
readonly source: string
|
|
121
|
+
readonly depth: number
|
|
122
|
+
readonly entity: KnowledgeLookupEntity
|
|
123
|
+
/** Neighbours by relation kind, keys sorted, each list by distance then id. */
|
|
124
|
+
readonly neighbours: Readonly<Record<string, readonly KnowledgeNeighbour[]>>
|
|
125
|
+
readonly documents: {
|
|
126
|
+
readonly covering: readonly KnowledgeDocumentRef[]
|
|
127
|
+
readonly mentioning: readonly KnowledgeDocumentRef[]
|
|
128
|
+
}
|
|
129
|
+
readonly handoff: AgentHandoffV1
|
|
130
|
+
readonly diagnostics: {
|
|
131
|
+
/** The reconciliation report the diagnostics came from, or null when no workflow run exists. */
|
|
132
|
+
readonly reportHash: string | null
|
|
133
|
+
readonly open: readonly KnowledgeDiagnostic[]
|
|
134
|
+
}
|
|
135
|
+
readonly evidence: readonly KnowledgeEvidence[]
|
|
136
|
+
readonly budget?: BudgetReport
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export type KnowledgeLookupOptions = {
|
|
140
|
+
readonly root?: string
|
|
141
|
+
/** The latest reconciliation report, when a workflow run has produced one. */
|
|
142
|
+
readonly report?: () => ReconciliationReportV1 | undefined
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const requireProjection = (index: DocBridgeIndexV1, tool: string): RetrievalIndexV1 => {
|
|
146
|
+
if (!index.projection) throw new Error(`${tool} needs an index with a retrieval projection. Run: ak-docs index`)
|
|
147
|
+
return index.projection
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** The opening of a body, cut at a word boundary, so an excerpt never ends mid-token. */
|
|
151
|
+
const excerptOf = (body: string | undefined): string | undefined => {
|
|
152
|
+
const text = (body ?? '').replace(/\s+/g, ' ').trim()
|
|
153
|
+
if (!text) return undefined
|
|
154
|
+
if (text.length <= EXCERPT_CHARS) return text
|
|
155
|
+
const cut = text.lastIndexOf(' ', EXCERPT_CHARS)
|
|
156
|
+
return `${text.slice(0, cut > EXCERPT_CHARS / 2 ? cut : EXCERPT_CHARS).trimEnd()}…`
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const evidenceSource = (kind: RetrievalKind): Evidence['source'] =>
|
|
160
|
+
kind === 'module' ? 'code' : kind === 'document' ? 'documentation' : kind === 'intent' || kind === 'change' ? 'configuration' : 'derived'
|
|
161
|
+
|
|
162
|
+
const withoutSummary = <T extends { readonly summary?: string }>(value: T): T => {
|
|
163
|
+
const { summary: _summary, ...rest } = value
|
|
164
|
+
return rest as T
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/*
|
|
168
|
+
* ---------------------------------------------------------------------------------------------
|
|
169
|
+
* knowledge.search
|
|
170
|
+
* ---------------------------------------------------------------------------------------------
|
|
171
|
+
*/
|
|
172
|
+
|
|
173
|
+
const searchSections = (): readonly BudgetedSection<KnowledgeSearchResponse>[] => [
|
|
174
|
+
{
|
|
175
|
+
name: 'evidenceExcerpts',
|
|
176
|
+
content: [],
|
|
177
|
+
strip: (payload) => ({ ...payload, results: payload.results.map(({ excerpt: _excerpt, ...result }) => result) }),
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: 'summaries',
|
|
181
|
+
content: [],
|
|
182
|
+
strip: (payload) => ({ ...payload, results: payload.results.map(withoutSummary) }),
|
|
183
|
+
},
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
const sectionsWithContent = <T>(sections: readonly BudgetedSection<T>[], content: (name: BudgetedSection<T>['name']) => unknown): BudgetedSection<T>[] =>
|
|
187
|
+
sections.map((section) => ({ ...section, content: content(section.name) }))
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Ranked entries for a query.
|
|
191
|
+
*
|
|
192
|
+
* With no `kinds`, this is exactly `searchIndex(index, query, limit)` — the CLI's call — with a
|
|
193
|
+
* title and an excerpt added to each result. With `kinds`, the ranking is fetched deeper and
|
|
194
|
+
* filtered, because a filter applied before the limit would return fewer than `limit` results
|
|
195
|
+
* for a query that mostly matched another kind.
|
|
196
|
+
*/
|
|
197
|
+
export const knowledgeSearch = (index: DocBridgeIndexV1, request: KnowledgeSearchRequest): KnowledgeSearchResponse => {
|
|
198
|
+
const projection = requireProjection(index, 'knowledge.search')
|
|
199
|
+
const limit = request.limit ?? 20
|
|
200
|
+
const options: SearchOptions = { ...(request.explain ? { explain: true } : {}), ...(request.agent ? { agent: true } : {}) }
|
|
201
|
+
const kinds = request.kinds && request.kinds.length ? [...new Set(request.kinds)].sort() : undefined
|
|
202
|
+
const matches = kinds
|
|
203
|
+
? searchIndex(index, request.query, Math.max(limit * 4, 40), options)
|
|
204
|
+
.filter((match) => match.kind !== undefined && kinds.includes(match.kind))
|
|
205
|
+
.slice(0, limit)
|
|
206
|
+
: searchIndex(index, request.query, limit, options)
|
|
207
|
+
const byId = new Map(projection.entries.map((entry) => [entry.id, entry]))
|
|
208
|
+
|
|
209
|
+
const results: KnowledgeSearchResult[] = matches.map((match) => {
|
|
210
|
+
const entry = match.entityId ? byId.get(match.entityId) : undefined
|
|
211
|
+
const excerpt = excerptOf(entry?.fields.body)
|
|
212
|
+
return { ...match, ...(entry ? { title: entry.title } : {}), ...(excerpt ? { excerpt } : {}) }
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
const response: KnowledgeSearchResponse = {
|
|
216
|
+
type: 'knowledge-search',
|
|
217
|
+
schemaVersion: KNOWLEDGE_TOOLS_SCHEMA_VERSION,
|
|
218
|
+
source: `projection:${projection.contentHash}`,
|
|
219
|
+
query: request.query,
|
|
220
|
+
...(kinds ? { kinds } : {}),
|
|
221
|
+
limit,
|
|
222
|
+
count: results.length,
|
|
223
|
+
results,
|
|
224
|
+
}
|
|
225
|
+
if (request.budgetTokens === undefined) return response
|
|
226
|
+
|
|
227
|
+
const sections = sectionsWithContent(searchSections(), (name) =>
|
|
228
|
+
name === 'evidenceExcerpts' ? results.map((result) => result.excerpt).filter(Boolean) : results.map((result) => result.summary).filter(Boolean),
|
|
229
|
+
)
|
|
230
|
+
const budgeted = applyBudget(response, sections, request.budgetTokens)
|
|
231
|
+
return { ...budgeted.payload, budget: budgeted.budget }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/*
|
|
235
|
+
* ---------------------------------------------------------------------------------------------
|
|
236
|
+
* knowledge.lookup
|
|
237
|
+
* ---------------------------------------------------------------------------------------------
|
|
238
|
+
*/
|
|
239
|
+
|
|
240
|
+
const toEntity = (entry: RetrievalEntry): KnowledgeLookupEntity => ({
|
|
241
|
+
id: entry.id,
|
|
242
|
+
kind: entry.kind,
|
|
243
|
+
path: entry.path,
|
|
244
|
+
title: entry.title,
|
|
245
|
+
...(entry.summary ? { summary: entry.summary } : {}),
|
|
246
|
+
...(entry.audience ? { audience: entry.audience } : {}),
|
|
247
|
+
aliases: entry.aliases,
|
|
248
|
+
...(entry.symbols?.length ? { symbols: entry.symbols } : {}),
|
|
249
|
+
tags: entry.tags,
|
|
250
|
+
provenance: entry.provenance,
|
|
251
|
+
confidence: entry.confidence,
|
|
252
|
+
contentHash: entry.contentHash,
|
|
253
|
+
...(entry.ownershipId ? { ownershipId: entry.ownershipId } : {}),
|
|
254
|
+
...(entry.graph.areaId ? { areaId: entry.graph.areaId } : {}),
|
|
255
|
+
...(entry.graph.packageId ? { packageId: entry.graph.packageId } : {}),
|
|
256
|
+
pagerank: entry.graph.pagerank,
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
const toDocumentRef = (entry: RetrievalEntry): KnowledgeDocumentRef => ({
|
|
260
|
+
id: entry.id,
|
|
261
|
+
path: entry.path,
|
|
262
|
+
title: entry.title,
|
|
263
|
+
...(entry.summary ? { summary: entry.summary } : {}),
|
|
264
|
+
...(entry.audience ? { audience: entry.audience } : {}),
|
|
265
|
+
pagerank: entry.graph.pagerank,
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
const byCanonicality = (a: RetrievalEntry, b: RetrievalEntry): number => b.graph.pagerank - a.graph.pagerank || a.id.localeCompare(b.id)
|
|
269
|
+
|
|
270
|
+
type Edge = { readonly kind: string; readonly id: string; readonly confidence: Confidence; readonly direction: KnowledgeNeighbour['direction'] }
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Every edge at an entry, both directions, plus the hierarchy the projection records as fields
|
|
274
|
+
* rather than edges: a module is contained by its area, an area contains its modules. The
|
|
275
|
+
* projection carries `contains` nowhere in `inbound`/`outbound` — it is hierarchy, not a
|
|
276
|
+
* relation the ranker walks — but an agent asking about an area wants its modules listed.
|
|
277
|
+
*/
|
|
278
|
+
const edgesAt = (projection: RetrievalIndexV1, entry: RetrievalEntry): Edge[] => {
|
|
279
|
+
const edges: Edge[] = [
|
|
280
|
+
...entry.graph.inbound.map((edge) => ({ ...edge, direction: 'inbound' as const })),
|
|
281
|
+
...entry.graph.outbound.map((edge) => ({ ...edge, direction: 'outbound' as const })),
|
|
282
|
+
]
|
|
283
|
+
const parent = entry.kind === 'module' || entry.kind === 'area' ? (entry.graph.areaId ?? entry.graph.packageId) : undefined
|
|
284
|
+
if (parent && parent !== entry.id) edges.push({ kind: 'contains', id: parent, confidence: 'observed', direction: 'inbound' })
|
|
285
|
+
if (entry.kind === 'area' || entry.kind === 'package') {
|
|
286
|
+
for (const child of projection.entries) {
|
|
287
|
+
if (child.id === entry.id) continue
|
|
288
|
+
const childParent = child.kind === 'area' || child.kind === 'module' ? (child.graph.areaId ?? child.graph.packageId) : undefined
|
|
289
|
+
if (childParent === entry.id) edges.push({ kind: 'contains', id: child.id, confidence: 'observed', direction: 'outbound' })
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return edges
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* The neighbourhood, breadth first, bounded by depth and by count per relation kind.
|
|
297
|
+
*
|
|
298
|
+
* An entry is recorded once, at the distance it was first reached, under the relation that
|
|
299
|
+
* reached it; the traversal visits entries in sorted id order at every level so the record is
|
|
300
|
+
* the same whatever order the projection listed them in.
|
|
301
|
+
*/
|
|
302
|
+
const neighboursOf = (projection: RetrievalIndexV1, byId: ReadonlyMap<string, RetrievalEntry>, target: RetrievalEntry, depth: number): Record<string, KnowledgeNeighbour[]> => {
|
|
303
|
+
const seen = new Set<string>([target.id])
|
|
304
|
+
const grouped = new Map<string, KnowledgeNeighbour[]>()
|
|
305
|
+
let frontier: RetrievalEntry[] = [target]
|
|
306
|
+
for (let distance = 1; distance <= depth && frontier.length; distance += 1) {
|
|
307
|
+
const next: RetrievalEntry[] = []
|
|
308
|
+
for (const current of frontier) {
|
|
309
|
+
const edges = edgesAt(projection, current).sort((a, b) => a.kind.localeCompare(b.kind) || a.id.localeCompare(b.id) || a.direction.localeCompare(b.direction))
|
|
310
|
+
for (const edge of edges) {
|
|
311
|
+
const entry = byId.get(edge.id)
|
|
312
|
+
if (!entry || seen.has(entry.id)) continue
|
|
313
|
+
seen.add(entry.id)
|
|
314
|
+
next.push(entry)
|
|
315
|
+
const list = grouped.get(edge.kind) ?? []
|
|
316
|
+
list.push({
|
|
317
|
+
id: entry.id,
|
|
318
|
+
kind: entry.kind,
|
|
319
|
+
path: entry.path,
|
|
320
|
+
title: entry.title,
|
|
321
|
+
...(entry.summary ? { summary: entry.summary } : {}),
|
|
322
|
+
direction: edge.direction,
|
|
323
|
+
confidence: edge.confidence,
|
|
324
|
+
distance,
|
|
325
|
+
...(distance > 1 ? { via: current.id } : {}),
|
|
326
|
+
})
|
|
327
|
+
grouped.set(edge.kind, list)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
frontier = next.sort((a, b) => a.id.localeCompare(b.id))
|
|
331
|
+
}
|
|
332
|
+
return Object.fromEntries(
|
|
333
|
+
[...grouped.entries()]
|
|
334
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
335
|
+
.map(([kind, list]) => [kind, list.sort((a, b) => a.distance - b.distance || a.id.localeCompare(b.id)).slice(0, MAX_NEIGHBOURS_PER_KIND)]),
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const SEVERITY_RANK: Readonly<Record<KnowledgeDiagnostic['severity'], number>> = { error: 0, warn: 1, info: 2, off: 3 }
|
|
340
|
+
|
|
341
|
+
/** Diagnostics that name the entity or point at its file and are not confirmed as fine. */
|
|
342
|
+
const openDiagnosticsFor = (report: ReconciliationReportV1 | undefined, target: RetrievalEntry): KnowledgeDiagnostic[] =>
|
|
343
|
+
(report?.diagnostics ?? [])
|
|
344
|
+
.filter((diagnostic) => diagnostic.status !== 'confirmed' && diagnostic.severity !== 'off')
|
|
345
|
+
.filter((diagnostic) => diagnostic.entityIds?.includes(target.id) || diagnostic.evidence.some((item) => item.path === target.path))
|
|
346
|
+
.sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] || a.id.localeCompare(b.id))
|
|
347
|
+
.slice(0, MAX_DIAGNOSTICS)
|
|
348
|
+
|
|
349
|
+
const lookupSections = (): readonly BudgetedSection<KnowledgeLookupResponse>[] => [
|
|
350
|
+
{
|
|
351
|
+
name: 'evidenceExcerpts',
|
|
352
|
+
content: [],
|
|
353
|
+
strip: (payload) => ({ ...payload, evidence: payload.evidence.map(({ excerpt: _excerpt, ...item }) => item) }),
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: 'related',
|
|
357
|
+
content: [],
|
|
358
|
+
strip: (payload) => {
|
|
359
|
+
const { related: _related, ...handoff } = payload.handoff
|
|
360
|
+
return { ...payload, handoff }
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: 'neighbours',
|
|
365
|
+
content: [],
|
|
366
|
+
strip: (payload) => ({ ...payload, neighbours: {} }),
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
name: 'summaries',
|
|
370
|
+
content: [],
|
|
371
|
+
strip: (payload) => ({
|
|
372
|
+
...payload,
|
|
373
|
+
entity: withoutSummary(payload.entity),
|
|
374
|
+
neighbours: Object.fromEntries(Object.entries(payload.neighbours).map(([kind, list]) => [kind, list.map(withoutSummary)])),
|
|
375
|
+
documents: { covering: payload.documents.covering.map(withoutSummary), mentioning: payload.documents.mentioning.map(withoutSummary) },
|
|
376
|
+
}),
|
|
377
|
+
},
|
|
378
|
+
]
|
|
379
|
+
|
|
380
|
+
/** Which entry a lookup names: by id first, then by path, through the same resolution the handoff uses. */
|
|
381
|
+
export const resolveLookupEntry = (projection: RetrievalIndexV1, request: Pick<KnowledgeLookupRequest, 'id' | 'path'>): RetrievalEntry => {
|
|
382
|
+
const key = request.id ?? request.path
|
|
383
|
+
if (!key) throw new Error('knowledge.lookup requires id or path')
|
|
384
|
+
const path = key.replace(/\/$/, '')
|
|
385
|
+
const entry = request.id ? resolveHandoffEntry(projection, request.id) : (projection.entries.find((item) => item.path === path) ?? resolveHandoffEntry(projection, path))
|
|
386
|
+
if (!entry) throw new Error(`Unknown entity "${key}". Try: ak-docs search "${key}"`)
|
|
387
|
+
return entry
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Everything an agent wants to know about one entity before editing, in one bounded response.
|
|
392
|
+
*
|
|
393
|
+
* The handoff is the one `handoff.resolve` returns for the same entity; the documents are the
|
|
394
|
+
* projection's `coveredBy` and `mentionedBy`, most canonical first; the diagnostics are the open
|
|
395
|
+
* ones from the latest reconciliation report that name the entity or its file; the evidence is
|
|
396
|
+
* the entity's own path and hash and those of the documents about it, each with the opening of
|
|
397
|
+
* its body as an excerpt — the first thing a budget sheds.
|
|
398
|
+
*/
|
|
399
|
+
export const knowledgeLookup = (index: DocBridgeIndexV1, config: DocBridgeConfigV1, request: KnowledgeLookupRequest, options: KnowledgeLookupOptions = {}): KnowledgeLookupResponse => {
|
|
400
|
+
const projection = requireProjection(index, 'knowledge.lookup')
|
|
401
|
+
const depth = Math.min(MAX_LOOKUP_DEPTH, Math.max(1, request.depth ?? 1))
|
|
402
|
+
const target = resolveLookupEntry(projection, request)
|
|
403
|
+
const byId = new Map(projection.entries.map((entry) => [entry.id, entry]))
|
|
404
|
+
const handoffOptions = options.root ? { root: options.root } : {}
|
|
405
|
+
|
|
406
|
+
const handoff =
|
|
407
|
+
target.kind === 'intent' || target.kind === 'change'
|
|
408
|
+
? (runQuery(index, config, { kind: target.kind, id: target.id, agent: true }, handoffOptions) as AgentHandoffV1)
|
|
409
|
+
: handoffForEntity(index, target.id, config, handoffOptions)
|
|
410
|
+
|
|
411
|
+
const documentsOf = (ids: readonly string[]): RetrievalEntry[] =>
|
|
412
|
+
ids.map((id) => byId.get(id)).filter((entry): entry is RetrievalEntry => entry !== undefined && entry.kind === 'document').sort(byCanonicality)
|
|
413
|
+
const covering = documentsOf(target.graph.coveredBy)
|
|
414
|
+
const mentioning = documentsOf(target.graph.mentionedBy).filter((entry) => !covering.includes(entry))
|
|
415
|
+
|
|
416
|
+
const report = options.report?.()
|
|
417
|
+
const evidence: KnowledgeEvidence[] = [
|
|
418
|
+
{
|
|
419
|
+
source: evidenceSource(target.kind),
|
|
420
|
+
path: target.path,
|
|
421
|
+
contentHash: target.contentHash,
|
|
422
|
+
context: `${target.kind} ${target.id}`,
|
|
423
|
+
...(excerptOf(target.fields.body) ? { excerpt: excerptOf(target.fields.body) as string } : {}),
|
|
424
|
+
},
|
|
425
|
+
...[...covering.map((entry) => ({ entry, context: `covers ${target.id}` })), ...mentioning.map((entry) => ({ entry, context: `mentions ${target.id}` }))]
|
|
426
|
+
.slice(0, MAX_EVIDENCE - 1)
|
|
427
|
+
.map(({ entry, context }) => ({
|
|
428
|
+
source: 'documentation' as const,
|
|
429
|
+
path: entry.path,
|
|
430
|
+
contentHash: entry.contentHash,
|
|
431
|
+
context,
|
|
432
|
+
...(excerptOf(entry.fields.body) ? { excerpt: excerptOf(entry.fields.body) as string } : {}),
|
|
433
|
+
})),
|
|
434
|
+
]
|
|
435
|
+
|
|
436
|
+
const response: KnowledgeLookupResponse = {
|
|
437
|
+
type: 'knowledge-lookup',
|
|
438
|
+
schemaVersion: KNOWLEDGE_TOOLS_SCHEMA_VERSION,
|
|
439
|
+
source: `projection:${projection.contentHash}`,
|
|
440
|
+
depth,
|
|
441
|
+
entity: toEntity(target),
|
|
442
|
+
neighbours: neighboursOf(projection, byId, target, depth),
|
|
443
|
+
documents: { covering: covering.map(toDocumentRef), mentioning: mentioning.map(toDocumentRef) },
|
|
444
|
+
handoff,
|
|
445
|
+
diagnostics: { reportHash: report?.contentHash ?? null, open: openDiagnosticsFor(report, target) },
|
|
446
|
+
evidence,
|
|
447
|
+
}
|
|
448
|
+
if (request.budgetTokens === undefined) return response
|
|
449
|
+
|
|
450
|
+
const sections = sectionsWithContent(lookupSections(), (name) => {
|
|
451
|
+
if (name === 'evidenceExcerpts') return response.evidence.map((item) => item.excerpt).filter(Boolean)
|
|
452
|
+
if (name === 'related') return response.handoff.related ?? []
|
|
453
|
+
if (name === 'neighbours') return response.neighbours
|
|
454
|
+
return [
|
|
455
|
+
response.entity.summary,
|
|
456
|
+
...Object.values(response.neighbours).flatMap((list) => list.map((item) => item.summary)),
|
|
457
|
+
...response.documents.covering.map((item) => item.summary),
|
|
458
|
+
...response.documents.mentioning.map((item) => item.summary),
|
|
459
|
+
].filter(Boolean)
|
|
460
|
+
})
|
|
461
|
+
const budgeted = applyBudget(response, sections, request.budgetTokens)
|
|
462
|
+
return { ...budgeted.payload, budget: budgeted.budget }
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/*
|
|
466
|
+
* ---------------------------------------------------------------------------------------------
|
|
467
|
+
* handoff.resolve with a budget
|
|
468
|
+
* ---------------------------------------------------------------------------------------------
|
|
469
|
+
*/
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* A handoff trimmed to a budget. Its droppable sections are `related` and the note that repeats
|
|
473
|
+
* the target's summary; `startHere`, `readBeforeEditing`, `editRoots`, `checks` and `evidence`
|
|
474
|
+
* are what an agent acts on and are never shed.
|
|
475
|
+
*/
|
|
476
|
+
export const budgetedHandoff = (index: DocBridgeIndexV1, handoff: AgentHandoffV1, budgetTokens: number): AgentHandoffV1 => {
|
|
477
|
+
const entry = index.projection && handoff.metadata?.entityId ? resolveHandoffEntry(index.projection, String(handoff.metadata.entityId)) : undefined
|
|
478
|
+
const summaryNotes = entry?.summary ? handoff.notes.filter((note) => note === entry.summary) : []
|
|
479
|
+
const sections: BudgetedSection<AgentHandoffV1>[] = [
|
|
480
|
+
{
|
|
481
|
+
name: 'related',
|
|
482
|
+
content: handoff.related ?? [],
|
|
483
|
+
strip: (payload) => {
|
|
484
|
+
const { related: _related, ...rest } = payload
|
|
485
|
+
return rest
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
name: 'summaries',
|
|
490
|
+
content: summaryNotes,
|
|
491
|
+
strip: (payload) => ({ ...payload, notes: payload.notes.filter((note) => !summaryNotes.includes(note)) }),
|
|
492
|
+
},
|
|
493
|
+
]
|
|
494
|
+
const budgeted = applyBudget(handoff, sections, budgetTokens)
|
|
495
|
+
return { ...budgeted.payload, budget: budgeted.budget }
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/*
|
|
499
|
+
* ---------------------------------------------------------------------------------------------
|
|
500
|
+
* Text renderings
|
|
501
|
+
* ---------------------------------------------------------------------------------------------
|
|
502
|
+
*/
|
|
503
|
+
|
|
504
|
+
const budgetLine = (budget: BudgetReport | undefined): string[] =>
|
|
505
|
+
budget
|
|
506
|
+
? [`Budget: ${budget.tokens.total}/${budget.tokens.budget} tokens (${budget.tokenMethod}), fits: ${budget.fits ? 'yes' : 'no'}, dropped: ${budget.dropped.length ? budget.dropped.join(', ') : 'nothing'}`]
|
|
507
|
+
: []
|
|
508
|
+
|
|
509
|
+
const joined = (parts: readonly (string | undefined)[]): string => parts.filter((part): part is string => Boolean(part && part.trim())).join('\n\n')
|
|
510
|
+
|
|
511
|
+
/** The search payload as prose: one numbered block per result, through `formatRetrievedDocuments`. */
|
|
512
|
+
export const formatKnowledgeSearchText = (response: KnowledgeSearchResponse): string => {
|
|
513
|
+
const documents: RetrievedDocument[] = response.results.map((result) => ({
|
|
514
|
+
id: result.entityId ?? result.id,
|
|
515
|
+
source: result.path,
|
|
516
|
+
score: result.score,
|
|
517
|
+
content: joined([`${result.title ?? result.id} [${result.kind ?? result.type}] score=${result.score.toFixed(2)}${result.confidence ? ` confidence=${result.confidence}` : ''}`, result.summary, result.excerpt]),
|
|
518
|
+
}))
|
|
519
|
+
return [`Query: ${response.query} (${response.count} result${response.count === 1 ? '' : 's'})`, ...budgetLine(response.budget), formatRetrievedDocuments(documents)].filter(Boolean).join('\n\n')
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** The lookup payload as prose: the entity, then one block per section that has content. */
|
|
523
|
+
export const formatKnowledgeLookupText = (response: KnowledgeLookupResponse): string => {
|
|
524
|
+
const { entity, handoff } = response
|
|
525
|
+
const documents: RetrievedDocument[] = [
|
|
526
|
+
{
|
|
527
|
+
id: entity.id,
|
|
528
|
+
source: entity.path,
|
|
529
|
+
content: joined([`${entity.title} [${entity.kind}] confidence=${entity.confidence}`, entity.summary, response.evidence[0]?.excerpt]),
|
|
530
|
+
},
|
|
531
|
+
]
|
|
532
|
+
const neighbourLines = Object.entries(response.neighbours).flatMap(([kind, list]) => list.map((item) => `${kind} ${item.direction === 'inbound' ? '←' : '→'} ${item.id} (${item.kind}, ${item.distance} hop${item.distance === 1 ? '' : 's'})`))
|
|
533
|
+
if (neighbourLines.length) documents.push({ id: 'neighbours', content: neighbourLines.join('\n') })
|
|
534
|
+
const documentLines = [
|
|
535
|
+
...response.documents.covering.map((item) => `covers: ${item.path}${item.summary ? ` — ${item.summary}` : ''}`),
|
|
536
|
+
...response.documents.mentioning.map((item) => `mentions: ${item.path}${item.summary ? ` — ${item.summary}` : ''}`),
|
|
537
|
+
]
|
|
538
|
+
if (documentLines.length) documents.push({ id: 'documents', content: documentLines.join('\n') })
|
|
539
|
+
documents.push({
|
|
540
|
+
id: 'handoff',
|
|
541
|
+
content: [
|
|
542
|
+
`startHere: ${handoff.startHere}`,
|
|
543
|
+
`readBeforeEditing: ${handoff.readBeforeEditing.join(', ')}`,
|
|
544
|
+
`editRoots: ${handoff.editRoots.join(', ')}`,
|
|
545
|
+
`checks: ${handoff.checks.join(', ') || '(none)'}`,
|
|
546
|
+
...(handoff.related?.length ? [`related: ${handoff.related.map((row) => `${row.direction} ${row.id} (${row.strength})`).join(', ')}`] : []),
|
|
547
|
+
].join('\n'),
|
|
548
|
+
})
|
|
549
|
+
if (response.diagnostics.open.length) {
|
|
550
|
+
documents.push({ id: 'diagnostics', content: response.diagnostics.open.map((item) => `[${item.severity}] ${item.code}: ${item.message}`).join('\n') })
|
|
551
|
+
}
|
|
552
|
+
documents.push({ id: 'evidence', content: response.evidence.map((item) => `${item.path}${item.contentHash ? ` @${item.contentHash.slice(0, 12)}` : ''}${item.context ? ` (${item.context})` : ''}`).join('\n') })
|
|
553
|
+
return [...budgetLine(response.budget), formatRetrievedDocuments(documents)].join('\n\n')
|
|
554
|
+
}
|