@agentskit/doc-bridge 0.1.0-alpha.1 → 0.1.0-alpha.3
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 +28 -0
- package/README.md +1 -1
- package/dist/cli/program.js +416 -148
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +6 -3
- package/dist/config/index.js.map +1 -1
- package/dist/{index-CD_zmKXf.d.ts → index-CPUJbTbg.d.ts} +19 -10
- package/dist/index.d.ts +25 -6
- package/dist/index.js +398 -132
- package/dist/index.js.map +1 -1
- package/docs/DOGFOOD-ROUND2.md +142 -0
- package/docs/DOGFOOD.md +92 -0
- package/package.json +32 -14
- package/scripts/prepare.mjs +44 -0
- package/src/cli/program.ts +947 -0
- package/src/config/defaults.ts +63 -0
- package/src/config/define-config.ts +6 -0
- package/src/config/index.ts +15 -0
- package/src/config/load-config.ts +138 -0
- package/src/config/schema.ts +294 -0
- package/src/federation/llms.ts +154 -0
- package/src/gates/run-gates.ts +274 -0
- package/src/index-builder/build-handoffs.ts +232 -0
- package/src/index-builder/build-index.ts +137 -0
- package/src/index-builder/capabilities.ts +37 -0
- package/src/index-builder/content-hash.ts +19 -0
- package/src/index-builder/human-adapters/core.ts +100 -0
- package/src/index-builder/human-adapters/docusaurus.ts +113 -0
- package/src/index-builder/human-adapters/fumadocs.ts +70 -0
- package/src/index-builder/human-adapters/index.ts +52 -0
- package/src/index-builder/human-adapters/plain-markdown.ts +10 -0
- package/src/index-builder/llms-txt.ts +19 -0
- package/src/index-builder/plugins/human-markdown.ts +7 -0
- package/src/index-builder/plugins/pnpm-monorepo.ts +72 -0
- package/src/index-builder/scan-corpus.ts +185 -0
- package/src/index.ts +120 -0
- package/src/intelligence/adapter.ts +90 -0
- package/src/intelligence/chat.ts +148 -0
- package/src/intelligence/peers.ts +59 -0
- package/src/intelligence/rag.ts +98 -0
- package/src/lib/glob-expand.ts +33 -0
- package/src/lib/markdown.ts +117 -0
- package/src/lib/package-manager.ts +104 -0
- package/src/lib/paths.ts +6 -0
- package/src/lib/walk.ts +45 -0
- package/src/mcp/server.ts +276 -0
- package/src/memory/ingest.ts +51 -0
- package/src/memory/pipeline.ts +119 -0
- package/src/query/load-index.ts +25 -0
- package/src/query/query.ts +142 -0
- package/src/query/search.ts +126 -0
- package/src/retriever/doc-bridge-retriever.ts +62 -0
- package/src/schemas/agent-handoff.ts +82 -0
- package/src/schemas/doc-bridge-index.ts +108 -0
- package/src/schemas/json-schemas.ts +157 -0
- package/src/schemas/memory-candidate.ts +37 -0
- package/src/shims/agentskit-peers.d.ts +80 -0
- package/src/validate.ts +67 -0
- package/src/version.ts +1 -0
- package/tsconfig.json +21 -0
- package/tsup.config.ts +15 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { DocBridgeConfigV1 } from '../config/schema.js'
|
|
2
|
+
import {
|
|
3
|
+
normalizeAgentHandoff,
|
|
4
|
+
type AgentHandoffV1,
|
|
5
|
+
type AgentSearchV1,
|
|
6
|
+
} from '../schemas/agent-handoff.js'
|
|
7
|
+
import type { DocBridgeIndexV1 } from '../schemas/doc-bridge-index.js'
|
|
8
|
+
import { searchIndex } from './search.js'
|
|
9
|
+
|
|
10
|
+
export type QueryKind = 'package' | 'ownership' | 'intent' | 'change' | 'search'
|
|
11
|
+
|
|
12
|
+
export type QueryRequest = {
|
|
13
|
+
readonly kind: QueryKind
|
|
14
|
+
readonly id?: string
|
|
15
|
+
readonly term?: string
|
|
16
|
+
readonly agent?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type QueryResult =
|
|
20
|
+
| { readonly type: 'package' | 'ownership' | 'intent' | 'change' | 'search'; readonly data: unknown }
|
|
21
|
+
| AgentHandoffV1
|
|
22
|
+
| AgentSearchV1
|
|
23
|
+
|
|
24
|
+
const handoffForPackage = (
|
|
25
|
+
index: DocBridgeIndexV1,
|
|
26
|
+
id: string,
|
|
27
|
+
config: DocBridgeConfigV1,
|
|
28
|
+
): AgentHandoffV1 => {
|
|
29
|
+
const fromIndex = index.handoffs?.[id]
|
|
30
|
+
if (fromIndex) return normalizeAgentHandoff(fromIndex)
|
|
31
|
+
|
|
32
|
+
const owner = index.lookup?.ownership?.[id]
|
|
33
|
+
if (!owner) throw new Error(`Unknown package/ownership id "${id}". Try: ak-docs list packages`)
|
|
34
|
+
|
|
35
|
+
return normalizeAgentHandoff({
|
|
36
|
+
type: 'agent-handoff',
|
|
37
|
+
source: config.index?.outFile ?? '.doc-bridge/index.json',
|
|
38
|
+
target: {
|
|
39
|
+
type: 'package',
|
|
40
|
+
id,
|
|
41
|
+
path: owner.path,
|
|
42
|
+
...(owner.group ? { group: owner.group } : {}),
|
|
43
|
+
...(owner.layer ? { layer: owner.layer } : {}),
|
|
44
|
+
},
|
|
45
|
+
startHere: owner.agentDoc ?? config.corpus.agent.index ?? '',
|
|
46
|
+
readBeforeEditing: [owner.agentDoc, 'AGENTS.md'].filter(Boolean),
|
|
47
|
+
editRoots: [owner.path],
|
|
48
|
+
checks: [...owner.checks],
|
|
49
|
+
...(owner.humanDoc ? { humanDoc: owner.humanDoc } : {}),
|
|
50
|
+
notes: owner.purpose ? [owner.purpose] : [],
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const runQuery = (
|
|
55
|
+
index: DocBridgeIndexV1,
|
|
56
|
+
config: DocBridgeConfigV1,
|
|
57
|
+
req: QueryRequest,
|
|
58
|
+
): QueryResult => {
|
|
59
|
+
if (req.kind === 'search') {
|
|
60
|
+
const term = req.term ?? req.id ?? ''
|
|
61
|
+
const matches = searchIndex(index, term)
|
|
62
|
+
if (req.agent) {
|
|
63
|
+
const payload: AgentSearchV1 = {
|
|
64
|
+
type: 'agent-search',
|
|
65
|
+
schemaVersion: 1,
|
|
66
|
+
source: config.index?.outFile ?? '.doc-bridge/index.json',
|
|
67
|
+
term,
|
|
68
|
+
count: matches.length,
|
|
69
|
+
bestMatch: matches[0]
|
|
70
|
+
? {
|
|
71
|
+
type: matches[0].type,
|
|
72
|
+
id: matches[0].id,
|
|
73
|
+
path: matches[0].path,
|
|
74
|
+
...(matches[0].summary ? { summary: matches[0].summary } : {}),
|
|
75
|
+
score: matches[0].score,
|
|
76
|
+
}
|
|
77
|
+
: null,
|
|
78
|
+
matches: matches.slice(0, 8).map((m) => ({
|
|
79
|
+
type: m.type,
|
|
80
|
+
id: m.id,
|
|
81
|
+
path: m.path,
|
|
82
|
+
...(m.summary ? { summary: m.summary } : {}),
|
|
83
|
+
score: m.score,
|
|
84
|
+
})),
|
|
85
|
+
nextCommands: [...new Set(matches.slice(0, 5).map((m) =>
|
|
86
|
+
m.type === 'ownership' || index.lookup?.ownership?.[m.id]
|
|
87
|
+
? `ak-docs query ownership ${m.id} --agent`
|
|
88
|
+
: 'ak-docs list knowledge --text',
|
|
89
|
+
))],
|
|
90
|
+
}
|
|
91
|
+
return payload
|
|
92
|
+
}
|
|
93
|
+
return { type: 'search', data: { term, count: matches.length, matches } }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const id = req.id
|
|
97
|
+
if (!id) throw new Error(`Missing id for query kind "${req.kind}"`)
|
|
98
|
+
|
|
99
|
+
if (req.kind === 'package' || req.kind === 'ownership') {
|
|
100
|
+
if (req.agent) return handoffForPackage(index, id, config)
|
|
101
|
+
const owner = index.lookup?.ownership?.[id]
|
|
102
|
+
return { type: req.kind, data: owner ?? index.handoffs?.[id] ?? null }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (req.kind === 'intent') {
|
|
106
|
+
const intent = index.lookup?.intents?.[id]
|
|
107
|
+
if (!intent) throw new Error(`Unknown intent "${id}"`)
|
|
108
|
+
if (req.agent) {
|
|
109
|
+
return normalizeAgentHandoff({
|
|
110
|
+
type: 'agent-handoff',
|
|
111
|
+
source: config.index?.outFile ?? '.doc-bridge/index.json',
|
|
112
|
+
target: { type: 'intent', id },
|
|
113
|
+
startHere: intent.paths[0] ?? '',
|
|
114
|
+
readBeforeEditing: [...intent.paths],
|
|
115
|
+
editRoots: [],
|
|
116
|
+
checks: [],
|
|
117
|
+
notes: [intent.title],
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
return { type: 'intent', data: intent }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (req.kind === 'change') {
|
|
124
|
+
const change = index.lookup?.changes?.[id]
|
|
125
|
+
if (!change) throw new Error(`Unknown change route "${id}"`)
|
|
126
|
+
if (req.agent) {
|
|
127
|
+
return normalizeAgentHandoff({
|
|
128
|
+
type: 'agent-handoff',
|
|
129
|
+
source: config.index?.outFile ?? '.doc-bridge/index.json',
|
|
130
|
+
target: { type: 'change', id },
|
|
131
|
+
startHere: change.startHere,
|
|
132
|
+
readBeforeEditing: ['AGENTS.md', config.corpus.agent.index ?? ''].filter(Boolean),
|
|
133
|
+
editRoots: [change.startHere],
|
|
134
|
+
checks: [],
|
|
135
|
+
notes: [change.title],
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
return { type: 'change', data: change }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
throw new Error(`Unsupported query kind: ${req.kind as string}`)
|
|
142
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { DocBridgeIndexV1 } from '../schemas/doc-bridge-index.js'
|
|
2
|
+
|
|
3
|
+
export type SearchMatch = {
|
|
4
|
+
readonly type: string
|
|
5
|
+
readonly id: string
|
|
6
|
+
readonly path: string
|
|
7
|
+
readonly summary?: string
|
|
8
|
+
readonly score: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const tokenize = (value: string): string[] =>
|
|
12
|
+
value
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.split(/[^a-z0-9@/_-]+/)
|
|
15
|
+
.filter((t) => t.length >= 2)
|
|
16
|
+
|
|
17
|
+
const PACKAGE_INTENT =
|
|
18
|
+
/\b(package|module|pkg|edit|change|where|owns?|ownership|handoff|start)\b/i
|
|
19
|
+
|
|
20
|
+
const scoreHay = (tokens: readonly string[], hay: string, weight = 1): number => {
|
|
21
|
+
let score = 0
|
|
22
|
+
for (const token of tokens) {
|
|
23
|
+
if (!hay.includes(token)) continue
|
|
24
|
+
score += token.length * weight
|
|
25
|
+
// whole-word-ish bonus
|
|
26
|
+
if (new RegExp(`(?:^|[^a-z0-9])${token}(?:[^a-z0-9]|$)`).test(hay)) {
|
|
27
|
+
score += token.length
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return score
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Exact / near-exact identity boost so "core" ranks the core package over mentions of @agentskit/core. */
|
|
34
|
+
const identityBoost = (id: string, path: string, tokens: readonly string[], term: string): number => {
|
|
35
|
+
const idLower = id.toLowerCase()
|
|
36
|
+
const termLower = term.toLowerCase().trim()
|
|
37
|
+
const base = path.split('/').pop()?.replace(/\.mdx?$/i, '').toLowerCase() ?? ''
|
|
38
|
+
let boost = 0
|
|
39
|
+
|
|
40
|
+
if (idLower === termLower || base === termLower) boost += 200
|
|
41
|
+
if (tokens.length === 1 && (idLower === tokens[0] || base === tokens[0])) boost += 200
|
|
42
|
+
|
|
43
|
+
for (const token of tokens) {
|
|
44
|
+
if (idLower === token) boost += 120
|
|
45
|
+
else if (idLower.startsWith(`${token}-`) || idLower.endsWith(`-${token}`)) boost += 40
|
|
46
|
+
else if (idLower.includes(token) && idLower.length <= token.length + 4) boost += 30
|
|
47
|
+
if (base === token) boost += 100
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Prefer short ids when term is the id (package name)
|
|
51
|
+
if (tokens.includes(idLower)) boost += Math.max(0, 40 - idLower.length)
|
|
52
|
+
|
|
53
|
+
return boost
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const preferOwnership = (term: string): boolean =>
|
|
57
|
+
PACKAGE_INTENT.test(term) || /^(where|how).*(edit|change|package|module)/i.test(term)
|
|
58
|
+
|
|
59
|
+
export const searchIndex = (index: DocBridgeIndexV1, term: string, limit = 20): SearchMatch[] => {
|
|
60
|
+
const tokens = tokenize(term)
|
|
61
|
+
if (!tokens.length) return []
|
|
62
|
+
|
|
63
|
+
const wantOwnership = preferOwnership(term)
|
|
64
|
+
const byPath = new Map<string, SearchMatch>()
|
|
65
|
+
|
|
66
|
+
const consider = (match: SearchMatch) => {
|
|
67
|
+
const key = match.path
|
|
68
|
+
const existing = byPath.get(key)
|
|
69
|
+
if (!existing) {
|
|
70
|
+
byPath.set(key, match)
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
// Prefer ownership over knowledge for same path; else higher score
|
|
74
|
+
const prefer =
|
|
75
|
+
match.score > existing.score ||
|
|
76
|
+
(match.score === existing.score && match.type === 'ownership' && existing.type !== 'ownership') ||
|
|
77
|
+
(wantOwnership && match.type === 'ownership' && existing.type !== 'ownership' && match.score >= existing.score - 20)
|
|
78
|
+
if (prefer) byPath.set(key, match)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const entry of index.knowledge) {
|
|
82
|
+
const body = (entry as { body?: string }).body ?? ''
|
|
83
|
+
const hay = `${entry.id} ${entry.title} ${entry.description ?? ''} ${entry.path} ${body}`.toLowerCase()
|
|
84
|
+
let score = scoreHay(tokens, hay, 1)
|
|
85
|
+
score += identityBoost(entry.id, entry.path, tokens, term)
|
|
86
|
+
if (score > 0) {
|
|
87
|
+
consider({
|
|
88
|
+
type: 'knowledge',
|
|
89
|
+
id: entry.id,
|
|
90
|
+
path: entry.path,
|
|
91
|
+
...(entry.description ? { summary: entry.description } : {}),
|
|
92
|
+
score,
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const [id, owner] of Object.entries(index.lookup?.ownership ?? {})) {
|
|
98
|
+
const path = owner.agentDoc ?? owner.path
|
|
99
|
+
const hay = `${id} ${owner.path} ${owner.purpose ?? ''} ${owner.group ?? ''} ${owner.agentDoc ?? ''} ${owner.humanDoc ?? ''}`.toLowerCase()
|
|
100
|
+
let score = scoreHay(tokens, hay, 2)
|
|
101
|
+
score += identityBoost(id, path, tokens, term)
|
|
102
|
+
// Ownership is primary for routing questions
|
|
103
|
+
if (wantOwnership) score += 25
|
|
104
|
+
score += 15 // slight base preference for actionable ownership targets
|
|
105
|
+
if (score > 0) {
|
|
106
|
+
consider({
|
|
107
|
+
type: 'ownership',
|
|
108
|
+
id,
|
|
109
|
+
path,
|
|
110
|
+
...(owner.purpose ? { summary: owner.purpose } : {}),
|
|
111
|
+
score,
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return [...byPath.values()].sort((a, b) => {
|
|
117
|
+
if (b.score !== a.score) return b.score - a.score
|
|
118
|
+
// Tie-break: exact id match, then ownership, then shorter id
|
|
119
|
+
const aExact = tokens.includes(a.id.toLowerCase()) ? 1 : 0
|
|
120
|
+
const bExact = tokens.includes(b.id.toLowerCase()) ? 1 : 0
|
|
121
|
+
if (bExact !== aExact) return bExact - aExact
|
|
122
|
+
if (a.type === 'ownership' && b.type !== 'ownership') return -1
|
|
123
|
+
if (b.type === 'ownership' && a.type !== 'ownership') return 1
|
|
124
|
+
return a.id.localeCompare(b.id)
|
|
125
|
+
}).slice(0, limit)
|
|
126
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { DocBridgeIndexV1 } from '../schemas/doc-bridge-index.js'
|
|
2
|
+
import { searchIndex } from '../query/search.js'
|
|
3
|
+
|
|
4
|
+
export type DocBridgeRetrieverOptions = {
|
|
5
|
+
readonly property?: string
|
|
6
|
+
readonly limit?: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type DocBridgeRetrievedChunk = {
|
|
10
|
+
readonly chunkKey: string
|
|
11
|
+
readonly property: string
|
|
12
|
+
readonly type: string
|
|
13
|
+
readonly id: string
|
|
14
|
+
readonly path: string
|
|
15
|
+
readonly title?: string
|
|
16
|
+
readonly summary?: string
|
|
17
|
+
readonly score: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type DocBridgeRetriever = {
|
|
21
|
+
readonly retrieve: (
|
|
22
|
+
query: string,
|
|
23
|
+
options?: Pick<DocBridgeRetrieverOptions, 'limit'>,
|
|
24
|
+
) => DocBridgeRetrievedChunk[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const chunkKey = (property: string, type: string, id: string): string =>
|
|
28
|
+
`${property}:${type}:${id}`
|
|
29
|
+
|
|
30
|
+
export const retrieveDocBridgeChunks = (
|
|
31
|
+
index: DocBridgeIndexV1,
|
|
32
|
+
query: string,
|
|
33
|
+
options: DocBridgeRetrieverOptions = {},
|
|
34
|
+
): DocBridgeRetrievedChunk[] => {
|
|
35
|
+
const property = options.property ?? index.project?.name ?? 'local'
|
|
36
|
+
const limit = options.limit ?? 8
|
|
37
|
+
|
|
38
|
+
return searchIndex(index, query, limit).map((match) => {
|
|
39
|
+
const knowledge = index.knowledge.find((entry) => entry.id === match.id && entry.path === match.path)
|
|
40
|
+
const owner = index.lookup?.ownership?.[match.id]
|
|
41
|
+
const type = match.type === 'ownership' ? 'ownership' : (knowledge?.type ?? match.type)
|
|
42
|
+
const summary = match.summary ?? owner?.purpose
|
|
43
|
+
return {
|
|
44
|
+
chunkKey: chunkKey(property, type, match.id),
|
|
45
|
+
property,
|
|
46
|
+
type,
|
|
47
|
+
id: match.id,
|
|
48
|
+
path: match.path,
|
|
49
|
+
...(knowledge?.title ? { title: knowledge.title } : {}),
|
|
50
|
+
...(summary ? { summary } : {}),
|
|
51
|
+
score: match.score,
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const createDocBridgeRetriever = (
|
|
57
|
+
index: DocBridgeIndexV1,
|
|
58
|
+
options: DocBridgeRetrieverOptions = {},
|
|
59
|
+
): DocBridgeRetriever => ({
|
|
60
|
+
retrieve: (query, req = {}) =>
|
|
61
|
+
retrieveDocBridgeChunks(index, query, { ...options, ...req }),
|
|
62
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const HANDOFF_SCHEMA_VERSION = 1 as const
|
|
4
|
+
|
|
5
|
+
export const HandoffTargetTypeSchema = z.enum([
|
|
6
|
+
'package',
|
|
7
|
+
'module',
|
|
8
|
+
'app',
|
|
9
|
+
'screen',
|
|
10
|
+
'flow',
|
|
11
|
+
'component',
|
|
12
|
+
'intent',
|
|
13
|
+
'change',
|
|
14
|
+
'search',
|
|
15
|
+
])
|
|
16
|
+
|
|
17
|
+
export type HandoffTargetType = z.infer<typeof HandoffTargetTypeSchema>
|
|
18
|
+
|
|
19
|
+
export const HandoffTargetSchema = z
|
|
20
|
+
.object({
|
|
21
|
+
type: HandoffTargetTypeSchema,
|
|
22
|
+
id: z.string().min(1).max(256),
|
|
23
|
+
path: z.string().min(1).max(512).optional(),
|
|
24
|
+
group: z.string().min(1).max(128).optional(),
|
|
25
|
+
layer: z.string().min(1).max(32).optional(),
|
|
26
|
+
})
|
|
27
|
+
.strict()
|
|
28
|
+
|
|
29
|
+
export type HandoffTarget = z.infer<typeof HandoffTargetSchema>
|
|
30
|
+
|
|
31
|
+
/** v1 — canonical AgentHandoff. Legacy payloads may omit schemaVersion. */
|
|
32
|
+
export const AgentHandoffV1Schema = z
|
|
33
|
+
.object({
|
|
34
|
+
type: z.literal('agent-handoff'),
|
|
35
|
+
schemaVersion: z.literal(HANDOFF_SCHEMA_VERSION).default(HANDOFF_SCHEMA_VERSION),
|
|
36
|
+
source: z.string().min(1).max(512),
|
|
37
|
+
target: HandoffTargetSchema,
|
|
38
|
+
startHere: z.string().min(1).max(512),
|
|
39
|
+
readBeforeEditing: z.array(z.string().min(1).max(512)).max(64),
|
|
40
|
+
editRoots: z.array(z.string().min(1).max(512)).max(32),
|
|
41
|
+
checks: z.array(z.string().min(1).max(256)).max(32),
|
|
42
|
+
humanDoc: z.string().min(1).max(512).nullable().optional(),
|
|
43
|
+
playbookPatterns: z.array(z.string().url()).max(16).optional(),
|
|
44
|
+
notes: z.array(z.string().min(1).max(1024)).max(16),
|
|
45
|
+
})
|
|
46
|
+
.strict()
|
|
47
|
+
|
|
48
|
+
export type AgentHandoffV1 = z.infer<typeof AgentHandoffV1Schema>
|
|
49
|
+
|
|
50
|
+
/** Accept legacy handoffs without schemaVersion. */
|
|
51
|
+
export const AgentHandoffLegacySchema = AgentHandoffV1Schema.omit({ schemaVersion: true }).extend({
|
|
52
|
+
schemaVersion: z.literal(HANDOFF_SCHEMA_VERSION).optional(),
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
export const AgentSearchMatchSchema = z
|
|
56
|
+
.object({
|
|
57
|
+
type: z.string().min(1).max(64),
|
|
58
|
+
id: z.string().min(1).max(256),
|
|
59
|
+
path: z.string().min(1).max(512),
|
|
60
|
+
summary: z.string().max(2048).optional(),
|
|
61
|
+
score: z.number().optional(),
|
|
62
|
+
refs: z.number().int().nonnegative().optional(),
|
|
63
|
+
})
|
|
64
|
+
.strict()
|
|
65
|
+
|
|
66
|
+
export const AgentSearchV1Schema = z
|
|
67
|
+
.object({
|
|
68
|
+
type: z.literal('agent-search'),
|
|
69
|
+
schemaVersion: z.literal(HANDOFF_SCHEMA_VERSION).default(HANDOFF_SCHEMA_VERSION),
|
|
70
|
+
source: z.string().min(1).max(512),
|
|
71
|
+
term: z.string().min(1).max(512),
|
|
72
|
+
count: z.number().int().nonnegative(),
|
|
73
|
+
bestMatch: AgentSearchMatchSchema.nullable(),
|
|
74
|
+
matches: z.array(AgentSearchMatchSchema).max(32),
|
|
75
|
+
nextCommands: z.array(z.string().min(1).max(512)).max(16),
|
|
76
|
+
})
|
|
77
|
+
.strict()
|
|
78
|
+
|
|
79
|
+
export type AgentSearchV1 = z.infer<typeof AgentSearchV1Schema>
|
|
80
|
+
|
|
81
|
+
export const normalizeAgentHandoff = (input: unknown): AgentHandoffV1 =>
|
|
82
|
+
AgentHandoffV1Schema.parse(AgentHandoffLegacySchema.parse(input))
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { AgentHandoffLegacySchema } from './agent-handoff.js'
|
|
4
|
+
|
|
5
|
+
export const INDEX_SCHEMA_VERSION = 1 as const
|
|
6
|
+
|
|
7
|
+
export const ContentHashAlgoSchema = z.literal('sha256-normalized-v1')
|
|
8
|
+
|
|
9
|
+
export const EcosystemPropertySchema = z
|
|
10
|
+
.object({
|
|
11
|
+
id: z.string().min(1).max(64),
|
|
12
|
+
name: z.string().min(1).max(128),
|
|
13
|
+
url: z.string().url().optional(),
|
|
14
|
+
llms: z.string().url().optional(),
|
|
15
|
+
})
|
|
16
|
+
.strict()
|
|
17
|
+
|
|
18
|
+
export const KnowledgeEntrySchema = z
|
|
19
|
+
.object({
|
|
20
|
+
id: z.string().min(1).max(256),
|
|
21
|
+
type: z.string().min(1).max(128),
|
|
22
|
+
title: z.string().min(1).max(256),
|
|
23
|
+
path: z.string().min(1).max(512),
|
|
24
|
+
description: z.string().max(2_048).optional(),
|
|
25
|
+
/** Flattened body excerpt for full-text search (not for display). */
|
|
26
|
+
body: z.string().max(8_000).optional(),
|
|
27
|
+
links: z.array(z.string().min(1).max(512)).max(64).optional(),
|
|
28
|
+
tags: z.array(z.string().min(1).max(64)).max(32).optional(),
|
|
29
|
+
})
|
|
30
|
+
.strict()
|
|
31
|
+
|
|
32
|
+
export const CapabilityRefSchema = z
|
|
33
|
+
.object({
|
|
34
|
+
id: z.string().min(1).max(256),
|
|
35
|
+
kind: z.string().min(1).max(64),
|
|
36
|
+
description: z.string().max(512).optional(),
|
|
37
|
+
})
|
|
38
|
+
.strict()
|
|
39
|
+
|
|
40
|
+
export const OwnershipRecordSchema = z
|
|
41
|
+
.object({
|
|
42
|
+
id: z.string().min(1).max(256),
|
|
43
|
+
path: z.string().min(1).max(512),
|
|
44
|
+
group: z.string().min(1).max(128).optional(),
|
|
45
|
+
layer: z.string().min(1).max(32).optional(),
|
|
46
|
+
purpose: z.string().max(1024).optional(),
|
|
47
|
+
checks: z.array(z.string().min(1).max(256)).max(32),
|
|
48
|
+
agentDoc: z.string().min(1).max(512).optional(),
|
|
49
|
+
humanDoc: z.string().min(1).max(512).optional(),
|
|
50
|
+
readme: z.string().min(1).max(512).optional(),
|
|
51
|
+
})
|
|
52
|
+
.strict()
|
|
53
|
+
|
|
54
|
+
export const IndexLookupSchema = z
|
|
55
|
+
.object({
|
|
56
|
+
packages: z.array(z.string().min(1).max(256)).max(2_000),
|
|
57
|
+
ownership: z.record(z.string().min(1).max(256), OwnershipRecordSchema).optional(),
|
|
58
|
+
intents: z
|
|
59
|
+
.record(
|
|
60
|
+
z.string().min(1).max(128),
|
|
61
|
+
z
|
|
62
|
+
.object({
|
|
63
|
+
id: z.string().min(1).max(128),
|
|
64
|
+
title: z.string().min(1).max(256),
|
|
65
|
+
paths: z.array(z.string().min(1).max(512)).max(32),
|
|
66
|
+
})
|
|
67
|
+
.strict(),
|
|
68
|
+
)
|
|
69
|
+
.optional(),
|
|
70
|
+
changes: z
|
|
71
|
+
.record(
|
|
72
|
+
z.string().min(1).max(128),
|
|
73
|
+
z
|
|
74
|
+
.object({
|
|
75
|
+
id: z.string().min(1).max(128),
|
|
76
|
+
title: z.string().min(1).max(256),
|
|
77
|
+
startHere: z.string().min(1).max(512),
|
|
78
|
+
relatedPackages: z.array(z.string().min(1).max(256)).max(32).optional(),
|
|
79
|
+
})
|
|
80
|
+
.strict(),
|
|
81
|
+
)
|
|
82
|
+
.optional(),
|
|
83
|
+
})
|
|
84
|
+
.strict()
|
|
85
|
+
|
|
86
|
+
export const DocBridgeIndexV1Schema = z
|
|
87
|
+
.object({
|
|
88
|
+
schemaVersion: z.literal(INDEX_SCHEMA_VERSION),
|
|
89
|
+
contentHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
90
|
+
contentHashAlgo: ContentHashAlgoSchema,
|
|
91
|
+
generatedAt: z.string().datetime().optional(),
|
|
92
|
+
project: z
|
|
93
|
+
.object({
|
|
94
|
+
name: z.string().min(1).max(128),
|
|
95
|
+
root: z.string().min(1).max(512).optional(),
|
|
96
|
+
})
|
|
97
|
+
.strict()
|
|
98
|
+
.optional(),
|
|
99
|
+
properties: z.array(EcosystemPropertySchema).max(16).optional(),
|
|
100
|
+
knowledge: z.array(KnowledgeEntrySchema).max(10_000),
|
|
101
|
+
capabilities: z.array(CapabilityRefSchema).max(5_000).optional(),
|
|
102
|
+
handoffs: z.record(z.string().min(1).max(256), AgentHandoffLegacySchema).optional(),
|
|
103
|
+
lookup: IndexLookupSchema.optional(),
|
|
104
|
+
})
|
|
105
|
+
.strict()
|
|
106
|
+
|
|
107
|
+
export type DocBridgeIndexV1 = z.infer<typeof DocBridgeIndexV1Schema>
|
|
108
|
+
export type KnowledgeEntry = z.infer<typeof KnowledgeEntrySchema>
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
type JsonSchema = {
|
|
2
|
+
readonly [key: string]: unknown
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const stringArray = (maxItems: number): JsonSchema => ({
|
|
6
|
+
type: 'array',
|
|
7
|
+
items: { type: 'string', minLength: 1 },
|
|
8
|
+
maxItems,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
export const AgentHandoffV1JsonSchema = {
|
|
12
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
13
|
+
$id: 'https://agentskit.io/schemas/doc-bridge/agent-handoff-v1.schema.json',
|
|
14
|
+
title: 'AgentHandoff v1',
|
|
15
|
+
type: 'object',
|
|
16
|
+
additionalProperties: false,
|
|
17
|
+
required: [
|
|
18
|
+
'type',
|
|
19
|
+
'schemaVersion',
|
|
20
|
+
'source',
|
|
21
|
+
'target',
|
|
22
|
+
'startHere',
|
|
23
|
+
'readBeforeEditing',
|
|
24
|
+
'editRoots',
|
|
25
|
+
'checks',
|
|
26
|
+
'notes',
|
|
27
|
+
],
|
|
28
|
+
properties: {
|
|
29
|
+
type: { const: 'agent-handoff' },
|
|
30
|
+
schemaVersion: { const: 1 },
|
|
31
|
+
source: { type: 'string', minLength: 1, maxLength: 512 },
|
|
32
|
+
target: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
additionalProperties: false,
|
|
35
|
+
required: ['type', 'id'],
|
|
36
|
+
properties: {
|
|
37
|
+
type: {
|
|
38
|
+
enum: ['package', 'module', 'app', 'screen', 'flow', 'component', 'intent', 'change', 'search'],
|
|
39
|
+
},
|
|
40
|
+
id: { type: 'string', minLength: 1, maxLength: 256 },
|
|
41
|
+
path: { type: 'string', minLength: 1, maxLength: 512 },
|
|
42
|
+
group: { type: 'string', minLength: 1, maxLength: 128 },
|
|
43
|
+
layer: { type: 'string', minLength: 1, maxLength: 32 },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
startHere: { type: 'string', minLength: 1, maxLength: 512 },
|
|
47
|
+
readBeforeEditing: stringArray(64),
|
|
48
|
+
editRoots: stringArray(32),
|
|
49
|
+
checks: stringArray(32),
|
|
50
|
+
humanDoc: { anyOf: [{ type: 'string', minLength: 1, maxLength: 512 }, { type: 'null' }] },
|
|
51
|
+
playbookPatterns: { type: 'array', items: { type: 'string', format: 'uri' }, maxItems: 16 },
|
|
52
|
+
notes: stringArray(16),
|
|
53
|
+
},
|
|
54
|
+
} as const satisfies JsonSchema
|
|
55
|
+
|
|
56
|
+
export const MemoryCandidateV1JsonSchema = {
|
|
57
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
58
|
+
$id: 'https://agentskit.io/schemas/doc-bridge/memory-candidate-v1.schema.json',
|
|
59
|
+
title: 'MemoryCandidate v1',
|
|
60
|
+
type: 'object',
|
|
61
|
+
additionalProperties: false,
|
|
62
|
+
required: ['schemaVersion', 'id', 'source', 'fact', 'suggestedType', 'confidence', 'references'],
|
|
63
|
+
properties: {
|
|
64
|
+
schemaVersion: { const: 1 },
|
|
65
|
+
id: { type: 'string', minLength: 1, maxLength: 256 },
|
|
66
|
+
source: { enum: ['cursor', 'claude', 'codex', 'copilot', 'agent-memory', 'manual'] },
|
|
67
|
+
rawPath: { type: 'string', minLength: 1, maxLength: 512 },
|
|
68
|
+
fact: { type: 'string', minLength: 1, maxLength: 8000 },
|
|
69
|
+
why: { type: 'string', maxLength: 4000 },
|
|
70
|
+
howToApply: { type: 'string', maxLength: 4000 },
|
|
71
|
+
suggestedType: { enum: ['user', 'feedback', 'project', 'reference', 'unknown'] },
|
|
72
|
+
confidence: { type: 'number', minimum: 0, maximum: 1 },
|
|
73
|
+
references: stringArray(64),
|
|
74
|
+
},
|
|
75
|
+
} as const satisfies JsonSchema
|
|
76
|
+
|
|
77
|
+
export const DocBridgeIndexV1JsonSchema = {
|
|
78
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
79
|
+
$id: 'https://agentskit.io/schemas/doc-bridge/doc-bridge-index-v1.schema.json',
|
|
80
|
+
title: 'DocBridgeIndex v1',
|
|
81
|
+
type: 'object',
|
|
82
|
+
additionalProperties: false,
|
|
83
|
+
required: ['schemaVersion', 'contentHash', 'contentHashAlgo', 'knowledge'],
|
|
84
|
+
properties: {
|
|
85
|
+
schemaVersion: { const: 1 },
|
|
86
|
+
contentHash: { type: 'string', pattern: '^[a-f0-9]{64}$' },
|
|
87
|
+
contentHashAlgo: { const: 'sha256-normalized-v1' },
|
|
88
|
+
generatedAt: { type: 'string', format: 'date-time' },
|
|
89
|
+
project: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
additionalProperties: false,
|
|
92
|
+
properties: {
|
|
93
|
+
name: { type: 'string', minLength: 1, maxLength: 128 },
|
|
94
|
+
root: { type: 'string', minLength: 1, maxLength: 512 },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
properties: {
|
|
98
|
+
type: 'array',
|
|
99
|
+
maxItems: 16,
|
|
100
|
+
items: {
|
|
101
|
+
type: 'object',
|
|
102
|
+
additionalProperties: false,
|
|
103
|
+
required: ['id', 'name'],
|
|
104
|
+
properties: {
|
|
105
|
+
id: { type: 'string', minLength: 1, maxLength: 64 },
|
|
106
|
+
name: { type: 'string', minLength: 1, maxLength: 128 },
|
|
107
|
+
url: { type: 'string', format: 'uri' },
|
|
108
|
+
llms: { type: 'string', format: 'uri' },
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
knowledge: {
|
|
113
|
+
type: 'array',
|
|
114
|
+
maxItems: 10000,
|
|
115
|
+
items: {
|
|
116
|
+
type: 'object',
|
|
117
|
+
additionalProperties: false,
|
|
118
|
+
required: ['id', 'type', 'title', 'path'],
|
|
119
|
+
properties: {
|
|
120
|
+
id: { type: 'string', minLength: 1, maxLength: 256 },
|
|
121
|
+
type: { type: 'string', minLength: 1, maxLength: 128 },
|
|
122
|
+
title: { type: 'string', minLength: 1, maxLength: 256 },
|
|
123
|
+
path: { type: 'string', minLength: 1, maxLength: 512 },
|
|
124
|
+
description: { type: 'string', maxLength: 2048 },
|
|
125
|
+
body: { type: 'string', maxLength: 8000 },
|
|
126
|
+
links: stringArray(64),
|
|
127
|
+
tags: stringArray(32),
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
capabilities: {
|
|
132
|
+
type: 'array',
|
|
133
|
+
maxItems: 5000,
|
|
134
|
+
items: {
|
|
135
|
+
type: 'object',
|
|
136
|
+
additionalProperties: false,
|
|
137
|
+
required: ['id', 'kind'],
|
|
138
|
+
properties: {
|
|
139
|
+
id: { type: 'string', minLength: 1, maxLength: 256 },
|
|
140
|
+
kind: { type: 'string', minLength: 1, maxLength: 64 },
|
|
141
|
+
description: { type: 'string', maxLength: 512 },
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
handoffs: {
|
|
146
|
+
type: 'object',
|
|
147
|
+
additionalProperties: { type: 'object' },
|
|
148
|
+
},
|
|
149
|
+
lookup: { type: 'object' },
|
|
150
|
+
},
|
|
151
|
+
} as const satisfies JsonSchema
|
|
152
|
+
|
|
153
|
+
export const DocBridgeJsonSchemas = {
|
|
154
|
+
agentHandoffV1: AgentHandoffV1JsonSchema,
|
|
155
|
+
docBridgeIndexV1: DocBridgeIndexV1JsonSchema,
|
|
156
|
+
memoryCandidateV1: MemoryCandidateV1JsonSchema,
|
|
157
|
+
} as const
|