@agentskit/doc-bridge 1.9.0 → 1.10.1
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 +49 -0
- package/action.yml +1 -1
- package/dist/cli/program.js +46 -20
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +11 -2
- package/dist/config/index.js.map +1 -1
- package/dist/{index-Beor6Yhi.d.ts → index-7wYGbllW.d.ts} +1 -0
- package/dist/index.d.ts +31 -6
- package/dist/index.js +47 -20
- package/dist/index.js.map +1 -1
- package/docs/spec/config-v1.md +11 -1
- package/mcpb/manifest.json +1 -1
- package/package.json +11 -11
- package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
- package/src/config/schema.ts +10 -1
- package/src/discovery/areas.ts +16 -1
- package/src/discovery/repository.ts +1 -0
- package/src/index.ts +1 -0
- package/src/retrieval/project.ts +33 -6
- package/src/schemas/retrieval-index.ts +6 -1
- package/src/version.ts +1 -1
package/docs/spec/config-v1.md
CHANGED
|
@@ -478,6 +478,8 @@ areas?: {
|
|
|
478
478
|
depth?: number
|
|
479
479
|
/** Directories that contain areas rather than being one. */
|
|
480
480
|
roots?: string[]
|
|
481
|
+
/** Glob patterns for directories that hold code without being a unit of architecture. */
|
|
482
|
+
exclude?: string[]
|
|
481
483
|
}
|
|
482
484
|
```
|
|
483
485
|
|
|
@@ -486,8 +488,16 @@ modules. `src` is not an area in any useful sense; `src/query` is. So `roots` na
|
|
|
486
488
|
directories that hold areas — by default `src`, `lib`, `app`, `source`, `server`, `client`,
|
|
487
489
|
`packages`, `apps` — and `depth` says how many levels below such a root an area sits.
|
|
488
490
|
|
|
491
|
+
`exclude` names what the convention gets wrong in the other direction. A monorepo where every
|
|
492
|
+
package keeps `tests/` and `fixtures/` beside `src/` derives an area per directory, and
|
|
493
|
+
connectivity then asks for a document about a folder of test data — on one 26-package monorepo,
|
|
494
|
+
43 of 81 undocumented areas were exactly that. A candidate matching an exclude pattern is not
|
|
495
|
+
derived, and its modules fall to the most specific area that still encloses them, or to none,
|
|
496
|
+
which is the honest answer for a folder of fixtures.
|
|
497
|
+
|
|
489
498
|
Areas are derived, never declared, with one exception that matters: **any path an ownership record
|
|
490
|
-
names becomes an area**, whatever the convention says
|
|
499
|
+
names becomes an area**, whatever the convention says — including a path `exclude` matches, because
|
|
500
|
+
a person saying a directory is a unit outranks a pattern saying it is not. A configuration that reads
|
|
491
501
|
`path: "src/mcp"` is a human stating that the directory is a unit, and the graph should have an
|
|
492
502
|
entity for it. Such an area carries `metadata.ownershipId`, which is what lets an agent document
|
|
493
503
|
declaring `id` plus `editRoot` resolve to the thing it owns.
|
package/mcpb/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifest_version": "0.3",
|
|
3
3
|
"name": "doc-bridge",
|
|
4
4
|
"display_name": "Doc Bridge",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.10.1",
|
|
6
6
|
"description": "Deterministic repository handoffs for coding agents, running locally without an LLM or API key.",
|
|
7
7
|
"long_description": "Doc Bridge turns a repository's own documentation and ownership metadata into deterministic handoffs: where an agent should start, which paths it may edit, which checks it must run, and when a human must take over. The local connector exposes the same read-only contract available through Doc Bridge CLI and CI.",
|
|
8
8
|
"author": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentskit/doc-bridge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.1",
|
|
4
4
|
"mcpName": "io.github.AgentsKit-io/doc-bridge",
|
|
5
5
|
"description": "Human↔agent documentation bridge — deterministic handoffs, doc-site links, memory→docs, optional AgentsKit RAG/chat.",
|
|
6
6
|
"type": "module",
|
|
@@ -123,28 +123,28 @@
|
|
|
123
123
|
},
|
|
124
124
|
"devDependencies": {
|
|
125
125
|
"@agentskit/chat": "0.4.1",
|
|
126
|
-
"@agentskit/core": "1.12.
|
|
126
|
+
"@agentskit/core": "1.12.9",
|
|
127
127
|
"@agentskit/ink": "0.10.9",
|
|
128
128
|
"@agentskit/react": "0.8.3",
|
|
129
129
|
"@anthropic-ai/mcpb": "2.1.2",
|
|
130
130
|
"@changesets/cli": "^3.0.1",
|
|
131
131
|
"@lhci/cli": "^0.15.1",
|
|
132
|
-
"@playwright/test": "1.
|
|
132
|
+
"@playwright/test": "1.63.0",
|
|
133
133
|
"@tailwindcss/postcss": "4.3.3",
|
|
134
134
|
"@types/mdast": "^4.0.4",
|
|
135
135
|
"@types/node": "^26.4.0",
|
|
136
|
-
"@types/react": "19.
|
|
137
|
-
"@types/react-dom": "19.
|
|
136
|
+
"@types/react": "19.3.0",
|
|
137
|
+
"@types/react-dom": "19.3.0",
|
|
138
138
|
"@vitest/coverage-v8": "4.1.11",
|
|
139
139
|
"esbuild": "^0.28.2",
|
|
140
|
-
"fumadocs-core": "16.15.
|
|
140
|
+
"fumadocs-core": "16.15.10",
|
|
141
141
|
"fumadocs-mdx": "15.4.0",
|
|
142
|
-
"fumadocs-ui": "16.15.
|
|
143
|
-
"lucide-react": "1.
|
|
144
|
-
"next": "16.3.
|
|
142
|
+
"fumadocs-ui": "16.15.10",
|
|
143
|
+
"lucide-react": "1.45.0",
|
|
144
|
+
"next": "16.3.5",
|
|
145
145
|
"proxy-agent": "8.0.2",
|
|
146
|
-
"react": "19.
|
|
147
|
-
"react-dom": "19.
|
|
146
|
+
"react": "19.3.0",
|
|
147
|
+
"react-dom": "19.3.0",
|
|
148
148
|
"tailwindcss": "4.3.3",
|
|
149
149
|
"tsup": "^8.5.0",
|
|
150
150
|
"vitest": "^4.1.11"
|
package/src/config/schema.ts
CHANGED
|
@@ -249,12 +249,21 @@ export const AnalysisConfigSchema = z
|
|
|
249
249
|
/**
|
|
250
250
|
* How code areas are derived — the unit of architecture between a package and a file.
|
|
251
251
|
* `roots` names directories that contain areas rather than being one (`src` holds
|
|
252
|
-
* `src/query`); `depth` is how many levels below such a root an area sits
|
|
252
|
+
* `src/query`); `depth` is how many levels below such a root an area sits; `exclude`
|
|
253
|
+
* names directories that hold code without being a unit of architecture.
|
|
253
254
|
*/
|
|
254
255
|
areas: z
|
|
255
256
|
.object({
|
|
256
257
|
depth: z.number().int().min(1).max(8).optional(),
|
|
257
258
|
roots: z.array(z.string().min(1).max(128)).max(32).optional(),
|
|
259
|
+
/**
|
|
260
|
+
* Glob patterns for directories that are not areas. A monorepo where every package
|
|
261
|
+
* keeps `tests/` and `fixtures/` beside `src/` derives one area per directory, and
|
|
262
|
+
* then connectivity asks for a document about a folder of test data. An ownership
|
|
263
|
+
* record naming an excluded path still makes it an area: a person saying a directory
|
|
264
|
+
* is a unit outranks a pattern saying it is not.
|
|
265
|
+
*/
|
|
266
|
+
exclude: z.array(z.string().min(1).max(256)).max(64).optional(),
|
|
258
267
|
})
|
|
259
268
|
.strict()
|
|
260
269
|
.optional(),
|
package/src/discovery/areas.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { minimatch } from 'minimatch'
|
|
2
|
+
|
|
1
3
|
import { toPosix } from '../lib/paths.js'
|
|
2
4
|
import { entityId } from './identity.js'
|
|
3
5
|
|
|
@@ -58,6 +60,15 @@ export type DeriveAreasOptions = {
|
|
|
58
60
|
readonly ownership?: readonly AreaOwnership[]
|
|
59
61
|
readonly depth?: number
|
|
60
62
|
readonly roots?: readonly string[]
|
|
63
|
+
/**
|
|
64
|
+
* Glob patterns for directories that hold code without being a unit of architecture.
|
|
65
|
+
*
|
|
66
|
+
* A candidate that matches is not derived as an area, and its modules fall to the most
|
|
67
|
+
* specific area that still encloses them — or to none, which is the honest answer for a
|
|
68
|
+
* folder of fixtures. An ownership record naming the same path still wins: a person saying
|
|
69
|
+
* a directory is a unit outranks a pattern saying it is not.
|
|
70
|
+
*/
|
|
71
|
+
readonly exclude?: readonly string[]
|
|
61
72
|
}
|
|
62
73
|
|
|
63
74
|
/*
|
|
@@ -124,11 +135,15 @@ export const deriveAreas = (options: DeriveAreasOptions): readonly DerivedArea[]
|
|
|
124
135
|
const roots = options.roots ?? [...DEFAULT_AREA_ROOTS]
|
|
125
136
|
const ownership = (options.ownership ?? []).map((record) => ({ ...record, path: normalize(record.path) }))
|
|
126
137
|
|
|
138
|
+
const excluded = options.exclude ?? []
|
|
139
|
+
const isExcluded = (path: string): boolean =>
|
|
140
|
+
excluded.some((pattern) => minimatch(path, pattern, { dot: true }))
|
|
141
|
+
|
|
127
142
|
/** Candidate path to the package it belongs to. */
|
|
128
143
|
const candidates = new Map<string, string>()
|
|
129
144
|
for (const module of options.modules) {
|
|
130
145
|
const path = conventionalAreaPath(module, depth, roots)
|
|
131
|
-
if (path && !candidates.has(path)) candidates.set(path, module.packageId)
|
|
146
|
+
if (path && !candidates.has(path) && !isExcluded(path)) candidates.set(path, module.packageId)
|
|
132
147
|
}
|
|
133
148
|
// An ownership record naming a directory that holds observed code is an area by declaration.
|
|
134
149
|
for (const record of ownership) {
|
|
@@ -648,6 +648,7 @@ export const discoverRepository = (opts: DiscoveryOptions = {}): DiscoverySnapsh
|
|
|
648
648
|
ownership: Object.entries(opts.config?.routing?.options?.ownership ?? {}).map(([id, record]) => ({ id, path: record.path })),
|
|
649
649
|
...(opts.config?.analysis?.areas?.depth !== undefined ? { depth: opts.config.analysis.areas.depth } : {}),
|
|
650
650
|
...(opts.config?.analysis?.areas?.roots !== undefined ? { roots: opts.config.analysis.areas.roots } : {}),
|
|
651
|
+
...(opts.config?.analysis?.areas?.exclude !== undefined ? { exclude: opts.config.analysis.areas.exclude } : {}),
|
|
651
652
|
})
|
|
652
653
|
const areasById = new Map(areas.map((area) => [area.id, area]))
|
|
653
654
|
const areasByPath = new Map(areas.map((area) => [area.path, area.id]))
|
package/src/index.ts
CHANGED
package/src/retrieval/project.ts
CHANGED
|
@@ -37,7 +37,7 @@ import { resolveSearchParams, resolveSearchWeights } from './weights.js'
|
|
|
37
37
|
* from what the snapshot says about it rather than from what is on disk now.
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
|
-
export const RETRIEVAL_PROJECTION_VERSION =
|
|
40
|
+
export const RETRIEVAL_PROJECTION_VERSION = 2 as const
|
|
41
41
|
|
|
42
42
|
/** Documentation body kept for search. Long enough to answer a question, short enough to ship. */
|
|
43
43
|
export const DOCUMENT_BODY_LIMIT = 4_000
|
|
@@ -51,6 +51,31 @@ const MAX_SUMMARY = 400
|
|
|
51
51
|
/** The overlay hash when there is no overlay: the hash of an empty accepted set. */
|
|
52
52
|
export const EMPTY_OVERLAY_HASH = sha256NormalizedV1({ accepted: [] })
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* What the snapshot observed, without the revision it observed it at.
|
|
56
|
+
*
|
|
57
|
+
* `snapshot.contentHash` seals the whole artifact, `sourceRevision` included — the commit SHA when
|
|
58
|
+
* the working tree is clean, a digest of the scanned files when it is not. That is right for an
|
|
59
|
+
* artifact whose job is to say what one revision looked like, and wrong as a projection input: the
|
|
60
|
+
* projection is a function of what was found, not of where it was found. Sealing the revision into
|
|
61
|
+
* it made an index that any commit invalidates without one thing it describes having changed — so
|
|
62
|
+
* an index committed to a repository was stale the moment it landed, because landing it is a
|
|
63
|
+
* commit, and a freshness gate could never pass twice.
|
|
64
|
+
*
|
|
65
|
+
* Entities and relations are the projection's whole input; the analyzer identity comes with them,
|
|
66
|
+
* because two analyzer versions that observe the same entities and relations have nothing left to
|
|
67
|
+
* disagree about, and one that observes different ones is caught by the entities.
|
|
68
|
+
*/
|
|
69
|
+
export const snapshotObservationHash = (
|
|
70
|
+
snapshot: Pick<DiscoverySnapshotV1, 'entities' | 'relations' | 'pipelineVersion' | 'analyzerVersions'>,
|
|
71
|
+
): string =>
|
|
72
|
+
sha256NormalizedV1({
|
|
73
|
+
pipelineVersion: snapshot.pipelineVersion,
|
|
74
|
+
analyzerVersions: snapshot.analyzerVersions,
|
|
75
|
+
entities: snapshot.entities,
|
|
76
|
+
relations: snapshot.relations,
|
|
77
|
+
})
|
|
78
|
+
|
|
54
79
|
const CONFIDENCE_RANK: Readonly<Record<Confidence, number>> = { observed: 0, declared: 1, fuzzy: 2, proposed: 3 }
|
|
55
80
|
|
|
56
81
|
/** The weaker of two confidences: a chain is as trustworthy as its least trustworthy link. */
|
|
@@ -115,7 +140,7 @@ export type RetrievalOverlayInput = {
|
|
|
115
140
|
}
|
|
116
141
|
|
|
117
142
|
export type ProjectRetrievalOptions = {
|
|
118
|
-
readonly snapshot: Pick<DiscoverySnapshotV1, 'contentHash' | 'entities' | 'relations'>
|
|
143
|
+
readonly snapshot: Pick<DiscoverySnapshotV1, 'contentHash' | 'entities' | 'relations' | 'pipelineVersion' | 'analyzerVersions'>
|
|
119
144
|
readonly config: DocBridgeConfigV1 | undefined
|
|
120
145
|
readonly routes?: RetrievalRoutes
|
|
121
146
|
readonly curated?: readonly CuratedDocument[]
|
|
@@ -459,13 +484,15 @@ export const projectRetrievalIndex = (options: ProjectRetrievalOptions): Retriev
|
|
|
459
484
|
entries,
|
|
460
485
|
}
|
|
461
486
|
/*
|
|
462
|
-
* The hash is over the inputs, not the output: the projection is a function, so
|
|
463
|
-
*
|
|
464
|
-
*
|
|
487
|
+
* The hash is over the inputs, not the output: the projection is a function, so equal input
|
|
488
|
+
* hashes mean an equal artifact, and a reader checking freshness compares hashes instead of
|
|
489
|
+
* re-projecting. `snapshotHash` stays on the artifact as provenance — which snapshot this came
|
|
490
|
+
* from — but the seal uses the observation, so the same repository projects to the same hash
|
|
491
|
+
* whatever revision it was scanned at.
|
|
465
492
|
*/
|
|
466
493
|
const contentHash = sha256NormalizedV1({
|
|
467
494
|
projectionVersion: RETRIEVAL_PROJECTION_VERSION,
|
|
468
|
-
|
|
495
|
+
observationHash: snapshotObservationHash(snapshot),
|
|
469
496
|
overlayHash: base.overlayHash,
|
|
470
497
|
configurationHash: base.configurationHash,
|
|
471
498
|
lexiconVersion: base.lexiconVersion,
|
|
@@ -136,8 +136,13 @@ export const RetrievalIndexV1Schema = z
|
|
|
136
136
|
schemaVersion: z.literal(RETRIEVAL_INDEX_SCHEMA_VERSION),
|
|
137
137
|
contentHash: hash,
|
|
138
138
|
contentHashAlgo: z.literal('sha256-normalized-v1'),
|
|
139
|
-
/**
|
|
139
|
+
/**
|
|
140
|
+
* Which snapshot this was projected from. Provenance, not a seal input: it carries the
|
|
141
|
+
* snapshot's `sourceRevision`, and the projection is a function of what the snapshot observed
|
|
142
|
+
* rather than of the revision it was observed at.
|
|
143
|
+
*/
|
|
140
144
|
snapshotHash: hash,
|
|
145
|
+
/** The inputs the projection is a function of. Same hashes, same projection. */
|
|
141
146
|
overlayHash: hash,
|
|
142
147
|
configurationHash: hash,
|
|
143
148
|
lexiconVersion: z.number().int().nonnegative().max(1_000),
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '1.
|
|
1
|
+
export const PACKAGE_VERSION = '1.10.1'
|