@agentskit/doc-bridge 1.7.45 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +465 -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 +15861 -6223
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +86 -6
- package/dist/config/index.js.map +1 -1
- package/dist/{index-BUL0q7s8.d.ts → index-Beor6Yhi.d.ts} +133 -1
- package/dist/index.d.ts +9871 -1062
- package/dist/index.js +16126 -6125
- 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 +147 -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 +25 -6
- 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 +117 -2
- package/src/conformance/documentation-standard-v1.ts +8 -6
- package/src/discovery/areas.ts +182 -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 +484 -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,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Graph signals v1
|
|
3
|
+
description: What Doc Bridge computes from the repository graph, what each signal means, and what it deliberately does not claim.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Graph signals v1
|
|
7
|
+
|
|
8
|
+
The knowledge snapshot is a graph, and some questions are only answerable as graph questions: which
|
|
9
|
+
document is the entry point, which module every change reaches through, which directories move
|
|
10
|
+
together, where the imports run in a circle.
|
|
11
|
+
|
|
12
|
+
These are computed with [graphology](https://graphology.github.io) from the snapshot, on demand.
|
|
13
|
+
The graph is a working structure: it is never serialised, `DiscoverySnapshotV1` does not change, and
|
|
14
|
+
graphology's own format never reaches disk. Nodes and edges are inserted in sorted order and every
|
|
15
|
+
score is rounded before it is returned, so two runs over the same snapshot agree exactly — including
|
|
16
|
+
after the input order is shuffled.
|
|
17
|
+
|
|
18
|
+
## The signals
|
|
19
|
+
|
|
20
|
+
| Signal | Computed from | Over |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| **Canonicality** | PageRank | `links-to` and `covers` |
|
|
23
|
+
| **Centrality** | betweenness, normalized | `imports` and `re-exports` |
|
|
24
|
+
| **Proximity** | bounded shortest path | imports, documentation, `mentions`, `depends-on` |
|
|
25
|
+
| **Import cycles** | depth-first search | `imports` and `re-exports` |
|
|
26
|
+
| **Area suggestions** | seeded Louvain communities | `imports` and `re-exports` |
|
|
27
|
+
|
|
28
|
+
External and unresolved endpoints are left out unless a caller asks for them: a dependency on a
|
|
29
|
+
third-party package is a fact about the repository, not a part of its architecture.
|
|
30
|
+
|
|
31
|
+
**Canonicality** answers where a reader should start. A page many documents link to, or that covers
|
|
32
|
+
many entities, outranks a leaf note nothing points at — however recently the leaf was edited.
|
|
33
|
+
|
|
34
|
+
**Centrality** answers how much of the dependency structure runs through one module. It is a review
|
|
35
|
+
signal: a change there reaches further than its diff suggests. It is *not* a statement about runtime
|
|
36
|
+
availability, and the diagnostic says so, because "single point of failure" is a claim about
|
|
37
|
+
deployment that a static import graph cannot make.
|
|
38
|
+
|
|
39
|
+
**Proximity** answers how many hops apart two entities are, bounded — three by default. Unbounded
|
|
40
|
+
proximity is not useful: at ten hops everything is related to everything. `contains` is excluded on
|
|
41
|
+
purpose. It is hierarchy, and including it puts every module in an area two hops from every other
|
|
42
|
+
one, which is true and tells a reader nothing.
|
|
43
|
+
|
|
44
|
+
**Import cycles** are reported with every edge that forms them. `graphology-dag` answers whether a
|
|
45
|
+
cycle exists; reporting one needs the path, because a diagnostic whose loop a reader cannot trace is
|
|
46
|
+
a claim rather than a finding. The search is bounded in count, and the cycle is rotated so the
|
|
47
|
+
lexicographically smallest node comes first — the same cycle found from two different entry points
|
|
48
|
+
is one finding.
|
|
49
|
+
|
|
50
|
+
## Area suggestions are suggestions
|
|
51
|
+
|
|
52
|
+
A Louvain community is a hypothesis: these modules move together, so perhaps they are one unit. It
|
|
53
|
+
never becomes an [area](./config-v1.md#analysisareas-optional) on its own. An area is derived from
|
|
54
|
+
the repository's own structure or declared by a human, and a clustering algorithm is neither.
|
|
55
|
+
|
|
56
|
+
Suggestions travel as `coverage` entries with `analyzer: graph`, `scope: area-suggestion:<path>` and
|
|
57
|
+
status `not-analyzed` — the honest status, because the clustering ran but whether the cluster is an
|
|
58
|
+
area is a question nobody has answered. A directory that is already an area produces no suggestion.
|
|
59
|
+
Community detection draws from a seeded generator, so a suggestion is reproducible rather than a
|
|
60
|
+
different guess each run.
|
|
61
|
+
|
|
62
|
+
## Reading the graph as memory
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { createDocBridgeGraphMemory } from '@agentskit/doc-bridge'
|
|
66
|
+
|
|
67
|
+
const graph = createDocBridgeGraphMemory(snapshot)
|
|
68
|
+
await graph.getNode('module:src/mcp/server.ts')
|
|
69
|
+
await graph.findEdges({ label: 'covers', to: 'area:src/mcp' })
|
|
70
|
+
await graph.neighbors('module:src/mcp/server.ts', { depth: 2 })
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This satisfies `GraphMemory` from `@agentskit/memory`, so an agent built on AgentsKit walks
|
|
74
|
+
repository structure with the same three calls it uses for its own memory. A second argument layers
|
|
75
|
+
extra entities and relations over the observation, in the snapshot's own vocabulary, so an approved
|
|
76
|
+
enrichment overlay plugs in unchanged.
|
|
77
|
+
|
|
78
|
+
Writes are accepted and kept in process. The snapshot is an observation and a caller cannot edit it,
|
|
79
|
+
so `upsertNode` lands in a working layer above the projection and `deleteNode` masks rather than
|
|
80
|
+
erases — an agent can annotate what it is exploring without any of it being mistaken for something
|
|
81
|
+
the repository said. `clear()` drops the working layer and leaves the projection intact.
|
|
82
|
+
|
|
83
|
+
## What consumes them
|
|
84
|
+
|
|
85
|
+
`centrality-risk` in the rules engine is betweenness now. It used to count how many
|
|
86
|
+
undocumented-relation findings were attached to an entity, which measures documentation debt and
|
|
87
|
+
calls it architecture: a module every import path runs through scored zero if it happened to be
|
|
88
|
+
documented. Without a graph the rule reports nothing at all, which is better than reporting the
|
|
89
|
+
wrong thing under a name people act on.
|
|
90
|
+
|
|
91
|
+
The rule's threshold reads as a rank when it is 1 or more — `3` means "flag the three most central
|
|
92
|
+
entities" — and as a minimum betweenness when it is below 1.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Incremental scan v1
|
|
3
|
+
description: How Doc Bridge hashes each file-backed entity, when a second scan may reuse one, and what it refuses to reuse.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Incremental scan v1
|
|
7
|
+
|
|
8
|
+
Every `module`, `document` and `package` entity carries the hash of its file in its first evidence
|
|
9
|
+
item. An `external` entity carries none: it is a name in a manifest, not a file on disk.
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"id": "module:src/query/search.ts",
|
|
14
|
+
"kind": "module",
|
|
15
|
+
"evidence": [{ "source": "code", "path": "src/query/search.ts", "contentHash": "…" }]
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`EvidenceSchema.contentHash` had existed since the first schema and discovery never filled it, so
|
|
20
|
+
every cache and every overlay could be keyed only on "the whole repository changed" — which is true
|
|
21
|
+
between any two commits and therefore useless. With a hash per file, a consumer can expire one
|
|
22
|
+
entry, and a second scan can skip the expensive part: the TypeScript parse and the Markdown parse,
|
|
23
|
+
where nearly all of discovery's time goes.
|
|
24
|
+
|
|
25
|
+
A document's hash is taken after a leading byte-order mark is stripped. A mark is not content: a
|
|
26
|
+
file that only gained one parses to the same tree, and should not invalidate anything.
|
|
27
|
+
|
|
28
|
+
## Reuse
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
const cold = discoverRepository({ root, config })
|
|
32
|
+
const fast = discoverRepository({ root, config, previous: cold })
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`previous` is an offer, not an instruction. Discovery reuses an entity only when reuse cannot change
|
|
36
|
+
the answer, and two different things can change it:
|
|
37
|
+
|
|
38
|
+
- **An entity's own fields depend on its own bytes.** A hash match is enough.
|
|
39
|
+
- **A relation depends on what else exists.** A module importing `./new.js` resolved to nothing
|
|
40
|
+
before that file was added and resolves to a module after; a document mentioning `rank` points at
|
|
41
|
+
whichever module declares it. So relation reuse also requires that the universe the references
|
|
42
|
+
resolve against is identical.
|
|
43
|
+
|
|
44
|
+
Two fingerprints capture that universe, both derived from the previous snapshot rather than stored
|
|
45
|
+
in it — everything they cover is in the snapshot already, and a stored fingerprint is one more
|
|
46
|
+
thing that can be stale or forged.
|
|
47
|
+
|
|
48
|
+
| Fingerprint | Covers | Gates |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| Module universe | module paths, packages, compiler options | reuse of a module's relations |
|
|
51
|
+
| Resolution | the module universe, plus document paths, area paths and which module declares each exported symbol | reuse of a document |
|
|
52
|
+
|
|
53
|
+
A reused entity's relations are replayed against the entity set the scan is producing. An edge
|
|
54
|
+
whose internal target is gone is dropped rather than carried: the file it pointed at was renamed or
|
|
55
|
+
deleted, and a graph that keeps the edge is lying about the repository. An external or unresolved
|
|
56
|
+
endpoint is re-added instead, because such an entity is in the snapshot only because something
|
|
57
|
+
referenced it, and the thing that referenced it is exactly what was reused.
|
|
58
|
+
|
|
59
|
+
## What is refused outright
|
|
60
|
+
|
|
61
|
+
A hash says a file has not changed. It says nothing about whether this code would still read it the
|
|
62
|
+
same way — an analyzer that learns to record a document's headings produces different entities from
|
|
63
|
+
identical bytes, and a configuration change moves area boundaries and runtime-wiring detection. So
|
|
64
|
+
the whole snapshot is refused unless it was produced by this `pipelineVersion`, these
|
|
65
|
+
`analyzerVersions` and this `configurationHash`, and a snapshot that does not declare all three is
|
|
66
|
+
refused as well. Trusting an undeclared input with a repository scan is how a cache becomes a
|
|
67
|
+
source of wrong answers.
|
|
68
|
+
|
|
69
|
+
A cache that is only usually right is worse than no cache. Reuse either produces the snapshot a
|
|
70
|
+
cold scan would produce, or it does not happen.
|
|
71
|
+
|
|
72
|
+
A reused entity also replays the per-file `coverage` its analyzer produced, because the aggregate
|
|
73
|
+
entries are derived from those rather than stored. An aggregate that cannot be rebuilt from what
|
|
74
|
+
the snapshot carries is an aggregate a fast scan gets wrong: a fact that lives only in a local
|
|
75
|
+
variable during a parse is a fact the next scan cannot replay. `dynamic-imports:<path>` and
|
|
76
|
+
`runtime-wiring:<path>` therefore record every observed load and wiring call — `complete` when the
|
|
77
|
+
target is statically known, `not-analyzed` when it is not.
|
|
78
|
+
|
|
79
|
+
## The run explains itself
|
|
80
|
+
|
|
81
|
+
A run that finishes in a tenth of the time has to be able to say why, or nobody can tell a working
|
|
82
|
+
cache from a broken scan. One `coverage` entry reports it:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"analyzer": "repository",
|
|
87
|
+
"scope": "reused-entities",
|
|
88
|
+
"status": "complete",
|
|
89
|
+
"reason": "Reused 102 entities and skipped 102 of 102 parse(s). Nothing needed re-parsing."
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`status` is `complete` when everything reusable was reused, `partial` when reuse was refused — the
|
|
94
|
+
reason then names what changed — and `not-applicable` when there was no previous snapshot to reuse.
|
|
95
|
+
|
|
96
|
+
This entry is the one part of a snapshot that describes the *run* rather than the repository. The
|
|
97
|
+
entities, the relations and every other coverage entry are byte-identical to a cold scan's, which
|
|
98
|
+
is what the tests assert; the snapshot's own `contentHash` covers this entry too, so a warm scan
|
|
99
|
+
and a cold scan of the same tree hash differently. That is why the CLI does not yet pass a previous
|
|
100
|
+
snapshot: the artifacts it writes are compared across runs, and the caching layer has to decide
|
|
101
|
+
what a report keys on before a fast scan starts feeding it. `contentHash` and `sourceRevision`
|
|
102
|
+
semantics are otherwise untouched.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Markdown analyzer v1
|
|
3
|
+
description: What Doc Bridge reads from a Markdown document, and the relations it observes from prose.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Markdown analyzer v1
|
|
7
|
+
|
|
8
|
+
Documentation is parsed with remark (CommonMark plus GFM), not with regular expressions, so a
|
|
9
|
+
document's own prose becomes evidence. Every relation below is `observed` and carries the file and
|
|
10
|
+
line the claim was made on.
|
|
11
|
+
|
|
12
|
+
| Relation | From → to | Observed from |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| `links-to` | document → document | a relative link that resolves to a scanned document |
|
|
15
|
+
| `mentions` | document → module or package | inline code or link text equal to a scanned path or a package name |
|
|
16
|
+
| `mentions-symbol` | document → module | inline code equal to an exported name of exactly one module |
|
|
17
|
+
| `covers` | document → anything | a `docbridge` declaration, unchanged |
|
|
18
|
+
|
|
19
|
+
A symbol resolves to the module that **declares** it, never to a barrel that re-exports it. When
|
|
20
|
+
two modules declare the same name the reference resolves to neither: the tokens and their lines
|
|
21
|
+
are reported as a `markdown` coverage note instead, because sending an agent to one of two
|
|
22
|
+
possible definitions is worse than sending it nowhere. The same rule governs near-misses — an
|
|
23
|
+
unresolved path-shaped reference is matched with Jaro-Winkler and accepted only at 0.92 or above
|
|
24
|
+
*and* with a single candidate, recorded as `metadata.confidence: "fuzzy"`.
|
|
25
|
+
|
|
26
|
+
Document entities gain what the parser can see: `title`, headings to depth three with their
|
|
27
|
+
lines, a bounded `summary`, `wordCount`, the frontmatter subset (`type`, `audience`, `owner`,
|
|
28
|
+
`lifecycle`, `tier`), any generated regions, and the file's `contentHash` on its evidence. A
|
|
29
|
+
document declaring `audience` overrides the path heuristic that classifies it; `type` overrides it
|
|
30
|
+
only when it names an audience, since in practice `type` names a document kind.
|
|
31
|
+
|
|
32
|
+
A document referencing more than 64 entities records `evidenceTruncated` and a coverage note. An
|
|
33
|
+
index page's sixty-fifth link is not knowledge, and an unbounded list is not evidence.
|
|
34
|
+
|
|
35
|
+
## Generated regions
|
|
36
|
+
|
|
37
|
+
```markdown
|
|
38
|
+
<!-- doc-bridge:generated hash=8f79947 -->
|
|
39
|
+
…generator output…
|
|
40
|
+
<!-- /doc-bridge:generated -->
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Mentions inside a generated region are ignored, so Doc Bridge never reads its own output back in
|
|
44
|
+
as evidence about the repository. An unclosed marker owns the rest of the file. The regions are
|
|
45
|
+
recorded on the document entity, which is what lets the audit report a manual edit inside one.
|
|
46
|
+
|
|
47
|
+
## The `docbridge` block
|
|
48
|
+
|
|
49
|
+
The block is real YAML validated by a schema, so quoted lists, flow mappings, anchors and
|
|
50
|
+
multi-line strings work as they do in every other tool. Schema violations report the field:
|
|
51
|
+
`docbridge.relations.0: Unrecognized key: note`. Each `DOCBRIDGE_*` code is preserved — a
|
|
52
|
+
repository failing its build on one keeps failing on the same one — and a block YAML cannot read
|
|
53
|
+
at all falls back to the line-oriented scanner, because on a mangled block a diagnostic per line
|
|
54
|
+
helps the author more than a single parser error.
|
|
55
|
+
|
|
56
|
+
## The cost of a near-miss
|
|
57
|
+
|
|
58
|
+
Near-miss resolution is the analyzer's only super-linear step: every unresolved path-shaped
|
|
59
|
+
reference is a query against every document, module and area path in the repository. On a monorepo
|
|
60
|
+
of 4 100 documents and 9 240 modules the naive form of that — rebuilding the candidate list per
|
|
61
|
+
document, then computing Jaro-Winkler against each candidate — did not finish.
|
|
62
|
+
|
|
63
|
+
Two bounds make it cheap, and both are bounds on Jaro's match count `m` rather than heuristics.
|
|
64
|
+
`m` cannot exceed the shorter string, so `jaro ≤ (m/|a| + m/|b| + 1) / 3`; with the prefix bonus
|
|
65
|
+
bounded by `jw ≤ 0.6·jaro + 0.4`, a threshold of 0.92 admits only candidates whose length is
|
|
66
|
+
within 0.6× to 1.67× the query's. And `m` cannot exceed the number of characters the two strings
|
|
67
|
+
share, which one pass over a 128-slot count vector answers, where a similarity computation costs a
|
|
68
|
+
pass over one string per character of the other.
|
|
69
|
+
|
|
70
|
+
Both only ever over-estimate `m`, so a candidate they skip could not have matched: the index is a
|
|
71
|
+
speed-up with no effect on results, tied scores included. `MarkdownResolution.pathIndex` carries it,
|
|
72
|
+
built once per run; an analyzer called without one builds its own and produces the same output more
|
|
73
|
+
slowly.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: MCP knowledge tools v1
|
|
3
|
+
description: One search and one lookup that answer inside a declared token budget, findings in the shape the ecosystem consumes, and a doctor that measures what retrieval can see.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# MCP knowledge tools v1
|
|
7
|
+
|
|
8
|
+
An agent about to edit `src/mcp` used to need four calls — a search, a handoff, the diagnostics,
|
|
9
|
+
the relations — and got back payloads whose size it could only estimate as `contextBytes / 4`.
|
|
10
|
+
Two tools now answer that question in one bounded response, the existing tools stay as they are,
|
|
11
|
+
and the doctor stops reporting a health it did not measure.
|
|
12
|
+
|
|
13
|
+
## `knowledge.search`
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{ "query": "where do I add a new MCP tool", "kinds": ["document", "area"], "limit": 5, "explain": true, "budgetTokens": 600 }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Ranks the retrieval projection with the same `searchIndex` the CLI uses, so with no `kinds` the
|
|
20
|
+
`results` are what `ak-docs search <query> --json` prints for the same query and index — a test
|
|
21
|
+
compares the two rather than assuming it. Each result adds the entry's `title` and an `excerpt`,
|
|
22
|
+
the opening of the projected body. `kinds` filters after ranking, from a deeper ranking, so the
|
|
23
|
+
order never changes and a filtered call still returns up to `limit`. `explain` attaches the
|
|
24
|
+
matched terms and every score component, as on the CLI.
|
|
25
|
+
|
|
26
|
+
## `knowledge.lookup`
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{ "id": "area:src/mcp", "depth": 1, "budgetTokens": 900 }
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Accepts an entity id, an ownership id, an alias or a `path`, through the same resolution
|
|
33
|
+
`handoff.resolve` uses. One response carries:
|
|
34
|
+
|
|
35
|
+
| Section | What it is |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| `entity` | id, kind, path, title, summary, aliases, symbols, tags, provenance, confidence, content hash, ownership, area, package, PageRank |
|
|
38
|
+
| `neighbours` | every entry within `depth` hops, grouped by relation kind (`covers`, `mentions`, `links-to`, `imports`, `contains`, …), each with direction, confidence, distance and the entry it was reached through |
|
|
39
|
+
| `documents` | the documents that `cover` the entity and those that `mention` it, most canonical first |
|
|
40
|
+
| `handoff` | exactly what `handoff.resolve` returns for the entity, `related` included |
|
|
41
|
+
| `diagnostics` | the open diagnostics of the latest reconciliation report that name the entity or point at its file, with the report hash — or `reportHash: null` when no workflow run exists |
|
|
42
|
+
| `evidence` | the entity's path and content hash, then those of the documents about it, each with an `excerpt` |
|
|
43
|
+
|
|
44
|
+
`contains` is not a projection edge — it is hierarchy — but an agent asking about an area wants its
|
|
45
|
+
modules listed, so the lookup synthesises it from `areaId` and `packageId`. Neighbours are
|
|
46
|
+
bounded per relation kind and visited in sorted order, so two lookups over the same projection
|
|
47
|
+
produce the same response. `depth` is at most 3.
|
|
48
|
+
|
|
49
|
+
`format: "text"` on either tool renders the same payload as prose through
|
|
50
|
+
`formatRetrievedDocuments`, for clients that prefer it.
|
|
51
|
+
|
|
52
|
+
## Budgets
|
|
53
|
+
|
|
54
|
+
When `budgetTokens` is present, both tools and `handoff.resolve` trim through `compileBudget`:
|
|
55
|
+
each droppable section becomes a message, oldest first in the order it may be dropped, the payload
|
|
56
|
+
with every section removed becomes the last message, and `drop-oldest` with `keepRecent: 1` sheds
|
|
57
|
+
sections from the front until the rest fits. The declared order is
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
evidenceExcerpts → related → neighbours → summaries
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
and nothing else is ever dropped: the entity, the evidence paths and hashes, the handoff fields an
|
|
64
|
+
agent acts on and the open diagnostics survive every budget. A payload whose undroppable core
|
|
65
|
+
still exceeds the budget reports `fits: false` with every section dropped, rather than truncating
|
|
66
|
+
what it may not drop. A section a payload does not have — search has no `related`, a handoff has
|
|
67
|
+
no `neighbours` — is absent from the report, never reported dropped.
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"budget": {
|
|
72
|
+
"budgetTokens": 900,
|
|
73
|
+
"tokens": { "total": 790, "budget": 900, "core": 684, "sections": { "evidenceExcerpts": 59, "related": 290, "neighbours": 229, "summaries": 106 } },
|
|
74
|
+
"fits": true,
|
|
75
|
+
"order": ["evidenceExcerpts", "related", "neighbours", "summaries"],
|
|
76
|
+
"kept": ["summaries"],
|
|
77
|
+
"dropped": ["evidenceExcerpts", "related", "neighbours"],
|
|
78
|
+
"tokenMethod": "approximate"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Tokens are counted with `approximateCounter` — four characters per token plus two per message,
|
|
84
|
+
over the serialised sections — and reported as `tokenMethod: "approximate"`. `compileBudget` and
|
|
85
|
+
`approximateCounter` are mirrored in `src/budget/compile.ts` because `@agentskit/core` is an
|
|
86
|
+
optional peer and every query surface answers with no peer installed; a test runs the real
|
|
87
|
+
`compileBudget` over the same messages and asserts the two agree on every token count, every
|
|
88
|
+
dropped message and `fits`. On a handoff the droppable sections are `related` and the note that
|
|
89
|
+
repeats the target's summary; `budget` is an optional field of `AgentHandoffV1`, so a budgeted
|
|
90
|
+
handoff is still a valid handoff.
|
|
91
|
+
|
|
92
|
+
## Every existing tool keeps working
|
|
93
|
+
|
|
94
|
+
`handoff.resolve`, `doc.search`, `doc.get`, `gate.status`, `retriever.query`, `memory.*`,
|
|
95
|
+
`registry.topology` and `docbridge.*` keep their names, arguments and payloads. `handoff.resolve`
|
|
96
|
+
gains an optional `budgetTokens`; `docbridge.diagnostics` gains an optional `format`. The new tools
|
|
97
|
+
are appended to the advertised list and to the configuration's `surfaces.mcp.tools` default, so a
|
|
98
|
+
configuration that names its tools explicitly is unchanged until it names them.
|
|
99
|
+
|
|
100
|
+
## Canonical findings
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
ak-docs check --json --format finding
|
|
104
|
+
docbridge.diagnostics { "format": "finding" }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Both emit every reconciliation diagnostic as a `Finding` from `@agentskit/core/finding`, with
|
|
108
|
+
severities drawn from `SEVERITY_ORDER`, so Code Review, AKOS and dashboards read Doc Bridge with no
|
|
109
|
+
parser of their own. Internal severities map `error → high`, `warn → medium`, `info → low`,
|
|
110
|
+
`off → info`; nothing is `critical`, because a documentation finding never takes a system down.
|
|
111
|
+
`title` is the code as words, `detail` the message, `category` the status, `location` the first
|
|
112
|
+
evidence path and line, `ref` the code, and `metadata` carries the internal code, status,
|
|
113
|
+
severity, evidence and entity ids, so nothing is lost. `confidence` follows the status: an
|
|
114
|
+
observed relation is certain, a stale declaration less so, a coverage gap least of all. Findings
|
|
115
|
+
are ordered most severe first and by id within a severity.
|
|
116
|
+
|
|
117
|
+
This is a reporter, not a migration: `KnowledgeDiagnostic`, `RuleFinding` and
|
|
118
|
+
`DocumentationAuditFinding` keep their shapes, and the rule verdict still decides the exit code
|
|
119
|
+
of `check`. A test imports the real package and asserts assignability and the severity order.
|
|
120
|
+
|
|
121
|
+
## The measured doctor
|
|
122
|
+
|
|
123
|
+
Three dimensions join the score and can lower the grade:
|
|
124
|
+
|
|
125
|
+
| Dimension | Measures | Points |
|
|
126
|
+
| --- | --- | --- |
|
|
127
|
+
| Reachability | the share of the snapshot's document entities present in the retrieval projection | 15 |
|
|
128
|
+
| Connectivity | the mean of: areas with at least one covering or mentioning document; documents with at least one edge into code | 15 |
|
|
129
|
+
| Benchmark | hit@3 over the golden suite at `retrieval.benchmark.suite` (`docs/bench/retrieval-suite-v1.json` by default) | 10 |
|
|
130
|
+
|
|
131
|
+
The existing dimensions — index present and fresh, agent docs, human guides, gates — make up the
|
|
132
|
+
other sixty. A repository with no golden suite reports the benchmark as `not-analyzed`, scores
|
|
133
|
+
nothing for it and says so in the issues; it is never silently omitted.
|
|
134
|
+
|
|
135
|
+
An A requires all three: reachability at 100 percent, connectivity at 80 percent or more, and a
|
|
136
|
+
measured hit@3 of 80 percent or more. A score of 90 that misses one of them is a B, and the text
|
|
137
|
+
report says which. Reachability must be complete because a document retrieval cannot find is a
|
|
138
|
+
document the product does not deliver; the other two have a floor rather than a ceiling.
|
|
139
|
+
|
|
140
|
+
On this repository every document is in the projection and the benchmark is at 88.3 percent, but
|
|
141
|
+
20 of 39 areas have no document about them and 55 of 100 documents do not point at code —
|
|
142
|
+
connectivity 47 percent, grade B. That is the honest state. A test proves the other direction on
|
|
143
|
+
the same repository: one document out of the projection is enough to lose the A, and an index
|
|
144
|
+
with no projection at all — what the builder produced before the corpus projection — is at zero.
|
|
145
|
+
|
|
146
|
+
The doctor's `ok` is unchanged: it is still "no error-severity issue and the gates pass", so a
|
|
147
|
+
CI step that runs `ak-docs doctor --text` fails on a stale index, not on a B.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Measured enrichment v1
|
|
3
|
+
description: What an enrichment run cost, what it invented, whether it improved retrieval at all, and the study measurements — mechanical task expectations, the assisted arm, tokens to first evidence — that decide the same questions for a controlled round.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Measured enrichment v1
|
|
7
|
+
|
|
8
|
+
An enrichment stage nobody measures is a stage nobody can defend.
|
|
9
|
+
|
|
10
|
+
[Enrichment overlay v1](./enrichment-overlay-v1.md) made agent proposals typed, validated and
|
|
11
|
+
bounded. It did not say whether any of it helped. The overlay reported what it accepted, which is
|
|
12
|
+
the one number an agent cannot fail: a curator that proposes a hundred things and has ninety
|
|
13
|
+
rejected looks exactly like one that proposes ten good ones. And the controlled study could not
|
|
14
|
+
answer the question either — its last round recorded zero semantic successes in both arms, because
|
|
15
|
+
a task whose only success criterion is a model's opinion produces no signal, and its third arm has
|
|
16
|
+
been reserved since the first suite without ever running.
|
|
17
|
+
|
|
18
|
+
This document is the measurement layer: the whole shape of an enrichment run, the retrieval delta
|
|
19
|
+
that can block it, and the study machinery that asks the same questions of a controlled round.
|
|
20
|
+
|
|
21
|
+
## What the run reports
|
|
22
|
+
|
|
23
|
+
`EnrichmentStats` in `src/schemas/enrichment.ts` is written by `runEnrichment` and stored on the
|
|
24
|
+
overlay. It is deliberately outside the overlay's content hash — two runs over one unchanged
|
|
25
|
+
repository must agree on their decisions while disagreeing about how long they took.
|
|
26
|
+
|
|
27
|
+
| Field | Meaning |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| `byKind` | Per proposal kind: `proposed`, `accepted`, `pending`, `rejected`. Sorted by kind, so two runs produce the same bytes. |
|
|
30
|
+
| `rejectionReasons` | A histogram over the closed list of rejection reasons, sorted by reason. A run's rejections are comparable across runs rather than a log to read. |
|
|
31
|
+
| `inventedReferences` | Rejections that named something the repository does not contain. |
|
|
32
|
+
| `agentRuns`, `inputBytes`, `outputBytes` | What was actually sent and received. Batched packs, so a run is not a call count. |
|
|
33
|
+
| `cacheHits`, `cacheHitRate` | Hits, and hits over hits plus runs, rounded to six places. |
|
|
34
|
+
| `wallTimeMs` | Measured from a monotonic clock, not derived. `runEnrichment` takes a `clock` option so a test can pin it. |
|
|
35
|
+
| `expired` | Entries whose target content hash moved since they were accepted. |
|
|
36
|
+
|
|
37
|
+
`enrichmentCost(stats)` in `src/enrich/stats.ts` is the same numbers as an object for a reader who
|
|
38
|
+
only wants the bill; `ak-docs enrich --json` reports it under `cost`.
|
|
39
|
+
|
|
40
|
+
### Invented references
|
|
41
|
+
|
|
42
|
+
`INVENTED_RELATION_REASONS` is `unknown-endpoint`, `unknown-entity`, `unknown-scope`,
|
|
43
|
+
`unknown-directory`, `unknown-diagnostic`. `inventedReferences` counts exactly those, and it is
|
|
44
|
+
reported next to the rejection total rather than folded into it.
|
|
45
|
+
|
|
46
|
+
The distinction is not cosmetic. A curator that classifies a document badly is wrong about a
|
|
47
|
+
judgement, and a validator rejecting it is the system working. A curator that proposes a relation
|
|
48
|
+
to a module the repository does not contain is making things up, and that number must trend to
|
|
49
|
+
zero or the agent is unusable. Folding the two together hides the second inside the first.
|
|
50
|
+
|
|
51
|
+
A schema failure or an unknown kind is a rejection and not an invention: `invalid-kind` is not in
|
|
52
|
+
the list, and a test asserts the counts stay apart.
|
|
53
|
+
|
|
54
|
+
### Stability
|
|
55
|
+
|
|
56
|
+
`enrichmentStability(current, previous)` compares a run with the one before it and reports both
|
|
57
|
+
halves of the question at once:
|
|
58
|
+
|
|
59
|
+
- `overlayHashIdentical` — a deterministic agent over an unchanged repository must reach this. The
|
|
60
|
+
overlay hash covers decisions, not `acceptedAt` and not `stats`, so a second run that is answered
|
|
61
|
+
entirely from the cache produces the same hash as the first.
|
|
62
|
+
- `proposalIdShare` — for a live model, which will not reach an identical hash, the share of
|
|
63
|
+
proposal identifiers present in both runs. The share is over the **union** of the two runs'
|
|
64
|
+
identifiers, so a run that merely proposes fewer things does not score as more stable.
|
|
65
|
+
|
|
66
|
+
`sharedProposalIds`, `proposalIds` and `previousProposalIds` are reported next to the share, because
|
|
67
|
+
a share of 0.5 over two identifiers and over two hundred are different facts.
|
|
68
|
+
|
|
69
|
+
## The retrieval delta
|
|
70
|
+
|
|
71
|
+
`measureOverlayRetrievalDelta` in `src/bench/overlay-delta.ts` answers the question the rest of the
|
|
72
|
+
statistics cannot: did the overlay make retrieval better?
|
|
73
|
+
|
|
74
|
+
It runs the golden suite twice over **one snapshot** — once with the accepted overlay projected,
|
|
75
|
+
once with `overlay: 'ignore'` — so the only difference between the two runs is the overlay. Not a
|
|
76
|
+
re-scan, not a different revision, not a different configuration. Neither half needs an index on
|
|
77
|
+
disk: both are projected from the snapshot the caller passes.
|
|
78
|
+
|
|
79
|
+
The rule is asymmetric on purpose. An overlay may leave retrieval unchanged and it may improve it,
|
|
80
|
+
but it must not lower `hitAt3` (`OVERLAY_BLOCKING_METRIC`): aliases, summaries and rank hints an
|
|
81
|
+
agent proposed exist to help an agent find things. A drop is reported as `regression: true` with
|
|
82
|
+
status `regressed`, and the caller exits non-zero. It is a finding about the agent, never a new
|
|
83
|
+
baseline.
|
|
84
|
+
|
|
85
|
+
The result carries both metric sets, a per-metric delta with `improved` and `worsened` (lower is
|
|
86
|
+
better for `meanContextBytes`, `meanApproxTokens` and `zeroResultRate`), and — the part that makes a
|
|
87
|
+
regression actionable — `lostCases` and `gainedCases`, the case ids that changed at hit@3.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# The overlay on disk, against the configured golden suite.
|
|
91
|
+
ak-docs bench retrieval docs/bench/retrieval-suite-v1.json --overlay --text
|
|
92
|
+
|
|
93
|
+
# Or as part of the run that produced it.
|
|
94
|
+
ak-docs enrich --retrieval-delta --json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Both exit 1 on a regression. Both report the delta in text and in JSON: `formatOverlayRetrievalDeltaText`
|
|
98
|
+
for a person, `retrievalDelta` (or `overlayDelta`) for a machine. The delta is opt-in because it runs
|
|
99
|
+
the suite twice, which is the right cost for an answer about whether the overlay helped and the wrong
|
|
100
|
+
cost for every routine run.
|
|
101
|
+
|
|
102
|
+
## Study task expectations
|
|
103
|
+
|
|
104
|
+
The study's correction is the same idea one level up: state what retrieval is expected to return,
|
|
105
|
+
and check it deterministically.
|
|
106
|
+
|
|
107
|
+
A task in `docs/study/task-suite-v1.json` may now declare `expectedEntities`, `expectedDocuments`
|
|
108
|
+
and `retrievalQueries`. The references are **opaque** — `primary-entrypoint`, not `docs/alpha.md` —
|
|
109
|
+
because the task suite is publication-bound and a repository path in it is a privacy failure, not a
|
|
110
|
+
convenience. A query with nothing expected is refused by the schema: it would check nothing.
|
|
111
|
+
|
|
112
|
+
The resolution lives in a second artifact, `StudyExpectationsV1` in `src/study/expectations.ts`,
|
|
113
|
+
which declares `scope: 'local'` and binds to the suite by `taskSuiteHash`. It maps each reference to
|
|
114
|
+
the concrete entity ids or document paths it stands for in one repository on the operator's disk.
|
|
115
|
+
It is never published; putting it under `docs/study/` would fail the privacy gate, which is the
|
|
116
|
+
intended outcome rather than a bug. References resolved against a suite that has moved on are
|
|
117
|
+
refused outright.
|
|
118
|
+
|
|
119
|
+
`studyRetrievalSuite` turns the pair into an ordinary Open Eval Format suite — one case per task
|
|
120
|
+
query, with the resolved targets as `expectedTargets` and the task, repository and category carried
|
|
121
|
+
in metadata — so the same command, the same ranking and the same metrics that gate this
|
|
122
|
+
repository's retrieval answer the study's mechanical questions. `checkStudyExpectations` runs it:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
ak-docs study expectations docs/study/task-suite-v1.json \
|
|
126
|
+
--expectations ./local-study-expectations.json --index .doc-bridge/index.json --text
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`ok` requires that every reference resolved **and** every case hit. Two failures are reported rather
|
|
130
|
+
than smoothed over: a task that declares no expectations is listed under `withoutExpectations` as
|
|
131
|
+
unchecked, never counted as a pass, and an unresolved reference fails the check, because an
|
|
132
|
+
expectation nobody resolved is an expectation nobody tested — which is exactly the failure this
|
|
133
|
+
replaces. The command exits 1 when the check does not pass.
|
|
134
|
+
|
|
135
|
+
`validateStudyTaskSuite(suite, { requireExpectations: true })` names the tasks that cannot be checked
|
|
136
|
+
mechanically. It is off by default: a suite written before expectations existed is still a valid
|
|
137
|
+
suite.
|
|
138
|
+
|
|
139
|
+
### Where the model adjudicator stops
|
|
140
|
+
|
|
141
|
+
A rubric item may now be prose, or prose with the mechanical check that decides it
|
|
142
|
+
(`acceptance-checks`, `evidence-coverage`, `retrieval-expectations`). `mechanicalRubricItems` and
|
|
143
|
+
`modelRubricItems` split the rubric on that field, and `adjudicatorRubric(task)` is what the
|
|
144
|
+
adjudicator input carries: `rubric` holds only the items no checker can settle, and the settled ones
|
|
145
|
+
travel next to them as `mechanical` — verdicts, for context, not for review. The instruction says so
|
|
146
|
+
in the same words.
|
|
147
|
+
|
|
148
|
+
Handing a model an item the runner already measured invites it to disagree with a measurement, which
|
|
149
|
+
is how a study ends up with an opinion where it had a number.
|
|
150
|
+
|
|
151
|
+
## The assisted arm
|
|
152
|
+
|
|
153
|
+
`registry-assisted` has been reserved since the first suite and has never executed. It now reports
|
|
154
|
+
its own readiness, and `assistedArmReadiness(plan, providers, suite)` is the only thing that decides
|
|
155
|
+
whether it runs:
|
|
156
|
+
|
|
157
|
+
| Status | When | Effect |
|
|
158
|
+
| --- | --- | --- |
|
|
159
|
+
| `unavailable` | No assisted scenario in the plan; the scenario names no agent identity and version; no provider CLI for the scenario and a model the suite uses | Its executions are recorded as unavailable observations |
|
|
160
|
+
| `ready` with `undeclared` | The scenario declares no `promptVersion`, or no `agentBudget` | The arm runs; the run says what it could not name |
|
|
161
|
+
| `ready` | Everything declared | The arm runs |
|
|
162
|
+
|
|
163
|
+
An unavailable arm is **recorded, not skipped**: a scenario absent from a ledger is
|
|
164
|
+
indistinguishable from one that was never planned, and comparing the arms it planned is the study's
|
|
165
|
+
whole purpose. Each such execution lands in the ledger with `execution.status: 'unavailable'`,
|
|
166
|
+
`errorCode: 'registry-unavailable'`, and an automated adjudication of `blocked` carrying the reason.
|
|
167
|
+
A missing Registry is a fact about the environment, not a reason to lose the other two arms —
|
|
168
|
+
`assertRunInputs` skips provider validation for those executions so the rest of the run proceeds.
|
|
169
|
+
|
|
170
|
+
A missing declaration is the other way round: losing the third arm over an undeclared prompt version
|
|
171
|
+
would be worse than running it without one, so the arm runs and `undeclared` says what a reader
|
|
172
|
+
cannot reconstruct from the ledger — the prompt it used, or the agent's cost apart from the model's.
|
|
173
|
+
The run summary prints the status, the reason and the undeclared fields.
|
|
174
|
+
|
|
175
|
+
A run plan may declare `promptVersion` and `agentBudget` only on `registry-assisted`: the other two
|
|
176
|
+
arms have no agent to budget.
|
|
177
|
+
|
|
178
|
+
## Tokens to first evidence
|
|
179
|
+
|
|
180
|
+
The parent PRD's second claim is that an agent reaches correct grounded evidence for fewer tokens
|
|
181
|
+
than by reading the repository. Total tokens at the end of a task does not test that claim: a run
|
|
182
|
+
that wandered for ten thousand tokens and then found the answer looks the same as one that landed on
|
|
183
|
+
it immediately.
|
|
184
|
+
|
|
185
|
+
`tokensToFirstEvidence` is a canonical provider measurement — tokens consumed before correct
|
|
186
|
+
grounded evidence was in hand — and the metrics report its p95 per scenario as
|
|
187
|
+
`tokensToFirstEvidenceP95`, rounded up to whole tokens. It is in the improvement list, so a round
|
|
188
|
+
that reaches evidence sooner reads as an improvement, and absent or partial coverage is reported as
|
|
189
|
+
`tokensToFirstEvidence` or `tokensToFirstEvidence-partial` under `missingMetrics` rather than as a
|
|
190
|
+
zero.
|
|
191
|
+
|
|
192
|
+
The assisted arm's enrichment agent is costed apart from the model: `registryAgentInputTokens`,
|
|
193
|
+
`registryAgentOutputTokens`, `registryAgentCostUsd` and `registryAgentRuns`. `registryAgentCostUsd`
|
|
194
|
+
is added into `totalCostUsd`, so the arm cannot look cheap by charging its work to a line nobody
|
|
195
|
+
adds up, and `missingMetrics` names it when an assisted observation reports no agent cost at all.
|
|
196
|
+
The scenario line in `--text` prints the p95 next to the agent cost and its run count.
|
|
197
|
+
|
|
198
|
+
## Invariants
|
|
199
|
+
|
|
200
|
+
- `stats` is outside the overlay's content hash. Cost and timing may differ between two runs that
|
|
201
|
+
decided identically.
|
|
202
|
+
- `inventedReferences` counts only `INVENTED_RELATION_REASONS`, and is reported separately from the
|
|
203
|
+
rejection total.
|
|
204
|
+
- `proposalIdShare` is over the union of both runs' identifiers.
|
|
205
|
+
- The retrieval delta builds both indexes from one snapshot. An overlay on disk cannot leak into the
|
|
206
|
+
baseline half.
|
|
207
|
+
- An overlay that lowers hit@3 is a regression with a non-zero exit, in every mode.
|
|
208
|
+
- A study task's expectations are opaque references. A path or a URL in a publication-bound artifact
|
|
209
|
+
is a privacy failure, and the privacy gate is what says so.
|
|
210
|
+
- An unresolved reference fails the check; a task without expectations is reported unchecked and is
|
|
211
|
+
never a pass.
|
|
212
|
+
- A rubric item with a mechanical check never reaches the model adjudicator.
|
|
213
|
+
- An unavailable assisted arm is recorded as unavailable observations, and never fails the study.
|
|
214
|
+
|
|
215
|
+
## Deviation: the committed study suite
|
|
216
|
+
|
|
217
|
+
The mechanism above is delivered and tested; the committed 24-task suite, run plan and ledgers are
|
|
218
|
+
**not** rewritten to use it.
|
|
219
|
+
|
|
220
|
+
Two reasons, both about not making the artifacts worse. The suite's content hash is bound to
|
|
221
|
+
published artifacts — the run plan's `taskSuiteHash`, the observation ledgers, the verification
|
|
222
|
+
binding — and changing it invalidates every one of them. And the concrete targets an expectations
|
|
223
|
+
file would resolve belong to the six study repositories, which are not present in this repository,
|
|
224
|
+
so the references could only be guessed.
|
|
225
|
+
|
|
226
|
+
An operator adding expectations to a round therefore does three things: add `expectedEntities`,
|
|
227
|
+
`expectedDocuments` and `retrievalQueries` to the tasks, re-seal the suite, and write the local
|
|
228
|
+
expectations file that resolves the references against their checkouts. `ak-docs study expectations`
|
|
229
|
+
then reports which tasks can be answered mechanically and which cannot.
|