@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.
Files changed (62) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +1 -1
  3. package/dist/cli/program.js +416 -148
  4. package/dist/cli/program.js.map +1 -1
  5. package/dist/config/index.d.ts +1 -1
  6. package/dist/config/index.js +6 -3
  7. package/dist/config/index.js.map +1 -1
  8. package/dist/{index-CD_zmKXf.d.ts → index-CPUJbTbg.d.ts} +19 -10
  9. package/dist/index.d.ts +25 -6
  10. package/dist/index.js +398 -132
  11. package/dist/index.js.map +1 -1
  12. package/docs/DOGFOOD-ROUND2.md +142 -0
  13. package/docs/DOGFOOD.md +92 -0
  14. package/package.json +32 -14
  15. package/scripts/prepare.mjs +44 -0
  16. package/src/cli/program.ts +947 -0
  17. package/src/config/defaults.ts +63 -0
  18. package/src/config/define-config.ts +6 -0
  19. package/src/config/index.ts +15 -0
  20. package/src/config/load-config.ts +138 -0
  21. package/src/config/schema.ts +294 -0
  22. package/src/federation/llms.ts +154 -0
  23. package/src/gates/run-gates.ts +274 -0
  24. package/src/index-builder/build-handoffs.ts +232 -0
  25. package/src/index-builder/build-index.ts +137 -0
  26. package/src/index-builder/capabilities.ts +37 -0
  27. package/src/index-builder/content-hash.ts +19 -0
  28. package/src/index-builder/human-adapters/core.ts +100 -0
  29. package/src/index-builder/human-adapters/docusaurus.ts +113 -0
  30. package/src/index-builder/human-adapters/fumadocs.ts +70 -0
  31. package/src/index-builder/human-adapters/index.ts +52 -0
  32. package/src/index-builder/human-adapters/plain-markdown.ts +10 -0
  33. package/src/index-builder/llms-txt.ts +19 -0
  34. package/src/index-builder/plugins/human-markdown.ts +7 -0
  35. package/src/index-builder/plugins/pnpm-monorepo.ts +72 -0
  36. package/src/index-builder/scan-corpus.ts +185 -0
  37. package/src/index.ts +120 -0
  38. package/src/intelligence/adapter.ts +90 -0
  39. package/src/intelligence/chat.ts +148 -0
  40. package/src/intelligence/peers.ts +59 -0
  41. package/src/intelligence/rag.ts +98 -0
  42. package/src/lib/glob-expand.ts +33 -0
  43. package/src/lib/markdown.ts +117 -0
  44. package/src/lib/package-manager.ts +104 -0
  45. package/src/lib/paths.ts +6 -0
  46. package/src/lib/walk.ts +45 -0
  47. package/src/mcp/server.ts +276 -0
  48. package/src/memory/ingest.ts +51 -0
  49. package/src/memory/pipeline.ts +119 -0
  50. package/src/query/load-index.ts +25 -0
  51. package/src/query/query.ts +142 -0
  52. package/src/query/search.ts +126 -0
  53. package/src/retriever/doc-bridge-retriever.ts +62 -0
  54. package/src/schemas/agent-handoff.ts +82 -0
  55. package/src/schemas/doc-bridge-index.ts +108 -0
  56. package/src/schemas/json-schemas.ts +157 -0
  57. package/src/schemas/memory-candidate.ts +37 -0
  58. package/src/shims/agentskit-peers.d.ts +80 -0
  59. package/src/validate.ts +67 -0
  60. package/src/version.ts +1 -0
  61. package/tsconfig.json +21 -0
  62. 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