@agentskit/doc-bridge 1.7.44 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +471 -0
- package/CONTRIBUTING.md +29 -4
- package/README.md +87 -40
- package/SECURITY.md +7 -0
- package/action.yml +1 -1
- package/bin/ak-docs.js +2 -2
- package/bin/ak-verify.js +13 -7
- package/dist/cli/program.d.ts +3 -1
- package/dist/cli/program.js +15888 -6061
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +91 -9
- package/dist/config/index.js.map +1 -1
- package/dist/index-Beor6Yhi.d.ts +792 -0
- package/dist/index.d.ts +9979 -3257
- package/dist/index.js +15954 -5774
- package/dist/index.js.map +1 -1
- package/docs/MARKETPLACE.md +1 -1
- package/docs/PRD-documentation-efficiency-study.md +406 -0
- package/docs/PRD-knowledge-retrieval-and-enrichment.md +466 -0
- package/docs/RELEASE.md +22 -8
- package/docs/adr/0002-documentation-audit-boundary.md +22 -0
- package/docs/adr/0003-study-protocol-and-historical-evidence.md +40 -0
- package/docs/adr/0004-controlled-study-runner.md +25 -0
- package/docs/adr/0005-documentation-quality-and-criticality.md +20 -0
- package/docs/adr/0006-registry-semantic-grounding.md +20 -0
- package/docs/adr/0007-longitudinal-study-metrics.md +21 -0
- package/docs/adr/0008-study-verification-boundary.md +21 -0
- package/docs/adr/0009-study-provider-cli-adapter.md +25 -0
- package/docs/agent-corpus/INDEX.md +14 -3
- package/docs/agent-corpus/OVERVIEW.md +25 -0
- package/docs/agent-corpus/chat.md +7 -3
- package/docs/agent-corpus/cli.md +18 -2
- package/docs/agent-corpus/conformance.md +14 -2
- package/docs/agent-corpus/doc-bridge.md +48 -1
- package/docs/agent-corpus/doctor.md +10 -2
- package/docs/agent-corpus/gates.md +6 -2
- package/docs/agent-corpus/mcp.md +15 -2
- package/docs/agent-corpus/memory.md +6 -2
- package/docs/agent-corpus/query.md +35 -2
- package/docs/bench/README.md +122 -0
- package/docs/bench/retrieval-baseline-v1.json +28 -0
- package/docs/bench/retrieval-suite-v1.json +1033 -0
- package/docs/chat-and-rag.md +3 -2
- package/docs/for-agents.md +9 -1
- package/docs/getting-started.md +4 -11
- package/docs/guides/gate-ci.md +11 -1
- package/docs/guides/install-and-run.md +9 -65
- package/docs/index.md +22 -1
- package/docs/knowledge-engine-runbook.md +51 -4
- package/docs/landing/assets/context-payload-reduction.svg +21 -0
- package/docs/landing/assets/controlled-ab-comparison.svg +30 -0
- package/docs/landing/index.html +119 -5
- package/docs/loop-workflow.md +117 -0
- package/docs/mcp.md +6 -1
- package/docs/parity/public-claims-v1.json +145 -0
- package/docs/playbook/doc-bridge-pattern.md +1 -1
- package/docs/query.md +90 -2
- package/docs/recipes/index-pipeline.md +1 -1
- package/docs/schemas/agent-handoff-v1.md +15 -0
- package/docs/schemas/doc-bridge-index-v1.md +65 -0
- package/docs/spec/benchmark-v1.md +39 -1
- package/docs/spec/cli.md +30 -10
- package/docs/spec/config-v1.md +192 -8
- package/docs/spec/documentation-audit-v1.md +61 -0
- package/docs/spec/enrichment-overlay-v1.md +241 -0
- package/docs/spec/graph-signals-v1.md +92 -0
- package/docs/spec/incremental-scan-v1.md +102 -0
- package/docs/spec/markdown-analyzer-v1.md +73 -0
- package/docs/spec/mcp-knowledge-tools-v1.md +147 -0
- package/docs/spec/measured-enrichment-v1.md +229 -0
- package/docs/spec/public-parity-v1.md +119 -0
- package/docs/spec/registry-agents.md +6 -0
- package/docs/spec/render-v1.md +122 -0
- package/docs/spec/retrieval-index-v1.md +164 -0
- package/docs/spec/study-metrics-v1.md +58 -0
- package/docs/spec/study-protocol-v1.md +46 -0
- package/docs/spec/study-provider-cli-v1.md +116 -0
- package/docs/spec/study-runner-v1.md +35 -0
- package/docs/spec/study-task-suite-v1.md +41 -0
- package/docs/spec/study-verification-v1.md +40 -0
- package/docs/study/README.md +84 -0
- package/docs/study/ab-adjudicated-cost-analysis-v1.md +29 -0
- package/docs/study/ab-adjudicated-cost-plan-v1.json +33 -0
- package/docs/study/ab-adjudicated-cost-plan-v2-v1.json +33 -0
- package/docs/study/ab-adjudicated-cost-result-v1.json +80 -0
- package/docs/study/ab-baseline-analysis-v1.md +21 -0
- package/docs/study/ab-baseline-plan-v1.json +33 -0
- package/docs/study/ab-baseline-recovery-plan-v1.json +33 -0
- package/docs/study/ab-baseline-result-v1.json +79 -0
- package/docs/study/documentation-audit-round-2026-08-31.json +183 -0
- package/docs/study/historical-evidence-v1.json +252 -0
- package/docs/study/observation-ledger-v1.json +30632 -0
- package/docs/study/phase3-task-coverage-v1.json +34 -0
- package/docs/study/phase4-public-pilot-ledger-v1.json +1344 -0
- package/docs/study/phase4-public-pilot-result-v1.json +52 -0
- package/docs/study/phase4-public-pilot-run-plan-v1.json +26 -0
- package/docs/study/phase4-public-pilot-task-suite-v1.json +71 -0
- package/docs/study/pilot-round-2026-08-31.json +46 -0
- package/docs/study/protocol-v1.json +90 -0
- package/docs/study/publication-gate-v1.md +45 -0
- package/docs/study/quality-scorecard-cycle-plan.md +545 -0
- package/docs/study/quality-scorecard-v1.json +38 -0
- package/docs/study/round-1-adjudicated-smoke-v1.json +30642 -0
- package/docs/study/round-1-instrumentation-plan-v1.md +39 -0
- package/docs/study/round-2-expanded-adjudication-v1.json +91 -0
- package/docs/study/round-2-expanded-validation-v1.md +58 -0
- package/docs/study/round-3-evidence-contract-v1.json +75 -0
- package/docs/study/round-3-evidence-contract-v1.md +57 -0
- package/docs/study/round-4-confirmation-v1.json +75 -0
- package/docs/study/round-4-confirmation-v1.md +55 -0
- package/docs/study/run-plan-v1.json +33 -0
- package/docs/study/semantic-adjudication-cycle-8.md +20 -0
- package/docs/study/task-suite-v1.json +96 -0
- package/docs/study/token-efficiency-plan-v1.md +337 -0
- package/docs/study/token-efficiency-protocol-v2.json +62 -0
- package/docs/study/verification-binding-v1.json +27 -0
- package/docs/validation-cycle-plan.md +33 -0
- package/docs/verification-harness.md +15 -6
- package/ecosystem-claims.json +2 -2
- package/ecosystem-upstream.json +2 -2
- package/ecosystem.json +4 -4
- package/mcpb/manifest.json +9 -1
- package/package.json +89 -72
- package/scripts/check-ecosystem-upstream.mjs +36 -7
- package/scripts/report-visual-check.mjs +20 -3
- package/skills/doc-bridge-handoff/fixtures/synthetic-repo/docs/for-agents/packages/payments.md +7 -0
- package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
- package/src/agents/registry-adapter.ts +192 -24
- package/src/audit/documentation.ts +513 -0
- package/src/bench/baseline.ts +198 -0
- package/src/bench/overlay-delta.ts +139 -0
- package/src/bench/retrieval.ts +319 -0
- package/src/budget/compile.ts +91 -0
- package/src/budget/sections.ts +70 -0
- package/src/cli/demo.ts +2 -2
- package/src/cli/program.ts +699 -79
- package/src/cli/usage.ts +71 -0
- package/src/config/defaults.ts +1 -0
- package/src/config/index.ts +4 -0
- package/src/config/load-config.ts +7 -1
- package/src/config/schema.ts +121 -4
- package/src/conformance/documentation-standard-v1.ts +22 -14
- package/src/discovery/areas.ts +182 -0
- package/src/discovery/documentation.ts +255 -23
- package/src/discovery/identity.ts +24 -0
- package/src/discovery/incremental.ts +314 -0
- package/src/discovery/inputs.ts +110 -0
- package/src/discovery/markdown.ts +481 -0
- package/src/discovery/repository.ts +557 -125
- package/src/doctor/run-doctor.ts +246 -27
- package/src/enrich/approvals.ts +190 -0
- package/src/enrich/cache.ts +93 -0
- package/src/enrich/context-pack.ts +272 -0
- package/src/enrich/overlay.ts +255 -0
- package/src/enrich/review.ts +106 -0
- package/src/enrich/stage.ts +374 -0
- package/src/enrich/stats.ts +100 -0
- package/src/enrich/validate.ts +410 -0
- package/src/federation/llms.ts +74 -24
- package/src/findings/report.ts +103 -0
- package/src/fixes/proposals.ts +4 -3
- package/src/graph/build.ts +356 -0
- package/src/graph/memory.ts +208 -0
- package/src/index-builder/build-handoffs.ts +22 -11
- package/src/index-builder/build-index.ts +132 -3
- package/src/index-builder/human-adapters/fumadocs.ts +1 -1
- package/src/index-builder/llms-txt.ts +48 -8
- package/src/index-builder/project-corpus.ts +111 -0
- package/src/index-builder/watch-index.ts +1 -1
- package/src/index.ts +630 -2
- package/src/lib/bounded-text.ts +15 -10
- package/src/lib/fuzzy-match.ts +235 -0
- package/src/mcp/knowledge.ts +554 -0
- package/src/mcp/server.ts +113 -18
- package/src/metrics/benchmark.ts +21 -0
- package/src/parity/check.ts +309 -0
- package/src/parity/claims.ts +259 -0
- package/src/parity/resolve.ts +160 -0
- package/src/query/handoff.ts +326 -0
- package/src/query/load-index.ts +53 -1
- package/src/query/query.ts +92 -59
- package/src/query/search.ts +289 -92
- package/src/query/text.ts +155 -0
- package/src/reconciliation/reconcile.ts +148 -15
- package/src/render/data.ts +356 -0
- package/src/render/engine.ts +398 -0
- package/src/render/generated.ts +77 -0
- package/src/render/render.ts +209 -0
- package/src/render/template-source.ts +52 -0
- package/src/render/templates.ts +289 -0
- package/src/report/html.ts +23 -17
- package/src/retrieval/bm25.ts +161 -0
- package/src/retrieval/project.ts +495 -0
- package/src/retrieval/rank.ts +383 -0
- package/src/retrieval/weights.ts +39 -0
- package/src/retriever/doc-bridge-retriever.ts +100 -15
- package/src/rules/engine.ts +45 -12
- package/src/safety/repository.ts +1 -1
- package/src/schemas/agent-handoff.ts +56 -0
- package/src/schemas/budget.ts +37 -0
- package/src/schemas/doc-bridge-index.ts +53 -2
- package/src/schemas/enrichment.ts +369 -0
- package/src/schemas/json-schemas.ts +39 -2
- package/src/schemas/knowledge.ts +19 -3
- package/src/schemas/retrieval-index.ts +152 -0
- package/src/shims/graphology.d.ts +91 -0
- package/src/study/adjudication.ts +196 -0
- package/src/study/execution.ts +350 -0
- package/src/study/expectations.ts +219 -0
- package/src/study/metrics.ts +467 -0
- package/src/study/protocol.ts +271 -0
- package/src/study/provider-cli.ts +115 -0
- package/src/study/provider-telemetry.ts +47 -0
- package/src/study/quality-scorecard.ts +164 -0
- package/src/study/runner.ts +461 -0
- package/src/study/task-suite.ts +321 -0
- package/src/study/verification.ts +134 -0
- package/src/validate.ts +8 -5
- package/src/version.ts +1 -1
- package/src/workflow/engine.ts +36 -11
- package/dist/index-C2PCQSrB.d.ts +0 -2251
- package/scripts/verification-harness.mjs +0 -483
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Enrichment overlay v1
|
|
3
|
+
description: Typed agent proposals, the deterministic validators that decide them, the overlay that stores the decisions, and how little of it the deterministic layer is allowed to feel.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Enrichment overlay v1
|
|
7
|
+
|
|
8
|
+
A Registry agent may enrich the knowledge graph. It may never become an authority over it.
|
|
9
|
+
|
|
10
|
+
`AgentProposalV1` could say "review this finding". It could not say that a document is canonical
|
|
11
|
+
for an area, that an alias should resolve to an entity, or that a relation exists with a given
|
|
12
|
+
confidence — and nothing stored what it said, nothing consumed it, the deterministic cache was an
|
|
13
|
+
in-process `Map`, and `ak-docs suggest` sent the whole redacted snapshot, 740 KB on this
|
|
14
|
+
repository, in one call. This document is the replacement: proposals typed per claim, decided by
|
|
15
|
+
validators that have no judgement to exercise, stored in an overlay bound to the content hash of
|
|
16
|
+
what each entry describes, and projected into retrieval with an influence that is bounded by
|
|
17
|
+
construction.
|
|
18
|
+
|
|
19
|
+
## Typed proposals
|
|
20
|
+
|
|
21
|
+
`EnrichmentProposalV1` in `src/schemas/enrichment.ts` is a discriminated union on `kind`. The
|
|
22
|
+
envelope is the same for every kind:
|
|
23
|
+
|
|
24
|
+
| Field | Meaning |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `proposalId` | The hash of kind, entity, target content hash, agent identity, prompt version — and, for kinds one entity can carry several of, the payload field that tells them apart (`alias`, `phrase`, the relation's `to` and `kind`, `against`, `with`, `area` and `missing`, `directories`). A re-run over unchanged inputs produces the same id; a summary is a slot, an alias is a set. |
|
|
27
|
+
| `entity` | Must exist in the snapshot the proposal was made against. |
|
|
28
|
+
| `targetContentHash` | The entity's content hash at proposal time: the file hash for a file-backed entity, a hash of the entity as recorded for an area. The entry expires when it moves. |
|
|
29
|
+
| `confidence`, `reason` | 0..1, and up to 1 000 characters. |
|
|
30
|
+
| `evidence` | At least one item, and every item present — by source, path and lines — in the snapshot or the report. |
|
|
31
|
+
| `relatedDiagnosticIds` | Optional; every id must exist in the report. |
|
|
32
|
+
| `origin` | `agentId`, `agentVersion`, `promptVersion`, and optionally `model` and `provider`. |
|
|
33
|
+
| `baseSnapshotHash` | The snapshot it was made against. |
|
|
34
|
+
| `payload` | Per kind, below. |
|
|
35
|
+
|
|
36
|
+
| Kind | Payload | Deterministic validator | Policy |
|
|
37
|
+
| --- | --- | --- | --- |
|
|
38
|
+
| `classify-document` | `type`, `audience`, `lifecycle`, `criticality` | enumerated values; the entity is a document | accept by policy |
|
|
39
|
+
| `summarize` | `summary` ≤ 400 chars, `language` | length; redaction scan; differs from the current summary | accept by policy |
|
|
40
|
+
| `add-alias` | `alias` ≤ 64 chars | no collision with an id, name, title, filename stem or alias — exact, or Jaro-Winkler ≥ 0.95 | accept by policy |
|
|
41
|
+
| `add-intent` | `phrase` ≤ 120 chars, `language` | length; language tag present | accept by policy |
|
|
42
|
+
| `mark-canonical` | `scope` | the scope exists; the entity is a document; at most one canonical document per scope after the merge — a second one is a `canonical-conflict` | human approval |
|
|
43
|
+
| `propose-relation` | `from`, `to`, `kind`, `detection` | both endpoints exist; the kind is one of `covers`, `mentions`, `links-to`, `depends-on`, `related-to`, `documents`; not already observed; every evidence item lies inside one endpoint | human approval |
|
|
44
|
+
| `flag-contradiction` | `against`, `claim`, `observed` | both entities exist; evidence inside both | human approval |
|
|
45
|
+
| `flag-redundancy` | `with` | both are documents; not already exact duplicates | human approval |
|
|
46
|
+
| `flag-gap` | `area`, `missing` | the area exists; not already flagged with the same text | accepted as a finding, never as a fact |
|
|
47
|
+
| `rank-hint` | `relevance`: `strong` or `weak` | the entity exists | accept by policy, bounded weight |
|
|
48
|
+
| `suggest-area` | `directories`, `name` | every directory exists; no overlap with an existing area | human approval, then configuration, never an entity |
|
|
49
|
+
|
|
50
|
+
Any other kind is recorded as rejected with reason `invalid-kind`. The full reason list is
|
|
51
|
+
`ENRICHMENT_REJECTION_REASONS`; every rejection carries one of them, so a run's rejections are a
|
|
52
|
+
histogram and not a log.
|
|
53
|
+
|
|
54
|
+
Envelope checks run before the kind's own rule, in a fixed order: `invalid-kind`, `no-evidence`,
|
|
55
|
+
`schema`, `base-snapshot-mismatch`, `unknown-entity`, `stale-target`, `unknown-diagnostic`,
|
|
56
|
+
`evidence-outside-artifacts`, `proposal-id-mismatch`. A proposal that fails the envelope never
|
|
57
|
+
reaches a kind validator.
|
|
58
|
+
|
|
59
|
+
## The overlay
|
|
60
|
+
|
|
61
|
+
`EnrichmentOverlayV1` lives at `.doc-bridge/enrich/overlay.json`:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"type": "enrichment-overlay",
|
|
66
|
+
"schemaVersion": 1,
|
|
67
|
+
"contentHash": "…",
|
|
68
|
+
"baseSnapshotHash": "…",
|
|
69
|
+
"accepted": [{ "proposal": { "…": "…" }, "acceptedAt": "2026-09-14T00:00:00.000Z", "acceptedBy": "policy" }],
|
|
70
|
+
"pending": [{ "proposal": { "…": "…" }, "approvalId": "…", "note": "canonical-conflict" }],
|
|
71
|
+
"rejected": [{ "proposalId": "…", "kind": "propose-relation", "entity": "…", "reason": "unknown-endpoint", "detail": "…" }],
|
|
72
|
+
"stats": { "byKind": { "summarize": { "proposed": 4, "accepted": 4, "pending": 0, "rejected": 0 } }, "rejectionReasons": {}, "agentRuns": 1, "cacheHits": 0, "packs": 4, "inputBytes": 0, "outputBytes": 0, "expired": 0 }
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`acceptedBy` is `policy` for accept-by-policy kinds and a person's name otherwise; the schema
|
|
77
|
+
refuses `policy` on a human-approval kind and refuses an `acceptedBy` equal to the proposal's
|
|
78
|
+
`origin.agentId`. `contentHash` is over the decisions — every accepted, pending and rejected entry
|
|
79
|
+
and the hashes they bind to — and not over `acceptedAt` or `stats`, because two runs over one
|
|
80
|
+
unchanged repository must produce one overlay hash and a timestamp is not a fact about the
|
|
81
|
+
repository.
|
|
82
|
+
|
|
83
|
+
Reading never writes. `readEnrichmentOverlay` returns nothing for a missing, unreadable, malformed
|
|
84
|
+
or hash-mismatched file, and a reader that got nothing behaves as if there were no overlay.
|
|
85
|
+
|
|
86
|
+
**Staleness is per entry.** At projection time an accepted entry whose `targetContentHash` no
|
|
87
|
+
longer matches its entity — or whose other endpoint no longer exists — is expired in the result
|
|
88
|
+
and excluded from ranking; its siblings for unchanged entities survive. The file is untouched by a
|
|
89
|
+
read; the next `ak-docs enrich` moves the expired entries to `rejected` with reason `expired`.
|
|
90
|
+
|
|
91
|
+
**Reproducible acceptance.** `revalidateEnrichmentOverlay` re-runs the validators over the stored
|
|
92
|
+
proposals and returns the partition they produce; a test asserts it equals the stored one. An
|
|
93
|
+
overlay whose partition does not reproduce was edited by hand or outlived its validators, and
|
|
94
|
+
either way it is no longer evidence of a decision.
|
|
95
|
+
|
|
96
|
+
## The stage
|
|
97
|
+
|
|
98
|
+
`enrich` is a workflow stage between `reconcile` and `evaluate`, run only by `ak-docs enrich` or
|
|
99
|
+
`ak-docs check --enrich`. A plain `check` leaves its step pending and `evaluate` reads a null
|
|
100
|
+
previous output. A missing, failed, timed-out or stale enrichment never changes a `check` result:
|
|
101
|
+
`check --enrich` reports `enrichment.status: failed` with the error and carries on.
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
$ ak-docs enrich --text
|
|
105
|
+
Roles: curator=ecosystem-doc-bridge-corpus-scanner
|
|
106
|
+
Packs: 92 (agent calls 3, cache hits 89, re-run 3)
|
|
107
|
+
Accepted: 180 Pending: 4 Rejected: 7 Expired: 3
|
|
108
|
+
summarize: accepted 89, pending 0, rejected 1
|
|
109
|
+
…
|
|
110
|
+
Overlay: 7c1e… (overlay.json)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The stage:
|
|
114
|
+
|
|
115
|
+
1. builds one context pack per target entity for each configured role and batches them by area;
|
|
116
|
+
2. answers each pack from the cache or from the agent, caching what the agent said per pack —
|
|
117
|
+
including nothing, so silence is not asked for twice;
|
|
118
|
+
3. validates everything through the partition against the snapshot, the report and the overlay
|
|
119
|
+
already on disk; policy kinds are accepted, human kinds are requested from the approval gate;
|
|
120
|
+
4. adjudicates what two roles could not settle, if a third identity is configured;
|
|
121
|
+
5. merges with the stored overlay — decisions people made survive while their target does — and
|
|
122
|
+
writes it.
|
|
123
|
+
|
|
124
|
+
The step's workflow input names the overlay it attaches (`{ reportHash, overlayHash }`), because
|
|
125
|
+
an approval changes the overlay without changing the report, and the engine refuses a step whose
|
|
126
|
+
input did not move but whose output did.
|
|
127
|
+
|
|
128
|
+
## Context packs and the cache
|
|
129
|
+
|
|
130
|
+
Agents never receive the snapshot. `buildContextPacks` in `src/enrich/context-pack.ts` builds
|
|
131
|
+
one pack per target entity:
|
|
132
|
+
|
|
133
|
+
| Section | Content | Bound |
|
|
134
|
+
| --- | --- | --- |
|
|
135
|
+
| `target` | id, kind, name, path, content hash, aliases, its own evidence items, bounded metadata | — |
|
|
136
|
+
| `neighbours` | the other end of every relation touching the target, with kind, path, content hash, the relation and its direction; sorted by kind then id | 32 |
|
|
137
|
+
| `diagnostics` | open diagnostics naming the target or citing its file | 16 |
|
|
138
|
+
| `evidence` | the target's own file, redacted, read only if it still hashes to what the snapshot recorded | 12 KB |
|
|
139
|
+
| `budget` | `maxBytes`, `bytes`, `dropped` | 64 KB default, `intelligence.registry.maxPackBytes` |
|
|
140
|
+
|
|
141
|
+
Over budget, sections are dropped in a declared order — excerpt bytes (halved until it fits, then
|
|
142
|
+
dropped), then diagnostics from the end, then neighbours from the end — and the target is never
|
|
143
|
+
dropped. This mirrors `compileBudget` from `@agentskit/core` with a byte counter and the
|
|
144
|
+
`drop-oldest` strategy over sections ordered least-important-first; a test cross-checks the two,
|
|
145
|
+
and the mirror is what runs, because a pack must be the same pack whether or not an optional peer
|
|
146
|
+
is installed. Every string in a pack passes `redactSecrets`, asserted by test.
|
|
147
|
+
|
|
148
|
+
`packHash` covers the target's and every neighbour's content hash and nothing else. The cache key
|
|
149
|
+
is the hash of task, agent identity and version, prompt version and pack hash; entries live under
|
|
150
|
+
`.doc-bridge/enrich/cache/<key>.json`. An unchanged repository therefore makes zero agent calls,
|
|
151
|
+
and a one-document change re-runs only the packs whose hash moved — the changed document's, and
|
|
152
|
+
any pack it was a neighbour of.
|
|
153
|
+
|
|
154
|
+
## Protocol v2
|
|
155
|
+
|
|
156
|
+
The adapter (`src/agents/registry-adapter.ts`) keeps `run` for `AgentProposalV1` over protocol
|
|
157
|
+
`doc-bridge.registry-agent.v1` — `ak-docs suggest` still works — and gains `enrich(task, packs)`
|
|
158
|
+
over `doc-bridge.registry-agent.v2`:
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{ "protocol": "doc-bridge.registry-agent.v2", "task": "curate", "role": "curator", "promptVersion": "1", "packs": [ … ], "capabilities": ["pack.read", "proposal.write"], "network": false, "shell": false, "deterministic": true }
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The answer is one JSON object `{ "proposals": [ … ] }`. `task` is `curate`, `review` or
|
|
165
|
+
`adjudicate`; for `adjudicate` the packs are followed by one `adjudication-request` item listing
|
|
166
|
+
the disputed proposals, and `proposals` carries `EnrichmentAdjudicationV1` values. The adapter
|
|
167
|
+
checks transport and budget and returns the raw array; grounding is the validators' job, and
|
|
168
|
+
keeping it there is what makes a stored overlay reproducible from its proposals. The same CLI or
|
|
169
|
+
local runner module serves both protocols and tells them apart by `protocol`.
|
|
170
|
+
|
|
171
|
+
## Roles
|
|
172
|
+
|
|
173
|
+
| Role | Task | Targets | Kinds it is for |
|
|
174
|
+
| --- | --- | --- | --- |
|
|
175
|
+
| curator | `curate` | documents | classification, summaries, aliases, intents, canonical markers, redundancy, gaps |
|
|
176
|
+
| reviewer | `review` | documents, areas | relations, contradictions, gaps, rank hints, area suggestions |
|
|
177
|
+
| adjudicator | `adjudicate` | the disputed entities | canonical conflicts and disputed contradictions only |
|
|
178
|
+
|
|
179
|
+
Roles are configuration under `intelligence.registry.roles`; the default is the configured agent
|
|
180
|
+
as curator only. The adjudicator must be a different agent identity from both others —
|
|
181
|
+
`resolveEnrichmentRoles` refuses the configuration otherwise — and the validator rejects any
|
|
182
|
+
adjudication whose origin matches a proposal it judges as `self-adjudication`. An adjudication
|
|
183
|
+
judges; it never approves: the losers are rejected as `adjudicated`, the winner stays pending for
|
|
184
|
+
the person the kind's policy requires, and a settled dispute is not reopened by the cache
|
|
185
|
+
replaying the proposals that caused it.
|
|
186
|
+
|
|
187
|
+
## Approvals
|
|
188
|
+
|
|
189
|
+
Human approval goes through `createApprovalGate` from `@agentskit/core/hitl` over a file-backed
|
|
190
|
+
`ApprovalStore` under `.doc-bridge/approvals/`, one JSON record per approval. The gate contract is
|
|
191
|
+
mirrored in-repo (`src/enrich/approvals.ts`) for the same reason every ecosystem contract is —
|
|
192
|
+
`@agentskit/core` is an optional peer — and the real gate is used through `importPeer` when it is
|
|
193
|
+
installed; a test writes through both over one store and reads the same records.
|
|
194
|
+
|
|
195
|
+
An approval id is the hash of `proposalId` and `targetContentHash`, so an approval given for one
|
|
196
|
+
version of a document cannot be replayed against the next. `ak-docs enrich approve <proposalId>
|
|
197
|
+
--by <name>`, MCP `docbridge.proposals { action: "enrich-approve" }` and a rendered review page
|
|
198
|
+
call the same `decideEnrichment`: the gate decides first, the overlay moves second, and if the
|
|
199
|
+
gate refuses — already decided, unknown — the overlay is untouched. An approver equal to the
|
|
200
|
+
proposal's author is refused; `policy` is not a person. `ak-docs fix approve` records its
|
|
201
|
+
approval through the same gate under `doc-bridge.fix`, bound to the fix proposal id and its
|
|
202
|
+
content hash.
|
|
203
|
+
|
|
204
|
+
## Projection and bounded influence
|
|
205
|
+
|
|
206
|
+
`projectEnrichmentOverlay(overlay, snapshot)` turns the live accepted set into what
|
|
207
|
+
`projectRetrievalIndex` reads:
|
|
208
|
+
|
|
209
|
+
| Accepted kind | Effect in the projection |
|
|
210
|
+
| --- | --- |
|
|
211
|
+
| `add-alias` | one more alias on the entry |
|
|
212
|
+
| `summarize` | the entry's summary, only when the entity has none of its own |
|
|
213
|
+
| `add-intent` | an `intent` entry with `provenance: proposed` |
|
|
214
|
+
| `mark-canonical` | the `canonical` tag, and 0.8 of the signal |
|
|
215
|
+
| `rank-hint` | 1 (strong) or 0.5 (weak) of the signal |
|
|
216
|
+
| `propose-relation` | one more edge with `confidence: proposed`; a `covers` edge counts toward `coveredBy` and canonicality |
|
|
217
|
+
| `flag-*`, `suggest-area` | nothing: findings and suggestions are for a reviewer, not for ranking |
|
|
218
|
+
|
|
219
|
+
The signal is a per-entry share in 0..1, carried on the entry as `agentSignal`, and the ranker
|
|
220
|
+
multiplies it by `ACCEPTED_SIGNALS_WEIGHT` = 15% of the exact-id boost (30 of 200). It applies to
|
|
221
|
+
lexical hits only, like every other tie-breaker. An accepted signal can therefore reorder near-ties
|
|
222
|
+
and can never lift an entry past one the query named exactly — asserted by test. Without an
|
|
223
|
+
overlay, `ACCEPTED_SIGNALS_WEIGHT × 0` is what it always was, and the projection is byte-identical.
|
|
224
|
+
|
|
225
|
+
The projection's `overlayHash` is the hash of the live accepted set — `EMPTY_OVERLAY_HASH` when
|
|
226
|
+
nothing is live — so it is part of the projection's identity and `IndexStaleError` sees a changed
|
|
227
|
+
overlay. `withAcceptedRelations` merges live proposed relations into a snapshot for the graph, the
|
|
228
|
+
HTML report and the memory view, and `assertObservedSurvive` checks that every observed entity and
|
|
229
|
+
relation is present and unchanged afterwards; the report draws a proposed edge dashed.
|
|
230
|
+
|
|
231
|
+
The index builder consults the overlay only while `intelligence.registry.enabled` is true:
|
|
232
|
+
switching the Registry off restores the deterministic baseline exactly.
|
|
233
|
+
|
|
234
|
+
## What never happens
|
|
235
|
+
|
|
236
|
+
- No overlay entry deletes or alters observed data. The projection asserts that every observed
|
|
237
|
+
entity and relation survives enrichment.
|
|
238
|
+
- No agent is called from `check`, `index`, `search`, `query`, `render` or MCP. The enrich stage
|
|
239
|
+
runs only on request, and nothing reachable from the query path imports `src/agents`.
|
|
240
|
+
- No agent approves anything — its own output, another agent's, or an adjudication's winner.
|
|
241
|
+
- No read writes. A corrupt overlay is no overlay.
|
|
@@ -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.
|