@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,326 @@
|
|
|
1
|
+
import type { DocBridgeConfigV1 } from '../config/schema.js'
|
|
2
|
+
import { defaultChecksForTarget } from '../lib/package-manager.js'
|
|
3
|
+
import { normalizeAgentHandoff, type AgentHandoffV1, type HandoffRelated } from '../schemas/agent-handoff.js'
|
|
4
|
+
import type { DocBridgeIndexV1 } from '../schemas/doc-bridge-index.js'
|
|
5
|
+
import type { RetrievalEntry, RetrievalIndexV1 } from '../schemas/retrieval-index.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A handoff for any entity the projection knows.
|
|
9
|
+
*
|
|
10
|
+
* `handoffForPackage` could answer for an ownership record and nothing else: an agent asking
|
|
11
|
+
* about a module, an area or a document got "unknown package". Now the answer is derived from
|
|
12
|
+
* the graph — which documents cover the target, which mention it, what its code depends on — and
|
|
13
|
+
* every field says which relations produced it, so a handoff that points somewhere odd is a
|
|
14
|
+
* finding about the repository rather than a mystery about the tool.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export type HandoffOptions = {
|
|
18
|
+
/** The project root, for the package-script fallback when nothing declares checks. */
|
|
19
|
+
readonly root?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type OwnershipRecord = NonNullable<NonNullable<DocBridgeIndexV1['lookup']>['ownership']>[string]
|
|
23
|
+
|
|
24
|
+
const MAX_RELATED = 3
|
|
25
|
+
const MAX_READ_BEFORE = 2
|
|
26
|
+
const MAX_RELATED_EVIDENCE = 3
|
|
27
|
+
|
|
28
|
+
const dirname = (path: string): string => path.split('/').slice(0, -1).join('/') || '.'
|
|
29
|
+
|
|
30
|
+
const isHttp = (value: string): boolean => /^https?:\/\//.test(value)
|
|
31
|
+
|
|
32
|
+
const bridgeFor = (config: DocBridgeConfigV1, id: string, humanDoc: string | undefined): AgentHandoffV1['bridge'] | undefined =>
|
|
33
|
+
humanDoc
|
|
34
|
+
? { humanDoc: isHttp(humanDoc) ? 'external' : 'linked' }
|
|
35
|
+
: config.corpus.human
|
|
36
|
+
? { humanDoc: 'missing', action: 'ak-docs bootstrap agent-docs', bootstrap: `docs/for-agents/human/${id}.md` }
|
|
37
|
+
: undefined
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The handoff for an index built before the projection existed: an ownership record, or nothing.
|
|
41
|
+
*/
|
|
42
|
+
const legacyHandoff = (index: DocBridgeIndexV1, id: string, config: DocBridgeConfigV1): AgentHandoffV1 => {
|
|
43
|
+
const fromIndex = index.handoffs?.[id]
|
|
44
|
+
if (fromIndex) return normalizeAgentHandoff(fromIndex)
|
|
45
|
+
|
|
46
|
+
const owner = index.lookup?.ownership?.[id]
|
|
47
|
+
if (!owner) throw new Error(`Unknown package/ownership id "${id}". Try: ak-docs list packages`)
|
|
48
|
+
const bridge = bridgeFor(config, id, owner.humanDoc)
|
|
49
|
+
|
|
50
|
+
return normalizeAgentHandoff({
|
|
51
|
+
type: 'agent-handoff',
|
|
52
|
+
source: config.index?.outFile ?? '.doc-bridge/index.json',
|
|
53
|
+
target: {
|
|
54
|
+
type: 'package',
|
|
55
|
+
id,
|
|
56
|
+
path: owner.path,
|
|
57
|
+
...(owner.group ? { group: owner.group } : {}),
|
|
58
|
+
...(owner.layer ? { layer: owner.layer } : {}),
|
|
59
|
+
},
|
|
60
|
+
startHere: owner.agentDoc ?? config.corpus.agent.index ?? '',
|
|
61
|
+
readBeforeEditing: [owner.agentDoc, 'AGENTS.md'].filter(Boolean),
|
|
62
|
+
editRoots: [owner.path],
|
|
63
|
+
checks: [...owner.checks],
|
|
64
|
+
...(owner.humanDoc ? { humanDoc: owner.humanDoc } : {}),
|
|
65
|
+
...(bridge ? { bridge } : {}),
|
|
66
|
+
notes: [
|
|
67
|
+
...(owner.purpose ? [owner.purpose] : []),
|
|
68
|
+
...(!owner.humanDoc && config.corpus.human ? [`Human guide missing for ${id}. Run: ak-docs bootstrap agent-docs`] : []),
|
|
69
|
+
],
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Which entry an identifier names: an entity id, an ownership id, an alias, or a path.
|
|
75
|
+
*
|
|
76
|
+
* Ownership ids win over aliases because they are what a routing command has always accepted;
|
|
77
|
+
* a path is accepted last, since `src/query` is a directory before it is a name.
|
|
78
|
+
*/
|
|
79
|
+
export const resolveHandoffEntry = (projection: RetrievalIndexV1, id: string): RetrievalEntry | undefined => {
|
|
80
|
+
const trimmed = id.replace(/\/$/, '')
|
|
81
|
+
return (
|
|
82
|
+
projection.entries.find((entry) => entry.id === trimmed) ??
|
|
83
|
+
projection.entries.find((entry) => entry.ownershipId === trimmed) ??
|
|
84
|
+
projection.entries.find((entry) => entry.aliases.includes(trimmed)) ??
|
|
85
|
+
projection.entries.find((entry) => entry.path === trimmed && (entry.kind === 'area' || entry.kind === 'package')) ??
|
|
86
|
+
projection.entries.find((entry) => entry.path === trimmed)
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type DocumentPick = { readonly path: string; readonly reason: string }
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Where to start reading, and why.
|
|
94
|
+
*
|
|
95
|
+
* Order of authority: a document that declares it covers the target; one that mentions it; one
|
|
96
|
+
* that links to those; and, failing all of that, the most canonical document the unit's
|
|
97
|
+
* ownership names. Within each tier the more canonical page comes first, so the entry point
|
|
98
|
+
* other pages point at beats the leaf note that happens to mention the target too.
|
|
99
|
+
*/
|
|
100
|
+
const documentsFor = (
|
|
101
|
+
projection: RetrievalIndexV1,
|
|
102
|
+
byId: ReadonlyMap<string, RetrievalEntry>,
|
|
103
|
+
target: RetrievalEntry,
|
|
104
|
+
ownership: OwnershipRecord | undefined,
|
|
105
|
+
): DocumentPick[] => {
|
|
106
|
+
const picks: DocumentPick[] = []
|
|
107
|
+
const seen = new Set<string>()
|
|
108
|
+
const add = (entry: RetrievalEntry | undefined, reason: string): void => {
|
|
109
|
+
if (!entry || entry.kind !== 'document' || seen.has(entry.path)) return
|
|
110
|
+
seen.add(entry.path)
|
|
111
|
+
picks.push({ path: entry.path, reason })
|
|
112
|
+
}
|
|
113
|
+
const byRank = (ids: readonly string[]): RetrievalEntry[] =>
|
|
114
|
+
ids
|
|
115
|
+
.map((id) => byId.get(id))
|
|
116
|
+
.filter((entry): entry is RetrievalEntry => Boolean(entry))
|
|
117
|
+
.sort((a, b) => b.graph.pagerank - a.graph.pagerank || a.id.localeCompare(b.id))
|
|
118
|
+
|
|
119
|
+
if (ownership?.agentDoc) add(projection.entries.find((entry) => entry.path === ownership.agentDoc), `ownership ${ownership.id} names it as the agent document`)
|
|
120
|
+
for (const entry of byRank(target.graph.coveredBy)) add(entry, `covers ${target.id}`)
|
|
121
|
+
for (const entry of byRank(target.graph.mentionedBy)) add(entry, `mentions ${target.id}`)
|
|
122
|
+
if (target.kind === 'document') {
|
|
123
|
+
add(target, 'the target itself')
|
|
124
|
+
for (const edge of target.graph.outbound.filter((item) => item.kind === 'links-to')) add(byId.get(edge.id), `${target.id} links to it`)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// One links-to hop from what covers or mentions the target: the page that introduces the page.
|
|
128
|
+
for (const near of [...picks]) {
|
|
129
|
+
const document = projection.entries.find((entry) => entry.path === near.path)
|
|
130
|
+
for (const edge of document?.graph.inbound.filter((item) => item.kind === 'links-to') ?? []) add(byId.get(edge.id), `links to ${document?.id}`)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return picks
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** The strongest import relationships between this unit and the other areas, both directions. */
|
|
137
|
+
const relatedAreas = (projection: RetrievalIndexV1, byId: ReadonlyMap<string, RetrievalEntry>, target: RetrievalEntry): HandoffRelated[] => {
|
|
138
|
+
const unitOf = (entry: RetrievalEntry): string | undefined =>
|
|
139
|
+
target.kind === 'package' ? entry.graph.packageId : (entry.graph.areaId ?? entry.graph.packageId)
|
|
140
|
+
const mine = target.kind === 'module' ? unitOf(target) : target.id
|
|
141
|
+
if (!mine) return []
|
|
142
|
+
|
|
143
|
+
const crossings = new Map<string, { direction: HandoffRelated['direction']; count: number; examples: string[] }>()
|
|
144
|
+
const note = (unit: string, direction: HandoffRelated['direction'], example: string): void => {
|
|
145
|
+
const key = `${direction}:${unit}`
|
|
146
|
+
const existing = crossings.get(key)
|
|
147
|
+
if (existing) {
|
|
148
|
+
existing.count += 1
|
|
149
|
+
if (existing.examples.length < MAX_RELATED_EVIDENCE) existing.examples.push(example)
|
|
150
|
+
} else crossings.set(key, { direction, count: 1, examples: [example] })
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const module of projection.entries) {
|
|
154
|
+
if (module.kind !== 'module') continue
|
|
155
|
+
const from = unitOf(module)
|
|
156
|
+
if (!from) continue
|
|
157
|
+
for (const edge of module.graph.outbound) {
|
|
158
|
+
if (edge.kind !== 'imports' && edge.kind !== 're-exports') continue
|
|
159
|
+
const imported = byId.get(edge.id)
|
|
160
|
+
const to = imported ? unitOf(imported) : undefined
|
|
161
|
+
if (!to || to === from) continue
|
|
162
|
+
if (from === mine) note(to, 'imports', `${module.path} → ${imported?.path ?? edge.id}`)
|
|
163
|
+
else if (to === mine) note(from, 'imported-by', `${module.path} → ${imported?.path ?? edge.id}`)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const rows = [...crossings.entries()]
|
|
168
|
+
.map(([key, value]) => ({ unit: key.slice(key.indexOf(':') + 1), ...value }))
|
|
169
|
+
.sort((a, b) => b.count - a.count || a.unit.localeCompare(b.unit))
|
|
170
|
+
const pick = (direction: HandoffRelated['direction']): HandoffRelated[] =>
|
|
171
|
+
rows
|
|
172
|
+
.filter((row) => row.direction === direction)
|
|
173
|
+
.slice(0, MAX_RELATED)
|
|
174
|
+
.map((row) => ({
|
|
175
|
+
id: row.unit,
|
|
176
|
+
path: byId.get(row.unit)?.path ?? row.unit,
|
|
177
|
+
direction,
|
|
178
|
+
strength: row.count,
|
|
179
|
+
evidence: row.examples,
|
|
180
|
+
}))
|
|
181
|
+
return [...pick('imports'), ...pick('imported-by')]
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
type Checks = { readonly checks: readonly string[]; readonly source: string; readonly reason: string }
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Which checks to run, and where that answer came from.
|
|
188
|
+
*
|
|
189
|
+
* An ownership override wins; then whatever the index recorded when it merged frontmatter,
|
|
190
|
+
* package scripts and defaults (it recorded the source at the time); then the package-manager
|
|
191
|
+
* default for the unit's package, which needs the project root to read package.json.
|
|
192
|
+
*/
|
|
193
|
+
const checksFor = (
|
|
194
|
+
index: DocBridgeIndexV1,
|
|
195
|
+
config: DocBridgeConfigV1,
|
|
196
|
+
target: RetrievalEntry,
|
|
197
|
+
ownership: OwnershipRecord | undefined,
|
|
198
|
+
unitPath: string,
|
|
199
|
+
packageEntry: RetrievalEntry | undefined,
|
|
200
|
+
options: HandoffOptions,
|
|
201
|
+
): Checks => {
|
|
202
|
+
const override = ownership ? config.routing?.options?.ownership?.[ownership.id]?.checks : undefined
|
|
203
|
+
if (override?.length) return { checks: override, source: 'ownership', reason: `routing.options.ownership.${ownership?.id}.checks` }
|
|
204
|
+
if (ownership?.checks.length) {
|
|
205
|
+
const source = ownership.checksSource ?? 'default'
|
|
206
|
+
return { checks: ownership.checks, source, reason: `ownership ${ownership.id} recorded its checks from ${source}` }
|
|
207
|
+
}
|
|
208
|
+
// A module or area inside an owned package inherits that package's checks.
|
|
209
|
+
const owningRecord = packageEntry?.ownershipId ? index.lookup?.ownership?.[packageEntry.ownershipId] : undefined
|
|
210
|
+
if (owningRecord?.checks.length) {
|
|
211
|
+
const source = owningRecord.checksSource ?? 'default'
|
|
212
|
+
return { checks: owningRecord.checks, source, reason: `inherited from ownership ${owningRecord.id} (${source})` }
|
|
213
|
+
}
|
|
214
|
+
if (options.root) {
|
|
215
|
+
const strict = (config.gates?.preset ?? 'minimal') !== 'minimal'
|
|
216
|
+
const packageName = packageEntry?.aliases.find((alias) => alias.includes('/')) ?? packageEntry?.title
|
|
217
|
+
const checks = defaultChecksForTarget(options.root, {
|
|
218
|
+
packageId: packageEntry?.id ?? target.id,
|
|
219
|
+
packagePath: packageEntry?.path ?? unitPath,
|
|
220
|
+
...(packageName ? { packageName } : {}),
|
|
221
|
+
strict,
|
|
222
|
+
})
|
|
223
|
+
return { checks, source: 'default', reason: 'package-manager default for the unit\'s package' }
|
|
224
|
+
}
|
|
225
|
+
return { checks: [], source: 'default', reason: 'nothing declares checks and no project root was given to read package scripts' }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const targetType = (kind: RetrievalEntry['kind']): AgentHandoffV1['target']['type'] =>
|
|
229
|
+
kind === 'area' ? 'area' : kind === 'document' ? 'document' : kind === 'module' ? 'module' : 'package'
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Build the handoff for a package, an area, a module or a document.
|
|
233
|
+
*
|
|
234
|
+
* `editRoots` is the unit an agent may write in: the area or package itself, a module's area,
|
|
235
|
+
* a document's own path. `startHere` and `readBeforeEditing` come from the documentation graph.
|
|
236
|
+
* `related` names the areas this unit's code depends on and that depend on it, with the import
|
|
237
|
+
* that proves each. `explain` says which relation produced each field.
|
|
238
|
+
*/
|
|
239
|
+
export const handoffForEntity = (index: DocBridgeIndexV1, id: string, config: DocBridgeConfigV1, options: HandoffOptions = {}): AgentHandoffV1 => {
|
|
240
|
+
const projection = index.projection
|
|
241
|
+
if (!projection) return legacyHandoff(index, id, config)
|
|
242
|
+
|
|
243
|
+
const target = resolveHandoffEntry(projection, id)
|
|
244
|
+
if (!target) {
|
|
245
|
+
const fallback = index.lookup?.ownership?.[id] ? legacyHandoff(index, id, config) : undefined
|
|
246
|
+
if (fallback) return fallback
|
|
247
|
+
throw new Error(`Unknown entity "${id}". Try: ak-docs list packages, or ak-docs search "${id}"`)
|
|
248
|
+
}
|
|
249
|
+
if (target.kind === 'intent' || target.kind === 'change') {
|
|
250
|
+
throw new Error(`"${id}" is a ${target.kind} route. Try: ak-docs query ${target.kind} ${target.id} --agent`)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const byId = new Map(projection.entries.map((entry) => [entry.id, entry]))
|
|
254
|
+
const ownership = target.ownershipId ? index.lookup?.ownership?.[target.ownershipId] : undefined
|
|
255
|
+
const area = target.graph.areaId ? byId.get(target.graph.areaId) : undefined
|
|
256
|
+
const packageEntry = target.graph.packageId ? byId.get(target.graph.packageId) : target.kind === 'package' ? target : undefined
|
|
257
|
+
const explain: Record<string, string[]> = {}
|
|
258
|
+
|
|
259
|
+
// The unit an agent may write in.
|
|
260
|
+
const unit =
|
|
261
|
+
target.kind === 'module'
|
|
262
|
+
? area
|
|
263
|
+
? { path: area.path, reason: `contained by area ${area.id}` }
|
|
264
|
+
: packageEntry
|
|
265
|
+
? { path: packageEntry.path, reason: `contained by package ${packageEntry.id}` }
|
|
266
|
+
: { path: dirname(target.path), reason: 'the directory of the module; no area or package contains it' }
|
|
267
|
+
: { path: ownership?.path ?? target.path, reason: ownership ? `ownership ${ownership.id} path` : `the ${target.kind} itself` }
|
|
268
|
+
explain.editRoots = [unit.reason]
|
|
269
|
+
|
|
270
|
+
const documents = documentsFor(projection, byId, target, ownership)
|
|
271
|
+
const startHere = documents[0]?.path ?? config.corpus.agent.index ?? target.path
|
|
272
|
+
explain.startHere = [documents[0] ? documents[0].reason : 'no document covers, mentions or links to the target; the corpus index is the fallback']
|
|
273
|
+
const readBefore = documents.slice(1, 1 + MAX_READ_BEFORE)
|
|
274
|
+
explain.readBeforeEditing = readBefore.map((pick) => `${pick.path}: ${pick.reason}`)
|
|
275
|
+
|
|
276
|
+
const checks = checksFor(index, config, target, ownership, unit.path, packageEntry, options)
|
|
277
|
+
explain.checks = [checks.reason]
|
|
278
|
+
|
|
279
|
+
const related = target.kind === 'document' ? [] : relatedAreas(projection, byId, target)
|
|
280
|
+
if (related.length) explain.related = related.map((row) => `${row.direction} ${row.id}: ${row.strength} import(s), e.g. ${row.evidence[0] ?? ''}`.trim())
|
|
281
|
+
|
|
282
|
+
const humanDoc = ownership?.humanDoc
|
|
283
|
+
const bridge = bridgeFor(config, ownership?.id ?? target.id, humanDoc)
|
|
284
|
+
const evidence: NonNullable<AgentHandoffV1['evidence']> = [
|
|
285
|
+
{
|
|
286
|
+
source: target.kind === 'module' ? 'code' : target.kind === 'document' ? 'documentation' : 'derived',
|
|
287
|
+
path: target.path,
|
|
288
|
+
contentHash: target.contentHash,
|
|
289
|
+
context: `${target.kind} ${target.id}`,
|
|
290
|
+
},
|
|
291
|
+
...documents.slice(0, 1 + MAX_READ_BEFORE).map((pick) => ({ source: 'documentation' as const, path: pick.path, context: pick.reason })),
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
return normalizeAgentHandoff({
|
|
295
|
+
type: 'agent-handoff',
|
|
296
|
+
source: config.index?.outFile ?? '.doc-bridge/index.json',
|
|
297
|
+
target: {
|
|
298
|
+
type: targetType(target.kind),
|
|
299
|
+
id: ownership?.id ?? target.id,
|
|
300
|
+
path: target.path,
|
|
301
|
+
...(ownership?.group ? { group: ownership.group } : {}),
|
|
302
|
+
...(ownership?.layer ? { layer: ownership.layer } : {}),
|
|
303
|
+
},
|
|
304
|
+
startHere,
|
|
305
|
+
readBeforeEditing: [...new Set([...readBefore.map((pick) => pick.path), 'AGENTS.md'])],
|
|
306
|
+
editRoots: [unit.path],
|
|
307
|
+
checks: [...checks.checks],
|
|
308
|
+
...(humanDoc ? { humanDoc } : {}),
|
|
309
|
+
...(bridge ? { bridge } : {}),
|
|
310
|
+
notes: [
|
|
311
|
+
...(target.summary ? [target.summary] : []),
|
|
312
|
+
...(!humanDoc && config.corpus.human && ownership ? [`Human guide missing for ${ownership.id}. Run: ak-docs bootstrap agent-docs`] : []),
|
|
313
|
+
],
|
|
314
|
+
...(related.length ? { related } : {}),
|
|
315
|
+
explain,
|
|
316
|
+
evidence,
|
|
317
|
+
metadata: {
|
|
318
|
+
entityId: target.id,
|
|
319
|
+
kind: target.kind,
|
|
320
|
+
checksSource: checks.source,
|
|
321
|
+
confidence: target.confidence,
|
|
322
|
+
...(area ? { areaId: area.id } : {}),
|
|
323
|
+
...(packageEntry ? { packageId: packageEntry.id } : {}),
|
|
324
|
+
},
|
|
325
|
+
})
|
|
326
|
+
}
|
package/src/query/load-index.ts
CHANGED
|
@@ -4,6 +4,10 @@ import { join, resolve } from 'node:path'
|
|
|
4
4
|
import type { DocBridgeConfigV1 } from '../config/schema.js'
|
|
5
5
|
import type { DocBridgeIndexV1 } from '../schemas/doc-bridge-index.js'
|
|
6
6
|
import { parseDocBridgeIndex } from '../validate.js'
|
|
7
|
+
import { buildDocBridgeIndex } from '../index-builder/build-index.js'
|
|
8
|
+
import { repositoryInputs } from '../index-builder/project-corpus.js'
|
|
9
|
+
import { GRAPH_ANALYZER_VERSION } from '../graph/build.js'
|
|
10
|
+
import { SEARCH_LEXICON_VERSION } from './text.js'
|
|
7
11
|
|
|
8
12
|
export class IndexNotFoundError extends Error {
|
|
9
13
|
constructor(readonly path: string) {
|
|
@@ -12,6 +16,13 @@ export class IndexNotFoundError extends Error {
|
|
|
12
16
|
}
|
|
13
17
|
}
|
|
14
18
|
|
|
19
|
+
export class IndexStaleError extends Error {
|
|
20
|
+
constructor(readonly path: string, readonly actual: string, readonly expected: string) {
|
|
21
|
+
super(`Index is stale at ${path}. Run: ak-docs index`)
|
|
22
|
+
this.name = 'IndexStaleError'
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
15
26
|
export const indexFilePath = (root: string, config: DocBridgeConfigV1): string =>
|
|
16
27
|
join(root, config.index?.outFile ?? '.doc-bridge/index.json')
|
|
17
28
|
|
|
@@ -22,4 +33,45 @@ export const loadDocBridgeIndex = (root: string, config: DocBridgeConfigV1): Doc
|
|
|
22
33
|
return parseDocBridgeIndex(raw)
|
|
23
34
|
}
|
|
24
35
|
|
|
25
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Load only an index whose content matches the current repository inputs.
|
|
38
|
+
*
|
|
39
|
+
* Every query goes through here, so the check has to be cheap. An index that records its inputs
|
|
40
|
+
* is verified by re-hashing those inputs — one walk — instead of rebuilding the whole index,
|
|
41
|
+
* which on a repository of any size meant projecting and parsing the corpus on every search. The
|
|
42
|
+
* hash covers the input files and the configuration, and the recorded lexicon version is checked
|
|
43
|
+
* too, because a changed stopword list changes ranking without changing a single file.
|
|
44
|
+
*
|
|
45
|
+
* An index built before inputs were recorded falls back to the full rebuild, so an older artifact
|
|
46
|
+
* is still validated rather than trusted.
|
|
47
|
+
*/
|
|
48
|
+
export const loadFreshDocBridgeIndex = (root: string, config: DocBridgeConfigV1): DocBridgeIndexV1 => {
|
|
49
|
+
const index = loadDocBridgeIndex(root, config)
|
|
50
|
+
|
|
51
|
+
if (index.inputs && index.retrieval) {
|
|
52
|
+
const inputs = repositoryInputs(root, config)
|
|
53
|
+
/*
|
|
54
|
+
* The projection is a function of the snapshot, the overlay and the configuration under a
|
|
55
|
+
* given lexicon and graph-metrics version; a change to either version changes ranking without
|
|
56
|
+
* changing a file, so both are checked next to the inputs.
|
|
57
|
+
*/
|
|
58
|
+
const projectionFresh =
|
|
59
|
+
!index.projection ||
|
|
60
|
+
(index.projection.lexiconVersion === SEARCH_LEXICON_VERSION && index.projection.graphMetricsVersion === GRAPH_ANALYZER_VERSION)
|
|
61
|
+
const fresh =
|
|
62
|
+
index.inputs.hash === inputs.hash &&
|
|
63
|
+
index.inputs.projectionVersion === inputs.projectionVersion &&
|
|
64
|
+
index.retrieval.lexiconVersion === SEARCH_LEXICON_VERSION &&
|
|
65
|
+
projectionFresh
|
|
66
|
+
if (!fresh) throw new IndexStaleError(indexFilePath(root, config), index.inputs.hash, inputs.hash)
|
|
67
|
+
return index
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const expected = buildDocBridgeIndex({ root, config, write: false }).index
|
|
71
|
+
if (index.contentHash !== expected.contentHash) {
|
|
72
|
+
throw new IndexStaleError(indexFilePath(root, config), index.contentHash, expected.contentHash)
|
|
73
|
+
}
|
|
74
|
+
return index
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const resolveRoot = (cwd?: string): string => resolve(cwd ?? process.cwd())
|
package/src/query/query.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { DocBridgeConfigV1 } from '../config/schema.js'
|
|
2
2
|
import {
|
|
3
3
|
normalizeAgentHandoff,
|
|
4
|
+
AgentQueryModeSchema,
|
|
4
5
|
type AgentHandoffV1,
|
|
6
|
+
type AgentQueryMode,
|
|
5
7
|
type AgentSearchV1,
|
|
6
8
|
} from '../schemas/agent-handoff.js'
|
|
7
9
|
import type { DocBridgeIndexV1 } from '../schemas/doc-bridge-index.js'
|
|
10
|
+
import { handoffForEntity, type HandoffOptions } from './handoff.js'
|
|
8
11
|
import { searchIndex } from './search.js'
|
|
12
|
+
import { Buffer } from 'node:buffer'
|
|
9
13
|
|
|
10
14
|
export type QueryKind = 'package' | 'ownership' | 'intent' | 'change' | 'search'
|
|
11
15
|
|
|
@@ -14,6 +18,10 @@ export type QueryRequest = {
|
|
|
14
18
|
readonly id?: string
|
|
15
19
|
readonly term?: string
|
|
16
20
|
readonly agent?: boolean
|
|
21
|
+
/** Attach the matched terms and scoring components to search results. Never changes the ranking. */
|
|
22
|
+
readonly explain?: boolean
|
|
23
|
+
readonly mode?: AgentQueryMode
|
|
24
|
+
readonly contextBudgetTokens?: number
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
export type QueryResult =
|
|
@@ -21,63 +29,91 @@ export type QueryResult =
|
|
|
21
29
|
| AgentHandoffV1
|
|
22
30
|
| AgentSearchV1
|
|
23
31
|
|
|
24
|
-
const
|
|
25
|
-
index: DocBridgeIndexV1,
|
|
26
|
-
id: string,
|
|
27
|
-
config: DocBridgeConfigV1,
|
|
28
|
-
): AgentHandoffV1 => {
|
|
29
|
-
const fromIndex = index.handoffs?.[id]
|
|
30
|
-
if (fromIndex) return normalizeAgentHandoff(fromIndex)
|
|
31
|
-
|
|
32
|
-
const owner = index.lookup?.ownership?.[id]
|
|
33
|
-
if (!owner) throw new Error(`Unknown package/ownership id "${id}". Try: ak-docs list packages`)
|
|
32
|
+
export const DEFAULT_AGENT_CONTEXT_BUDGET_TOKENS = 32
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
humanDoc: 'missing' as const,
|
|
42
|
-
action: 'ak-docs bootstrap agent-docs',
|
|
43
|
-
bootstrap: `docs/for-agents/human/${id}.md`,
|
|
44
|
-
}
|
|
45
|
-
: undefined
|
|
34
|
+
const modeLimits: Record<AgentQueryMode, { readonly matches: number; readonly nextCommands: number }> = {
|
|
35
|
+
discovery: { matches: 8, nextCommands: 5 },
|
|
36
|
+
editing: { matches: 5, nextCommands: 3 },
|
|
37
|
+
debugging: { matches: 6, nextCommands: 4 },
|
|
38
|
+
documentation: { matches: 6, nextCommands: 3 },
|
|
39
|
+
}
|
|
46
40
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
41
|
+
const boundedAgentContext = (
|
|
42
|
+
matches: readonly AgentSearchV1['matches'][number][],
|
|
43
|
+
nextCommands: readonly string[],
|
|
44
|
+
contextBudgetTokens: number,
|
|
45
|
+
): { readonly matches: AgentSearchV1['matches']; readonly nextCommands: string[]; readonly contextBytes: number; readonly truncated: boolean } => {
|
|
46
|
+
if (!Number.isInteger(contextBudgetTokens) || contextBudgetTokens < 1 || contextBudgetTokens > 1_000_000) throw new Error('contextBudgetTokens must be an integer between 1 and 1000000.')
|
|
47
|
+
const maxBytes = contextBudgetTokens * 4
|
|
48
|
+
let boundedMatches = matches.map((match) => ({ ...match }))
|
|
49
|
+
let boundedCommands = [...nextCommands]
|
|
50
|
+
let truncated = false
|
|
51
|
+
const contextBytes = () => Buffer.byteLength(JSON.stringify({ matches: boundedMatches, nextCommands: boundedCommands }), 'utf8')
|
|
52
|
+
while (contextBytes() > maxBytes) {
|
|
53
|
+
const summaryIndex = [...boundedMatches].map((match) => match.summary !== undefined).lastIndexOf(true)
|
|
54
|
+
if (summaryIndex >= 0) {
|
|
55
|
+
const match = boundedMatches[summaryIndex]
|
|
56
|
+
if (match) {
|
|
57
|
+
const { summary: _summary, ...withoutSummary } = match
|
|
58
|
+
boundedMatches[summaryIndex] = withoutSummary
|
|
59
|
+
truncated = true
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (boundedCommands.length > 1) {
|
|
64
|
+
boundedCommands.pop()
|
|
65
|
+
truncated = true
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
if (boundedMatches.length > 1) {
|
|
69
|
+
boundedMatches.pop()
|
|
70
|
+
truncated = true
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
if (boundedCommands.length > 0) {
|
|
74
|
+
boundedCommands.pop()
|
|
75
|
+
truncated = true
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
const finalContextBytes = contextBytes()
|
|
81
|
+
if (finalContextBytes > maxBytes) throw new Error(`contextBudgetTokens ${contextBudgetTokens} is too small for the minimum grounded result.`)
|
|
82
|
+
return { matches: boundedMatches, nextCommands: boundedCommands, contextBytes: finalContextBytes, truncated }
|
|
70
83
|
}
|
|
71
84
|
|
|
72
85
|
export const runQuery = (
|
|
73
86
|
index: DocBridgeIndexV1,
|
|
74
87
|
config: DocBridgeConfigV1,
|
|
75
88
|
req: QueryRequest,
|
|
89
|
+
options: HandoffOptions = {},
|
|
76
90
|
): QueryResult => {
|
|
77
91
|
if (req.kind === 'search') {
|
|
78
92
|
const term = req.term ?? req.id ?? ''
|
|
79
|
-
const matches = searchIndex(index, term)
|
|
93
|
+
const matches = searchIndex(index, term, 20, { ...(req.explain ? { explain: true } : {}), ...(req.agent ? { agent: true } : {}) })
|
|
80
94
|
if (req.agent) {
|
|
95
|
+
const mode = AgentQueryModeSchema.parse(req.mode ?? 'discovery')
|
|
96
|
+
const limits = modeLimits[mode]
|
|
97
|
+
const focusedMatches = matches[0] && (matches[0].type === 'intent' || matches[0].type === 'change')
|
|
98
|
+
? matches.filter((match) => match.type === matches[0]?.type).slice(0, 3)
|
|
99
|
+
: matches.slice(0, limits.matches)
|
|
100
|
+
const agentMatches = focusedMatches.map((m) => ({
|
|
101
|
+
type: m.type,
|
|
102
|
+
id: m.id,
|
|
103
|
+
path: m.path,
|
|
104
|
+
...(m.summary ? { summary: m.summary } : {}),
|
|
105
|
+
}))
|
|
106
|
+
const nextCommands = [...new Set(focusedMatches.slice(0, limits.nextCommands).map((m) =>
|
|
107
|
+
m.type === 'intent'
|
|
108
|
+
? `ak-docs query intent ${m.id} --agent`
|
|
109
|
+
: m.type === 'change'
|
|
110
|
+
? `ak-docs query change ${m.id} --agent`
|
|
111
|
+
: m.type === 'ownership' || index.lookup?.ownership?.[m.id]
|
|
112
|
+
? `ak-docs query ownership ${m.id} --agent`
|
|
113
|
+
: 'ak-docs list knowledge --text',
|
|
114
|
+
))]
|
|
115
|
+
const budget = req.contextBudgetTokens ?? DEFAULT_AGENT_CONTEXT_BUDGET_TOKENS
|
|
116
|
+
const bounded = boundedAgentContext(agentMatches, nextCommands, budget)
|
|
81
117
|
const payload: AgentSearchV1 = {
|
|
82
118
|
type: 'agent-search',
|
|
83
119
|
schemaVersion: 1,
|
|
@@ -90,21 +126,18 @@ export const runQuery = (
|
|
|
90
126
|
id: matches[0].id,
|
|
91
127
|
path: matches[0].path,
|
|
92
128
|
...(matches[0].summary ? { summary: matches[0].summary } : {}),
|
|
93
|
-
score: matches[0].score,
|
|
94
129
|
}
|
|
95
130
|
: null,
|
|
96
|
-
matches: matches
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
: 'ak-docs list knowledge --text',
|
|
107
|
-
))],
|
|
131
|
+
matches: bounded.matches,
|
|
132
|
+
nextCommands: bounded.nextCommands,
|
|
133
|
+
telemetry: {
|
|
134
|
+
contextBytes: bounded.contextBytes,
|
|
135
|
+
estimatedTokens: Math.ceil(bounded.contextBytes / 4),
|
|
136
|
+
tokenMethod: 'estimate',
|
|
137
|
+
contextBudgetTokens: budget,
|
|
138
|
+
mode,
|
|
139
|
+
truncated: bounded.truncated,
|
|
140
|
+
},
|
|
108
141
|
}
|
|
109
142
|
return payload
|
|
110
143
|
}
|
|
@@ -115,7 +148,7 @@ export const runQuery = (
|
|
|
115
148
|
if (!id) throw new Error(`Missing id for query kind "${req.kind}"`)
|
|
116
149
|
|
|
117
150
|
if (req.kind === 'package' || req.kind === 'ownership') {
|
|
118
|
-
if (req.agent) return
|
|
151
|
+
if (req.agent) return handoffForEntity(index, id, config, options)
|
|
119
152
|
const owner = index.lookup?.ownership?.[id]
|
|
120
153
|
return { type: req.kind, data: owner ?? index.handoffs?.[id] ?? null }
|
|
121
154
|
}
|