@agentskit/doc-bridge 0.1.0-alpha.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 +28 -0
- package/CODE_OF_CONDUCT.md +7 -0
- package/CONTRIBUTING.md +32 -0
- package/LICENSE +21 -0
- package/README.md +137 -0
- package/SECURITY.md +24 -0
- package/bin/ak-docs.js +6 -0
- package/dist/cli/program.d.ts +3 -0
- package/dist/cli/program.js +3155 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +361 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index-CD_zmKXf.d.ts +932 -0
- package/dist/index.d.ts +1608 -0
- package/dist/index.js +2566 -0
- package/dist/index.js.map +1 -0
- package/docs/POSITIONING.md +78 -0
- package/docs/RELEASE.md +75 -0
- package/docs/chat-and-rag.md +48 -0
- package/docs/examples.md +50 -0
- package/docs/getting-started.md +111 -0
- package/docs/mcp.md +50 -0
- package/docs/schemas/agent-handoff-v1.md +32 -0
- package/docs/schemas/doc-bridge-index-v1.md +71 -0
- package/docs/schemas/memory-candidate-v1.md +32 -0
- package/docs/spec/cli.md +137 -0
- package/docs/spec/config-v1.md +625 -0
- package/docs/spec/playbook-feedback.md +77 -0
- package/docs/spec/registry-agents.md +65 -0
- package/examples/docusaurus-only.config.ts +18 -0
- package/examples/docusaurus-with-memory.config.ts +37 -0
- package/examples/fumadocs-only.config.ts +18 -0
- package/examples/fumadocs-with-chat.config.ts +42 -0
- package/examples/minimal-plain-markdown.config.ts +13 -0
- package/examples/pnpm-monorepo.config.ts +19 -0
- package/package.json +105 -0
|
@@ -0,0 +1,3155 @@
|
|
|
1
|
+
// src/cli/program.ts
|
|
2
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync14, writeFileSync as writeFileSync2 } from "fs";
|
|
3
|
+
import { dirname as dirname4, resolve as resolve6 } from "path";
|
|
4
|
+
import { createInterface } from "readline/promises";
|
|
5
|
+
|
|
6
|
+
// src/config/load-config.ts
|
|
7
|
+
import { existsSync, readFileSync } from "fs";
|
|
8
|
+
import { basename, dirname, join, resolve } from "path";
|
|
9
|
+
import vm from "vm";
|
|
10
|
+
|
|
11
|
+
// src/config/defaults.ts
|
|
12
|
+
var DEFAULT_AGENT_EXCLUDE = ["**/node_modules/**", "**/.git/**"];
|
|
13
|
+
var applyConfigDefaults = (config) => {
|
|
14
|
+
const agentRoot = config.corpus.agent.root;
|
|
15
|
+
const agentIndex = config.corpus.agent.index ?? `${agentRoot}/INDEX.md`;
|
|
16
|
+
return {
|
|
17
|
+
...config,
|
|
18
|
+
corpus: {
|
|
19
|
+
...config.corpus,
|
|
20
|
+
agent: {
|
|
21
|
+
...config.corpus.agent,
|
|
22
|
+
index: agentIndex,
|
|
23
|
+
include: config.corpus.agent.include ?? ["**/*.md"],
|
|
24
|
+
exclude: config.corpus.agent.exclude ?? [...DEFAULT_AGENT_EXCLUDE]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
index: {
|
|
28
|
+
outFile: ".doc-bridge/index.json",
|
|
29
|
+
contentHash: "sha256-normalized-v1",
|
|
30
|
+
llmsTxt: {
|
|
31
|
+
enabled: true,
|
|
32
|
+
outFile: "llms.txt",
|
|
33
|
+
...config.index?.llmsTxt
|
|
34
|
+
},
|
|
35
|
+
capabilities: {
|
|
36
|
+
enabled: true,
|
|
37
|
+
outFile: ".doc-bridge/capabilities.json",
|
|
38
|
+
...config.index?.capabilities
|
|
39
|
+
},
|
|
40
|
+
...config.index
|
|
41
|
+
},
|
|
42
|
+
gates: {
|
|
43
|
+
preset: "minimal",
|
|
44
|
+
...config.gates
|
|
45
|
+
},
|
|
46
|
+
surfaces: {
|
|
47
|
+
cli: {
|
|
48
|
+
bin: "ak-docs",
|
|
49
|
+
defaultFormat: "json",
|
|
50
|
+
...config.surfaces?.cli
|
|
51
|
+
},
|
|
52
|
+
mcp: {
|
|
53
|
+
enabled: true,
|
|
54
|
+
tools: ["handoff.resolve", "doc.search", "doc.get", "gate.status"],
|
|
55
|
+
transport: "stdio",
|
|
56
|
+
...config.surfaces?.mcp
|
|
57
|
+
},
|
|
58
|
+
...config.surfaces
|
|
59
|
+
},
|
|
60
|
+
intelligence: config.intelligence ? {
|
|
61
|
+
enabled: false,
|
|
62
|
+
...config.intelligence,
|
|
63
|
+
chat: config.intelligence.chat ? { handoffFirst: true, ...config.intelligence.chat } : void 0
|
|
64
|
+
} : { enabled: false }
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// src/config/schema.ts
|
|
69
|
+
import { z } from "zod";
|
|
70
|
+
var CONFIG_SCHEMA_VERSION = 1;
|
|
71
|
+
var HumanCorpusPluginIdSchema = z.enum([
|
|
72
|
+
"plain-markdown",
|
|
73
|
+
"fumadocs",
|
|
74
|
+
"docusaurus",
|
|
75
|
+
"mkdocs",
|
|
76
|
+
"vitepress",
|
|
77
|
+
"custom"
|
|
78
|
+
]);
|
|
79
|
+
var AgentCorpusConfigSchema = z.object({
|
|
80
|
+
root: z.string().min(1).max(512),
|
|
81
|
+
index: z.string().min(1).max(512).optional(),
|
|
82
|
+
include: z.array(z.string().min(1).max(256)).max(64).optional(),
|
|
83
|
+
exclude: z.array(z.string().min(1).max(256)).max(64).optional(),
|
|
84
|
+
okf: z.object({
|
|
85
|
+
requireType: z.boolean().optional(),
|
|
86
|
+
allowedTypes: z.array(z.string().min(1).max(128)).max(64).optional()
|
|
87
|
+
}).strict().optional()
|
|
88
|
+
}).strict();
|
|
89
|
+
var HumanCorpusConfigSchema = z.object({
|
|
90
|
+
plugin: HumanCorpusPluginIdSchema,
|
|
91
|
+
options: z.record(z.string(), z.unknown()).optional()
|
|
92
|
+
}).strict();
|
|
93
|
+
var IndexConfigSchema = z.object({
|
|
94
|
+
outFile: z.string().min(1).max(512).optional(),
|
|
95
|
+
llmsTxt: z.object({
|
|
96
|
+
enabled: z.boolean().optional(),
|
|
97
|
+
outFile: z.string().min(1).max(512).optional(),
|
|
98
|
+
preamble: z.string().max(4e3).optional()
|
|
99
|
+
}).strict().optional(),
|
|
100
|
+
capabilities: z.object({
|
|
101
|
+
enabled: z.boolean().optional(),
|
|
102
|
+
outFile: z.string().min(1).max(512).optional()
|
|
103
|
+
}).strict().optional(),
|
|
104
|
+
contentHash: z.literal("sha256-normalized-v1").optional()
|
|
105
|
+
}).strict();
|
|
106
|
+
var OwnershipEntrySchema = z.object({
|
|
107
|
+
path: z.string().min(1).max(512),
|
|
108
|
+
group: z.string().min(1).max(128).optional(),
|
|
109
|
+
layer: z.string().min(1).max(32).optional(),
|
|
110
|
+
purpose: z.string().max(1024).optional(),
|
|
111
|
+
checks: z.array(z.string().min(1).max(256)).max(32).optional(),
|
|
112
|
+
agentDoc: z.string().min(1).max(512).optional(),
|
|
113
|
+
humanDoc: z.string().min(1).max(512).optional()
|
|
114
|
+
}).strict();
|
|
115
|
+
var RoutingConfigSchema = z.object({
|
|
116
|
+
plugin: z.enum(["pnpm-monorepo", "npm-workspaces", "yarn-workspaces", "custom"]).optional(),
|
|
117
|
+
options: z.object({
|
|
118
|
+
packages: z.array(z.string().min(1).max(256)).max(128).optional(),
|
|
119
|
+
ownership: z.record(z.string().min(1).max(256), OwnershipEntrySchema).optional(),
|
|
120
|
+
intents: z.array(
|
|
121
|
+
z.object({
|
|
122
|
+
id: z.string().min(1).max(128),
|
|
123
|
+
title: z.string().min(1).max(256),
|
|
124
|
+
paths: z.array(z.string().min(1).max(512)).max(32)
|
|
125
|
+
}).strict()
|
|
126
|
+
).max(128).optional(),
|
|
127
|
+
changes: z.array(
|
|
128
|
+
z.object({
|
|
129
|
+
id: z.string().min(1).max(128),
|
|
130
|
+
title: z.string().min(1).max(256),
|
|
131
|
+
startHere: z.string().min(1).max(512),
|
|
132
|
+
relatedPackages: z.array(z.string().min(1).max(256)).max(32).optional()
|
|
133
|
+
}).strict()
|
|
134
|
+
).max(128).optional()
|
|
135
|
+
}).strict().optional()
|
|
136
|
+
}).strict();
|
|
137
|
+
var GatesConfigSchema = z.object({
|
|
138
|
+
preset: z.enum(["minimal", "standard", "strict"]).optional(),
|
|
139
|
+
include: z.array(
|
|
140
|
+
z.enum([
|
|
141
|
+
"index-freshness",
|
|
142
|
+
"human-guide-links",
|
|
143
|
+
"link-rot",
|
|
144
|
+
"okf-type",
|
|
145
|
+
"docs-style",
|
|
146
|
+
"routing-currency",
|
|
147
|
+
"bootstrap-size"
|
|
148
|
+
])
|
|
149
|
+
).max(16).optional(),
|
|
150
|
+
exclude: z.array(
|
|
151
|
+
z.enum([
|
|
152
|
+
"index-freshness",
|
|
153
|
+
"human-guide-links",
|
|
154
|
+
"link-rot",
|
|
155
|
+
"okf-type",
|
|
156
|
+
"docs-style",
|
|
157
|
+
"routing-currency",
|
|
158
|
+
"bootstrap-size"
|
|
159
|
+
])
|
|
160
|
+
).max(16).optional(),
|
|
161
|
+
options: z.record(z.string(), z.unknown()).optional()
|
|
162
|
+
}).strict();
|
|
163
|
+
var SurfacesConfigSchema = z.object({
|
|
164
|
+
cli: z.object({
|
|
165
|
+
bin: z.string().min(1).max(64).optional(),
|
|
166
|
+
defaultFormat: z.enum(["json", "text"]).optional()
|
|
167
|
+
}).strict().optional(),
|
|
168
|
+
mcp: z.object({
|
|
169
|
+
enabled: z.boolean().optional(),
|
|
170
|
+
tools: z.array(
|
|
171
|
+
z.enum([
|
|
172
|
+
"handoff.resolve",
|
|
173
|
+
"doc.search",
|
|
174
|
+
"doc.get",
|
|
175
|
+
"gate.status",
|
|
176
|
+
"playbook.pattern.get",
|
|
177
|
+
"retriever.query",
|
|
178
|
+
"memory.classify",
|
|
179
|
+
"memory.promoteDraft",
|
|
180
|
+
"registry.topology"
|
|
181
|
+
])
|
|
182
|
+
).max(16).optional(),
|
|
183
|
+
transport: z.enum(["stdio", "http"]).optional(),
|
|
184
|
+
http: z.object({
|
|
185
|
+
port: z.number().int().min(1).max(65535).optional(),
|
|
186
|
+
path: z.string().min(1).max(256).optional()
|
|
187
|
+
}).strict().optional()
|
|
188
|
+
}).strict().optional()
|
|
189
|
+
}).strict();
|
|
190
|
+
var IntelligenceConfigSchema = z.object({
|
|
191
|
+
enabled: z.boolean().optional(),
|
|
192
|
+
adapter: z.object({
|
|
193
|
+
provider: z.enum(["openai", "anthropic", "ollama", "openrouter", "custom"]),
|
|
194
|
+
model: z.string().min(1).max(128).optional(),
|
|
195
|
+
apiKeyEnv: z.string().min(0).max(128).optional(),
|
|
196
|
+
baseUrl: z.string().url().optional(),
|
|
197
|
+
options: z.record(z.string(), z.unknown()).optional()
|
|
198
|
+
}).strict().optional(),
|
|
199
|
+
chat: z.object({
|
|
200
|
+
enabled: z.boolean().optional(),
|
|
201
|
+
sources: z.array(z.enum(["agent", "human", "federation"])).max(8).optional(),
|
|
202
|
+
handoffFirst: z.boolean().optional()
|
|
203
|
+
}).strict().optional(),
|
|
204
|
+
retriever: z.object({
|
|
205
|
+
enabled: z.boolean().optional(),
|
|
206
|
+
mode: z.enum(["local", "remote", "bm25", "agentskit-rag"]).optional(),
|
|
207
|
+
embedModel: z.string().min(1).max(128).optional(),
|
|
208
|
+
chunkSize: z.number().int().min(128).max(16384).optional(),
|
|
209
|
+
options: z.record(z.string(), z.unknown()).optional()
|
|
210
|
+
}).strict().optional(),
|
|
211
|
+
memory: z.object({
|
|
212
|
+
enabled: z.boolean().optional(),
|
|
213
|
+
adapters: z.array(z.enum(["playbook-memory", "cursor-rules", "session-export", "bootstrap-delta"])).max(8).optional(),
|
|
214
|
+
ingestDir: z.string().min(1).max(512).optional(),
|
|
215
|
+
classify: z.boolean().optional(),
|
|
216
|
+
promote: z.object({
|
|
217
|
+
enabled: z.boolean().optional(),
|
|
218
|
+
targets: z.array(z.enum(["agent", "human", "agents-md"])).max(8).optional(),
|
|
219
|
+
requireApproval: z.boolean().optional()
|
|
220
|
+
}).strict().optional()
|
|
221
|
+
}).strict().optional(),
|
|
222
|
+
runtime: z.enum(["agentskit", "custom"]).optional(),
|
|
223
|
+
runtimeModule: z.string().min(1).max(512).optional()
|
|
224
|
+
}).strict();
|
|
225
|
+
var FederationConfigSchema = z.object({
|
|
226
|
+
enabled: z.boolean().optional(),
|
|
227
|
+
sources: z.array(
|
|
228
|
+
z.object({
|
|
229
|
+
id: z.string().min(1).max(64),
|
|
230
|
+
llmsTxt: z.string().min(1).max(512).optional(),
|
|
231
|
+
rawBaseUrl: z.string().url().optional(),
|
|
232
|
+
includeInRetriever: z.boolean().optional(),
|
|
233
|
+
includeInChat: z.boolean().optional()
|
|
234
|
+
}).strict()
|
|
235
|
+
).max(16).optional()
|
|
236
|
+
}).strict();
|
|
237
|
+
var DocBridgeConfigV1Schema = z.object({
|
|
238
|
+
schemaVersion: z.literal(CONFIG_SCHEMA_VERSION),
|
|
239
|
+
project: z.object({
|
|
240
|
+
name: z.string().min(1).max(128).optional(),
|
|
241
|
+
root: z.string().min(1).max(512).optional()
|
|
242
|
+
}).strict().optional(),
|
|
243
|
+
corpus: z.object({
|
|
244
|
+
agent: AgentCorpusConfigSchema,
|
|
245
|
+
human: z.union([HumanCorpusConfigSchema, z.array(HumanCorpusConfigSchema).max(8)]).optional()
|
|
246
|
+
}).strict(),
|
|
247
|
+
index: IndexConfigSchema.optional(),
|
|
248
|
+
routing: RoutingConfigSchema.optional(),
|
|
249
|
+
gates: GatesConfigSchema.optional(),
|
|
250
|
+
surfaces: SurfacesConfigSchema.optional(),
|
|
251
|
+
intelligence: IntelligenceConfigSchema.optional(),
|
|
252
|
+
federation: FederationConfigSchema.optional()
|
|
253
|
+
}).strict();
|
|
254
|
+
|
|
255
|
+
// src/config/load-config.ts
|
|
256
|
+
var CONFIG_CANDIDATES = [
|
|
257
|
+
"doc-bridge.config.ts",
|
|
258
|
+
"doc-bridge.config.mts",
|
|
259
|
+
"doc-bridge.config.js",
|
|
260
|
+
"doc-bridge.config.mjs",
|
|
261
|
+
"doc-bridge.config.json",
|
|
262
|
+
"doc-bridge.config.yaml",
|
|
263
|
+
"doc-bridge.config.yml",
|
|
264
|
+
"package.json"
|
|
265
|
+
];
|
|
266
|
+
var ConfigNotFoundError = class extends Error {
|
|
267
|
+
constructor(cwd) {
|
|
268
|
+
super(
|
|
269
|
+
`No doc-bridge config found in ${cwd}. Run: ak-docs init \u2014 or pass --config <path>.`
|
|
270
|
+
);
|
|
271
|
+
this.cwd = cwd;
|
|
272
|
+
this.name = "ConfigNotFoundError";
|
|
273
|
+
}
|
|
274
|
+
cwd;
|
|
275
|
+
};
|
|
276
|
+
var findConfigPath = (cwd, explicitPath) => {
|
|
277
|
+
if (explicitPath) {
|
|
278
|
+
const abs = resolve(cwd, explicitPath);
|
|
279
|
+
return existsSync(abs) ? abs : null;
|
|
280
|
+
}
|
|
281
|
+
for (const name of CONFIG_CANDIDATES) {
|
|
282
|
+
const candidate = join(cwd, name);
|
|
283
|
+
if (!existsSync(candidate)) continue;
|
|
284
|
+
if (name !== "package.json") return candidate;
|
|
285
|
+
const pkg = parseJsonConfig(readFileSync(candidate, "utf8"), candidate);
|
|
286
|
+
if (pkg.docBridge) return candidate;
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
};
|
|
290
|
+
var parseJsonConfig = (raw, path) => {
|
|
291
|
+
try {
|
|
292
|
+
return JSON.parse(raw);
|
|
293
|
+
} catch (error) {
|
|
294
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
295
|
+
throw new Error(`Failed to parse JSON config at ${path}: ${message}`);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
var parseCodeConfig = (raw, path) => {
|
|
299
|
+
const code = raw.replace(/import\s+type\s+[\s\S]*?;?\n/g, "").replace(/import\s+\{\s*defineConfig\s*\}\s+from\s+['"]@agentskit\/doc-bridge(?:\/config)?['"];?\n?/g, "").replace(/\s+satisfies\s+[A-Za-z0-9_.$<>{}\[\],\s]+(?=\s*(?:;|\)|$))/g, "").replace(/export\s+default/, "module.exports.default =");
|
|
300
|
+
if (/\bimport\b/.test(code)) {
|
|
301
|
+
throw new Error(`Unsupported import in ${path}. Static config only supports defineConfig imports.`);
|
|
302
|
+
}
|
|
303
|
+
const sandbox = {
|
|
304
|
+
module: { exports: {} },
|
|
305
|
+
exports: {},
|
|
306
|
+
defineConfig: (config) => config
|
|
307
|
+
};
|
|
308
|
+
try {
|
|
309
|
+
vm.runInNewContext(code, sandbox, { timeout: 250, filename: path });
|
|
310
|
+
} catch (error) {
|
|
311
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
312
|
+
throw new Error(`Failed to load config at ${path}: ${message}`);
|
|
313
|
+
}
|
|
314
|
+
return sandbox.module.exports.default;
|
|
315
|
+
};
|
|
316
|
+
var parseConfig = (input) => {
|
|
317
|
+
const result = DocBridgeConfigV1Schema.safeParse(input);
|
|
318
|
+
if (result.success) return result.data;
|
|
319
|
+
throw new Error(
|
|
320
|
+
`Invalid doc-bridge config:
|
|
321
|
+
${result.error.issues.map(
|
|
322
|
+
(issue) => ` - ${issue.path.join(".") || "(root)"}: ${issue.message}`
|
|
323
|
+
).join("\n")}`
|
|
324
|
+
);
|
|
325
|
+
};
|
|
326
|
+
var loadConfig = (opts = {}) => {
|
|
327
|
+
const cwd = resolve(opts.cwd ?? process.cwd());
|
|
328
|
+
const path = findConfigPath(cwd, opts.explicitPath);
|
|
329
|
+
if (!path) throw new ConfigNotFoundError(cwd);
|
|
330
|
+
const raw = readFileSync(path, "utf8");
|
|
331
|
+
const ext = path.split(".").pop()?.toLowerCase();
|
|
332
|
+
if (ext === "yaml" || ext === "yml") {
|
|
333
|
+
throw new Error(`YAML config is not supported yet (${path}). Use doc-bridge.config.json for now.`);
|
|
334
|
+
}
|
|
335
|
+
const parsed = basename(path) === "package.json" ? parseJsonConfig(raw, path).docBridge : ext === "ts" || ext === "mts" || ext === "js" || ext === "mjs" ? parseCodeConfig(raw, path) : parseJsonConfig(raw, path);
|
|
336
|
+
const config = applyConfigDefaults(parseConfig(parsed));
|
|
337
|
+
return { config, path };
|
|
338
|
+
};
|
|
339
|
+
var projectRootFromConfigPath = (configFilePath, projectRootField) => {
|
|
340
|
+
const configDir = dirname(resolve(configFilePath));
|
|
341
|
+
if (projectRootField) return resolve(configDir, projectRootField);
|
|
342
|
+
return configDir;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// src/index-builder/build-index.ts
|
|
346
|
+
import { mkdirSync, readFileSync as readFileSync7, writeFileSync } from "fs";
|
|
347
|
+
import { dirname as dirname3, join as join9 } from "path";
|
|
348
|
+
|
|
349
|
+
// src/lib/paths.ts
|
|
350
|
+
import { resolve as resolve2 } from "path";
|
|
351
|
+
var toPosix = (value) => value.split("\\").join("/");
|
|
352
|
+
|
|
353
|
+
// src/index-builder/scan-corpus.ts
|
|
354
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
355
|
+
import { join as join3 } from "path";
|
|
356
|
+
|
|
357
|
+
// src/lib/markdown.ts
|
|
358
|
+
var parseFrontmatter = (markdown) => {
|
|
359
|
+
const normalized = markdown.replace(/^\uFEFF/, "");
|
|
360
|
+
if (!normalized.startsWith("---\n") && !normalized.startsWith("---\r\n")) {
|
|
361
|
+
return { data: {}, body: markdown };
|
|
362
|
+
}
|
|
363
|
+
const endMatch = /\r?\n---\r?\n/.exec(normalized.slice(3));
|
|
364
|
+
if (!endMatch || endMatch.index === void 0) {
|
|
365
|
+
return { data: {}, body: markdown };
|
|
366
|
+
}
|
|
367
|
+
const block = normalized.slice(4, 3 + endMatch.index);
|
|
368
|
+
const body = normalized.slice(3 + endMatch.index + endMatch[0].length);
|
|
369
|
+
const data = {};
|
|
370
|
+
for (const rawLine of block.split(/\r?\n/)) {
|
|
371
|
+
const line = rawLine.trim();
|
|
372
|
+
if (!line || line.startsWith("#")) continue;
|
|
373
|
+
const m = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
|
|
374
|
+
if (!m?.[1]) continue;
|
|
375
|
+
const key = m[1];
|
|
376
|
+
const raw = (m[2] ?? "").trim();
|
|
377
|
+
if (raw === "true") data[key] = true;
|
|
378
|
+
else if (raw === "false") data[key] = false;
|
|
379
|
+
else if (raw.startsWith("[") && raw.endsWith("]")) {
|
|
380
|
+
data[key] = raw.slice(1, -1).split(",").map((part) => part.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
381
|
+
} else {
|
|
382
|
+
data[key] = raw.replace(/^['"]|['"]$/g, "");
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return { data, body };
|
|
386
|
+
};
|
|
387
|
+
var frontmatterString = (data, key) => {
|
|
388
|
+
const value = data[key];
|
|
389
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
390
|
+
};
|
|
391
|
+
var frontmatterStringList = (data, key) => {
|
|
392
|
+
const value = data[key];
|
|
393
|
+
if (Array.isArray(value)) return value.filter((item) => typeof item === "string");
|
|
394
|
+
if (typeof value === "string" && value.length > 0) return [value];
|
|
395
|
+
return void 0;
|
|
396
|
+
};
|
|
397
|
+
var firstHeading = (markdown) => {
|
|
398
|
+
const { body } = parseFrontmatter(markdown);
|
|
399
|
+
for (const line of body.split("\n")) {
|
|
400
|
+
const m = /^#\s+(.+)$/.exec(line.trim());
|
|
401
|
+
if (m?.[1]) return m[1].trim();
|
|
402
|
+
}
|
|
403
|
+
return void 0;
|
|
404
|
+
};
|
|
405
|
+
var firstParagraph = (markdown) => {
|
|
406
|
+
const { body } = parseFrontmatter(markdown);
|
|
407
|
+
const lines = body.split("\n");
|
|
408
|
+
const buf = [];
|
|
409
|
+
for (const line of lines) {
|
|
410
|
+
const t = line.trim();
|
|
411
|
+
if (!t) {
|
|
412
|
+
if (buf.length) break;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (t.startsWith("#")) continue;
|
|
416
|
+
if (t.startsWith("---")) continue;
|
|
417
|
+
buf.push(t);
|
|
418
|
+
if (buf.join(" ").length > 40) break;
|
|
419
|
+
}
|
|
420
|
+
const text = buf.join(" ").trim();
|
|
421
|
+
return text || void 0;
|
|
422
|
+
};
|
|
423
|
+
var slugFromPath = (relPath) => {
|
|
424
|
+
const base = relPath.replace(/\.mdx?$/, "");
|
|
425
|
+
const parts = base.split("/");
|
|
426
|
+
return parts[parts.length - 1] ?? base;
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// src/lib/walk.ts
|
|
430
|
+
import { readdirSync, statSync } from "fs";
|
|
431
|
+
import { join as join2 } from "path";
|
|
432
|
+
var DEFAULT_SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "coverage", ".doc-bridge"]);
|
|
433
|
+
var walkFiles = (root, opts) => {
|
|
434
|
+
const extensions = opts?.extensions ?? [".md"];
|
|
435
|
+
const skip = opts?.skipDirs ?? DEFAULT_SKIP;
|
|
436
|
+
const out = [];
|
|
437
|
+
const visit = (dir) => {
|
|
438
|
+
let entries = [];
|
|
439
|
+
try {
|
|
440
|
+
entries = readdirSync(dir);
|
|
441
|
+
} catch {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
for (const name of entries) {
|
|
445
|
+
const abs = join2(dir, name);
|
|
446
|
+
let st;
|
|
447
|
+
try {
|
|
448
|
+
st = statSync(abs);
|
|
449
|
+
} catch {
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (st.isDirectory()) {
|
|
453
|
+
if (skip.has(name)) continue;
|
|
454
|
+
visit(abs);
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (!st.isFile()) continue;
|
|
458
|
+
if (extensions.some((ext) => name.endsWith(ext))) {
|
|
459
|
+
out.push(toPosix(abs));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
visit(root);
|
|
464
|
+
return out.sort();
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
// src/index-builder/scan-corpus.ts
|
|
468
|
+
var scanAgentCorpus = (root, config) => {
|
|
469
|
+
const agentRoot = join3(root, config.corpus.agent.root);
|
|
470
|
+
const files = walkFiles(agentRoot, { extensions: [".md", ".mdx"] });
|
|
471
|
+
const corpusRelRoot = toPosix(config.corpus.agent.root);
|
|
472
|
+
return files.map((abs) => {
|
|
473
|
+
const relToCorpus = toPosix(abs.replace(`${toPosix(agentRoot)}/`, ""));
|
|
474
|
+
const relPath = `${corpusRelRoot}/${relToCorpus}`;
|
|
475
|
+
const raw = readFileSync2(abs, "utf8");
|
|
476
|
+
const { data: frontmatter } = parseFrontmatter(raw);
|
|
477
|
+
const id = frontmatterString(frontmatter, "id") ?? frontmatterString(frontmatter, "package") ?? slugFromPath(relToCorpus);
|
|
478
|
+
const title = firstHeading(raw) ?? id;
|
|
479
|
+
const description = firstParagraph(raw);
|
|
480
|
+
return {
|
|
481
|
+
id,
|
|
482
|
+
type: "agent-doc",
|
|
483
|
+
title,
|
|
484
|
+
path: relPath,
|
|
485
|
+
absPath: abs,
|
|
486
|
+
relPath,
|
|
487
|
+
frontmatter,
|
|
488
|
+
...description ? { description } : {}
|
|
489
|
+
};
|
|
490
|
+
});
|
|
491
|
+
};
|
|
492
|
+
var guessAgentDocForPackage = (corpus, packageId) => {
|
|
493
|
+
const exact = corpus.find(
|
|
494
|
+
(doc) => frontmatterString(doc.frontmatter, "package") === packageId || doc.relPath.endsWith(`/packages/${packageId}.md`) || doc.relPath.endsWith(`/${packageId}.md`) || doc.id === packageId
|
|
495
|
+
);
|
|
496
|
+
return exact?.path;
|
|
497
|
+
};
|
|
498
|
+
var ownershipFromFrontmatter = (corpus) => {
|
|
499
|
+
const out = [];
|
|
500
|
+
for (const doc of corpus) {
|
|
501
|
+
const id = frontmatterString(doc.frontmatter, "package") ?? (frontmatterString(doc.frontmatter, "type") === "package" ? frontmatterString(doc.frontmatter, "id") ?? doc.id : void 0);
|
|
502
|
+
const path = frontmatterString(doc.frontmatter, "editRoot") ?? frontmatterString(doc.frontmatter, "path");
|
|
503
|
+
if (!id || !path) continue;
|
|
504
|
+
const purpose = frontmatterString(doc.frontmatter, "purpose") ?? doc.description;
|
|
505
|
+
const checks = frontmatterStringList(doc.frontmatter, "checks");
|
|
506
|
+
const humanDoc = frontmatterString(doc.frontmatter, "humanDoc");
|
|
507
|
+
out.push({
|
|
508
|
+
id,
|
|
509
|
+
path,
|
|
510
|
+
agentDoc: doc.path,
|
|
511
|
+
...purpose ? { purpose } : {},
|
|
512
|
+
...checks ? { checks } : {},
|
|
513
|
+
...humanDoc ? { humanDoc } : {}
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
return out;
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
// src/index-builder/build-handoffs.ts
|
|
520
|
+
var defaultChecks = (config) => {
|
|
521
|
+
const preset = config.gates?.preset ?? "minimal";
|
|
522
|
+
if (preset === "strict" || preset === "standard") return ["npm test", "npm run lint"];
|
|
523
|
+
return ["npm test"];
|
|
524
|
+
};
|
|
525
|
+
var collectPackages = (config, discovered, corpus) => {
|
|
526
|
+
const byId = /* @__PURE__ */ new Map();
|
|
527
|
+
for (const [id, entry] of Object.entries(config.routing?.options?.ownership ?? {})) {
|
|
528
|
+
byId.set(id, { id, path: entry.path });
|
|
529
|
+
}
|
|
530
|
+
for (const pkg of discovered) {
|
|
531
|
+
const existing = byId.get(pkg.id);
|
|
532
|
+
if (!existing) {
|
|
533
|
+
byId.set(pkg.id, pkg);
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
byId.set(pkg.id, {
|
|
537
|
+
id: pkg.id,
|
|
538
|
+
path: existing.path || pkg.path,
|
|
539
|
+
...pkg.name ? { name: pkg.name } : existing.name ? { name: existing.name } : {}
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
for (const seed of ownershipFromFrontmatter(corpus)) {
|
|
543
|
+
const existing = byId.get(seed.id);
|
|
544
|
+
if (!existing) {
|
|
545
|
+
byId.set(seed.id, { id: seed.id, path: seed.path });
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (!existing.path) {
|
|
549
|
+
byId.set(seed.id, { ...existing, path: seed.path });
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
553
|
+
};
|
|
554
|
+
var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}) => {
|
|
555
|
+
const ownership = {};
|
|
556
|
+
const handoffs = {};
|
|
557
|
+
const checks = defaultChecks(config);
|
|
558
|
+
const fmSeeds = new Map(ownershipFromFrontmatter(corpus).map((seed) => [seed.id, seed]));
|
|
559
|
+
for (const pkg of packages) {
|
|
560
|
+
const override = config.routing?.options?.ownership?.[pkg.id];
|
|
561
|
+
const fm = fmSeeds.get(pkg.id);
|
|
562
|
+
const agentDoc = override?.agentDoc ?? fm?.agentDoc ?? guessAgentDocForPackage(corpus, pkg.id) ?? config.corpus.agent.index;
|
|
563
|
+
const startHere = agentDoc ?? "";
|
|
564
|
+
const purpose = override?.purpose ?? fm?.purpose;
|
|
565
|
+
const humanDoc = override?.humanDoc ?? fm?.humanDoc ?? humanDocs[pkg.id];
|
|
566
|
+
const record = {
|
|
567
|
+
id: pkg.id,
|
|
568
|
+
path: override?.path ?? fm?.path ?? pkg.path,
|
|
569
|
+
checks: [...override?.checks ?? fm?.checks ?? checks],
|
|
570
|
+
...override?.group ? { group: override.group } : {},
|
|
571
|
+
...override?.layer ? { layer: override.layer } : {},
|
|
572
|
+
...purpose ? { purpose } : {},
|
|
573
|
+
...humanDoc ? { humanDoc } : {},
|
|
574
|
+
...agentDoc ? { agentDoc } : {}
|
|
575
|
+
};
|
|
576
|
+
ownership[pkg.id] = record;
|
|
577
|
+
handoffs[pkg.id] = {
|
|
578
|
+
type: "agent-handoff",
|
|
579
|
+
schemaVersion: 1,
|
|
580
|
+
source: indexOutFile,
|
|
581
|
+
target: {
|
|
582
|
+
type: "package",
|
|
583
|
+
id: pkg.id,
|
|
584
|
+
path: record.path,
|
|
585
|
+
...record.group ? { group: record.group } : {},
|
|
586
|
+
...record.layer ? { layer: record.layer } : {}
|
|
587
|
+
},
|
|
588
|
+
startHere,
|
|
589
|
+
readBeforeEditing: [agentDoc, "AGENTS.md"].filter(
|
|
590
|
+
(v, i, a) => Boolean(v) && a.indexOf(v) === i
|
|
591
|
+
),
|
|
592
|
+
editRoots: [record.path],
|
|
593
|
+
checks: [...record.checks],
|
|
594
|
+
...record.humanDoc ? { humanDoc: record.humanDoc } : {},
|
|
595
|
+
notes: record.purpose ? [record.purpose] : []
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
const intents = {};
|
|
599
|
+
for (const intent of config.routing?.options?.intents ?? []) {
|
|
600
|
+
intents[intent.id] = { id: intent.id, title: intent.title, paths: intent.paths };
|
|
601
|
+
}
|
|
602
|
+
const changes = {};
|
|
603
|
+
for (const change of config.routing?.options?.changes ?? []) {
|
|
604
|
+
changes[change.id] = {
|
|
605
|
+
id: change.id,
|
|
606
|
+
title: change.title,
|
|
607
|
+
startHere: change.startHere,
|
|
608
|
+
...change.relatedPackages ? { relatedPackages: change.relatedPackages } : {}
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
lookup: {
|
|
613
|
+
packages: packages.map((p) => p.id),
|
|
614
|
+
ownership,
|
|
615
|
+
intents,
|
|
616
|
+
changes
|
|
617
|
+
},
|
|
618
|
+
handoffs
|
|
619
|
+
};
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
// src/version.ts
|
|
623
|
+
var PACKAGE_VERSION = "0.1.0-alpha.1";
|
|
624
|
+
|
|
625
|
+
// src/index-builder/capabilities.ts
|
|
626
|
+
var renderCapabilitiesJson = (config, index, paths) => {
|
|
627
|
+
const payload = {
|
|
628
|
+
schemaVersion: 1,
|
|
629
|
+
type: "doc-bridge-capabilities",
|
|
630
|
+
package: "@agentskit/doc-bridge",
|
|
631
|
+
version: PACKAGE_VERSION,
|
|
632
|
+
project: index.project,
|
|
633
|
+
contentHash: index.contentHash,
|
|
634
|
+
artifacts: {
|
|
635
|
+
index: paths.index,
|
|
636
|
+
...paths.llmsTxt ? { llmsTxt: paths.llmsTxt } : {}
|
|
637
|
+
},
|
|
638
|
+
capabilities: [
|
|
639
|
+
"index",
|
|
640
|
+
"query",
|
|
641
|
+
"search",
|
|
642
|
+
"agent-handoff",
|
|
643
|
+
"gates",
|
|
644
|
+
...config.surfaces?.mcp?.enabled === false ? [] : ["mcp"],
|
|
645
|
+
...config.corpus.human ? ["human-docs"] : []
|
|
646
|
+
],
|
|
647
|
+
surfaces: {
|
|
648
|
+
cli: config.surfaces?.cli,
|
|
649
|
+
mcp: config.surfaces?.mcp
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
return `${JSON.stringify(payload, null, 2)}
|
|
653
|
+
`;
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
// src/index-builder/content-hash.ts
|
|
657
|
+
import { createHash } from "crypto";
|
|
658
|
+
var sortValue = (value) => {
|
|
659
|
+
if (Array.isArray(value)) return value.map(sortValue);
|
|
660
|
+
if (value && typeof value === "object") {
|
|
661
|
+
const record = value;
|
|
662
|
+
return Object.fromEntries(
|
|
663
|
+
Object.keys(record).sort().map((key) => [key, sortValue(record[key])])
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
return value;
|
|
667
|
+
};
|
|
668
|
+
var sha256NormalizedV1 = (payload) => {
|
|
669
|
+
const normalized = JSON.stringify(sortValue(payload));
|
|
670
|
+
return createHash("sha256").update(normalized, "utf8").digest("hex");
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
// src/index-builder/llms-txt.ts
|
|
674
|
+
var renderLlmsTxt = (config, knowledge, projectName2) => {
|
|
675
|
+
const preamble = config.index?.llmsTxt?.preamble ?? `# ${projectName2}
|
|
676
|
+
|
|
677
|
+
> Agent-readable documentation index generated by ak-docs (@agentskit/doc-bridge).
|
|
678
|
+
`;
|
|
679
|
+
const lines = knowledge.slice(0, 500).map((entry) => {
|
|
680
|
+
const desc = entry.description ? `: ${entry.description}` : "";
|
|
681
|
+
return `- [${entry.title}](${entry.path})${desc}`;
|
|
682
|
+
});
|
|
683
|
+
return `${preamble.trim()}
|
|
684
|
+
|
|
685
|
+
## Knowledge
|
|
686
|
+
|
|
687
|
+
${lines.join("\n")}
|
|
688
|
+
`;
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
// src/index-builder/human-adapters/docusaurus.ts
|
|
692
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
|
|
693
|
+
import { join as join5 } from "path";
|
|
694
|
+
import vm2 from "vm";
|
|
695
|
+
|
|
696
|
+
// src/index-builder/human-adapters/core.ts
|
|
697
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
698
|
+
import { join as join4 } from "path";
|
|
699
|
+
var optionString = (options, keys) => {
|
|
700
|
+
for (const key of keys) {
|
|
701
|
+
const value = options?.[key];
|
|
702
|
+
if (typeof value === "string" && value) return value;
|
|
703
|
+
}
|
|
704
|
+
return void 0;
|
|
705
|
+
};
|
|
706
|
+
var parseFrontmatter2 = (raw) => {
|
|
707
|
+
if (!raw.startsWith("---\n")) return {};
|
|
708
|
+
const end = raw.indexOf("\n---", 4);
|
|
709
|
+
if (end === -1) return {};
|
|
710
|
+
const out = {};
|
|
711
|
+
for (const line of raw.slice(4, end).split("\n")) {
|
|
712
|
+
const match = /^([A-Za-z0-9_-]+):\s*(.+?)\s*$/.exec(line);
|
|
713
|
+
if (match?.[1] && match[2]) out[match[1]] = match[2].replace(/^['"]|['"]$/g, "");
|
|
714
|
+
}
|
|
715
|
+
return out;
|
|
716
|
+
};
|
|
717
|
+
var docId = (relToHumanRoot, raw) => {
|
|
718
|
+
const meta = parseFrontmatter2(raw);
|
|
719
|
+
return meta.package ?? meta.module ?? meta.id ?? slugFromPath(relToHumanRoot);
|
|
720
|
+
};
|
|
721
|
+
var routeSlug = (relToHumanRoot, opts) => relToHumanRoot.split("/").filter((part) => !opts?.stripGroups || !/^\(.+\)$/.test(part)).join("/").replace(/(?:^|\/)index\.mdx?$/, "").replace(/\.mdx?$/, "");
|
|
722
|
+
var humanUrl = (slug, urlPrefix) => {
|
|
723
|
+
if (typeof urlPrefix !== "string" || !urlPrefix) return slug;
|
|
724
|
+
const prefix = urlPrefix.replace(/\/$/, "");
|
|
725
|
+
return slug ? `${prefix}/${slug.replace(/^\//, "")}` : prefix;
|
|
726
|
+
};
|
|
727
|
+
var scanMarkdownDocs = (root, humanRoot, options) => {
|
|
728
|
+
const out = [];
|
|
729
|
+
const absRoot = join4(root, humanRoot);
|
|
730
|
+
for (const abs of walkFiles(absRoot, { extensions: [".md", ".mdx"] })) {
|
|
731
|
+
const relToHumanRoot = toPosix(abs.replace(`${toPosix(absRoot)}/`, ""));
|
|
732
|
+
const raw = readFileSync3(abs, "utf8");
|
|
733
|
+
if (options?.includeRelPath && !options.includeRelPath(relToHumanRoot, raw)) continue;
|
|
734
|
+
out.push({
|
|
735
|
+
id: options?.idForDoc?.(relToHumanRoot, raw) ?? docId(relToHumanRoot, raw),
|
|
736
|
+
url: humanUrl(
|
|
737
|
+
options?.slugForDoc?.(relToHumanRoot, raw) ?? routeSlug(relToHumanRoot, options?.stripGroups ? { stripGroups: true } : void 0),
|
|
738
|
+
options?.urlPrefix
|
|
739
|
+
),
|
|
740
|
+
path: abs
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
return out;
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
// src/index-builder/human-adapters/docusaurus.ts
|
|
747
|
+
var docusaurusFileSlug = (relPath) => {
|
|
748
|
+
const parts = relPath.split("/");
|
|
749
|
+
const file = parts.at(-1) ?? "";
|
|
750
|
+
const base = file.replace(/\.mdx?$/, "");
|
|
751
|
+
const parent = parts.at(-2);
|
|
752
|
+
if (/^(index|readme)$/i.test(base) || base === parent) return parts.slice(0, -1).join("/");
|
|
753
|
+
return routeSlug(relPath);
|
|
754
|
+
};
|
|
755
|
+
var docusaurusSlug = (relPath, raw) => {
|
|
756
|
+
const slug = parseFrontmatter2(raw).slug;
|
|
757
|
+
if (!slug) return docusaurusFileSlug(relPath);
|
|
758
|
+
return slug.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "");
|
|
759
|
+
};
|
|
760
|
+
var docusaurusSidebarId = (relPath, raw) => {
|
|
761
|
+
const id = parseFrontmatter2(raw).id;
|
|
762
|
+
if (!id) return routeSlug(relPath);
|
|
763
|
+
const parts = relPath.split("/").slice(0, -1);
|
|
764
|
+
return [...parts, id].filter(Boolean).join("/");
|
|
765
|
+
};
|
|
766
|
+
var docusaurusRecordId = (relPath, raw) => {
|
|
767
|
+
const frontmatter = parseFrontmatter2(raw);
|
|
768
|
+
return frontmatter.package ?? frontmatter.module ?? docusaurusSidebarId(relPath, raw);
|
|
769
|
+
};
|
|
770
|
+
var sidebarsValue = (file) => {
|
|
771
|
+
if (!existsSync2(file)) return void 0;
|
|
772
|
+
const raw = readFileSync4(file, "utf8").replace(/import\s+type\s+[\s\S]*?;?\n/g, "").replace(/:\s*[A-Za-z0-9_.$<>{}\[\],\s]+(?=\s*=)/g, "").replace(/\s+satisfies\s+[A-Za-z0-9_.$<>{}\[\],\s]+(?=\s*(?:;|\n|$))/g, "").replace(/export\s+default/, "module.exports =");
|
|
773
|
+
const sandbox = { module: { exports: {} }, exports: {} };
|
|
774
|
+
vm2.runInNewContext(raw, sandbox, { timeout: 250 });
|
|
775
|
+
return sandbox.module.exports;
|
|
776
|
+
};
|
|
777
|
+
var visitSidebar = (value, filter) => {
|
|
778
|
+
if (typeof value === "string") {
|
|
779
|
+
filter.ids.add(value);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
if (Array.isArray(value)) {
|
|
783
|
+
for (const item2 of value) visitSidebar(item2, filter);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (!value || typeof value !== "object") return;
|
|
787
|
+
const item = value;
|
|
788
|
+
if ((item.type === "doc" || item.type === "ref") && typeof item.id === "string") {
|
|
789
|
+
filter.ids.add(item.id);
|
|
790
|
+
}
|
|
791
|
+
if (item.type === "autogenerated" && typeof item.dirName === "string") {
|
|
792
|
+
filter.autogenDirs.push(item.dirName);
|
|
793
|
+
}
|
|
794
|
+
if (Array.isArray(item.items)) visitSidebar(item.items, filter);
|
|
795
|
+
if (item.link && typeof item.link === "object") visitSidebar(item.link, filter);
|
|
796
|
+
for (const child of Object.values(item)) {
|
|
797
|
+
if (Array.isArray(child)) visitSidebar(child, filter);
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
var readSidebars = (root, sidebarsFile) => {
|
|
801
|
+
if (!sidebarsFile) return { enabled: false, ids: /* @__PURE__ */ new Set(), autogenDirs: [] };
|
|
802
|
+
const value = sidebarsValue(join5(root, sidebarsFile));
|
|
803
|
+
const filter = { ids: /* @__PURE__ */ new Set(), autogenDirs: [] };
|
|
804
|
+
visitSidebar(value, filter);
|
|
805
|
+
return { enabled: true, ...filter };
|
|
806
|
+
};
|
|
807
|
+
var isIncludedBySidebar = (filter, id) => {
|
|
808
|
+
if (!filter.enabled) return true;
|
|
809
|
+
if (filter.ids.has(id)) return true;
|
|
810
|
+
return filter.autogenDirs.some((dir) => {
|
|
811
|
+
const prefix = dir === "." ? "" : dir.replace(/\/$/, "");
|
|
812
|
+
return prefix ? id === prefix || id.startsWith(`${prefix}/`) : true;
|
|
813
|
+
});
|
|
814
|
+
};
|
|
815
|
+
var docusaurusAdapter = {
|
|
816
|
+
plugin: "docusaurus",
|
|
817
|
+
scan: ({ root, config }) => {
|
|
818
|
+
const docsDir = optionString(config.options, ["docsDir", "root"]);
|
|
819
|
+
if (!docsDir) return [];
|
|
820
|
+
const sidebarFilter = readSidebars(root, optionString(config.options, ["sidebarsFile"]));
|
|
821
|
+
return scanMarkdownDocs(root, docsDir, {
|
|
822
|
+
includeRelPath: (relPath, raw) => isIncludedBySidebar(sidebarFilter, docusaurusSidebarId(relPath, raw)),
|
|
823
|
+
idForDoc: docusaurusRecordId,
|
|
824
|
+
slugForDoc: docusaurusSlug,
|
|
825
|
+
urlPrefix: config.options?.urlPrefix
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
// src/index-builder/human-adapters/fumadocs.ts
|
|
831
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
832
|
+
import { join as join6 } from "path";
|
|
833
|
+
var readMetaPages = (dir) => {
|
|
834
|
+
const file = join6(dir, "meta.json");
|
|
835
|
+
if (!existsSync3(file)) return void 0;
|
|
836
|
+
try {
|
|
837
|
+
const meta = JSON.parse(readFileSync5(file, "utf8"));
|
|
838
|
+
return Array.isArray(meta.pages) ? meta.pages.filter((page) => typeof page === "string") : void 0;
|
|
839
|
+
} catch {
|
|
840
|
+
return void 0;
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
var pageKey = (part) => part.replace(/\.mdx?$/, "");
|
|
844
|
+
var isDotFile = (relPath) => {
|
|
845
|
+
const file = relPath.split("/").at(-1) ?? "";
|
|
846
|
+
return file.startsWith(".");
|
|
847
|
+
};
|
|
848
|
+
var isListedByMeta = (contentRoot, relPath) => {
|
|
849
|
+
if (isDotFile(relPath)) return false;
|
|
850
|
+
const parts = relPath.split("/");
|
|
851
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
852
|
+
const dir = join6(contentRoot, ...parts.slice(0, i));
|
|
853
|
+
const pages = readMetaPages(dir);
|
|
854
|
+
if (!pages?.length || pages.includes("...")) continue;
|
|
855
|
+
const key = pageKey(parts[i] ?? "");
|
|
856
|
+
if (!pages.includes(key) && !pages.includes(`./${key}`)) return false;
|
|
857
|
+
}
|
|
858
|
+
return true;
|
|
859
|
+
};
|
|
860
|
+
var fumadocsAdapter = {
|
|
861
|
+
plugin: "fumadocs",
|
|
862
|
+
scan: ({ root, config }) => {
|
|
863
|
+
const contentDir = optionString(config.options, ["contentDir", "root"]);
|
|
864
|
+
if (!contentDir) return [];
|
|
865
|
+
const contentRoot = join6(root, contentDir);
|
|
866
|
+
return scanMarkdownDocs(root, contentDir, {
|
|
867
|
+
includeRelPath: (relPath) => isListedByMeta(contentRoot, relPath),
|
|
868
|
+
urlPrefix: config.options?.urlPrefix,
|
|
869
|
+
stripGroups: true
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
// src/index-builder/human-adapters/plain-markdown.ts
|
|
875
|
+
var plainMarkdownAdapter = {
|
|
876
|
+
plugin: "plain-markdown",
|
|
877
|
+
scan: ({ root, config }) => {
|
|
878
|
+
const humanRoot = optionString(config.options, ["root"]) ?? "docs";
|
|
879
|
+
return scanMarkdownDocs(root, humanRoot, { urlPrefix: config.options?.urlPrefix });
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
// src/index-builder/human-adapters/index.ts
|
|
884
|
+
var ADAPTERS = [
|
|
885
|
+
plainMarkdownAdapter,
|
|
886
|
+
fumadocsAdapter,
|
|
887
|
+
docusaurusAdapter
|
|
888
|
+
];
|
|
889
|
+
var humanConfigs = (config) => {
|
|
890
|
+
const human = config.corpus.human;
|
|
891
|
+
if (!human) return [];
|
|
892
|
+
return Array.isArray(human) ? human : [human];
|
|
893
|
+
};
|
|
894
|
+
var scanHumanDocRecords = (root, config) => {
|
|
895
|
+
const out = [];
|
|
896
|
+
const seen = /* @__PURE__ */ new Set();
|
|
897
|
+
for (const human of humanConfigs(config)) {
|
|
898
|
+
const adapter = ADAPTERS.find((candidate) => candidate.plugin === human.plugin);
|
|
899
|
+
if (!adapter) continue;
|
|
900
|
+
for (const record of adapter.scan({ root, config: human })) {
|
|
901
|
+
if (seen.has(record.id)) continue;
|
|
902
|
+
seen.add(record.id);
|
|
903
|
+
out.push(record);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
return out;
|
|
907
|
+
};
|
|
908
|
+
var scanHumanDocs = (root, config) => Object.fromEntries(scanHumanDocRecords(root, config).map((doc) => [doc.id, doc.url]));
|
|
909
|
+
|
|
910
|
+
// src/index-builder/plugins/pnpm-monorepo.ts
|
|
911
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
912
|
+
import { basename as basename2, join as join8 } from "path";
|
|
913
|
+
|
|
914
|
+
// src/lib/glob-expand.ts
|
|
915
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
916
|
+
import { join as join7 } from "path";
|
|
917
|
+
var expandWorkspaceGlobs = (root, patterns) => {
|
|
918
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
919
|
+
for (const pattern of patterns) {
|
|
920
|
+
const normalized = toPosix(pattern).replace(/\/$/, "");
|
|
921
|
+
if (!normalized.includes("*")) {
|
|
922
|
+
const abs = join7(root, normalized);
|
|
923
|
+
if (existsSync4(abs)) dirs.add(toPosix(abs));
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
const star = normalized.indexOf("*");
|
|
927
|
+
const base = join7(root, normalized.slice(0, star).replace(/\/$/, ""));
|
|
928
|
+
if (!existsSync4(base)) continue;
|
|
929
|
+
for (const name of readdirSync2(base)) {
|
|
930
|
+
const abs = join7(base, name);
|
|
931
|
+
try {
|
|
932
|
+
if (statSync2(abs).isDirectory()) dirs.add(toPosix(abs));
|
|
933
|
+
} catch {
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return [...dirs].sort();
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
// src/index-builder/plugins/pnpm-monorepo.ts
|
|
941
|
+
var parsePnpmWorkspace = (yaml) => {
|
|
942
|
+
const lines = yaml.split("\n");
|
|
943
|
+
const patterns = [];
|
|
944
|
+
let inPackages = false;
|
|
945
|
+
for (const line of lines) {
|
|
946
|
+
const trimmed = line.trim();
|
|
947
|
+
if (trimmed === "packages:") {
|
|
948
|
+
inPackages = true;
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
if (inPackages) {
|
|
952
|
+
if (trimmed.startsWith("- ")) {
|
|
953
|
+
patterns.push(trimmed.slice(2).trim().replace(/^['"]|['"]$/g, ""));
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
if (trimmed && !trimmed.startsWith("#")) inPackages = false;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return patterns;
|
|
960
|
+
};
|
|
961
|
+
var readPackageJson = (dir) => {
|
|
962
|
+
const file = join8(dir, "package.json");
|
|
963
|
+
if (!existsSync5(file)) return null;
|
|
964
|
+
try {
|
|
965
|
+
return JSON.parse(readFileSync6(file, "utf8"));
|
|
966
|
+
} catch {
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
var discoverPnpmPackages = (root, config) => {
|
|
971
|
+
const explicit = config.routing?.options?.packages;
|
|
972
|
+
let patterns = explicit;
|
|
973
|
+
if (!patterns?.length) {
|
|
974
|
+
const workspaceFile = join8(root, "pnpm-workspace.yaml");
|
|
975
|
+
if (existsSync5(workspaceFile)) {
|
|
976
|
+
patterns = parsePnpmWorkspace(readFileSync6(workspaceFile, "utf8"));
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
if (!patterns?.length) return [];
|
|
980
|
+
const dirs = expandWorkspaceGlobs(root, patterns);
|
|
981
|
+
const out = [];
|
|
982
|
+
for (const abs of dirs) {
|
|
983
|
+
const pkg = readPackageJson(abs);
|
|
984
|
+
if (!pkg) continue;
|
|
985
|
+
const rel = toPosix(abs.replace(`${toPosix(root)}/`, ""));
|
|
986
|
+
const folderId = basename2(abs);
|
|
987
|
+
const id = pkg.name?.startsWith("@") ? pkg.name.split("/").pop() ?? folderId : pkg.name ?? folderId;
|
|
988
|
+
out.push({ id, path: rel, ...pkg.name ? { name: pkg.name } : {} });
|
|
989
|
+
}
|
|
990
|
+
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
// src/index-builder/build-index.ts
|
|
994
|
+
var projectName = (root, config) => {
|
|
995
|
+
if (config.project?.name) return config.project.name;
|
|
996
|
+
try {
|
|
997
|
+
const pkg = JSON.parse(readFileSync7(join9(root, "package.json"), "utf8"));
|
|
998
|
+
if (pkg.name) return pkg.name;
|
|
999
|
+
} catch {
|
|
1000
|
+
}
|
|
1001
|
+
return toPosix(root.split("/").pop() ?? "project");
|
|
1002
|
+
};
|
|
1003
|
+
var existingGeneratedAt = (indexPath, contentHash) => {
|
|
1004
|
+
try {
|
|
1005
|
+
const index = JSON.parse(readFileSync7(indexPath, "utf8"));
|
|
1006
|
+
return index.contentHash === contentHash && typeof index.generatedAt === "string" ? index.generatedAt : void 0;
|
|
1007
|
+
} catch {
|
|
1008
|
+
return void 0;
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
var buildDocBridgeIndex = (opts) => {
|
|
1012
|
+
const root = opts.root ?? process.cwd();
|
|
1013
|
+
const config = opts.config;
|
|
1014
|
+
const write = opts.write ?? true;
|
|
1015
|
+
const outFile = config.index?.outFile ?? ".doc-bridge/index.json";
|
|
1016
|
+
const indexPath = join9(root, outFile);
|
|
1017
|
+
const corpus = scanAgentCorpus(root, config);
|
|
1018
|
+
const knowledge = corpus.map(({ absPath: _a, relPath: _r, frontmatter: _f, ...entry }) => entry);
|
|
1019
|
+
const shouldDiscover = config.routing?.plugin === "pnpm-monorepo" || Boolean(config.routing?.options?.packages?.length) || config.routing?.plugin === "npm-workspaces" || config.routing?.plugin === "yarn-workspaces";
|
|
1020
|
+
const discovered = shouldDiscover ? discoverPnpmPackages(root, config) : [];
|
|
1021
|
+
const packages = collectPackages(config, discovered, corpus);
|
|
1022
|
+
const humanDocs = scanHumanDocs(root, config);
|
|
1023
|
+
const { lookup, handoffs } = buildLookup(config, packages, corpus, outFile, humanDocs);
|
|
1024
|
+
const hashPayload = {
|
|
1025
|
+
schemaVersion: 1,
|
|
1026
|
+
knowledge,
|
|
1027
|
+
handoffs,
|
|
1028
|
+
lookup
|
|
1029
|
+
};
|
|
1030
|
+
const contentHash = sha256NormalizedV1(hashPayload);
|
|
1031
|
+
const index = {
|
|
1032
|
+
schemaVersion: 1,
|
|
1033
|
+
contentHash,
|
|
1034
|
+
contentHashAlgo: "sha256-normalized-v1",
|
|
1035
|
+
generatedAt: existingGeneratedAt(indexPath, contentHash) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1036
|
+
project: { name: projectName(root, config), root: "." },
|
|
1037
|
+
knowledge,
|
|
1038
|
+
handoffs,
|
|
1039
|
+
lookup
|
|
1040
|
+
};
|
|
1041
|
+
if (write) {
|
|
1042
|
+
mkdirSync(dirname3(indexPath), { recursive: true });
|
|
1043
|
+
writeFileSync(indexPath, `${JSON.stringify(index, null, 2)}
|
|
1044
|
+
`, "utf8");
|
|
1045
|
+
}
|
|
1046
|
+
let llmsTxtPath;
|
|
1047
|
+
let llmsTxtRelPath;
|
|
1048
|
+
if (config.index?.llmsTxt?.enabled !== false) {
|
|
1049
|
+
const llmsOut = config.index?.llmsTxt?.outFile ?? "llms.txt";
|
|
1050
|
+
llmsTxtRelPath = toPosix(llmsOut);
|
|
1051
|
+
llmsTxtPath = join9(root, llmsOut);
|
|
1052
|
+
if (write) {
|
|
1053
|
+
writeFileSync(
|
|
1054
|
+
llmsTxtPath,
|
|
1055
|
+
renderLlmsTxt(config, knowledge, index.project?.name ?? "project"),
|
|
1056
|
+
"utf8"
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
let capabilitiesPath;
|
|
1061
|
+
if (config.index?.capabilities?.enabled !== false) {
|
|
1062
|
+
const capabilitiesOut = config.index?.capabilities?.outFile ?? ".doc-bridge/capabilities.json";
|
|
1063
|
+
capabilitiesPath = join9(root, capabilitiesOut);
|
|
1064
|
+
if (write) {
|
|
1065
|
+
mkdirSync(dirname3(capabilitiesPath), { recursive: true });
|
|
1066
|
+
writeFileSync(
|
|
1067
|
+
capabilitiesPath,
|
|
1068
|
+
renderCapabilitiesJson(config, index, {
|
|
1069
|
+
index: toPosix(outFile),
|
|
1070
|
+
...llmsTxtRelPath ? { llmsTxt: llmsTxtRelPath } : {}
|
|
1071
|
+
}),
|
|
1072
|
+
"utf8"
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return {
|
|
1077
|
+
index,
|
|
1078
|
+
indexPath: toPosix(indexPath),
|
|
1079
|
+
...llmsTxtPath ? { llmsTxtPath: toPosix(llmsTxtPath) } : {},
|
|
1080
|
+
...capabilitiesPath ? { capabilitiesPath: toPosix(capabilitiesPath) } : {}
|
|
1081
|
+
};
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1084
|
+
// src/federation/llms.ts
|
|
1085
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
|
|
1086
|
+
import { resolve as resolve3 } from "path";
|
|
1087
|
+
|
|
1088
|
+
// src/query/search.ts
|
|
1089
|
+
var tokenize = (value) => value.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2);
|
|
1090
|
+
var searchIndex = (index, term, limit = 20) => {
|
|
1091
|
+
const tokens = tokenize(term);
|
|
1092
|
+
if (!tokens.length) return [];
|
|
1093
|
+
const matches = [];
|
|
1094
|
+
for (const entry of index.knowledge) {
|
|
1095
|
+
const hay = `${entry.id} ${entry.title} ${entry.description ?? ""} ${entry.path}`.toLowerCase();
|
|
1096
|
+
let score = 0;
|
|
1097
|
+
for (const token of tokens) {
|
|
1098
|
+
if (hay.includes(token)) score += token.length;
|
|
1099
|
+
}
|
|
1100
|
+
if (score > 0) {
|
|
1101
|
+
matches.push({
|
|
1102
|
+
type: "knowledge",
|
|
1103
|
+
id: entry.id,
|
|
1104
|
+
path: entry.path,
|
|
1105
|
+
...entry.description ? { summary: entry.description } : {},
|
|
1106
|
+
score
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
for (const [id, owner] of Object.entries(index.lookup?.ownership ?? {})) {
|
|
1111
|
+
const hay = `${id} ${owner.path} ${owner.purpose ?? ""} ${owner.group ?? ""}`.toLowerCase();
|
|
1112
|
+
let score = 0;
|
|
1113
|
+
for (const token of tokens) {
|
|
1114
|
+
if (hay.includes(token)) score += token.length * 2;
|
|
1115
|
+
}
|
|
1116
|
+
if (score > 0) {
|
|
1117
|
+
matches.push({
|
|
1118
|
+
type: "ownership",
|
|
1119
|
+
id,
|
|
1120
|
+
path: owner.agentDoc ?? owner.path,
|
|
1121
|
+
...owner.purpose ? { summary: owner.purpose } : {},
|
|
1122
|
+
score
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return matches.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
1127
|
+
};
|
|
1128
|
+
|
|
1129
|
+
// src/retriever/doc-bridge-retriever.ts
|
|
1130
|
+
var chunkKey = (property, type, id) => `${property}:${type}:${id}`;
|
|
1131
|
+
var retrieveDocBridgeChunks = (index, query, options = {}) => {
|
|
1132
|
+
const property = options.property ?? index.project?.name ?? "local";
|
|
1133
|
+
const limit = options.limit ?? 8;
|
|
1134
|
+
return searchIndex(index, query, limit).map((match) => {
|
|
1135
|
+
const knowledge = index.knowledge.find((entry) => entry.id === match.id && entry.path === match.path);
|
|
1136
|
+
const owner = index.lookup?.ownership?.[match.id];
|
|
1137
|
+
const type = match.type === "ownership" ? "ownership" : knowledge?.type ?? match.type;
|
|
1138
|
+
const summary = match.summary ?? owner?.purpose;
|
|
1139
|
+
return {
|
|
1140
|
+
chunkKey: chunkKey(property, type, match.id),
|
|
1141
|
+
property,
|
|
1142
|
+
type,
|
|
1143
|
+
id: match.id,
|
|
1144
|
+
path: match.path,
|
|
1145
|
+
...knowledge?.title ? { title: knowledge.title } : {},
|
|
1146
|
+
...summary ? { summary } : {},
|
|
1147
|
+
score: match.score
|
|
1148
|
+
};
|
|
1149
|
+
});
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
// src/federation/llms.ts
|
|
1153
|
+
var tokenize2 = (value) => value.toLowerCase().split(/[^a-z0-9]+/).filter((token) => token.length >= 2);
|
|
1154
|
+
var scoreText = (query, text) => {
|
|
1155
|
+
const hay = text.toLowerCase();
|
|
1156
|
+
return tokenize2(query).reduce((score, token) => score + (hay.includes(token) ? token.length : 0), 0);
|
|
1157
|
+
};
|
|
1158
|
+
var defaultFetchText = async (url) => {
|
|
1159
|
+
const signal = AbortSignal.timeout(5e3);
|
|
1160
|
+
const res = await fetch(url, { signal });
|
|
1161
|
+
if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.status}`);
|
|
1162
|
+
return res.text();
|
|
1163
|
+
};
|
|
1164
|
+
var sourceText = async (root, source, fetchText) => {
|
|
1165
|
+
if (/^https?:\/\//.test(source)) return fetchText(source);
|
|
1166
|
+
const path = resolve3(root, source);
|
|
1167
|
+
if (!existsSync6(path)) throw new Error(`Federation source not found: ${source}`);
|
|
1168
|
+
return readFileSync8(path, "utf8");
|
|
1169
|
+
};
|
|
1170
|
+
var sameOrigin = (base, target) => {
|
|
1171
|
+
if (!/^https?:\/\//.test(base) || !/^https?:\/\//.test(target)) return true;
|
|
1172
|
+
return new URL(base).origin === new URL(target).origin;
|
|
1173
|
+
};
|
|
1174
|
+
var parseLlmsTxtLinks = (raw) => {
|
|
1175
|
+
const links = [...raw.matchAll(/\[([^\]]+)\]\(([^)]+)\)(?::\s*([^\n]+))?/g)].map((match) => ({
|
|
1176
|
+
title: match[1] ?? match[2] ?? "link",
|
|
1177
|
+
url: match[2] ?? "",
|
|
1178
|
+
...match[3]?.trim() ? { description: match[3].trim() } : {}
|
|
1179
|
+
})).filter((link) => link.url);
|
|
1180
|
+
for (const match of raw.matchAll(/(?:^|\s)(?:Raw|llms\.txt|Full bundle|ZIP bundle)?:?\s*(https?:\/\/\S+)/gi)) {
|
|
1181
|
+
const url = match[1]?.replace(/[),.;]+$/, "");
|
|
1182
|
+
if (url && !links.some((link) => link.url === url)) links.push({ title: slugFromPath(url), url });
|
|
1183
|
+
}
|
|
1184
|
+
return links;
|
|
1185
|
+
};
|
|
1186
|
+
var chunksFromMarkdown = (property, raw, sourceUrl) => {
|
|
1187
|
+
const searchable = raw.includes("\n==== ") ? raw : raw.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
1188
|
+
const sections = searchable.includes("\n==== ") ? searchable.split(/\n====\s+/).filter((section) => section.trim()) : searchable.split(/\n(?=##?\s+)/);
|
|
1189
|
+
return sections.map((section, index) => {
|
|
1190
|
+
const title = /^title:\s*(.+)$/m.exec(section)?.[1]?.trim() ?? /^https?:\/\/\S+\/([^/\s]+)$/m.exec(section)?.[1]?.trim() ?? /^#+\s+(.+)$/m.exec(section)?.[1]?.trim() ?? slugFromPath(sourceUrl);
|
|
1191
|
+
const id = slugFromPath(title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")) || `${index}`;
|
|
1192
|
+
return {
|
|
1193
|
+
chunkKey: `${property}:federated:${id}`,
|
|
1194
|
+
property,
|
|
1195
|
+
type: "federated",
|
|
1196
|
+
id,
|
|
1197
|
+
path: sourceUrl,
|
|
1198
|
+
title,
|
|
1199
|
+
summary: section.trim().slice(0, 500),
|
|
1200
|
+
score: 0
|
|
1201
|
+
};
|
|
1202
|
+
});
|
|
1203
|
+
};
|
|
1204
|
+
var loadFederatedChunks = async (root, config, options = {}) => {
|
|
1205
|
+
const fetchText = options.fetchText ?? defaultFetchText;
|
|
1206
|
+
const chunks = [];
|
|
1207
|
+
for (const source of config.federation?.sources ?? []) {
|
|
1208
|
+
if (source.includeInRetriever === false || !source.llmsTxt) continue;
|
|
1209
|
+
const llms = await sourceText(root, source.llmsTxt, fetchText);
|
|
1210
|
+
chunks.push(...chunksFromMarkdown(source.id, llms, source.llmsTxt));
|
|
1211
|
+
const links = parseLlmsTxtLinks(llms);
|
|
1212
|
+
for (const link of links) {
|
|
1213
|
+
const url = !/^https?:\/\//.test(link.url) && source.rawBaseUrl ? `${source.rawBaseUrl.replace(/\/$/, "")}/${link.url.replace(/^\//, "")}` : link.url;
|
|
1214
|
+
if (!/\.(md|txt)(?:$|\?)/.test(url)) continue;
|
|
1215
|
+
if (!sameOrigin(source.llmsTxt, url)) continue;
|
|
1216
|
+
try {
|
|
1217
|
+
const raw = await sourceText(root, url, fetchText);
|
|
1218
|
+
chunks.push(...chunksFromMarkdown(source.id, raw, url));
|
|
1219
|
+
} catch {
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return chunks;
|
|
1224
|
+
};
|
|
1225
|
+
var retrieveHybridChunks = async (root, config, index, query, options = {}) => {
|
|
1226
|
+
const limit = options.limit ?? 8;
|
|
1227
|
+
const local = retrieveDocBridgeChunks(index, query, {
|
|
1228
|
+
property: config.project?.name ?? index.project?.name ?? "local",
|
|
1229
|
+
limit
|
|
1230
|
+
});
|
|
1231
|
+
const federated = (await loadFederatedChunks(root, config, options)).map((chunk) => ({
|
|
1232
|
+
...chunk,
|
|
1233
|
+
score: scoreText(query, `${chunk.title ?? ""} ${chunk.summary ?? ""} ${chunk.path}`)
|
|
1234
|
+
})).filter((chunk) => chunk.score > 0);
|
|
1235
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1236
|
+
for (const chunk of [...local, ...federated]) {
|
|
1237
|
+
const existing = byKey.get(chunk.chunkKey);
|
|
1238
|
+
if (!existing || chunk.score > existing.score) byKey.set(chunk.chunkKey, chunk);
|
|
1239
|
+
}
|
|
1240
|
+
return [...byKey.values()].sort((a, b) => b.score - a.score).slice(0, limit);
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
// src/gates/run-gates.ts
|
|
1244
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
1245
|
+
|
|
1246
|
+
// src/query/load-index.ts
|
|
1247
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
|
|
1248
|
+
import { join as join10, resolve as resolve4 } from "path";
|
|
1249
|
+
|
|
1250
|
+
// src/schemas/agent-handoff.ts
|
|
1251
|
+
import { z as z2 } from "zod";
|
|
1252
|
+
var HANDOFF_SCHEMA_VERSION = 1;
|
|
1253
|
+
var HandoffTargetTypeSchema = z2.enum([
|
|
1254
|
+
"package",
|
|
1255
|
+
"module",
|
|
1256
|
+
"app",
|
|
1257
|
+
"screen",
|
|
1258
|
+
"flow",
|
|
1259
|
+
"component",
|
|
1260
|
+
"intent",
|
|
1261
|
+
"change",
|
|
1262
|
+
"search"
|
|
1263
|
+
]);
|
|
1264
|
+
var HandoffTargetSchema = z2.object({
|
|
1265
|
+
type: HandoffTargetTypeSchema,
|
|
1266
|
+
id: z2.string().min(1).max(256),
|
|
1267
|
+
path: z2.string().min(1).max(512).optional(),
|
|
1268
|
+
group: z2.string().min(1).max(128).optional(),
|
|
1269
|
+
layer: z2.string().min(1).max(32).optional()
|
|
1270
|
+
}).strict();
|
|
1271
|
+
var AgentHandoffV1Schema = z2.object({
|
|
1272
|
+
type: z2.literal("agent-handoff"),
|
|
1273
|
+
schemaVersion: z2.literal(HANDOFF_SCHEMA_VERSION).default(HANDOFF_SCHEMA_VERSION),
|
|
1274
|
+
source: z2.string().min(1).max(512),
|
|
1275
|
+
target: HandoffTargetSchema,
|
|
1276
|
+
startHere: z2.string().min(1).max(512),
|
|
1277
|
+
readBeforeEditing: z2.array(z2.string().min(1).max(512)).max(64),
|
|
1278
|
+
editRoots: z2.array(z2.string().min(1).max(512)).max(32),
|
|
1279
|
+
checks: z2.array(z2.string().min(1).max(256)).max(32),
|
|
1280
|
+
humanDoc: z2.string().min(1).max(512).nullable().optional(),
|
|
1281
|
+
playbookPatterns: z2.array(z2.string().url()).max(16).optional(),
|
|
1282
|
+
notes: z2.array(z2.string().min(1).max(1024)).max(16)
|
|
1283
|
+
}).strict();
|
|
1284
|
+
var AgentHandoffLegacySchema = AgentHandoffV1Schema.omit({ schemaVersion: true }).extend({
|
|
1285
|
+
schemaVersion: z2.literal(HANDOFF_SCHEMA_VERSION).optional()
|
|
1286
|
+
});
|
|
1287
|
+
var AgentSearchMatchSchema = z2.object({
|
|
1288
|
+
type: z2.string().min(1).max(64),
|
|
1289
|
+
id: z2.string().min(1).max(256),
|
|
1290
|
+
path: z2.string().min(1).max(512),
|
|
1291
|
+
summary: z2.string().max(2048).optional(),
|
|
1292
|
+
score: z2.number().optional(),
|
|
1293
|
+
refs: z2.number().int().nonnegative().optional()
|
|
1294
|
+
}).strict();
|
|
1295
|
+
var AgentSearchV1Schema = z2.object({
|
|
1296
|
+
type: z2.literal("agent-search"),
|
|
1297
|
+
schemaVersion: z2.literal(HANDOFF_SCHEMA_VERSION).default(HANDOFF_SCHEMA_VERSION),
|
|
1298
|
+
source: z2.string().min(1).max(512),
|
|
1299
|
+
term: z2.string().min(1).max(512),
|
|
1300
|
+
count: z2.number().int().nonnegative(),
|
|
1301
|
+
bestMatch: AgentSearchMatchSchema.nullable(),
|
|
1302
|
+
matches: z2.array(AgentSearchMatchSchema).max(32),
|
|
1303
|
+
nextCommands: z2.array(z2.string().min(1).max(512)).max(16)
|
|
1304
|
+
}).strict();
|
|
1305
|
+
var normalizeAgentHandoff = (input) => AgentHandoffV1Schema.parse(AgentHandoffLegacySchema.parse(input));
|
|
1306
|
+
|
|
1307
|
+
// src/schemas/doc-bridge-index.ts
|
|
1308
|
+
import { z as z3 } from "zod";
|
|
1309
|
+
var INDEX_SCHEMA_VERSION = 1;
|
|
1310
|
+
var ContentHashAlgoSchema = z3.literal("sha256-normalized-v1");
|
|
1311
|
+
var EcosystemPropertySchema = z3.object({
|
|
1312
|
+
id: z3.string().min(1).max(64),
|
|
1313
|
+
name: z3.string().min(1).max(128),
|
|
1314
|
+
url: z3.string().url().optional(),
|
|
1315
|
+
llms: z3.string().url().optional()
|
|
1316
|
+
}).strict();
|
|
1317
|
+
var KnowledgeEntrySchema = z3.object({
|
|
1318
|
+
id: z3.string().min(1).max(256),
|
|
1319
|
+
type: z3.string().min(1).max(128),
|
|
1320
|
+
title: z3.string().min(1).max(256),
|
|
1321
|
+
path: z3.string().min(1).max(512),
|
|
1322
|
+
description: z3.string().max(1024).optional(),
|
|
1323
|
+
links: z3.array(z3.string().min(1).max(512)).max(64).optional(),
|
|
1324
|
+
tags: z3.array(z3.string().min(1).max(64)).max(32).optional()
|
|
1325
|
+
}).strict();
|
|
1326
|
+
var CapabilityRefSchema = z3.object({
|
|
1327
|
+
id: z3.string().min(1).max(256),
|
|
1328
|
+
kind: z3.string().min(1).max(64),
|
|
1329
|
+
description: z3.string().max(512).optional()
|
|
1330
|
+
}).strict();
|
|
1331
|
+
var OwnershipRecordSchema = z3.object({
|
|
1332
|
+
id: z3.string().min(1).max(256),
|
|
1333
|
+
path: z3.string().min(1).max(512),
|
|
1334
|
+
group: z3.string().min(1).max(128).optional(),
|
|
1335
|
+
layer: z3.string().min(1).max(32).optional(),
|
|
1336
|
+
purpose: z3.string().max(1024).optional(),
|
|
1337
|
+
checks: z3.array(z3.string().min(1).max(256)).max(32),
|
|
1338
|
+
agentDoc: z3.string().min(1).max(512).optional(),
|
|
1339
|
+
humanDoc: z3.string().min(1).max(512).optional(),
|
|
1340
|
+
readme: z3.string().min(1).max(512).optional()
|
|
1341
|
+
}).strict();
|
|
1342
|
+
var IndexLookupSchema = z3.object({
|
|
1343
|
+
packages: z3.array(z3.string().min(1).max(256)).max(2e3),
|
|
1344
|
+
ownership: z3.record(z3.string().min(1).max(256), OwnershipRecordSchema).optional(),
|
|
1345
|
+
intents: z3.record(
|
|
1346
|
+
z3.string().min(1).max(128),
|
|
1347
|
+
z3.object({
|
|
1348
|
+
id: z3.string().min(1).max(128),
|
|
1349
|
+
title: z3.string().min(1).max(256),
|
|
1350
|
+
paths: z3.array(z3.string().min(1).max(512)).max(32)
|
|
1351
|
+
}).strict()
|
|
1352
|
+
).optional(),
|
|
1353
|
+
changes: z3.record(
|
|
1354
|
+
z3.string().min(1).max(128),
|
|
1355
|
+
z3.object({
|
|
1356
|
+
id: z3.string().min(1).max(128),
|
|
1357
|
+
title: z3.string().min(1).max(256),
|
|
1358
|
+
startHere: z3.string().min(1).max(512),
|
|
1359
|
+
relatedPackages: z3.array(z3.string().min(1).max(256)).max(32).optional()
|
|
1360
|
+
}).strict()
|
|
1361
|
+
).optional()
|
|
1362
|
+
}).strict();
|
|
1363
|
+
var DocBridgeIndexV1Schema = z3.object({
|
|
1364
|
+
schemaVersion: z3.literal(INDEX_SCHEMA_VERSION),
|
|
1365
|
+
contentHash: z3.string().regex(/^[a-f0-9]{64}$/),
|
|
1366
|
+
contentHashAlgo: ContentHashAlgoSchema,
|
|
1367
|
+
generatedAt: z3.string().datetime().optional(),
|
|
1368
|
+
project: z3.object({
|
|
1369
|
+
name: z3.string().min(1).max(128),
|
|
1370
|
+
root: z3.string().min(1).max(512).optional()
|
|
1371
|
+
}).strict().optional(),
|
|
1372
|
+
properties: z3.array(EcosystemPropertySchema).max(16).optional(),
|
|
1373
|
+
knowledge: z3.array(KnowledgeEntrySchema).max(1e4),
|
|
1374
|
+
capabilities: z3.array(CapabilityRefSchema).max(5e3).optional(),
|
|
1375
|
+
handoffs: z3.record(z3.string().min(1).max(256), AgentHandoffLegacySchema).optional(),
|
|
1376
|
+
lookup: IndexLookupSchema.optional()
|
|
1377
|
+
}).strict();
|
|
1378
|
+
|
|
1379
|
+
// src/schemas/memory-candidate.ts
|
|
1380
|
+
import { z as z4 } from "zod";
|
|
1381
|
+
var MEMORY_CANDIDATE_SCHEMA_VERSION = 1;
|
|
1382
|
+
var MemorySourceSchema = z4.enum([
|
|
1383
|
+
"cursor",
|
|
1384
|
+
"claude",
|
|
1385
|
+
"codex",
|
|
1386
|
+
"copilot",
|
|
1387
|
+
"agent-memory",
|
|
1388
|
+
"manual"
|
|
1389
|
+
]);
|
|
1390
|
+
var MemorySuggestedTypeSchema = z4.enum([
|
|
1391
|
+
"user",
|
|
1392
|
+
"feedback",
|
|
1393
|
+
"project",
|
|
1394
|
+
"reference",
|
|
1395
|
+
"unknown"
|
|
1396
|
+
]);
|
|
1397
|
+
var MemoryCandidateV1Schema = z4.object({
|
|
1398
|
+
schemaVersion: z4.literal(MEMORY_CANDIDATE_SCHEMA_VERSION),
|
|
1399
|
+
id: z4.string().min(1).max(256),
|
|
1400
|
+
source: MemorySourceSchema,
|
|
1401
|
+
rawPath: z4.string().min(1).max(512).optional(),
|
|
1402
|
+
fact: z4.string().min(1).max(8e3),
|
|
1403
|
+
why: z4.string().max(4e3).optional(),
|
|
1404
|
+
howToApply: z4.string().max(4e3).optional(),
|
|
1405
|
+
suggestedType: MemorySuggestedTypeSchema,
|
|
1406
|
+
confidence: z4.number().min(0).max(1),
|
|
1407
|
+
references: z4.array(z4.string().min(1).max(512)).max(64)
|
|
1408
|
+
}).strict();
|
|
1409
|
+
|
|
1410
|
+
// src/validate.ts
|
|
1411
|
+
var zodIssues = (error) => error.issues.map((issue) => ({
|
|
1412
|
+
path: issue.path.join(".") || "(root)",
|
|
1413
|
+
message: issue.message
|
|
1414
|
+
}));
|
|
1415
|
+
var safeParseAgentHandoff = (input) => {
|
|
1416
|
+
const legacy = AgentHandoffLegacySchema.safeParse(input);
|
|
1417
|
+
if (!legacy.success) return { ok: false, issues: zodIssues(legacy.error) };
|
|
1418
|
+
const normalized = AgentHandoffV1Schema.safeParse(normalizeAgentHandoff(legacy.data));
|
|
1419
|
+
if (!normalized.success) return { ok: false, issues: zodIssues(normalized.error) };
|
|
1420
|
+
return { ok: true, value: normalized.data };
|
|
1421
|
+
};
|
|
1422
|
+
var parseAgentHandoff = (input) => {
|
|
1423
|
+
const result = safeParseAgentHandoff(input);
|
|
1424
|
+
if (!result.ok) {
|
|
1425
|
+
throw new Error(
|
|
1426
|
+
`Invalid AgentHandoff:
|
|
1427
|
+
${result.issues.map((i) => ` - ${i.path}: ${i.message}`).join("\n")}`
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
return result.value;
|
|
1431
|
+
};
|
|
1432
|
+
var parseDocBridgeIndex = (input) => DocBridgeIndexV1Schema.parse(input);
|
|
1433
|
+
var parseDocBridgeConfig = (input) => {
|
|
1434
|
+
const result = DocBridgeConfigV1Schema.safeParse(input);
|
|
1435
|
+
if (!result.success) {
|
|
1436
|
+
throw new Error(
|
|
1437
|
+
`Invalid doc-bridge config:
|
|
1438
|
+
${zodIssues(result.error).map((i) => ` - ${i.path}: ${i.message}`).join("\n")}`
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
return result.data;
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1444
|
+
// src/query/load-index.ts
|
|
1445
|
+
var IndexNotFoundError = class extends Error {
|
|
1446
|
+
constructor(path) {
|
|
1447
|
+
super(`Missing index at ${path}. Run: ak-docs index`);
|
|
1448
|
+
this.path = path;
|
|
1449
|
+
this.name = "IndexNotFoundError";
|
|
1450
|
+
}
|
|
1451
|
+
path;
|
|
1452
|
+
};
|
|
1453
|
+
var indexFilePath = (root, config) => join10(root, config.index?.outFile ?? ".doc-bridge/index.json");
|
|
1454
|
+
var loadDocBridgeIndex = (root, config) => {
|
|
1455
|
+
const path = indexFilePath(root, config);
|
|
1456
|
+
if (!existsSync7(path)) throw new IndexNotFoundError(path);
|
|
1457
|
+
const raw = JSON.parse(readFileSync9(path, "utf8"));
|
|
1458
|
+
return parseDocBridgeIndex(raw);
|
|
1459
|
+
};
|
|
1460
|
+
|
|
1461
|
+
// src/gates/run-gates.ts
|
|
1462
|
+
var SUPPORTED_GATES = ["index-freshness", "human-guide-links", "okf-type", "docs-style"];
|
|
1463
|
+
var runGate = (root, config, id) => {
|
|
1464
|
+
if (id === "human-guide-links") return runHumanGuideLinksGate(root, config);
|
|
1465
|
+
if (id === "okf-type") return runOkfTypeGate(root, config);
|
|
1466
|
+
if (id === "docs-style") return runDocsStyleGate(root, config);
|
|
1467
|
+
if (id !== "index-freshness") throw new Error(`Unsupported gate "${id}"`);
|
|
1468
|
+
let current;
|
|
1469
|
+
try {
|
|
1470
|
+
current = loadDocBridgeIndex(root, config).contentHash;
|
|
1471
|
+
} catch (error) {
|
|
1472
|
+
if (error instanceof IndexNotFoundError) {
|
|
1473
|
+
return { id, ok: false, message: error.message };
|
|
1474
|
+
}
|
|
1475
|
+
throw error;
|
|
1476
|
+
}
|
|
1477
|
+
const next = buildDocBridgeIndex({ root, config, write: false }).index.contentHash;
|
|
1478
|
+
if (current !== next) {
|
|
1479
|
+
return {
|
|
1480
|
+
id,
|
|
1481
|
+
ok: false,
|
|
1482
|
+
message: "Index is stale. Run: ak-docs index",
|
|
1483
|
+
expected: next,
|
|
1484
|
+
actual: current
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
return { id, ok: true, message: "Index is fresh", expected: next, actual: current };
|
|
1488
|
+
};
|
|
1489
|
+
var runHumanGuideLinksGate = (root, config) => {
|
|
1490
|
+
const urls = new Set(scanHumanDocRecords(root, config).map((doc) => doc.url));
|
|
1491
|
+
const index = buildDocBridgeIndex({ root, config, write: false }).index;
|
|
1492
|
+
const localHumanDocs = Object.values(index.handoffs ?? {}).map((handoff) => handoff.humanDoc).filter((url) => typeof url === "string" && url.length > 0).filter((url) => !/^https?:\/\//.test(url));
|
|
1493
|
+
const missing = localHumanDocs.filter((url, i, all) => !urls.has(url) && all.indexOf(url) === i);
|
|
1494
|
+
if (missing.length) {
|
|
1495
|
+
return {
|
|
1496
|
+
id: "human-guide-links",
|
|
1497
|
+
ok: false,
|
|
1498
|
+
message: `Broken humanDoc links: ${missing.join(", ")}`,
|
|
1499
|
+
expected: "all local humanDoc links resolve",
|
|
1500
|
+
actual: `${missing.length} broken`
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
return {
|
|
1504
|
+
id: "human-guide-links",
|
|
1505
|
+
ok: true,
|
|
1506
|
+
message: `Resolved ${localHumanDocs.length} humanDoc link(s)`
|
|
1507
|
+
};
|
|
1508
|
+
};
|
|
1509
|
+
var frontmatterType = (markdown) => {
|
|
1510
|
+
const match = /^---\n([\s\S]*?)\n---/.exec(markdown);
|
|
1511
|
+
return match?.[1]?.match(/^type:\s*['"]?([^'"\n#]+)['"]?/m)?.[1]?.trim();
|
|
1512
|
+
};
|
|
1513
|
+
var frontmatterField = (markdown, field) => {
|
|
1514
|
+
const match = /^---\n([\s\S]*?)\n---/.exec(markdown);
|
|
1515
|
+
return match?.[1]?.match(new RegExp(`^${field}:\\s*['"]?([^'"\\n#]+)['"]?`, "m"))?.[1]?.trim();
|
|
1516
|
+
};
|
|
1517
|
+
var runOkfTypeGate = (root, config) => {
|
|
1518
|
+
const required = config.corpus.agent.okf?.requireType ?? config.gates?.preset === "strict";
|
|
1519
|
+
if (!required) return { id: "okf-type", ok: true, message: "OKF type frontmatter not required" };
|
|
1520
|
+
const allowed = config.corpus.agent.okf?.allowedTypes;
|
|
1521
|
+
const bad = scanAgentCorpus(root, config).filter((doc) => doc.path !== config.corpus.agent.index).map((doc) => ({ path: doc.path, type: frontmatterType(readFileSync10(doc.absPath, "utf8")) })).filter((doc) => !doc.type || allowed && !allowed.includes(doc.type));
|
|
1522
|
+
if (bad.length) {
|
|
1523
|
+
return {
|
|
1524
|
+
id: "okf-type",
|
|
1525
|
+
ok: false,
|
|
1526
|
+
message: `Missing or invalid OKF type frontmatter: ${bad.map((doc) => doc.path).join(", ")}`,
|
|
1527
|
+
expected: allowed?.length ? `type: ${allowed.join(" | ")}` : "type frontmatter",
|
|
1528
|
+
actual: `${bad.length} invalid`
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
return { id: "okf-type", ok: true, message: "All agent docs have OKF type frontmatter" };
|
|
1532
|
+
};
|
|
1533
|
+
var DOCS_STYLE_RULES = {
|
|
1534
|
+
"google-dev-docs": [
|
|
1535
|
+
"title",
|
|
1536
|
+
"purpose",
|
|
1537
|
+
"audience",
|
|
1538
|
+
"task-orientation",
|
|
1539
|
+
"examples",
|
|
1540
|
+
"no-stale-wording"
|
|
1541
|
+
],
|
|
1542
|
+
"playbook-okf": ["title", "purpose", "owner-source", "no-stale-wording"]
|
|
1543
|
+
};
|
|
1544
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1545
|
+
var docsStyleOptions = (config) => {
|
|
1546
|
+
const raw = config.gates?.options?.["docs-style"];
|
|
1547
|
+
if (!isRecord(raw)) return { profile: "playbook-okf", required: DOCS_STYLE_RULES["playbook-okf"] };
|
|
1548
|
+
const profile = raw.profile === "google-dev-docs" || raw.profile === "playbook-okf" || raw.profile === "custom" ? raw.profile : "playbook-okf";
|
|
1549
|
+
const customRequired = Array.isArray(raw.required) ? raw.required.filter(
|
|
1550
|
+
(rule) => [
|
|
1551
|
+
"title",
|
|
1552
|
+
"purpose",
|
|
1553
|
+
"audience",
|
|
1554
|
+
"task-orientation",
|
|
1555
|
+
"examples",
|
|
1556
|
+
"owner-source",
|
|
1557
|
+
"no-stale-wording"
|
|
1558
|
+
].includes(String(rule))
|
|
1559
|
+
) : [];
|
|
1560
|
+
return {
|
|
1561
|
+
profile,
|
|
1562
|
+
required: profile === "custom" ? customRequired : DOCS_STYLE_RULES[profile]
|
|
1563
|
+
};
|
|
1564
|
+
};
|
|
1565
|
+
var missingStyleRules = (markdown, rules) => {
|
|
1566
|
+
const checks = {
|
|
1567
|
+
title: /^#\s+\S+/m.test(markdown),
|
|
1568
|
+
purpose: Boolean(frontmatterField(markdown, "purpose") || /^##\s+(Purpose|Goal)\b/im.test(markdown)),
|
|
1569
|
+
audience: Boolean(frontmatterField(markdown, "audience") || /^##\s+Audience\b/im.test(markdown)),
|
|
1570
|
+
"task-orientation": /^##\s+(How to|Usage|Workflow|Tasks?)\b/im.test(markdown),
|
|
1571
|
+
examples: /^##\s+Examples?\b/im.test(markdown) || /```[\s\S]*?```/.test(markdown),
|
|
1572
|
+
"owner-source": Boolean(
|
|
1573
|
+
frontmatterField(markdown, "owner") || frontmatterField(markdown, "source") || /^(Owner|Source):\s+\S+/im.test(markdown)
|
|
1574
|
+
),
|
|
1575
|
+
"no-stale-wording": !/\b(TODO|TBD|coming soon|eventually|placeholder)\b/i.test(markdown)
|
|
1576
|
+
};
|
|
1577
|
+
return rules.filter((rule) => !checks[rule]);
|
|
1578
|
+
};
|
|
1579
|
+
var runDocsStyleGate = (root, config) => {
|
|
1580
|
+
const { profile, required } = docsStyleOptions(config);
|
|
1581
|
+
if (!required.length) {
|
|
1582
|
+
return { id: "docs-style", ok: true, message: "No docs-style rules configured" };
|
|
1583
|
+
}
|
|
1584
|
+
const bad = scanAgentCorpus(root, config).filter((doc) => doc.path !== config.corpus.agent.index).map((doc) => ({
|
|
1585
|
+
path: doc.path,
|
|
1586
|
+
missing: missingStyleRules(readFileSync10(doc.absPath, "utf8"), required)
|
|
1587
|
+
})).filter((doc) => doc.missing.length > 0);
|
|
1588
|
+
if (bad.length) {
|
|
1589
|
+
return {
|
|
1590
|
+
id: "docs-style",
|
|
1591
|
+
ok: false,
|
|
1592
|
+
message: `Docs style issues: ${bad.map((doc) => `${doc.path} (${doc.missing.join(", ")})`).join("; ")}`,
|
|
1593
|
+
expected: `${profile}: ${required.join(", ")}`,
|
|
1594
|
+
actual: `${bad.length} file(s) with style issues`
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
return {
|
|
1598
|
+
id: "docs-style",
|
|
1599
|
+
ok: true,
|
|
1600
|
+
message: `All agent docs pass ${profile} docs-style rules`
|
|
1601
|
+
};
|
|
1602
|
+
};
|
|
1603
|
+
var runGates = (root, config, ids) => {
|
|
1604
|
+
const results = (ids ?? resolveGateIds(config)).map((id) => runGate(root, config, id));
|
|
1605
|
+
return { ok: results.every((result) => result.ok), results };
|
|
1606
|
+
};
|
|
1607
|
+
var resolveGateIds = (config) => {
|
|
1608
|
+
const preset = config.gates?.preset ?? "minimal";
|
|
1609
|
+
const ids = new Set(
|
|
1610
|
+
preset === "minimal" ? ["index-freshness"] : preset === "strict" ? ["index-freshness", "human-guide-links", "okf-type"] : ["index-freshness", "human-guide-links"]
|
|
1611
|
+
);
|
|
1612
|
+
for (const id of config.gates?.include ?? []) {
|
|
1613
|
+
if (SUPPORTED_GATES.includes(id)) ids.add(id);
|
|
1614
|
+
}
|
|
1615
|
+
for (const id of config.gates?.exclude ?? []) {
|
|
1616
|
+
if (SUPPORTED_GATES.includes(id)) ids.delete(id);
|
|
1617
|
+
}
|
|
1618
|
+
return [...ids];
|
|
1619
|
+
};
|
|
1620
|
+
|
|
1621
|
+
// src/query/query.ts
|
|
1622
|
+
var handoffForPackage = (index, id, config) => {
|
|
1623
|
+
const fromIndex = index.handoffs?.[id];
|
|
1624
|
+
if (fromIndex) return normalizeAgentHandoff(fromIndex);
|
|
1625
|
+
const owner = index.lookup?.ownership?.[id];
|
|
1626
|
+
if (!owner) throw new Error(`Unknown package/ownership id "${id}". Try: ak-docs list packages`);
|
|
1627
|
+
return normalizeAgentHandoff({
|
|
1628
|
+
type: "agent-handoff",
|
|
1629
|
+
source: config.index?.outFile ?? ".doc-bridge/index.json",
|
|
1630
|
+
target: {
|
|
1631
|
+
type: "package",
|
|
1632
|
+
id,
|
|
1633
|
+
path: owner.path,
|
|
1634
|
+
...owner.group ? { group: owner.group } : {},
|
|
1635
|
+
...owner.layer ? { layer: owner.layer } : {}
|
|
1636
|
+
},
|
|
1637
|
+
startHere: owner.agentDoc ?? config.corpus.agent.index ?? "",
|
|
1638
|
+
readBeforeEditing: [owner.agentDoc, "AGENTS.md"].filter(Boolean),
|
|
1639
|
+
editRoots: [owner.path],
|
|
1640
|
+
checks: [...owner.checks],
|
|
1641
|
+
...owner.humanDoc ? { humanDoc: owner.humanDoc } : {},
|
|
1642
|
+
notes: owner.purpose ? [owner.purpose] : []
|
|
1643
|
+
});
|
|
1644
|
+
};
|
|
1645
|
+
var runQuery = (index, config, req) => {
|
|
1646
|
+
if (req.kind === "search") {
|
|
1647
|
+
const term = req.term ?? req.id ?? "";
|
|
1648
|
+
const matches = searchIndex(index, term);
|
|
1649
|
+
if (req.agent) {
|
|
1650
|
+
const payload = {
|
|
1651
|
+
type: "agent-search",
|
|
1652
|
+
schemaVersion: 1,
|
|
1653
|
+
source: config.index?.outFile ?? ".doc-bridge/index.json",
|
|
1654
|
+
term,
|
|
1655
|
+
count: matches.length,
|
|
1656
|
+
bestMatch: matches[0] ? {
|
|
1657
|
+
type: matches[0].type,
|
|
1658
|
+
id: matches[0].id,
|
|
1659
|
+
path: matches[0].path,
|
|
1660
|
+
...matches[0].summary ? { summary: matches[0].summary } : {},
|
|
1661
|
+
score: matches[0].score
|
|
1662
|
+
} : null,
|
|
1663
|
+
matches: matches.slice(0, 8).map((m) => ({
|
|
1664
|
+
type: m.type,
|
|
1665
|
+
id: m.id,
|
|
1666
|
+
path: m.path,
|
|
1667
|
+
...m.summary ? { summary: m.summary } : {},
|
|
1668
|
+
score: m.score
|
|
1669
|
+
})),
|
|
1670
|
+
nextCommands: [...new Set(matches.slice(0, 5).map(
|
|
1671
|
+
(m) => m.type === "ownership" || index.lookup?.ownership?.[m.id] ? `ak-docs query ownership ${m.id} --agent` : "ak-docs list knowledge --text"
|
|
1672
|
+
))]
|
|
1673
|
+
};
|
|
1674
|
+
return payload;
|
|
1675
|
+
}
|
|
1676
|
+
return { type: "search", data: { term, count: matches.length, matches } };
|
|
1677
|
+
}
|
|
1678
|
+
const id = req.id;
|
|
1679
|
+
if (!id) throw new Error(`Missing id for query kind "${req.kind}"`);
|
|
1680
|
+
if (req.kind === "package" || req.kind === "ownership") {
|
|
1681
|
+
if (req.agent) return handoffForPackage(index, id, config);
|
|
1682
|
+
const owner = index.lookup?.ownership?.[id];
|
|
1683
|
+
return { type: req.kind, data: owner ?? index.handoffs?.[id] ?? null };
|
|
1684
|
+
}
|
|
1685
|
+
if (req.kind === "intent") {
|
|
1686
|
+
const intent = index.lookup?.intents?.[id];
|
|
1687
|
+
if (!intent) throw new Error(`Unknown intent "${id}"`);
|
|
1688
|
+
if (req.agent) {
|
|
1689
|
+
return normalizeAgentHandoff({
|
|
1690
|
+
type: "agent-handoff",
|
|
1691
|
+
source: config.index?.outFile ?? ".doc-bridge/index.json",
|
|
1692
|
+
target: { type: "intent", id },
|
|
1693
|
+
startHere: intent.paths[0] ?? "",
|
|
1694
|
+
readBeforeEditing: [...intent.paths],
|
|
1695
|
+
editRoots: [],
|
|
1696
|
+
checks: [],
|
|
1697
|
+
notes: [intent.title]
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
return { type: "intent", data: intent };
|
|
1701
|
+
}
|
|
1702
|
+
if (req.kind === "change") {
|
|
1703
|
+
const change = index.lookup?.changes?.[id];
|
|
1704
|
+
if (!change) throw new Error(`Unknown change route "${id}"`);
|
|
1705
|
+
if (req.agent) {
|
|
1706
|
+
return normalizeAgentHandoff({
|
|
1707
|
+
type: "agent-handoff",
|
|
1708
|
+
source: config.index?.outFile ?? ".doc-bridge/index.json",
|
|
1709
|
+
target: { type: "change", id },
|
|
1710
|
+
startHere: change.startHere,
|
|
1711
|
+
readBeforeEditing: ["AGENTS.md", config.corpus.agent.index ?? ""].filter(Boolean),
|
|
1712
|
+
editRoots: [change.startHere],
|
|
1713
|
+
checks: [],
|
|
1714
|
+
notes: [change.title]
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
return { type: "change", data: change };
|
|
1718
|
+
}
|
|
1719
|
+
throw new Error(`Unsupported query kind: ${req.kind}`);
|
|
1720
|
+
};
|
|
1721
|
+
|
|
1722
|
+
// src/intelligence/adapter.ts
|
|
1723
|
+
import { join as join11 } from "path";
|
|
1724
|
+
|
|
1725
|
+
// src/intelligence/peers.ts
|
|
1726
|
+
var PeerMissingError = class extends Error {
|
|
1727
|
+
constructor(peer, installHint) {
|
|
1728
|
+
super(
|
|
1729
|
+
`Optional peer "${peer}" is not installed.
|
|
1730
|
+
Install Layer 1 intelligence peers:
|
|
1731
|
+
${installHint}`
|
|
1732
|
+
);
|
|
1733
|
+
this.peer = peer;
|
|
1734
|
+
this.installHint = installHint;
|
|
1735
|
+
this.name = "PeerMissingError";
|
|
1736
|
+
}
|
|
1737
|
+
peer;
|
|
1738
|
+
installHint;
|
|
1739
|
+
};
|
|
1740
|
+
var LAYER1_INSTALL = "npm i -D @agentskit/rag @agentskit/ink @agentskit/adapters @agentskit/memory react";
|
|
1741
|
+
var isPeerResolutionFailure = (error) => {
|
|
1742
|
+
const parts = [];
|
|
1743
|
+
let cur = error;
|
|
1744
|
+
for (let i = 0; i < 6 && cur; i += 1) {
|
|
1745
|
+
if (cur instanceof Error) {
|
|
1746
|
+
parts.push(cur.message, cur.name);
|
|
1747
|
+
const code = cur.code;
|
|
1748
|
+
if (code) parts.push(String(code));
|
|
1749
|
+
cur = cur.cause;
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
parts.push(String(cur));
|
|
1753
|
+
break;
|
|
1754
|
+
}
|
|
1755
|
+
const text = parts.join(" ").toLowerCase();
|
|
1756
|
+
return text.includes("cannot find package") || text.includes("cannot find module") || text.includes("module not found") || text.includes("err_module_not_found") || text.includes("err_package_not_found") || text.includes("err_package_path_not_exported") || text.includes("failed to resolve module") || text.includes("failed to resolve") || // Node / network resolution edge cases for bare package imports
|
|
1757
|
+
text.includes("fetch failed") || text.includes("enotfound") || text.includes("getaddrinfo");
|
|
1758
|
+
};
|
|
1759
|
+
var importPeer = async (name) => {
|
|
1760
|
+
try {
|
|
1761
|
+
return await import(name);
|
|
1762
|
+
} catch (error) {
|
|
1763
|
+
if (isPeerResolutionFailure(error)) {
|
|
1764
|
+
throw new PeerMissingError(name, LAYER1_INSTALL);
|
|
1765
|
+
}
|
|
1766
|
+
throw error;
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
var layer1InstallHint = () => LAYER1_INSTALL;
|
|
1770
|
+
|
|
1771
|
+
// src/intelligence/adapter.ts
|
|
1772
|
+
var readEnv = (name) => {
|
|
1773
|
+
if (!name) return void 0;
|
|
1774
|
+
const value = process.env[name];
|
|
1775
|
+
return value && value.length > 0 ? value : void 0;
|
|
1776
|
+
};
|
|
1777
|
+
var resolveIntelligenceRuntime = async (config) => {
|
|
1778
|
+
if (!config.intelligence?.enabled) {
|
|
1779
|
+
throw new Error("Intelligence is disabled. Set intelligence.enabled: true in doc-bridge config.");
|
|
1780
|
+
}
|
|
1781
|
+
const adapterCfg = config.intelligence.adapter;
|
|
1782
|
+
if (!adapterCfg) {
|
|
1783
|
+
throw new Error("intelligence.adapter is required for chat/RAG (provider + optional model).");
|
|
1784
|
+
}
|
|
1785
|
+
const adapters = await importPeer("@agentskit/adapters");
|
|
1786
|
+
const provider = adapterCfg.provider;
|
|
1787
|
+
const model = adapterCfg.model;
|
|
1788
|
+
const apiKey = readEnv(adapterCfg.apiKeyEnv) ?? readEnv("OPENAI_API_KEY") ?? readEnv("ANTHROPIC_API_KEY");
|
|
1789
|
+
const baseUrl = adapterCfg.baseUrl;
|
|
1790
|
+
let adapter;
|
|
1791
|
+
let embed;
|
|
1792
|
+
switch (provider) {
|
|
1793
|
+
case "ollama": {
|
|
1794
|
+
adapter = adapters.ollama({ model: model ?? "llama3.2", ...baseUrl ? { baseUrl } : {} });
|
|
1795
|
+
embed = adapters.ollamaEmbedder({
|
|
1796
|
+
model: typeof adapterCfg.options?.embedModel === "string" ? adapterCfg.options.embedModel : "nomic-embed-text",
|
|
1797
|
+
...baseUrl ? { baseUrl } : {}
|
|
1798
|
+
});
|
|
1799
|
+
break;
|
|
1800
|
+
}
|
|
1801
|
+
case "openai": {
|
|
1802
|
+
if (!apiKey) throw new Error(`Missing API key env ${adapterCfg.apiKeyEnv ?? "OPENAI_API_KEY"}`);
|
|
1803
|
+
adapter = adapters.openai({ apiKey, ...model ? { model } : {}, ...baseUrl ? { baseUrl } : {} });
|
|
1804
|
+
embed = adapters.openaiEmbedder({ apiKey, model: "text-embedding-3-small" });
|
|
1805
|
+
break;
|
|
1806
|
+
}
|
|
1807
|
+
case "anthropic": {
|
|
1808
|
+
if (!apiKey) throw new Error(`Missing API key env ${adapterCfg.apiKeyEnv ?? "ANTHROPIC_API_KEY"}`);
|
|
1809
|
+
adapter = adapters.anthropic({ apiKey, ...model ? { model } : {} });
|
|
1810
|
+
const openaiKey = readEnv("OPENAI_API_KEY");
|
|
1811
|
+
if (openaiKey) embed = adapters.openaiEmbedder({ apiKey: openaiKey });
|
|
1812
|
+
else {
|
|
1813
|
+
throw new Error(
|
|
1814
|
+
"Anthropic chat requires an embedder for RAG. Set OPENAI_API_KEY for embeddings or use provider: ollama."
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
break;
|
|
1818
|
+
}
|
|
1819
|
+
case "openrouter": {
|
|
1820
|
+
if (!apiKey) throw new Error(`Missing API key env ${adapterCfg.apiKeyEnv ?? "OPENROUTER_API_KEY"}`);
|
|
1821
|
+
adapter = adapters.openrouter({ apiKey, ...model ? { model } : {} });
|
|
1822
|
+
embed = adapters.openaiEmbedder({
|
|
1823
|
+
apiKey,
|
|
1824
|
+
model: "text-embedding-3-small"
|
|
1825
|
+
// openrouter embeddings path may vary; adapters handle openai-compatible
|
|
1826
|
+
});
|
|
1827
|
+
break;
|
|
1828
|
+
}
|
|
1829
|
+
case "custom":
|
|
1830
|
+
throw new Error(
|
|
1831
|
+
"provider: custom requires a programmatic integration. Use ollama, openai, anthropic, or openrouter in v0.1."
|
|
1832
|
+
);
|
|
1833
|
+
default:
|
|
1834
|
+
throw new Error(`Unsupported intelligence.adapter.provider: ${String(provider)}`);
|
|
1835
|
+
}
|
|
1836
|
+
return { adapter, embed, provider, ...model ? { model } : {} };
|
|
1837
|
+
};
|
|
1838
|
+
var defaultVectorStorePath = (root) => join11(root, ".doc-bridge", "vectors");
|
|
1839
|
+
|
|
1840
|
+
// src/intelligence/rag.ts
|
|
1841
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
1842
|
+
import { join as join12 } from "path";
|
|
1843
|
+
var loadDocuments = (root, index, sources) => {
|
|
1844
|
+
const includeAgent = sources.includes("agent") || sources.length === 0;
|
|
1845
|
+
const docs = [];
|
|
1846
|
+
if (includeAgent) {
|
|
1847
|
+
for (const entry of index.knowledge) {
|
|
1848
|
+
const abs = join12(root, entry.path);
|
|
1849
|
+
let content = "";
|
|
1850
|
+
try {
|
|
1851
|
+
content = readFileSync11(abs, "utf8");
|
|
1852
|
+
} catch {
|
|
1853
|
+
content = [entry.title, entry.description].filter(Boolean).join("\n\n");
|
|
1854
|
+
}
|
|
1855
|
+
docs.push({
|
|
1856
|
+
id: entry.id,
|
|
1857
|
+
content,
|
|
1858
|
+
source: entry.path,
|
|
1859
|
+
metadata: { type: entry.type, title: entry.title, path: entry.path }
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
return docs;
|
|
1864
|
+
};
|
|
1865
|
+
var createDocBridgeRag = async (root, config, index) => {
|
|
1866
|
+
const { embed } = await resolveIntelligenceRuntime(config);
|
|
1867
|
+
const ragMod = await importPeer("@agentskit/rag");
|
|
1868
|
+
const memoryMod = await importPeer("@agentskit/memory");
|
|
1869
|
+
const storePath = typeof config.intelligence?.retriever?.options?.storePath === "string" ? join12(root, config.intelligence.retriever.options.storePath) : defaultVectorStorePath(root);
|
|
1870
|
+
const store = memoryMod.fileVectorMemory({ path: storePath });
|
|
1871
|
+
const rag = ragMod.createRAG({
|
|
1872
|
+
embed,
|
|
1873
|
+
store,
|
|
1874
|
+
topK: 6,
|
|
1875
|
+
chunkSize: 900,
|
|
1876
|
+
chunkOverlap: 120
|
|
1877
|
+
});
|
|
1878
|
+
const sources = config.intelligence?.chat?.sources ?? ["agent", "human"];
|
|
1879
|
+
return {
|
|
1880
|
+
storePath,
|
|
1881
|
+
retriever: rag,
|
|
1882
|
+
ingest: async () => {
|
|
1883
|
+
const documents = loadDocuments(root, index, sources);
|
|
1884
|
+
await rag.ingest(documents);
|
|
1885
|
+
return { documentCount: documents.length, storePath };
|
|
1886
|
+
},
|
|
1887
|
+
search: async (query, topK = 6) => {
|
|
1888
|
+
const hits = await rag.search(query, { topK });
|
|
1889
|
+
return hits.map((hit) => ({
|
|
1890
|
+
...hit.id ? { id: hit.id } : {},
|
|
1891
|
+
content: hit.content,
|
|
1892
|
+
...hit.score !== void 0 ? { score: hit.score } : {},
|
|
1893
|
+
...hit.source ? { source: hit.source } : {},
|
|
1894
|
+
...hit.metadata ? { metadata: hit.metadata } : {}
|
|
1895
|
+
}));
|
|
1896
|
+
}
|
|
1897
|
+
};
|
|
1898
|
+
};
|
|
1899
|
+
|
|
1900
|
+
// src/intelligence/chat.ts
|
|
1901
|
+
var wrapIntelligenceError = (error) => {
|
|
1902
|
+
if (error instanceof PeerMissingError) return error;
|
|
1903
|
+
if (isPeerResolutionFailure(error)) {
|
|
1904
|
+
return new PeerMissingError("@agentskit/*", layer1InstallHint());
|
|
1905
|
+
}
|
|
1906
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1907
|
+
if (/fetch failed|econnrefused|enotfound|network|socket/i.test(message)) {
|
|
1908
|
+
return new Error(
|
|
1909
|
+
`Intelligence provider request failed (${message}).
|
|
1910
|
+
Check the model server (e.g. \`ollama serve\`) and Layer 1 peers:
|
|
1911
|
+
${layer1InstallHint()}`
|
|
1912
|
+
);
|
|
1913
|
+
}
|
|
1914
|
+
return error instanceof Error ? error : new Error(message);
|
|
1915
|
+
};
|
|
1916
|
+
var handoffFirstHint = (index, config, question) => {
|
|
1917
|
+
if (config.intelligence?.chat?.handoffFirst === false) return void 0;
|
|
1918
|
+
const tokens = question.toLowerCase().split(/[^a-z0-9@/_-]+/).filter((t) => t.length >= 2);
|
|
1919
|
+
const packages = index.lookup?.packages ?? [];
|
|
1920
|
+
const hit = packages.find((id) => tokens.includes(id.toLowerCase()));
|
|
1921
|
+
if (!hit) return void 0;
|
|
1922
|
+
try {
|
|
1923
|
+
const handoff = runQuery(index, config, { kind: "ownership", id: hit, agent: true });
|
|
1924
|
+
return [
|
|
1925
|
+
"## Deterministic AgentHandoff (prefer for edits)",
|
|
1926
|
+
"```json",
|
|
1927
|
+
JSON.stringify(handoff, null, 2),
|
|
1928
|
+
"```",
|
|
1929
|
+
"Use startHere / editRoots / checks before broad exploration."
|
|
1930
|
+
].join("\n");
|
|
1931
|
+
} catch {
|
|
1932
|
+
return void 0;
|
|
1933
|
+
}
|
|
1934
|
+
};
|
|
1935
|
+
var systemPrompt = (index, config, provider, model, question) => {
|
|
1936
|
+
const prefix = question ? handoffFirstHint(index, config, question) : void 0;
|
|
1937
|
+
return [
|
|
1938
|
+
"You are a documentation assistant for this repository (doc-bridge).",
|
|
1939
|
+
"Prefer deterministic AgentHandoff fields for code edits.",
|
|
1940
|
+
"Cite file paths from retrieved context.",
|
|
1941
|
+
`Provider: ${provider}${model ? ` / ${model}` : ""}.`,
|
|
1942
|
+
"If unsure, suggest: ak-docs query ownership <id> --agent or MCP handoff.resolve.",
|
|
1943
|
+
prefix ?? ""
|
|
1944
|
+
].filter(Boolean).join("\n\n");
|
|
1945
|
+
};
|
|
1946
|
+
var runChatOnce = async (root, config, index, question) => {
|
|
1947
|
+
try {
|
|
1948
|
+
const { adapter, provider, model } = await resolveIntelligenceRuntime(config);
|
|
1949
|
+
const core = await importPeer("@agentskit/core");
|
|
1950
|
+
const rag = await createDocBridgeRag(root, config, index);
|
|
1951
|
+
await rag.ingest();
|
|
1952
|
+
const controller = core.createChatController({
|
|
1953
|
+
adapter,
|
|
1954
|
+
retriever: rag.retriever,
|
|
1955
|
+
system: systemPrompt(index, config, provider, model, question)
|
|
1956
|
+
});
|
|
1957
|
+
const result = await controller.send(question);
|
|
1958
|
+
const content = typeof result?.content === "string" ? result.content : JSON.stringify(result, null, 2);
|
|
1959
|
+
return {
|
|
1960
|
+
content,
|
|
1961
|
+
handoffPrefixed: Boolean(handoffFirstHint(index, config, question))
|
|
1962
|
+
};
|
|
1963
|
+
} catch (error) {
|
|
1964
|
+
throw wrapIntelligenceError(error);
|
|
1965
|
+
}
|
|
1966
|
+
};
|
|
1967
|
+
var startInkChat = async (root, config, index) => {
|
|
1968
|
+
try {
|
|
1969
|
+
const { adapter, provider, model } = await resolveIntelligenceRuntime(config);
|
|
1970
|
+
const rag = await createDocBridgeRag(root, config, index);
|
|
1971
|
+
await rag.ingest();
|
|
1972
|
+
const React = await importPeer("react");
|
|
1973
|
+
const ink = await importPeer("ink");
|
|
1974
|
+
const agentskitInk = await importPeer("@agentskit/ink");
|
|
1975
|
+
const App = () => {
|
|
1976
|
+
const chat = agentskitInk.useChat({
|
|
1977
|
+
adapter,
|
|
1978
|
+
retriever: rag.retriever,
|
|
1979
|
+
system: systemPrompt(index, config, provider, model)
|
|
1980
|
+
});
|
|
1981
|
+
return React.createElement(
|
|
1982
|
+
agentskitInk.ChatContainer,
|
|
1983
|
+
null,
|
|
1984
|
+
...chat.messages.map(
|
|
1985
|
+
(msg) => React.createElement(agentskitInk.Message, {
|
|
1986
|
+
key: msg.id,
|
|
1987
|
+
message: msg
|
|
1988
|
+
})
|
|
1989
|
+
),
|
|
1990
|
+
React.createElement(agentskitInk.InputBar, {
|
|
1991
|
+
chat,
|
|
1992
|
+
placeholder: "Ask about project docs (handoff-first RAG)\u2026"
|
|
1993
|
+
})
|
|
1994
|
+
);
|
|
1995
|
+
};
|
|
1996
|
+
const instance = ink.render(React.createElement(App));
|
|
1997
|
+
await instance.waitUntilExit();
|
|
1998
|
+
} catch (error) {
|
|
1999
|
+
throw wrapIntelligenceError(error);
|
|
2000
|
+
}
|
|
2001
|
+
};
|
|
2002
|
+
|
|
2003
|
+
// src/memory/ingest.ts
|
|
2004
|
+
import { existsSync as existsSync8, readFileSync as readFileSync12 } from "fs";
|
|
2005
|
+
import { join as join13 } from "path";
|
|
2006
|
+
var memoryFact = (raw, id) => firstParagraph(raw) ?? firstHeading(raw) ?? id;
|
|
2007
|
+
var relativePath = (root, abs) => toPosix(abs).replace(`${toPosix(root)}/`, "");
|
|
2008
|
+
var ingestMarkdownDir = (root, dir, source, confidence) => {
|
|
2009
|
+
if (!existsSync8(dir)) return [];
|
|
2010
|
+
return walkFiles(dir, { extensions: [".md", ".mdc"] }).map((abs) => {
|
|
2011
|
+
const rel = relativePath(root, abs);
|
|
2012
|
+
const raw = readFileSync12(abs, "utf8");
|
|
2013
|
+
const id = slugFromPath(rel);
|
|
2014
|
+
return {
|
|
2015
|
+
schemaVersion: 1,
|
|
2016
|
+
id,
|
|
2017
|
+
source,
|
|
2018
|
+
rawPath: rel,
|
|
2019
|
+
fact: memoryFact(raw, id),
|
|
2020
|
+
suggestedType: "project",
|
|
2021
|
+
confidence,
|
|
2022
|
+
references: []
|
|
2023
|
+
};
|
|
2024
|
+
});
|
|
2025
|
+
};
|
|
2026
|
+
var ingestCursorRules = (root) => {
|
|
2027
|
+
const dir = join13(root, ".cursor", "rules");
|
|
2028
|
+
return ingestMarkdownDir(root, dir, "cursor", 0.6);
|
|
2029
|
+
};
|
|
2030
|
+
var ingestAgentMemory = (root) => ingestMarkdownDir(root, join13(root, ".agent-memory"), "agent-memory", 0.7);
|
|
2031
|
+
var ingestMemoryCandidates = (root) => [
|
|
2032
|
+
...ingestAgentMemory(root),
|
|
2033
|
+
...ingestCursorRules(root)
|
|
2034
|
+
];
|
|
2035
|
+
|
|
2036
|
+
// src/memory/pipeline.ts
|
|
2037
|
+
var packageOwnership = /\b(?:package|module)\s+([a-z0-9._/-]+)\s+(?:owns|owner|responsible|routes?)\b/i;
|
|
2038
|
+
var secretPattern = /\b(?:api[_-]?key|token|secret|password)\s*[:=]\s*\S+/i;
|
|
2039
|
+
var privateEmailPattern = /\b[A-Z0-9._%+-]+@(?!example\.com\b)[A-Z0-9.-]+\.[A-Z]{2,}\b/i;
|
|
2040
|
+
var classifyRoute = (fact) => {
|
|
2041
|
+
if (/\b(noise|scratch|ignore|discard)\b/i.test(fact)) {
|
|
2042
|
+
return { route: "discard", reason: "marked as noise" };
|
|
2043
|
+
}
|
|
2044
|
+
if (/\b(playbook|pattern|principle|best practice)\b/i.test(fact)) {
|
|
2045
|
+
return { route: "playbook", reason: "generalizable pattern" };
|
|
2046
|
+
}
|
|
2047
|
+
if (/\b(user-facing|human guide|docs site|tutorial|guide)\b/i.test(fact)) {
|
|
2048
|
+
return { route: "human", reason: "user-facing documentation" };
|
|
2049
|
+
}
|
|
2050
|
+
return { route: "agent", reason: "project convention or ownership note" };
|
|
2051
|
+
};
|
|
2052
|
+
var classifyMemoryCandidates = (candidates, index) => candidates.map((candidate) => {
|
|
2053
|
+
const duplicate = searchIndex(index, candidate.fact, 1)[0];
|
|
2054
|
+
const targetMatch = packageOwnership.exec(candidate.fact);
|
|
2055
|
+
const route = classifyRoute(candidate.fact);
|
|
2056
|
+
return {
|
|
2057
|
+
candidate,
|
|
2058
|
+
...route,
|
|
2059
|
+
...targetMatch?.[1] ? { target: targetMatch[1] } : {},
|
|
2060
|
+
...duplicate && duplicate.score >= 16 ? { duplicateOf: duplicate.path } : {}
|
|
2061
|
+
};
|
|
2062
|
+
});
|
|
2063
|
+
var scanMemorySafety = (classifications) => classifications.flatMap(({ candidate }) => {
|
|
2064
|
+
const findings = [];
|
|
2065
|
+
if (secretPattern.test(candidate.fact)) {
|
|
2066
|
+
findings.push({
|
|
2067
|
+
candidateId: candidate.id,
|
|
2068
|
+
kind: "secret",
|
|
2069
|
+
message: "Potential secret-like value in memory fact"
|
|
2070
|
+
});
|
|
2071
|
+
}
|
|
2072
|
+
if (privateEmailPattern.test(candidate.fact)) {
|
|
2073
|
+
findings.push({
|
|
2074
|
+
candidateId: candidate.id,
|
|
2075
|
+
kind: "private-email",
|
|
2076
|
+
message: "Potential private email in memory fact"
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
return findings;
|
|
2080
|
+
});
|
|
2081
|
+
var draftMemoryPromotion = (classifications) => {
|
|
2082
|
+
const findings = scanMemorySafety(classifications);
|
|
2083
|
+
const safe = findings.length === 0;
|
|
2084
|
+
const lines = classifications.map(
|
|
2085
|
+
({ candidate, route, reason, target, duplicateOf }) => [
|
|
2086
|
+
`- ${candidate.id}: ${route}`,
|
|
2087
|
+
`reason=${reason}`,
|
|
2088
|
+
target ? `target=${target}` : void 0,
|
|
2089
|
+
duplicateOf ? `duplicateOf=${duplicateOf}` : void 0,
|
|
2090
|
+
`fact=${candidate.fact}`
|
|
2091
|
+
].filter(Boolean).join(" | ")
|
|
2092
|
+
);
|
|
2093
|
+
return {
|
|
2094
|
+
ok: safe,
|
|
2095
|
+
title: "Draft doc-bridge memory promotion",
|
|
2096
|
+
body: [
|
|
2097
|
+
"# Draft doc-bridge memory promotion",
|
|
2098
|
+
"",
|
|
2099
|
+
safe ? "Safety scan: pass." : "Safety scan: blocked. Redact findings before PR.",
|
|
2100
|
+
"",
|
|
2101
|
+
"## Candidates",
|
|
2102
|
+
"",
|
|
2103
|
+
...lines,
|
|
2104
|
+
"",
|
|
2105
|
+
"## Policy",
|
|
2106
|
+
"",
|
|
2107
|
+
"- Draft only; never auto-merge.",
|
|
2108
|
+
"- Run doc-bridge gates before opening a PR."
|
|
2109
|
+
].join("\n"),
|
|
2110
|
+
findings,
|
|
2111
|
+
classifications: [...classifications]
|
|
2112
|
+
};
|
|
2113
|
+
};
|
|
2114
|
+
|
|
2115
|
+
// src/mcp/server.ts
|
|
2116
|
+
import { readFileSync as readFileSync13, realpathSync } from "fs";
|
|
2117
|
+
import { relative, resolve as resolve5 } from "path";
|
|
2118
|
+
import { z as z5, ZodError } from "zod";
|
|
2119
|
+
var MCP_TOOLS = [
|
|
2120
|
+
{
|
|
2121
|
+
name: "handoff.resolve",
|
|
2122
|
+
description: "Resolve a package or ownership id to an AgentHandoff.",
|
|
2123
|
+
inputSchema: {
|
|
2124
|
+
type: "object",
|
|
2125
|
+
properties: { id: { type: "string" }, kind: { type: "string", enum: ["package", "ownership"] } },
|
|
2126
|
+
required: ["id"]
|
|
2127
|
+
}
|
|
2128
|
+
},
|
|
2129
|
+
{
|
|
2130
|
+
name: "doc.search",
|
|
2131
|
+
description: "Search the deterministic doc-bridge index.",
|
|
2132
|
+
inputSchema: {
|
|
2133
|
+
type: "object",
|
|
2134
|
+
properties: { term: { type: "string" }, limit: { type: "number" } },
|
|
2135
|
+
required: ["term"]
|
|
2136
|
+
}
|
|
2137
|
+
},
|
|
2138
|
+
{
|
|
2139
|
+
name: "doc.get",
|
|
2140
|
+
description: "Read an indexed agent documentation file by id or path.",
|
|
2141
|
+
inputSchema: {
|
|
2142
|
+
type: "object",
|
|
2143
|
+
properties: { id: { type: "string" }, path: { type: "string" } }
|
|
2144
|
+
}
|
|
2145
|
+
},
|
|
2146
|
+
{
|
|
2147
|
+
name: "gate.status",
|
|
2148
|
+
description: "Run the index-freshness gate.",
|
|
2149
|
+
inputSchema: { type: "object", properties: {} }
|
|
2150
|
+
},
|
|
2151
|
+
{
|
|
2152
|
+
name: "retriever.query",
|
|
2153
|
+
description: "Return local doc-bridge retriever chunks for a query.",
|
|
2154
|
+
inputSchema: {
|
|
2155
|
+
type: "object",
|
|
2156
|
+
properties: { query: { type: "string" }, limit: { type: "number" } },
|
|
2157
|
+
required: ["query"]
|
|
2158
|
+
}
|
|
2159
|
+
},
|
|
2160
|
+
{
|
|
2161
|
+
name: "memory.classify",
|
|
2162
|
+
description: "Classify local memory candidates into agent/human/playbook/discard routes.",
|
|
2163
|
+
inputSchema: { type: "object", properties: {} }
|
|
2164
|
+
},
|
|
2165
|
+
{
|
|
2166
|
+
name: "memory.promoteDraft",
|
|
2167
|
+
description: "Build a safe draft promotion body for local memory candidates.",
|
|
2168
|
+
inputSchema: { type: "object", properties: {} }
|
|
2169
|
+
},
|
|
2170
|
+
{
|
|
2171
|
+
name: "registry.topology",
|
|
2172
|
+
description: "Return the doc-curator registry topology.",
|
|
2173
|
+
inputSchema: { type: "object", properties: {} }
|
|
2174
|
+
}
|
|
2175
|
+
];
|
|
2176
|
+
var asRecord = (value) => value && typeof value === "object" ? value : {};
|
|
2177
|
+
var HandoffResolveArgsSchema = z5.object({
|
|
2178
|
+
id: z5.string().min(1),
|
|
2179
|
+
kind: z5.enum(["package", "ownership"]).optional()
|
|
2180
|
+
});
|
|
2181
|
+
var DocSearchArgsSchema = z5.object({
|
|
2182
|
+
term: z5.string().min(1),
|
|
2183
|
+
limit: z5.number().int().positive().max(100).optional()
|
|
2184
|
+
});
|
|
2185
|
+
var RetrieverQueryArgsSchema = z5.object({
|
|
2186
|
+
query: z5.string().min(1),
|
|
2187
|
+
limit: z5.number().int().positive().max(100).optional()
|
|
2188
|
+
});
|
|
2189
|
+
var DocGetArgsSchema = z5.object({
|
|
2190
|
+
id: z5.string().min(1).optional(),
|
|
2191
|
+
path: z5.string().min(1).optional()
|
|
2192
|
+
}).refine((args) => args.id || args.path, "doc.get requires id or path");
|
|
2193
|
+
var parseToolArgs = (tool, schema, value) => {
|
|
2194
|
+
try {
|
|
2195
|
+
return schema.parse(value);
|
|
2196
|
+
} catch (error) {
|
|
2197
|
+
if (error instanceof ZodError) {
|
|
2198
|
+
throw new Error(`${tool} invalid arguments: ${error.issues.map((issue) => issue.message).join(", ")}`);
|
|
2199
|
+
}
|
|
2200
|
+
throw error;
|
|
2201
|
+
}
|
|
2202
|
+
};
|
|
2203
|
+
var textResult = (value) => ({
|
|
2204
|
+
content: [
|
|
2205
|
+
{
|
|
2206
|
+
type: "text",
|
|
2207
|
+
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
2208
|
+
}
|
|
2209
|
+
]
|
|
2210
|
+
});
|
|
2211
|
+
var findDocPath = (index, args) => {
|
|
2212
|
+
if (args.path) {
|
|
2213
|
+
const doc2 = index.knowledge.find((entry) => entry.path === args.path);
|
|
2214
|
+
if (!doc2) throw new Error(`Unknown indexed doc path "${args.path}"`);
|
|
2215
|
+
return doc2.path;
|
|
2216
|
+
}
|
|
2217
|
+
const id = args.id ?? "";
|
|
2218
|
+
const doc = index.knowledge.find((entry) => entry.id === args.id);
|
|
2219
|
+
if (!doc) throw new Error(`Unknown doc id "${id}"`);
|
|
2220
|
+
return doc.path;
|
|
2221
|
+
};
|
|
2222
|
+
var resolveDocPath = (root, relPath) => {
|
|
2223
|
+
const rootAbs = realpathSync.native(root);
|
|
2224
|
+
const unresolved = resolve5(rootAbs, relPath);
|
|
2225
|
+
const unresolvedRel = relative(rootAbs, unresolved);
|
|
2226
|
+
if (unresolvedRel.startsWith("..")) throw new Error("doc.get path escapes project root");
|
|
2227
|
+
const abs = realpathSync.native(unresolved);
|
|
2228
|
+
const rel = relative(rootAbs, abs);
|
|
2229
|
+
if (rel.startsWith("..")) throw new Error("doc.get path escapes project root");
|
|
2230
|
+
return abs;
|
|
2231
|
+
};
|
|
2232
|
+
var handleMcpRequest = (ctx, request) => {
|
|
2233
|
+
if (request.method === "initialize") {
|
|
2234
|
+
return {
|
|
2235
|
+
protocolVersion: "2024-11-05",
|
|
2236
|
+
capabilities: { tools: {} },
|
|
2237
|
+
serverInfo: { name: "ak-docs", version: "0.1.0-alpha.1" }
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
if (request.method === "tools/list") return { tools: MCP_TOOLS };
|
|
2241
|
+
if (request.method === "tools/call") {
|
|
2242
|
+
const params = asRecord(request.params);
|
|
2243
|
+
const name = params.name;
|
|
2244
|
+
const args = asRecord(params.arguments);
|
|
2245
|
+
const index = () => ctx.loadIndex?.() ?? loadDocBridgeIndex(ctx.root, ctx.config);
|
|
2246
|
+
if (name === "handoff.resolve") {
|
|
2247
|
+
const parsed = parseToolArgs("handoff.resolve", HandoffResolveArgsSchema, args);
|
|
2248
|
+
return textResult(
|
|
2249
|
+
runQuery(index(), ctx.config, {
|
|
2250
|
+
kind: parsed.kind === "package" ? "package" : "ownership",
|
|
2251
|
+
id: parsed.id,
|
|
2252
|
+
agent: true
|
|
2253
|
+
})
|
|
2254
|
+
);
|
|
2255
|
+
}
|
|
2256
|
+
if (name === "doc.search") {
|
|
2257
|
+
const parsed = parseToolArgs("doc.search", DocSearchArgsSchema, args);
|
|
2258
|
+
return textResult(searchIndex(index(), parsed.term, parsed.limit ?? 20));
|
|
2259
|
+
}
|
|
2260
|
+
if (name === "doc.get") {
|
|
2261
|
+
const relPath = findDocPath(index(), parseToolArgs("doc.get", DocGetArgsSchema, args));
|
|
2262
|
+
return textResult(readFileSync13(resolveDocPath(ctx.root, relPath), "utf8"));
|
|
2263
|
+
}
|
|
2264
|
+
if (name === "gate.status") return textResult(runGates(ctx.root, ctx.config));
|
|
2265
|
+
if (name === "retriever.query") {
|
|
2266
|
+
const parsed = parseToolArgs("retriever.query", RetrieverQueryArgsSchema, args);
|
|
2267
|
+
return textResult(retrieveDocBridgeChunks(index(), parsed.query, parsed.limit ? { limit: parsed.limit } : {}));
|
|
2268
|
+
}
|
|
2269
|
+
if (name === "memory.classify") {
|
|
2270
|
+
return textResult(classifyMemoryCandidates(ingestMemoryCandidates(ctx.root), index()));
|
|
2271
|
+
}
|
|
2272
|
+
if (name === "memory.promoteDraft") {
|
|
2273
|
+
return textResult(draftMemoryPromotion(classifyMemoryCandidates(ingestMemoryCandidates(ctx.root), index())));
|
|
2274
|
+
}
|
|
2275
|
+
if (name === "registry.topology") {
|
|
2276
|
+
return textResult({
|
|
2277
|
+
id: "doc-curator",
|
|
2278
|
+
delegates: ["docs-chat", "knowledge-promoter", "code-review"],
|
|
2279
|
+
tools: ["handoff.resolve", "doc.search", "doc.get", "gate.status", "retriever.query"],
|
|
2280
|
+
steps: ["classify", "draft", "verify", "review"],
|
|
2281
|
+
mergePolicy: { autoMerge: false, requiresHuman: true }
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
throw new Error(`Unknown tool "${String(name)}"`);
|
|
2285
|
+
}
|
|
2286
|
+
if (request.method?.startsWith("notifications/")) return void 0;
|
|
2287
|
+
throw new Error(`Unsupported MCP method "${request.method ?? ""}"`);
|
|
2288
|
+
};
|
|
2289
|
+
var writeFrame = (payload) => {
|
|
2290
|
+
const body = JSON.stringify(payload);
|
|
2291
|
+
process.stdout.write(`Content-Length: ${Buffer.byteLength(body)}\r
|
|
2292
|
+
\r
|
|
2293
|
+
${body}`);
|
|
2294
|
+
};
|
|
2295
|
+
var respond = (ctx, request) => {
|
|
2296
|
+
if (request.id === void 0) {
|
|
2297
|
+
try {
|
|
2298
|
+
handleMcpRequest(ctx, request);
|
|
2299
|
+
} catch {
|
|
2300
|
+
}
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
try {
|
|
2304
|
+
const result = handleMcpRequest(ctx, request);
|
|
2305
|
+
writeFrame({ jsonrpc: "2.0", id: request.id, result: result ?? {} });
|
|
2306
|
+
} catch (error) {
|
|
2307
|
+
writeFrame({
|
|
2308
|
+
jsonrpc: "2.0",
|
|
2309
|
+
id: request.id,
|
|
2310
|
+
error: { code: -32e3, message: error instanceof Error ? error.message : String(error) }
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
};
|
|
2314
|
+
var startMcpStdioServer = (ctx) => {
|
|
2315
|
+
let buffer = Buffer.alloc(0);
|
|
2316
|
+
process.stdin.on("data", (chunk) => {
|
|
2317
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
2318
|
+
while (true) {
|
|
2319
|
+
const headerEnd = buffer.indexOf("\r\n\r\n");
|
|
2320
|
+
if (headerEnd === -1) return;
|
|
2321
|
+
const header = buffer.subarray(0, headerEnd).toString("utf8");
|
|
2322
|
+
const match = /content-length:\s*(\d+)/i.exec(header);
|
|
2323
|
+
if (!match?.[1]) {
|
|
2324
|
+
buffer = buffer.subarray(headerEnd + 4);
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
2327
|
+
const length = Number(match[1]);
|
|
2328
|
+
const bodyStart = headerEnd + 4;
|
|
2329
|
+
const bodyEnd = bodyStart + length;
|
|
2330
|
+
if (buffer.length < bodyEnd) return;
|
|
2331
|
+
const raw = buffer.subarray(bodyStart, bodyEnd).toString("utf8");
|
|
2332
|
+
buffer = buffer.subarray(bodyEnd);
|
|
2333
|
+
respond(ctx, JSON.parse(raw));
|
|
2334
|
+
}
|
|
2335
|
+
});
|
|
2336
|
+
process.stdin.resume();
|
|
2337
|
+
};
|
|
2338
|
+
|
|
2339
|
+
// src/cli/program.ts
|
|
2340
|
+
var usage = `ak-docs \u2014 human\u2194agent documentation bridge (@agentskit/doc-bridge)
|
|
2341
|
+
|
|
2342
|
+
Core (no API key):
|
|
2343
|
+
ak-docs init [--demo] [--scaffold-workspaces]
|
|
2344
|
+
ak-docs index
|
|
2345
|
+
ak-docs query <package|ownership|intent|change> <id> [--agent] [--text]
|
|
2346
|
+
ak-docs search <term> [--agent] [--text]
|
|
2347
|
+
ak-docs list <packages|intents|changes|knowledge> [--text]
|
|
2348
|
+
ak-docs ask [question] local consult (no LLM)
|
|
2349
|
+
ak-docs gate run [gate-id]
|
|
2350
|
+
ak-docs mcp
|
|
2351
|
+
ak-docs memory ingest|classify|promote
|
|
2352
|
+
ak-docs bootstrap agent-docs
|
|
2353
|
+
ak-docs validate-config | validate-handoff <file>
|
|
2354
|
+
|
|
2355
|
+
Intelligence (optional AgentsKit peers):
|
|
2356
|
+
ak-docs rag ingest|search <query>
|
|
2357
|
+
ak-docs chat terminal chat (Ink + RAG)
|
|
2358
|
+
ak-docs ask <question> --chat one-shot grounded answer
|
|
2359
|
+
|
|
2360
|
+
Advanced / ecosystem:
|
|
2361
|
+
ak-docs retrieve <query>
|
|
2362
|
+
ak-docs registry topology
|
|
2363
|
+
ak-docs playbook draft
|
|
2364
|
+
|
|
2365
|
+
Global flags:
|
|
2366
|
+
-h, --help --version
|
|
2367
|
+
--config <path> (project root = config file directory)
|
|
2368
|
+
--agent --json --text --chat --demo
|
|
2369
|
+
`;
|
|
2370
|
+
var QUERY_KINDS = /* @__PURE__ */ new Set(["package", "ownership", "intent", "change", "search"]);
|
|
2371
|
+
var LIST_KINDS = /* @__PURE__ */ new Set(["packages", "intents", "changes", "knowledge"]);
|
|
2372
|
+
var GATE_IDS = /* @__PURE__ */ new Set(["index-freshness", "human-guide-links", "okf-type", "docs-style"]);
|
|
2373
|
+
var parseArgs = (argv) => {
|
|
2374
|
+
const flags = /* @__PURE__ */ new Set();
|
|
2375
|
+
let configPath;
|
|
2376
|
+
const positional = [];
|
|
2377
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
2378
|
+
const arg = argv[i];
|
|
2379
|
+
if (!arg) continue;
|
|
2380
|
+
if (arg === "--config") {
|
|
2381
|
+
configPath = argv[i + 1];
|
|
2382
|
+
i += 1;
|
|
2383
|
+
continue;
|
|
2384
|
+
}
|
|
2385
|
+
if (arg.startsWith("-")) {
|
|
2386
|
+
flags.add(arg);
|
|
2387
|
+
continue;
|
|
2388
|
+
}
|
|
2389
|
+
positional.push(arg);
|
|
2390
|
+
}
|
|
2391
|
+
let command = "help";
|
|
2392
|
+
if (flags.has("--version") || flags.has("-V")) command = "version";
|
|
2393
|
+
else if (flags.has("--help") || flags.has("-h")) command = "help";
|
|
2394
|
+
else if (positional[0] === "validate-config") command = "validate-config";
|
|
2395
|
+
else if (positional[0] === "validate-handoff") command = "validate-handoff";
|
|
2396
|
+
else if (positional[0] === "init") command = "init";
|
|
2397
|
+
else if (positional[0] === "bootstrap") command = "bootstrap";
|
|
2398
|
+
else if (positional[0] === "memory") command = "memory";
|
|
2399
|
+
else if (positional[0] === "playbook") command = "playbook";
|
|
2400
|
+
else if (positional[0] === "registry") command = "registry";
|
|
2401
|
+
else if (positional[0] === "index") command = "index";
|
|
2402
|
+
else if (positional[0] === "gate") command = "gate";
|
|
2403
|
+
else if (positional[0] === "mcp") command = "mcp";
|
|
2404
|
+
else if (positional[0] === "query") command = "query";
|
|
2405
|
+
else if (positional[0] === "search") command = "search";
|
|
2406
|
+
else if (positional[0] === "retrieve") command = "retrieve";
|
|
2407
|
+
else if (positional[0] === "ask") command = "ask";
|
|
2408
|
+
else if (positional[0] === "chat") command = "chat";
|
|
2409
|
+
else if (positional[0] === "rag") command = "rag";
|
|
2410
|
+
else if (positional[0] === "list") command = "list";
|
|
2411
|
+
return { command, flags, configPath, positional };
|
|
2412
|
+
};
|
|
2413
|
+
var writeJson = (payload) => {
|
|
2414
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}
|
|
2415
|
+
`);
|
|
2416
|
+
};
|
|
2417
|
+
var writeLines = (lines) => {
|
|
2418
|
+
process.stdout.write(lines.length ? `${lines.join("\n")}
|
|
2419
|
+
` : "");
|
|
2420
|
+
};
|
|
2421
|
+
var textValue = (value) => {
|
|
2422
|
+
if (value === null || value === void 0) return "Not found";
|
|
2423
|
+
if (typeof value === "string") return value;
|
|
2424
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
2425
|
+
return JSON.stringify(value, null, 2);
|
|
2426
|
+
};
|
|
2427
|
+
var writeTextQuery = (payload) => {
|
|
2428
|
+
if (!payload || typeof payload !== "object" || !("data" in payload)) {
|
|
2429
|
+
writeLines([textValue(payload)]);
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
const result = payload;
|
|
2433
|
+
if (result.type === "search") {
|
|
2434
|
+
const data = result.data;
|
|
2435
|
+
const matches = data.matches ?? [];
|
|
2436
|
+
writeLines([
|
|
2437
|
+
`Search: ${String(data.term ?? "")}`,
|
|
2438
|
+
`Matches: ${String(data.count ?? matches.length)}`,
|
|
2439
|
+
...matches.map(
|
|
2440
|
+
(match) => [match.type, match.id, match.path, match.summary].filter(Boolean).join(" ")
|
|
2441
|
+
)
|
|
2442
|
+
]);
|
|
2443
|
+
return;
|
|
2444
|
+
}
|
|
2445
|
+
writeLines([textValue(result.data)]);
|
|
2446
|
+
};
|
|
2447
|
+
var writeTextSearch = (term, matches) => {
|
|
2448
|
+
writeLines([
|
|
2449
|
+
`Search: ${term}`,
|
|
2450
|
+
`Matches: ${matches.length}`,
|
|
2451
|
+
...matches.map(
|
|
2452
|
+
(match) => [match.type, match.id, match.path, match.summary].filter(Boolean).join(" ")
|
|
2453
|
+
)
|
|
2454
|
+
]);
|
|
2455
|
+
};
|
|
2456
|
+
var writeAsk = (question, matches, index) => {
|
|
2457
|
+
const best = matches[0];
|
|
2458
|
+
const owner = matches.find((match) => match.type === "ownership" || index.lookup?.ownership?.[match.id]);
|
|
2459
|
+
const bestQuery = owner ? `ak-docs query ownership ${owner.id} --agent` : "ak-docs list knowledge --text";
|
|
2460
|
+
writeLines([
|
|
2461
|
+
`Question: ${question}`,
|
|
2462
|
+
best ? `Best match: ${best.type} ${best.id} (${best.path})` : "Best match: none",
|
|
2463
|
+
"",
|
|
2464
|
+
"Matches:",
|
|
2465
|
+
...matches.length ? matches.slice(0, 5).map(
|
|
2466
|
+
(match) => [match.type, match.id, match.path, match.summary].filter(Boolean).join(" ")
|
|
2467
|
+
) : ["No local matches. Try: ak-docs search <term>"],
|
|
2468
|
+
"",
|
|
2469
|
+
"Next commands:",
|
|
2470
|
+
...best ? [
|
|
2471
|
+
`ak-docs search "${question}" --agent`,
|
|
2472
|
+
bestQuery
|
|
2473
|
+
] : ["ak-docs list knowledge --text"]
|
|
2474
|
+
]);
|
|
2475
|
+
};
|
|
2476
|
+
var readIndexedDoc = (root, config, idOrPath) => {
|
|
2477
|
+
const index = loadDocBridgeIndex(root, config);
|
|
2478
|
+
const entry = index.knowledge.find((doc) => doc.id === idOrPath || doc.path === idOrPath);
|
|
2479
|
+
if (!entry) throw new Error(`Unknown indexed doc "${idOrPath}". Try: search ${idOrPath}`);
|
|
2480
|
+
const abs = resolve6(root, entry.path);
|
|
2481
|
+
const rootAbs = resolve6(root);
|
|
2482
|
+
if (abs !== rootAbs && !abs.startsWith(`${rootAbs}/`)) {
|
|
2483
|
+
throw new Error(`Indexed doc escapes project root: ${entry.path}`);
|
|
2484
|
+
}
|
|
2485
|
+
return readFileSync14(abs, "utf8");
|
|
2486
|
+
};
|
|
2487
|
+
var runAskRepl = async (root, config) => {
|
|
2488
|
+
const index = loadDocBridgeIndex(root, config);
|
|
2489
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
2490
|
+
try {
|
|
2491
|
+
for (; ; ) {
|
|
2492
|
+
const line = (await rl.question("ak-docs> ")).trim();
|
|
2493
|
+
if (!line) continue;
|
|
2494
|
+
if (line === "exit" || line === "quit") return 0;
|
|
2495
|
+
const [command, ...rest] = line.split(/\s+/);
|
|
2496
|
+
const value = rest.join(" ").trim();
|
|
2497
|
+
try {
|
|
2498
|
+
if (command === "search") {
|
|
2499
|
+
writeTextSearch(value, searchIndex(index, value));
|
|
2500
|
+
} else if (command === "read" || command === "open") {
|
|
2501
|
+
process.stdout.write(`${readIndexedDoc(root, config, value).slice(0, 8e3)}
|
|
2502
|
+
`);
|
|
2503
|
+
} else if (command === "resolve") {
|
|
2504
|
+
writeJson(runQuery(index, config, { kind: "ownership", id: value, agent: true }));
|
|
2505
|
+
} else if (command === "gate") {
|
|
2506
|
+
const gateId = value || void 0;
|
|
2507
|
+
writeJson(runGates(root, config, gateId ? [gateId] : void 0));
|
|
2508
|
+
} else {
|
|
2509
|
+
writeAsk(line, searchIndex(index, line, 8), index);
|
|
2510
|
+
}
|
|
2511
|
+
} catch (error) {
|
|
2512
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
2513
|
+
`);
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
} finally {
|
|
2517
|
+
rl.close();
|
|
2518
|
+
}
|
|
2519
|
+
};
|
|
2520
|
+
var wantsTextOutput = (flags, config) => !flags.has("--agent") && (flags.has("--text") || !flags.has("--json") && config.surfaces?.cli?.defaultFormat === "text");
|
|
2521
|
+
var loadProject = (configPath) => {
|
|
2522
|
+
const loadOpts = configPath ? { explicitPath: configPath } : {};
|
|
2523
|
+
const { config, path } = loadConfig(loadOpts);
|
|
2524
|
+
parseDocBridgeConfig(config);
|
|
2525
|
+
const root = projectRootFromConfigPath(path, config.project?.root);
|
|
2526
|
+
return { config, configPath: path, root };
|
|
2527
|
+
};
|
|
2528
|
+
var indexDiagnostics = (config, result) => {
|
|
2529
|
+
const diagnostics = [];
|
|
2530
|
+
const onlyDoc = result.index.knowledge.length === 1 ? result.index.knowledge[0] : void 0;
|
|
2531
|
+
if (onlyDoc?.path === config.corpus.agent.index) {
|
|
2532
|
+
diagnostics.push(
|
|
2533
|
+
`Only the starter ${config.corpus.agent.index} was indexed.`,
|
|
2534
|
+
`Add agent docs under ${config.corpus.agent.root}/, then run ak-docs index again.`
|
|
2535
|
+
);
|
|
2536
|
+
}
|
|
2537
|
+
const handoffCount = Object.keys(result.index.handoffs ?? {}).length;
|
|
2538
|
+
if (handoffCount === 0) {
|
|
2539
|
+
diagnostics.push(
|
|
2540
|
+
"No ownership handoffs yet. Add routing.options.ownership, package frontmatter (package + editRoot), or a monorepo plugin."
|
|
2541
|
+
);
|
|
2542
|
+
}
|
|
2543
|
+
return diagnostics;
|
|
2544
|
+
};
|
|
2545
|
+
var diagnosticNextCommands = (config, result) => {
|
|
2546
|
+
const handoffIds = Object.keys(result.index.handoffs ?? {});
|
|
2547
|
+
if (handoffIds[0]) {
|
|
2548
|
+
return [
|
|
2549
|
+
`ak-docs query package ${handoffIds[0]} --agent`,
|
|
2550
|
+
`ak-docs list packages --text`,
|
|
2551
|
+
"ak-docs mcp"
|
|
2552
|
+
];
|
|
2553
|
+
}
|
|
2554
|
+
return [
|
|
2555
|
+
`mkdir -p ${config.corpus.agent.root}/packages`,
|
|
2556
|
+
`edit ${config.corpus.agent.root}/packages/<module>.md # frontmatter: package + editRoot`,
|
|
2557
|
+
"ak-docs index"
|
|
2558
|
+
];
|
|
2559
|
+
};
|
|
2560
|
+
var writeIfMissing = (path, contents) => {
|
|
2561
|
+
if (existsSync9(path)) return false;
|
|
2562
|
+
mkdirSync2(dirname4(path), { recursive: true });
|
|
2563
|
+
writeFileSync2(path, contents, "utf8");
|
|
2564
|
+
return true;
|
|
2565
|
+
};
|
|
2566
|
+
var demoOwnership = {
|
|
2567
|
+
example: {
|
|
2568
|
+
path: "src",
|
|
2569
|
+
purpose: "Starter ownership target \u2014 replace with your real modules",
|
|
2570
|
+
checks: ["npm test"],
|
|
2571
|
+
agentDoc: "docs/for-agents/packages/example.md"
|
|
2572
|
+
}
|
|
2573
|
+
};
|
|
2574
|
+
var initConfigObject = (withDemo) => ({
|
|
2575
|
+
schemaVersion: 1,
|
|
2576
|
+
corpus: { agent: { root: "docs/for-agents" } },
|
|
2577
|
+
...withDemo ? {
|
|
2578
|
+
routing: {
|
|
2579
|
+
options: {
|
|
2580
|
+
ownership: demoOwnership
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
} : {},
|
|
2584
|
+
gates: { preset: "minimal" }
|
|
2585
|
+
});
|
|
2586
|
+
var initConfigContents = (path, withDemo) => {
|
|
2587
|
+
if (path.endsWith(".ts") || path.endsWith(".mts")) {
|
|
2588
|
+
return [
|
|
2589
|
+
"import { defineConfig } from '@agentskit/doc-bridge/config'",
|
|
2590
|
+
"",
|
|
2591
|
+
"export default defineConfig({",
|
|
2592
|
+
" schemaVersion: 1,",
|
|
2593
|
+
" corpus: { agent: { root: 'docs/for-agents' } },",
|
|
2594
|
+
...withDemo ? [
|
|
2595
|
+
" routing: {",
|
|
2596
|
+
" options: {",
|
|
2597
|
+
" ownership: {",
|
|
2598
|
+
" example: { path: 'src', purpose: 'Starter ownership target', checks: ['npm test'], agentDoc: 'docs/for-agents/packages/example.md' },",
|
|
2599
|
+
" },",
|
|
2600
|
+
" },",
|
|
2601
|
+
" },"
|
|
2602
|
+
] : [],
|
|
2603
|
+
" gates: { preset: 'minimal' },",
|
|
2604
|
+
"})",
|
|
2605
|
+
""
|
|
2606
|
+
].join("\n");
|
|
2607
|
+
}
|
|
2608
|
+
return `${JSON.stringify(initConfigObject(withDemo), null, 2)}
|
|
2609
|
+
`;
|
|
2610
|
+
};
|
|
2611
|
+
var exampleAgentDoc = `---
|
|
2612
|
+
type: package
|
|
2613
|
+
package: example
|
|
2614
|
+
editRoot: src
|
|
2615
|
+
checks: [npm test]
|
|
2616
|
+
---
|
|
2617
|
+
|
|
2618
|
+
# example
|
|
2619
|
+
|
|
2620
|
+
Starter agent doc generated by \`ak-docs init --demo\`.
|
|
2621
|
+
|
|
2622
|
+
Describe ownership, boundaries, and how agents should change this module.
|
|
2623
|
+
|
|
2624
|
+
## Checks
|
|
2625
|
+
|
|
2626
|
+
- \`npm test\`
|
|
2627
|
+
`;
|
|
2628
|
+
var agentsMdSnippet = `# AGENTS.md
|
|
2629
|
+
|
|
2630
|
+
## Documentation routing (doc-bridge)
|
|
2631
|
+
|
|
2632
|
+
Before editing a package or module:
|
|
2633
|
+
|
|
2634
|
+
1. Run \`ak-docs query ownership <id> --agent\` (or MCP tool \`handoff.resolve\`)
|
|
2635
|
+
2. Read \`startHere\` and respect \`editRoots\` + \`checks\`
|
|
2636
|
+
3. Prefer agent docs under \`docs/for-agents/\`; human site links appear as \`humanDoc\`
|
|
2637
|
+
|
|
2638
|
+
Local consult without LLM: \`ak-docs ask "<question>"\`
|
|
2639
|
+
Optional grounded chat (AgentsKit peers): \`ak-docs chat\`
|
|
2640
|
+
`;
|
|
2641
|
+
var workspaceDocDraft = (id, path) => [
|
|
2642
|
+
"---",
|
|
2643
|
+
"type: package",
|
|
2644
|
+
"draft: true",
|
|
2645
|
+
`package: ${id}`,
|
|
2646
|
+
`editRoot: ${path}`,
|
|
2647
|
+
"---",
|
|
2648
|
+
"",
|
|
2649
|
+
`# ${id}`,
|
|
2650
|
+
"",
|
|
2651
|
+
"Draft generated by `ak-docs init --scaffold-workspaces`.",
|
|
2652
|
+
"",
|
|
2653
|
+
"## Ownership",
|
|
2654
|
+
"",
|
|
2655
|
+
`- Package: \`${path}\``,
|
|
2656
|
+
"",
|
|
2657
|
+
"## Notes",
|
|
2658
|
+
"",
|
|
2659
|
+
"- TODO: describe responsibility, boundaries, and checks.",
|
|
2660
|
+
""
|
|
2661
|
+
].join("\n");
|
|
2662
|
+
var scaffoldWorkspaceDocs = (root, config) => {
|
|
2663
|
+
const created = [];
|
|
2664
|
+
const skipped = [];
|
|
2665
|
+
for (const pkg of discoverPnpmPackages(root, config)) {
|
|
2666
|
+
const path = resolve6(root, config.corpus.agent.root, "packages", `${pkg.id}.md`);
|
|
2667
|
+
if (writeIfMissing(path, workspaceDocDraft(pkg.id, pkg.path))) created.push(path);
|
|
2668
|
+
else skipped.push(path);
|
|
2669
|
+
}
|
|
2670
|
+
return { created, skipped };
|
|
2671
|
+
};
|
|
2672
|
+
var bootstrapAgentDocs = (root, config) => {
|
|
2673
|
+
const created = [];
|
|
2674
|
+
const skipped = [];
|
|
2675
|
+
for (const doc of scanHumanDocRecords(root, config)) {
|
|
2676
|
+
const raw = readFileSync14(doc.path, "utf8");
|
|
2677
|
+
const body = raw.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
2678
|
+
const title = firstHeading(body) ?? doc.id;
|
|
2679
|
+
const description = firstParagraph(body);
|
|
2680
|
+
const draftPath = resolve6(root, config.corpus.agent.root, "human", `${doc.id}.md`);
|
|
2681
|
+
const draft = [
|
|
2682
|
+
"---",
|
|
2683
|
+
"type: knowledge",
|
|
2684
|
+
"draft: true",
|
|
2685
|
+
`id: ${doc.id}`,
|
|
2686
|
+
`humanDoc: ${doc.url}`,
|
|
2687
|
+
"---",
|
|
2688
|
+
"",
|
|
2689
|
+
`# ${title}`,
|
|
2690
|
+
"",
|
|
2691
|
+
"Draft generated by `ak-docs bootstrap agent-docs` from existing human docs.",
|
|
2692
|
+
"",
|
|
2693
|
+
...description ? ["## Source summary", "", description, ""] : [],
|
|
2694
|
+
"## Review checklist",
|
|
2695
|
+
"",
|
|
2696
|
+
"- TODO: confirm ownership, edit roots, and checks.",
|
|
2697
|
+
"- TODO: move this draft to the right agent-doc location if needed.",
|
|
2698
|
+
""
|
|
2699
|
+
].join("\n");
|
|
2700
|
+
if (writeIfMissing(draftPath, draft)) created.push(draftPath);
|
|
2701
|
+
else skipped.push(draftPath);
|
|
2702
|
+
}
|
|
2703
|
+
return { created, skipped };
|
|
2704
|
+
};
|
|
2705
|
+
var registryTopology = () => ({
|
|
2706
|
+
id: "doc-curator",
|
|
2707
|
+
delegates: ["docs-chat", "knowledge-promoter", "code-review"],
|
|
2708
|
+
tools: ["handoff.resolve", "doc.search", "doc.get", "gate.status", "retriever.query"],
|
|
2709
|
+
steps: ["classify", "draft", "verify", "review"],
|
|
2710
|
+
mergePolicy: { autoMerge: false, requiresHuman: true }
|
|
2711
|
+
});
|
|
2712
|
+
var runCli = (argv) => {
|
|
2713
|
+
const { command, flags, configPath, positional } = parseArgs(argv);
|
|
2714
|
+
if (command === "help") {
|
|
2715
|
+
process.stdout.write(usage);
|
|
2716
|
+
return 0;
|
|
2717
|
+
}
|
|
2718
|
+
if (command === "version") {
|
|
2719
|
+
process.stdout.write(`ak-docs ${PACKAGE_VERSION} (@agentskit/doc-bridge)
|
|
2720
|
+
`);
|
|
2721
|
+
return 0;
|
|
2722
|
+
}
|
|
2723
|
+
if (command === "validate-config") {
|
|
2724
|
+
try {
|
|
2725
|
+
const { config, path } = loadConfig(
|
|
2726
|
+
configPath ? { explicitPath: configPath } : {}
|
|
2727
|
+
);
|
|
2728
|
+
parseDocBridgeConfig(config);
|
|
2729
|
+
writeJson({ ok: true, path, schemaVersion: config.schemaVersion });
|
|
2730
|
+
return 0;
|
|
2731
|
+
} catch (error) {
|
|
2732
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
2733
|
+
`);
|
|
2734
|
+
return 1;
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
if (command === "validate-handoff") {
|
|
2738
|
+
const file = positional[1];
|
|
2739
|
+
if (!file) {
|
|
2740
|
+
process.stderr.write("Missing handoff JSON file path.\n");
|
|
2741
|
+
return 1;
|
|
2742
|
+
}
|
|
2743
|
+
try {
|
|
2744
|
+
const abs = resolve6(file);
|
|
2745
|
+
const raw = readFileSync14(abs, "utf8");
|
|
2746
|
+
const handoff = parseAgentHandoff(JSON.parse(raw));
|
|
2747
|
+
writeJson({ ok: true, handoff });
|
|
2748
|
+
return 0;
|
|
2749
|
+
} catch (error) {
|
|
2750
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
2751
|
+
`);
|
|
2752
|
+
return 1;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
if (command === "init") {
|
|
2756
|
+
const root = process.cwd();
|
|
2757
|
+
const withDemo = flags.has("--demo") || !flags.has("--no-demo");
|
|
2758
|
+
const configFile = resolve6(root, configPath ?? "doc-bridge.config.json");
|
|
2759
|
+
const docsIndex = resolve6(root, "docs/for-agents/INDEX.md");
|
|
2760
|
+
const exampleDoc = resolve6(root, "docs/for-agents/packages/example.md");
|
|
2761
|
+
const agentsMd = resolve6(root, "AGENTS.md");
|
|
2762
|
+
const configWritten = writeIfMissing(configFile, initConfigContents(configFile, withDemo));
|
|
2763
|
+
const indexWritten = writeIfMissing(
|
|
2764
|
+
docsIndex,
|
|
2765
|
+
withDemo ? "# Agent docs index\n\n- [example](./packages/example.md) \u2014 starter ownership target\n" : "# Agent docs index\n\nStart here for ownership, architecture, and task handoffs.\n"
|
|
2766
|
+
);
|
|
2767
|
+
const exampleWritten = withDemo ? writeIfMissing(exampleDoc, exampleAgentDoc) : false;
|
|
2768
|
+
const agentsWritten = writeIfMissing(agentsMd, agentsMdSnippet);
|
|
2769
|
+
if (withDemo) writeIfMissing(resolve6(root, "src/.gitkeep"), "");
|
|
2770
|
+
const scaffold = flags.has("--scaffold-workspaces") ? scaffoldWorkspaceDocs(root, loadProject(configFile).config) : void 0;
|
|
2771
|
+
writeJson({
|
|
2772
|
+
ok: true,
|
|
2773
|
+
configPath: configFile,
|
|
2774
|
+
demo: withDemo,
|
|
2775
|
+
created: {
|
|
2776
|
+
config: configWritten,
|
|
2777
|
+
index: indexWritten,
|
|
2778
|
+
...withDemo ? { exampleDoc: exampleWritten, srcStub: true } : {},
|
|
2779
|
+
agentsMd: agentsWritten,
|
|
2780
|
+
...scaffold ? { workspaceDocs: scaffold.created } : {}
|
|
2781
|
+
},
|
|
2782
|
+
...scaffold ? { skipped: { workspaceDocs: scaffold.skipped } } : {},
|
|
2783
|
+
nextCommands: withDemo ? ["ak-docs index", "ak-docs query package example --agent", "ak-docs list packages --text"] : ["ak-docs index", "ak-docs list knowledge --text"]
|
|
2784
|
+
});
|
|
2785
|
+
return 0;
|
|
2786
|
+
}
|
|
2787
|
+
if (command === "bootstrap") {
|
|
2788
|
+
if (positional[1] !== "agent-docs") {
|
|
2789
|
+
process.stderr.write("Usage: ak-docs bootstrap agent-docs [--config <path>]\n");
|
|
2790
|
+
return 1;
|
|
2791
|
+
}
|
|
2792
|
+
try {
|
|
2793
|
+
const { config, root } = loadProject(configPath);
|
|
2794
|
+
const result = bootstrapAgentDocs(root, config);
|
|
2795
|
+
writeJson({ ok: true, ...result });
|
|
2796
|
+
return 0;
|
|
2797
|
+
} catch (error) {
|
|
2798
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
2799
|
+
`);
|
|
2800
|
+
return 1;
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
if (command === "memory") {
|
|
2804
|
+
if (!["ingest", "classify", "promote"].includes(positional[1] ?? "")) {
|
|
2805
|
+
process.stderr.write("Usage: ak-docs memory <ingest|classify|promote> [--config <path>]\n");
|
|
2806
|
+
return 1;
|
|
2807
|
+
}
|
|
2808
|
+
try {
|
|
2809
|
+
const { config, root } = loadProject(configPath);
|
|
2810
|
+
const candidates = ingestMemoryCandidates(root);
|
|
2811
|
+
if (positional[1] === "ingest") {
|
|
2812
|
+
writeJson({ ok: true, count: candidates.length, candidates });
|
|
2813
|
+
return 0;
|
|
2814
|
+
}
|
|
2815
|
+
const index = loadDocBridgeIndex(root, config);
|
|
2816
|
+
const classifications = classifyMemoryCandidates(candidates, index);
|
|
2817
|
+
if (positional[1] === "classify") {
|
|
2818
|
+
writeJson({ ok: true, count: classifications.length, classifications });
|
|
2819
|
+
return 0;
|
|
2820
|
+
}
|
|
2821
|
+
const draft = draftMemoryPromotion(classifications);
|
|
2822
|
+
writeJson(draft);
|
|
2823
|
+
return draft.ok ? 0 : 1;
|
|
2824
|
+
} catch (error) {
|
|
2825
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
2826
|
+
`);
|
|
2827
|
+
return 1;
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
if (command === "registry") {
|
|
2831
|
+
if (positional[1] !== "topology") {
|
|
2832
|
+
process.stderr.write("Usage: ak-docs registry topology\n");
|
|
2833
|
+
return 1;
|
|
2834
|
+
}
|
|
2835
|
+
writeJson(registryTopology());
|
|
2836
|
+
return 0;
|
|
2837
|
+
}
|
|
2838
|
+
if (command === "playbook") {
|
|
2839
|
+
if (positional[1] !== "draft") {
|
|
2840
|
+
process.stderr.write("Usage: ak-docs playbook draft [--config <path>]\n");
|
|
2841
|
+
return 1;
|
|
2842
|
+
}
|
|
2843
|
+
try {
|
|
2844
|
+
const { config, root } = loadProject(configPath);
|
|
2845
|
+
const index = loadDocBridgeIndex(root, config);
|
|
2846
|
+
const draft = draftMemoryPromotion(classifyMemoryCandidates(ingestMemoryCandidates(root), index));
|
|
2847
|
+
writeJson({
|
|
2848
|
+
...draft,
|
|
2849
|
+
title: "Draft Playbook feedback promotion",
|
|
2850
|
+
pattern: "Doc Bridge Pattern"
|
|
2851
|
+
});
|
|
2852
|
+
return 0;
|
|
2853
|
+
} catch (error) {
|
|
2854
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
2855
|
+
`);
|
|
2856
|
+
return 1;
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
if (command === "index") {
|
|
2860
|
+
try {
|
|
2861
|
+
const { config, root } = loadProject(configPath);
|
|
2862
|
+
const result = buildDocBridgeIndex({ root, config });
|
|
2863
|
+
const diagnostics = indexDiagnostics(config, result);
|
|
2864
|
+
const handoffCount = Object.keys(result.index.handoffs ?? {}).length;
|
|
2865
|
+
writeJson({
|
|
2866
|
+
ok: true,
|
|
2867
|
+
indexPath: result.indexPath,
|
|
2868
|
+
...result.llmsTxtPath ? { llmsTxtPath: result.llmsTxtPath } : {},
|
|
2869
|
+
...result.capabilitiesPath ? { capabilitiesPath: result.capabilitiesPath } : {},
|
|
2870
|
+
contentHash: result.index.contentHash,
|
|
2871
|
+
knowledgeCount: result.index.knowledge.length,
|
|
2872
|
+
packageCount: result.index.lookup?.packages.length ?? 0,
|
|
2873
|
+
handoffCount,
|
|
2874
|
+
...diagnostics.length ? { diagnostics } : {},
|
|
2875
|
+
nextCommands: diagnosticNextCommands(config, result)
|
|
2876
|
+
});
|
|
2877
|
+
return 0;
|
|
2878
|
+
} catch (error) {
|
|
2879
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
2880
|
+
`);
|
|
2881
|
+
return 1;
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
if (command === "gate") {
|
|
2885
|
+
const action = positional[1];
|
|
2886
|
+
const gateId = positional[2];
|
|
2887
|
+
if (action !== "run") {
|
|
2888
|
+
process.stderr.write("Usage: ak-docs gate run [index-freshness]\n");
|
|
2889
|
+
return 1;
|
|
2890
|
+
}
|
|
2891
|
+
if (gateId && !GATE_IDS.has(gateId)) {
|
|
2892
|
+
process.stderr.write(
|
|
2893
|
+
`Unsupported gate "${gateId}". Supported gates: index-freshness, human-guide-links, okf-type, docs-style
|
|
2894
|
+
`
|
|
2895
|
+
);
|
|
2896
|
+
return 1;
|
|
2897
|
+
}
|
|
2898
|
+
try {
|
|
2899
|
+
const { config, root } = loadProject(configPath);
|
|
2900
|
+
const result = runGates(root, config, gateId ? [gateId] : void 0);
|
|
2901
|
+
writeJson(result);
|
|
2902
|
+
return result.ok ? 0 : 1;
|
|
2903
|
+
} catch (error) {
|
|
2904
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
2905
|
+
`);
|
|
2906
|
+
return 1;
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
if (command === "mcp") {
|
|
2910
|
+
try {
|
|
2911
|
+
const { config, root } = loadProject(configPath);
|
|
2912
|
+
startMcpStdioServer({ root, config });
|
|
2913
|
+
return void 0;
|
|
2914
|
+
} catch (error) {
|
|
2915
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
2916
|
+
`);
|
|
2917
|
+
return 1;
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
if (command === "query") {
|
|
2921
|
+
const kind = positional[1];
|
|
2922
|
+
const id = positional[2];
|
|
2923
|
+
if (!kind || !QUERY_KINDS.has(kind) || kind === "search") {
|
|
2924
|
+
process.stderr.write("Usage: ak-docs query <package|ownership|intent|change> <id> [--agent]\n");
|
|
2925
|
+
return 1;
|
|
2926
|
+
}
|
|
2927
|
+
if (!id) {
|
|
2928
|
+
process.stderr.write(`Missing id for query kind "${kind}".
|
|
2929
|
+
`);
|
|
2930
|
+
return 1;
|
|
2931
|
+
}
|
|
2932
|
+
try {
|
|
2933
|
+
const { config, root } = loadProject(configPath);
|
|
2934
|
+
const index = loadDocBridgeIndex(root, config);
|
|
2935
|
+
const result = runQuery(index, config, { kind, id, agent: flags.has("--agent") });
|
|
2936
|
+
if (wantsTextOutput(flags, config)) writeTextQuery(result);
|
|
2937
|
+
else writeJson(result);
|
|
2938
|
+
return 0;
|
|
2939
|
+
} catch (error) {
|
|
2940
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
2941
|
+
`);
|
|
2942
|
+
return 1;
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
if (command === "search") {
|
|
2946
|
+
const term = positional.slice(1).join(" ").trim();
|
|
2947
|
+
if (!term) {
|
|
2948
|
+
process.stderr.write("Usage: ak-docs search <term> [--agent]\n");
|
|
2949
|
+
return 1;
|
|
2950
|
+
}
|
|
2951
|
+
try {
|
|
2952
|
+
const { config, root } = loadProject(configPath);
|
|
2953
|
+
const index = loadDocBridgeIndex(root, config);
|
|
2954
|
+
if (flags.has("--agent")) {
|
|
2955
|
+
const result = runQuery(index, config, { kind: "search", term, agent: true });
|
|
2956
|
+
writeJson(result);
|
|
2957
|
+
} else {
|
|
2958
|
+
const matches = searchIndex(index, term);
|
|
2959
|
+
if (wantsTextOutput(flags, config)) writeTextSearch(term, matches);
|
|
2960
|
+
else writeJson({ term, count: matches.length, matches });
|
|
2961
|
+
}
|
|
2962
|
+
return 0;
|
|
2963
|
+
} catch (error) {
|
|
2964
|
+
if (error instanceof IndexNotFoundError) {
|
|
2965
|
+
process.stderr.write(`${error.message}
|
|
2966
|
+
`);
|
|
2967
|
+
return 1;
|
|
2968
|
+
}
|
|
2969
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
2970
|
+
`);
|
|
2971
|
+
return 1;
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
if (command === "retrieve") {
|
|
2975
|
+
const query = positional.slice(1).join(" ").trim();
|
|
2976
|
+
if (!query) {
|
|
2977
|
+
process.stderr.write("Usage: ak-docs retrieve <query> [--config <path>]\n");
|
|
2978
|
+
return 1;
|
|
2979
|
+
}
|
|
2980
|
+
return (async () => {
|
|
2981
|
+
try {
|
|
2982
|
+
const { config, root } = loadProject(configPath);
|
|
2983
|
+
const index = loadDocBridgeIndex(root, config);
|
|
2984
|
+
writeJson({ query, chunks: await retrieveHybridChunks(root, config, index, query) });
|
|
2985
|
+
return 0;
|
|
2986
|
+
} catch (error) {
|
|
2987
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
2988
|
+
`);
|
|
2989
|
+
return 1;
|
|
2990
|
+
}
|
|
2991
|
+
})();
|
|
2992
|
+
}
|
|
2993
|
+
if (command === "rag") {
|
|
2994
|
+
const action = positional[1];
|
|
2995
|
+
if (action !== "ingest" && action !== "search") {
|
|
2996
|
+
process.stderr.write("Usage: ak-docs rag ingest | ak-docs rag search <query>\n");
|
|
2997
|
+
return 1;
|
|
2998
|
+
}
|
|
2999
|
+
return (async () => {
|
|
3000
|
+
try {
|
|
3001
|
+
const { config, root } = loadProject(configPath);
|
|
3002
|
+
const index = loadDocBridgeIndex(root, config);
|
|
3003
|
+
const rag = await createDocBridgeRag(root, config, index);
|
|
3004
|
+
if (action === "ingest") {
|
|
3005
|
+
const result = await rag.ingest();
|
|
3006
|
+
writeJson({ ok: true, ...result });
|
|
3007
|
+
return 0;
|
|
3008
|
+
}
|
|
3009
|
+
const query = positional.slice(2).join(" ").trim();
|
|
3010
|
+
if (!query) {
|
|
3011
|
+
process.stderr.write("Usage: ak-docs rag search <query>\n");
|
|
3012
|
+
return 1;
|
|
3013
|
+
}
|
|
3014
|
+
const hits = await rag.search(query);
|
|
3015
|
+
writeJson({ query, count: hits.length, hits });
|
|
3016
|
+
return 0;
|
|
3017
|
+
} catch (error) {
|
|
3018
|
+
if (error instanceof PeerMissingError) {
|
|
3019
|
+
process.stderr.write(`${error.message}
|
|
3020
|
+
`);
|
|
3021
|
+
return 1;
|
|
3022
|
+
}
|
|
3023
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
3024
|
+
`);
|
|
3025
|
+
return 1;
|
|
3026
|
+
}
|
|
3027
|
+
})();
|
|
3028
|
+
}
|
|
3029
|
+
if (command === "chat") {
|
|
3030
|
+
return (async () => {
|
|
3031
|
+
try {
|
|
3032
|
+
const { config, root } = loadProject(configPath);
|
|
3033
|
+
if (!config.intelligence?.enabled || !config.intelligence.adapter) {
|
|
3034
|
+
process.stderr.write(
|
|
3035
|
+
`Chat requires intelligence.enabled and intelligence.adapter in doc-bridge config.
|
|
3036
|
+
Layer 1 peers: ${layer1InstallHint()}
|
|
3037
|
+
`
|
|
3038
|
+
);
|
|
3039
|
+
return 1;
|
|
3040
|
+
}
|
|
3041
|
+
const index = loadDocBridgeIndex(root, config);
|
|
3042
|
+
await startInkChat(root, config, index);
|
|
3043
|
+
return 0;
|
|
3044
|
+
} catch (error) {
|
|
3045
|
+
if (error instanceof PeerMissingError) {
|
|
3046
|
+
process.stderr.write(`${error.message}
|
|
3047
|
+
`);
|
|
3048
|
+
return 1;
|
|
3049
|
+
}
|
|
3050
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
3051
|
+
`);
|
|
3052
|
+
return 1;
|
|
3053
|
+
}
|
|
3054
|
+
})();
|
|
3055
|
+
}
|
|
3056
|
+
if (command === "ask") {
|
|
3057
|
+
const question = positional.slice(1).join(" ").trim();
|
|
3058
|
+
try {
|
|
3059
|
+
const { config, root } = loadProject(configPath);
|
|
3060
|
+
if (flags.has("--chat")) {
|
|
3061
|
+
if (!config.intelligence?.enabled || !config.intelligence.adapter) {
|
|
3062
|
+
process.stderr.write(
|
|
3063
|
+
`Chat mode requires intelligence.enabled and intelligence.adapter in doc-bridge config.
|
|
3064
|
+
Install peers: ${layer1InstallHint()}
|
|
3065
|
+
`
|
|
3066
|
+
);
|
|
3067
|
+
return 1;
|
|
3068
|
+
}
|
|
3069
|
+
if (!question) {
|
|
3070
|
+
process.stderr.write("Usage: ak-docs ask <question> --chat\n");
|
|
3071
|
+
return 1;
|
|
3072
|
+
}
|
|
3073
|
+
return (async () => {
|
|
3074
|
+
try {
|
|
3075
|
+
const index2 = loadDocBridgeIndex(root, config);
|
|
3076
|
+
const result = await runChatOnce(root, config, index2, question);
|
|
3077
|
+
writeLines([result.content]);
|
|
3078
|
+
return 0;
|
|
3079
|
+
} catch (error) {
|
|
3080
|
+
if (error instanceof PeerMissingError) {
|
|
3081
|
+
process.stderr.write(`${error.message}
|
|
3082
|
+
`);
|
|
3083
|
+
return 1;
|
|
3084
|
+
}
|
|
3085
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
3086
|
+
`);
|
|
3087
|
+
return 1;
|
|
3088
|
+
}
|
|
3089
|
+
})();
|
|
3090
|
+
}
|
|
3091
|
+
if (!question) {
|
|
3092
|
+
if (process.stdin.isTTY) return runAskRepl(root, config);
|
|
3093
|
+
process.stderr.write("Usage: ak-docs ask <question>, or run ak-docs ask in an interactive terminal.\n");
|
|
3094
|
+
return 1;
|
|
3095
|
+
}
|
|
3096
|
+
const index = loadDocBridgeIndex(root, config);
|
|
3097
|
+
writeAsk(question, searchIndex(index, question, 8), index);
|
|
3098
|
+
return 0;
|
|
3099
|
+
} catch (error) {
|
|
3100
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
3101
|
+
`);
|
|
3102
|
+
return 1;
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
if (command === "list") {
|
|
3106
|
+
const kind = positional[1];
|
|
3107
|
+
if (!kind || !LIST_KINDS.has(kind)) {
|
|
3108
|
+
process.stderr.write("Usage: ak-docs list <packages|intents|changes|knowledge>\n");
|
|
3109
|
+
return 1;
|
|
3110
|
+
}
|
|
3111
|
+
try {
|
|
3112
|
+
const { config, root } = loadProject(configPath);
|
|
3113
|
+
const index = loadDocBridgeIndex(root, config);
|
|
3114
|
+
if (kind === "packages") {
|
|
3115
|
+
const items2 = index.lookup?.packages ?? [];
|
|
3116
|
+
if (wantsTextOutput(flags, config)) writeLines(items2);
|
|
3117
|
+
else writeJson({ kind, items: items2 });
|
|
3118
|
+
return 0;
|
|
3119
|
+
}
|
|
3120
|
+
if (kind === "intents") {
|
|
3121
|
+
const items2 = Object.keys(index.lookup?.intents ?? {});
|
|
3122
|
+
if (wantsTextOutput(flags, config)) writeLines(items2);
|
|
3123
|
+
else writeJson({ kind, items: items2 });
|
|
3124
|
+
return 0;
|
|
3125
|
+
}
|
|
3126
|
+
if (kind === "changes") {
|
|
3127
|
+
const items2 = Object.keys(index.lookup?.changes ?? {});
|
|
3128
|
+
if (wantsTextOutput(flags, config)) writeLines(items2);
|
|
3129
|
+
else writeJson({ kind, items: items2 });
|
|
3130
|
+
return 0;
|
|
3131
|
+
}
|
|
3132
|
+
const items = index.knowledge.map((entry) => ({
|
|
3133
|
+
id: entry.id,
|
|
3134
|
+
title: entry.title,
|
|
3135
|
+
path: entry.path
|
|
3136
|
+
}));
|
|
3137
|
+
if (wantsTextOutput(flags, config)) {
|
|
3138
|
+
writeLines(items.map((item) => [item.id, item.path, item.title].join(" ")));
|
|
3139
|
+
} else {
|
|
3140
|
+
writeJson({ kind, items });
|
|
3141
|
+
}
|
|
3142
|
+
return 0;
|
|
3143
|
+
} catch (error) {
|
|
3144
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
3145
|
+
`);
|
|
3146
|
+
return 1;
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
process.stdout.write(usage);
|
|
3150
|
+
return 1;
|
|
3151
|
+
};
|
|
3152
|
+
export {
|
|
3153
|
+
runCli
|
|
3154
|
+
};
|
|
3155
|
+
//# sourceMappingURL=program.js.map
|