@agentskit/doc-bridge 0.1.0-alpha.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/README.md +139 -57
- package/action.yml +78 -0
- package/dist/cli/program.js +987 -69
- package/dist/cli/program.js.map +1 -1
- package/dist/index.d.ts +238 -26
- package/dist/index.js +704 -22
- package/dist/index.js.map +1 -1
- package/docs/DOGFOOD-ROUND3.md +74 -0
- package/docs/POSITIONING.md +2 -0
- package/docs/RELEASE.md +5 -3
- package/docs/chat-and-rag.md +2 -0
- package/docs/getting-started.md +14 -1
- package/docs/landing/index.html +299 -0
- package/docs/mcp.md +10 -1
- package/docs/ollama-demo.md +64 -0
- package/docs/playbook/doc-bridge-pattern.md +114 -0
- package/docs/recipes/index-pipeline.md +89 -0
- package/docs/skills/doc-bridge.md +64 -0
- package/docs/spec/cli.md +6 -0
- package/docs/spec/playbook-feedback.md +12 -4
- package/examples/demo-example/doc-bridge.config.json +23 -0
- package/examples/demo-example/docs/for-agents/INDEX.md +3 -0
- package/examples/demo-example/docs/for-agents/packages/example.md +14 -0
- package/examples/demo-example/package.json +7 -0
- package/examples/demo-example/src/.gitkeep +0 -0
- package/examples/demo-monorepo/doc-bridge.config.json +37 -0
- package/examples/demo-monorepo/docs/for-agents/INDEX.md +4 -0
- package/examples/demo-monorepo/docs/for-agents/packages/auth.md +22 -0
- package/examples/demo-monorepo/docs/for-agents/packages/billing.md +19 -0
- package/examples/demo-monorepo/docs/human/guides/auth.md +7 -0
- package/examples/demo-monorepo/package.json +7 -0
- package/examples/demo-monorepo/packages/auth/package.json +8 -0
- package/examples/demo-monorepo/packages/billing/package.json +7 -0
- package/examples/demo-monorepo/pnpm-workspace.yaml +2 -0
- package/examples/ollama-chat.config.ts +42 -0
- package/package.json +5 -2
- package/src/cli/demo.ts +143 -0
- package/src/cli/program.ts +194 -14
- package/src/doctor/badge.ts +53 -0
- package/src/doctor/run-doctor.ts +286 -0
- package/src/index-builder/build-handoffs.ts +19 -1
- package/src/index-builder/watch-index.ts +94 -0
- package/src/index.ts +24 -0
- package/src/mcp/install.ts +84 -0
- package/src/memory/github-pr.ts +190 -0
- package/src/playbook/doc-bridge-pattern.ts +121 -0
- package/src/query/query.ts +19 -1
- package/src/schemas/agent-handoff.ts +11 -0
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -372,6 +372,11 @@ var HandoffTargetSchema = z2.object({
|
|
|
372
372
|
group: z2.string().min(1).max(128).optional(),
|
|
373
373
|
layer: z2.string().min(1).max(32).optional()
|
|
374
374
|
}).strict();
|
|
375
|
+
var HandoffBridgeSchema = z2.object({
|
|
376
|
+
humanDoc: z2.enum(["linked", "missing", "external"]),
|
|
377
|
+
action: z2.string().min(1).max(256).optional(),
|
|
378
|
+
bootstrap: z2.string().min(1).max(256).optional()
|
|
379
|
+
}).strict();
|
|
375
380
|
var AgentHandoffV1Schema = z2.object({
|
|
376
381
|
type: z2.literal("agent-handoff"),
|
|
377
382
|
schemaVersion: z2.literal(HANDOFF_SCHEMA_VERSION).default(HANDOFF_SCHEMA_VERSION),
|
|
@@ -382,6 +387,7 @@ var AgentHandoffV1Schema = z2.object({
|
|
|
382
387
|
editRoots: z2.array(z2.string().min(1).max(512)).max(32),
|
|
383
388
|
checks: z2.array(z2.string().min(1).max(256)).max(32),
|
|
384
389
|
humanDoc: z2.string().min(1).max(512).nullable().optional(),
|
|
390
|
+
bridge: HandoffBridgeSchema.optional(),
|
|
385
391
|
playbookPatterns: z2.array(z2.string().url()).max(16).optional(),
|
|
386
392
|
notes: z2.array(z2.string().min(1).max(1024)).max(16)
|
|
387
393
|
}).strict();
|
|
@@ -1111,6 +1117,11 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
|
|
|
1111
1117
|
...agentDoc ? { agentDoc } : {}
|
|
1112
1118
|
};
|
|
1113
1119
|
ownership[pkg.id] = record;
|
|
1120
|
+
const bridge = record.humanDoc ? /^https?:\/\//.test(record.humanDoc) ? { humanDoc: "external" } : { humanDoc: "linked" } : config.corpus.human ? {
|
|
1121
|
+
humanDoc: "missing",
|
|
1122
|
+
action: "ak-docs bootstrap agent-docs",
|
|
1123
|
+
bootstrap: `docs/for-agents/human/${pkg.id}.md`
|
|
1124
|
+
} : void 0;
|
|
1114
1125
|
handoffs[pkg.id] = {
|
|
1115
1126
|
type: "agent-handoff",
|
|
1116
1127
|
schemaVersion: 1,
|
|
@@ -1129,7 +1140,11 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
|
|
|
1129
1140
|
editRoots: [record.path],
|
|
1130
1141
|
checks: [...record.checks],
|
|
1131
1142
|
...record.humanDoc ? { humanDoc: record.humanDoc } : {},
|
|
1132
|
-
|
|
1143
|
+
...bridge ? { bridge } : {},
|
|
1144
|
+
notes: [
|
|
1145
|
+
...record.purpose ? [record.purpose] : [],
|
|
1146
|
+
...!record.humanDoc && config.corpus.human ? [`Human guide missing for ${pkg.id}. Run: ak-docs bootstrap agent-docs`] : []
|
|
1147
|
+
]
|
|
1133
1148
|
};
|
|
1134
1149
|
}
|
|
1135
1150
|
const intents = {};
|
|
@@ -1157,7 +1172,7 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
|
|
|
1157
1172
|
};
|
|
1158
1173
|
|
|
1159
1174
|
// src/version.ts
|
|
1160
|
-
var PACKAGE_VERSION = "
|
|
1175
|
+
var PACKAGE_VERSION = "1.0.0";
|
|
1161
1176
|
|
|
1162
1177
|
// src/index-builder/capabilities.ts
|
|
1163
1178
|
var renderCapabilitiesJson = (config, index, paths) => {
|
|
@@ -1256,10 +1271,10 @@ var docId = (relToHumanRoot, raw) => {
|
|
|
1256
1271
|
return meta.package ?? meta.module ?? meta.id ?? slugFromPath(relToHumanRoot);
|
|
1257
1272
|
};
|
|
1258
1273
|
var routeSlug = (relToHumanRoot, opts) => relToHumanRoot.split("/").filter((part) => !opts?.stripGroups || !/^\(.+\)$/.test(part)).join("/").replace(/(?:^|\/)index\.mdx?$/, "").replace(/\.mdx?$/, "");
|
|
1259
|
-
var humanUrl = (
|
|
1260
|
-
if (typeof urlPrefix !== "string" || !urlPrefix) return
|
|
1274
|
+
var humanUrl = (slug2, urlPrefix) => {
|
|
1275
|
+
if (typeof urlPrefix !== "string" || !urlPrefix) return slug2;
|
|
1261
1276
|
const prefix = urlPrefix.replace(/\/$/, "");
|
|
1262
|
-
return
|
|
1277
|
+
return slug2 ? `${prefix}/${slug2.replace(/^\//, "")}` : prefix;
|
|
1263
1278
|
};
|
|
1264
1279
|
var scanMarkdownDocs = (root, humanRoot, options) => {
|
|
1265
1280
|
const out = [];
|
|
@@ -1290,9 +1305,9 @@ var docusaurusFileSlug = (relPath) => {
|
|
|
1290
1305
|
return routeSlug(relPath);
|
|
1291
1306
|
};
|
|
1292
1307
|
var docusaurusSlug = (relPath, raw) => {
|
|
1293
|
-
const
|
|
1294
|
-
if (!
|
|
1295
|
-
return
|
|
1308
|
+
const slug2 = parseFrontmatter2(raw).slug;
|
|
1309
|
+
if (!slug2) return docusaurusFileSlug(relPath);
|
|
1310
|
+
return slug2.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "");
|
|
1296
1311
|
};
|
|
1297
1312
|
var docusaurusSidebarId = (relPath, raw) => {
|
|
1298
1313
|
const id = parseFrontmatter2(raw).id;
|
|
@@ -2062,6 +2077,11 @@ var handoffForPackage = (index, id, config) => {
|
|
|
2062
2077
|
if (fromIndex) return normalizeAgentHandoff(fromIndex);
|
|
2063
2078
|
const owner = index.lookup?.ownership?.[id];
|
|
2064
2079
|
if (!owner) throw new Error(`Unknown package/ownership id "${id}". Try: ak-docs list packages`);
|
|
2080
|
+
const bridge = owner.humanDoc ? /^https?:\/\//.test(owner.humanDoc) ? { humanDoc: "external" } : { humanDoc: "linked" } : config.corpus.human ? {
|
|
2081
|
+
humanDoc: "missing",
|
|
2082
|
+
action: "ak-docs bootstrap agent-docs",
|
|
2083
|
+
bootstrap: `docs/for-agents/human/${id}.md`
|
|
2084
|
+
} : void 0;
|
|
2065
2085
|
return normalizeAgentHandoff({
|
|
2066
2086
|
type: "agent-handoff",
|
|
2067
2087
|
source: config.index?.outFile ?? ".doc-bridge/index.json",
|
|
@@ -2077,7 +2097,11 @@ var handoffForPackage = (index, id, config) => {
|
|
|
2077
2097
|
editRoots: [owner.path],
|
|
2078
2098
|
checks: [...owner.checks],
|
|
2079
2099
|
...owner.humanDoc ? { humanDoc: owner.humanDoc } : {},
|
|
2080
|
-
|
|
2100
|
+
...bridge ? { bridge } : {},
|
|
2101
|
+
notes: [
|
|
2102
|
+
...owner.purpose ? [owner.purpose] : [],
|
|
2103
|
+
...!owner.humanDoc && config.corpus.human ? [`Human guide missing for ${id}. Run: ak-docs bootstrap agent-docs`] : []
|
|
2104
|
+
]
|
|
2081
2105
|
});
|
|
2082
2106
|
};
|
|
2083
2107
|
var runQuery = (index, config, req) => {
|
|
@@ -2378,9 +2402,531 @@ var startMcpStdioServer = (ctx) => {
|
|
|
2378
2402
|
process.stdin.resume();
|
|
2379
2403
|
};
|
|
2380
2404
|
|
|
2405
|
+
// src/mcp/install.ts
|
|
2406
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync13, writeFileSync as writeFileSync2 } from "fs";
|
|
2407
|
+
import { homedir } from "os";
|
|
2408
|
+
import { dirname as dirname4, join as join13, resolve as resolve5 } from "path";
|
|
2409
|
+
var SERVER_NAME = "ak-docs";
|
|
2410
|
+
var mcpServerEntry = (root) => ({
|
|
2411
|
+
command: "npx",
|
|
2412
|
+
args: ["ak-docs", "mcp"],
|
|
2413
|
+
cwd: root
|
|
2414
|
+
});
|
|
2415
|
+
var readJson = (path) => {
|
|
2416
|
+
if (!existsSync9(path)) return {};
|
|
2417
|
+
try {
|
|
2418
|
+
const parsed = JSON.parse(readFileSync13(path, "utf8"));
|
|
2419
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
2420
|
+
} catch {
|
|
2421
|
+
return {};
|
|
2422
|
+
}
|
|
2423
|
+
};
|
|
2424
|
+
var writeJson = (path, value) => {
|
|
2425
|
+
mkdirSync2(dirname4(path), { recursive: true });
|
|
2426
|
+
writeFileSync2(path, `${JSON.stringify(value, null, 2)}
|
|
2427
|
+
`, "utf8");
|
|
2428
|
+
};
|
|
2429
|
+
var resolveTargetPath = (target, root) => {
|
|
2430
|
+
if (target === "cursor") return resolve5(root, ".cursor", "mcp.json");
|
|
2431
|
+
return join13(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
2432
|
+
};
|
|
2433
|
+
var installMcpConfig = (root, target) => {
|
|
2434
|
+
const configPath = resolveTargetPath(target, root);
|
|
2435
|
+
const created = !existsSync9(configPath);
|
|
2436
|
+
const existing = readJson(configPath);
|
|
2437
|
+
const servers = existing.mcpServers && typeof existing.mcpServers === "object" && !Array.isArray(existing.mcpServers) ? { ...existing.mcpServers } : {};
|
|
2438
|
+
servers[SERVER_NAME] = mcpServerEntry(root);
|
|
2439
|
+
writeJson(configPath, { ...existing, mcpServers: servers });
|
|
2440
|
+
const nextSteps = target === "cursor" ? [
|
|
2441
|
+
"Restart Cursor or reload MCP servers",
|
|
2442
|
+
"Paste the doc-bridge skill into Cursor rules (see docs/skills/doc-bridge.md)",
|
|
2443
|
+
"Before editing packages/* call handoff.resolve"
|
|
2444
|
+
] : [
|
|
2445
|
+
"Restart Claude Desktop",
|
|
2446
|
+
"Run ak-docs index after doc changes"
|
|
2447
|
+
];
|
|
2448
|
+
return {
|
|
2449
|
+
ok: true,
|
|
2450
|
+
target,
|
|
2451
|
+
configPath,
|
|
2452
|
+
created,
|
|
2453
|
+
serverName: SERVER_NAME,
|
|
2454
|
+
nextSteps
|
|
2455
|
+
};
|
|
2456
|
+
};
|
|
2457
|
+
var mcpSnippet = (root) => JSON.stringify({ mcpServers: { [SERVER_NAME]: mcpServerEntry(root) } }, null, 2);
|
|
2458
|
+
|
|
2459
|
+
// src/doctor/badge.ts
|
|
2460
|
+
var doctorBadgeMetrics = (report) => {
|
|
2461
|
+
const { packages } = report.coverage;
|
|
2462
|
+
const handoffPct = packages.total > 0 ? Math.round(packages.withAgentDoc / packages.total * 100) : 0;
|
|
2463
|
+
const bridgePct = packages.total > 0 ? Math.round(packages.withHumanDoc / packages.total * 100) : 0;
|
|
2464
|
+
return {
|
|
2465
|
+
handoffPct,
|
|
2466
|
+
bridgePct,
|
|
2467
|
+
score: report.score,
|
|
2468
|
+
grade: report.grade,
|
|
2469
|
+
packages: packages.total
|
|
2470
|
+
};
|
|
2471
|
+
};
|
|
2472
|
+
var badgeColor = (pct) => {
|
|
2473
|
+
if (pct >= 80) return "2ea44f";
|
|
2474
|
+
if (pct >= 50) return "dbab09";
|
|
2475
|
+
return "cb2431";
|
|
2476
|
+
};
|
|
2477
|
+
var formatDoctorBadgeMarkdown = (metrics) => {
|
|
2478
|
+
const handoff = `handoff_coverage-${metrics.handoffPct}%25-${badgeColor(metrics.handoffPct)}`;
|
|
2479
|
+
const bridge = `human_bridge-${metrics.bridgePct}%25-${badgeColor(metrics.bridgePct)}`;
|
|
2480
|
+
return [
|
|
2481
|
+
``,
|
|
2482
|
+
``,
|
|
2483
|
+
`}?style=flat-square)`
|
|
2484
|
+
].join(" ");
|
|
2485
|
+
};
|
|
2486
|
+
var formatDoctorBadgeJson = (metrics) => JSON.stringify(
|
|
2487
|
+
{
|
|
2488
|
+
schemaVersion: 1,
|
|
2489
|
+
...metrics,
|
|
2490
|
+
markdown: formatDoctorBadgeMarkdown(metrics),
|
|
2491
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2492
|
+
},
|
|
2493
|
+
null,
|
|
2494
|
+
2
|
|
2495
|
+
);
|
|
2496
|
+
|
|
2497
|
+
// src/doctor/run-doctor.ts
|
|
2498
|
+
var gradeForScore = (score) => {
|
|
2499
|
+
if (score >= 90) return "A";
|
|
2500
|
+
if (score >= 75) return "B";
|
|
2501
|
+
if (score >= 60) return "C";
|
|
2502
|
+
if (score >= 40) return "D";
|
|
2503
|
+
return "F";
|
|
2504
|
+
};
|
|
2505
|
+
var agentDocPaths = (index) => {
|
|
2506
|
+
const paths = /* @__PURE__ */ new Set();
|
|
2507
|
+
for (const owner of Object.values(index.lookup?.ownership ?? {})) {
|
|
2508
|
+
if (owner.agentDoc) paths.add(owner.agentDoc);
|
|
2509
|
+
}
|
|
2510
|
+
for (const handoff of Object.values(index.handoffs ?? {})) {
|
|
2511
|
+
if (handoff.startHere) paths.add(handoff.startHere);
|
|
2512
|
+
}
|
|
2513
|
+
return paths;
|
|
2514
|
+
};
|
|
2515
|
+
var computeScore = (coverage) => {
|
|
2516
|
+
let score = 0;
|
|
2517
|
+
if (coverage.freshness.hasIndex) score += 15;
|
|
2518
|
+
if (coverage.freshness.ok) score += 15;
|
|
2519
|
+
const { total, withAgentDoc, withHumanDoc } = coverage.packages;
|
|
2520
|
+
if (total > 0) {
|
|
2521
|
+
score += Math.round(withAgentDoc / total * 35);
|
|
2522
|
+
score += Math.round(withHumanDoc / total * 20);
|
|
2523
|
+
} else if (coverage.agentDocs.indexed > 0) {
|
|
2524
|
+
score += 35;
|
|
2525
|
+
}
|
|
2526
|
+
if (coverage.gates.ok) score += 15;
|
|
2527
|
+
else {
|
|
2528
|
+
const passed = coverage.gates.results.filter((gate) => gate.ok).length;
|
|
2529
|
+
const totalGates = coverage.gates.results.length || 1;
|
|
2530
|
+
score += Math.round(passed / totalGates * 10);
|
|
2531
|
+
}
|
|
2532
|
+
return Math.min(100, Math.max(0, score));
|
|
2533
|
+
};
|
|
2534
|
+
var buildIssues = (coverage) => {
|
|
2535
|
+
const issues = [];
|
|
2536
|
+
if (!coverage.freshness.hasIndex) {
|
|
2537
|
+
issues.push({
|
|
2538
|
+
severity: "error",
|
|
2539
|
+
code: "index-missing",
|
|
2540
|
+
message: "No doc-bridge index found.",
|
|
2541
|
+
action: "ak-docs index"
|
|
2542
|
+
});
|
|
2543
|
+
} else if (!coverage.freshness.ok) {
|
|
2544
|
+
issues.push({
|
|
2545
|
+
severity: "error",
|
|
2546
|
+
code: "index-stale",
|
|
2547
|
+
message: coverage.freshness.message,
|
|
2548
|
+
action: "ak-docs index"
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
for (const id of coverage.packages.missingAgentDoc) {
|
|
2552
|
+
issues.push({
|
|
2553
|
+
severity: "warn",
|
|
2554
|
+
code: "missing-agent-doc",
|
|
2555
|
+
message: `Package "${id}" has no dedicated agent doc.`,
|
|
2556
|
+
action: `ak-docs init --scaffold-workspaces # or edit docs/for-agents/packages/${id}.md`
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
for (const id of coverage.packages.missingHumanDoc) {
|
|
2560
|
+
issues.push({
|
|
2561
|
+
severity: "info",
|
|
2562
|
+
code: "missing-human-doc",
|
|
2563
|
+
message: `Package "${id}" has no linked human guide.`,
|
|
2564
|
+
action: "ak-docs bootstrap agent-docs"
|
|
2565
|
+
});
|
|
2566
|
+
}
|
|
2567
|
+
for (const gate of coverage.gates.results.filter((result) => !result.ok)) {
|
|
2568
|
+
issues.push({
|
|
2569
|
+
severity: gate.id === "index-freshness" ? "error" : "warn",
|
|
2570
|
+
code: `gate-${gate.id}`,
|
|
2571
|
+
message: gate.message,
|
|
2572
|
+
action: gate.id === "index-freshness" ? "ak-docs index" : "ak-docs gate run"
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
return issues;
|
|
2576
|
+
};
|
|
2577
|
+
var buildNextActions = (issues, coverage) => {
|
|
2578
|
+
const actions = /* @__PURE__ */ new Set();
|
|
2579
|
+
for (const issue of issues) {
|
|
2580
|
+
if (issue.action) actions.add(issue.action);
|
|
2581
|
+
}
|
|
2582
|
+
if (!coverage.freshness.hasIndex || !coverage.freshness.ok) {
|
|
2583
|
+
actions.add("ak-docs index");
|
|
2584
|
+
}
|
|
2585
|
+
if (coverage.packages.missingHumanDoc.length) {
|
|
2586
|
+
actions.add("ak-docs bootstrap agent-docs");
|
|
2587
|
+
}
|
|
2588
|
+
if (coverage.packages.total > 0) {
|
|
2589
|
+
const sample = coverage.packages.missingAgentDoc[0] ?? coverage.packages.missingHumanDoc[0];
|
|
2590
|
+
if (sample) actions.add(`ak-docs query package ${sample} --agent`);
|
|
2591
|
+
}
|
|
2592
|
+
if (!actions.size) {
|
|
2593
|
+
actions.add("ak-docs mcp install --cursor");
|
|
2594
|
+
actions.add("ak-docs gate run");
|
|
2595
|
+
}
|
|
2596
|
+
return [...actions].slice(0, 6);
|
|
2597
|
+
};
|
|
2598
|
+
var runDoctor = (root, config) => {
|
|
2599
|
+
let index;
|
|
2600
|
+
let hasIndex = true;
|
|
2601
|
+
let freshnessOk = false;
|
|
2602
|
+
let freshnessMessage = "Index is fresh";
|
|
2603
|
+
try {
|
|
2604
|
+
index = loadDocBridgeIndex(root, config);
|
|
2605
|
+
const next = buildDocBridgeIndex({ root, config, write: false }).index.contentHash;
|
|
2606
|
+
freshnessOk = index.contentHash === next;
|
|
2607
|
+
freshnessMessage = freshnessOk ? "Index is fresh" : "Index is stale. Run: ak-docs index";
|
|
2608
|
+
} catch (error) {
|
|
2609
|
+
if (error instanceof IndexNotFoundError) {
|
|
2610
|
+
hasIndex = false;
|
|
2611
|
+
freshnessOk = false;
|
|
2612
|
+
freshnessMessage = error.message;
|
|
2613
|
+
index = buildDocBridgeIndex({ root, config, write: false }).index;
|
|
2614
|
+
} else {
|
|
2615
|
+
throw error;
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
const ownership = Object.entries(index.lookup?.ownership ?? {});
|
|
2619
|
+
const missingAgentDoc = ownership.filter(([, owner]) => !owner.agentDoc || owner.agentDoc === config.corpus.agent.index).map(([id]) => id);
|
|
2620
|
+
const missingHumanDoc = ownership.filter(([, owner]) => !owner.humanDoc).map(([id]) => id);
|
|
2621
|
+
const indexedPaths = new Set(index.knowledge.map((entry) => entry.path));
|
|
2622
|
+
const expectedAgentDocs = agentDocPaths(index);
|
|
2623
|
+
const unindexed = [...expectedAgentDocs].filter((path) => !indexedPaths.has(path));
|
|
2624
|
+
const corpusDocs = scanAgentCorpus(root, config).filter(
|
|
2625
|
+
(doc) => doc.path !== config.corpus.agent.index
|
|
2626
|
+
);
|
|
2627
|
+
const gates = runGates(root, config);
|
|
2628
|
+
const coverage = {
|
|
2629
|
+
packages: {
|
|
2630
|
+
total: ownership.length,
|
|
2631
|
+
withAgentDoc: ownership.length - missingAgentDoc.length,
|
|
2632
|
+
withHumanDoc: ownership.length - missingHumanDoc.length,
|
|
2633
|
+
missingAgentDoc,
|
|
2634
|
+
missingHumanDoc
|
|
2635
|
+
},
|
|
2636
|
+
agentDocs: {
|
|
2637
|
+
total: corpusDocs.length,
|
|
2638
|
+
indexed: corpusDocs.filter((doc) => indexedPaths.has(doc.path)).length,
|
|
2639
|
+
unindexed: corpusDocs.filter((doc) => !indexedPaths.has(doc.path)).map((doc) => doc.path)
|
|
2640
|
+
},
|
|
2641
|
+
freshness: {
|
|
2642
|
+
ok: freshnessOk,
|
|
2643
|
+
message: freshnessMessage,
|
|
2644
|
+
hasIndex
|
|
2645
|
+
},
|
|
2646
|
+
gates
|
|
2647
|
+
};
|
|
2648
|
+
const issues = buildIssues(coverage);
|
|
2649
|
+
const score = computeScore(coverage);
|
|
2650
|
+
const nextActions = buildNextActions(issues, coverage);
|
|
2651
|
+
const report = {
|
|
2652
|
+
ok: issues.every((issue) => issue.severity !== "error") && gates.ok,
|
|
2653
|
+
score,
|
|
2654
|
+
grade: gradeForScore(score),
|
|
2655
|
+
coverage,
|
|
2656
|
+
badge: { handoffPct: 0, bridgePct: 0, score, grade: gradeForScore(score), packages: 0 },
|
|
2657
|
+
issues,
|
|
2658
|
+
nextActions
|
|
2659
|
+
};
|
|
2660
|
+
return { ...report, badge: doctorBadgeMetrics(report) };
|
|
2661
|
+
};
|
|
2662
|
+
var formatDoctorText = (report) => {
|
|
2663
|
+
const { coverage } = report;
|
|
2664
|
+
const handoffPct = coverage.packages.total > 0 ? Math.round(coverage.packages.withAgentDoc / coverage.packages.total * 100) : 0;
|
|
2665
|
+
const humanPct = coverage.packages.total > 0 ? Math.round(coverage.packages.withHumanDoc / coverage.packages.total * 100) : 0;
|
|
2666
|
+
const lines = [
|
|
2667
|
+
"doc-bridge doctor",
|
|
2668
|
+
"\u2500".repeat(40),
|
|
2669
|
+
`Score: ${report.score}/100 (${report.grade})`,
|
|
2670
|
+
"",
|
|
2671
|
+
"Coverage",
|
|
2672
|
+
` Packages: ${coverage.packages.total}`,
|
|
2673
|
+
` Agent docs: ${coverage.packages.withAgentDoc}/${coverage.packages.total} (${handoffPct}% handoff-ready)`,
|
|
2674
|
+
` Human guides: ${coverage.packages.withHumanDoc}/${coverage.packages.total} (${humanPct}% bridged)`,
|
|
2675
|
+
` Corpus indexed: ${coverage.agentDocs.indexed}/${coverage.agentDocs.total} agent docs`,
|
|
2676
|
+
` Index freshness: ${coverage.freshness.ok ? "fresh" : "stale or missing"}`,
|
|
2677
|
+
` Gates: ${coverage.gates.results.filter((g) => g.ok).length}/${coverage.gates.results.length} passing`,
|
|
2678
|
+
` Badge: handoff ${report.badge.handoffPct}% \xB7 bridge ${report.badge.bridgePct}%`
|
|
2679
|
+
];
|
|
2680
|
+
if (coverage.packages.missingHumanDoc.length) {
|
|
2681
|
+
lines.push("", "Missing humanDoc (bridge gap)", ...coverage.packages.missingHumanDoc.map((id) => ` \u2022 ${id}`));
|
|
2682
|
+
}
|
|
2683
|
+
if (coverage.packages.missingAgentDoc.length) {
|
|
2684
|
+
lines.push("", "Missing agent doc", ...coverage.packages.missingAgentDoc.map((id) => ` \u2022 ${id}`));
|
|
2685
|
+
}
|
|
2686
|
+
if (report.issues.length) {
|
|
2687
|
+
lines.push("", "Issues");
|
|
2688
|
+
for (const issue of report.issues.slice(0, 8)) {
|
|
2689
|
+
const icon = issue.severity === "error" ? "\u2717" : issue.severity === "warn" ? "!" : "\xB7";
|
|
2690
|
+
lines.push(` ${icon} [${issue.code}] ${issue.message}`);
|
|
2691
|
+
}
|
|
2692
|
+
if (report.issues.length > 8) {
|
|
2693
|
+
lines.push(` \u2026 +${report.issues.length - 8} more`);
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
lines.push("", "Next actions", ...report.nextActions.map((action) => ` \u2192 ${action}`));
|
|
2697
|
+
return lines;
|
|
2698
|
+
};
|
|
2699
|
+
|
|
2700
|
+
// src/index-builder/watch-index.ts
|
|
2701
|
+
import { existsSync as existsSync10, watch } from "fs";
|
|
2702
|
+
import { dirname as dirname5, resolve as resolve6 } from "path";
|
|
2703
|
+
var WATCH_PATTERN = /\.(md|mdx|json|ya?ml|mdc)$/i;
|
|
2704
|
+
var collectWatchRoots = (root, config, configPath) => {
|
|
2705
|
+
const roots = /* @__PURE__ */ new Set();
|
|
2706
|
+
roots.add(resolve6(root, config.corpus.agent.root));
|
|
2707
|
+
const humanSources = config.corpus.human ? Array.isArray(config.corpus.human) ? config.corpus.human : [config.corpus.human] : [];
|
|
2708
|
+
for (const source of humanSources) {
|
|
2709
|
+
const humanOpts = source.options ?? {};
|
|
2710
|
+
for (const key of ["contentDir", "docsDir", "root"]) {
|
|
2711
|
+
const value = humanOpts[key];
|
|
2712
|
+
if (typeof value === "string" && value.length) roots.add(resolve6(root, value));
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
if (configPath) roots.add(dirname5(resolve6(configPath)));
|
|
2716
|
+
return [...roots].filter((dir) => existsSync10(dir));
|
|
2717
|
+
};
|
|
2718
|
+
var watchDocBridgeIndex = (opts) => {
|
|
2719
|
+
const debounceMs = opts.debounceMs ?? 350;
|
|
2720
|
+
let timer;
|
|
2721
|
+
let running = false;
|
|
2722
|
+
const rebuild = () => {
|
|
2723
|
+
if (timer) clearTimeout(timer);
|
|
2724
|
+
timer = setTimeout(() => {
|
|
2725
|
+
if (running) return;
|
|
2726
|
+
running = true;
|
|
2727
|
+
try {
|
|
2728
|
+
const result = buildDocBridgeIndex({ root: opts.root, config: opts.config });
|
|
2729
|
+
const handoffCount = Object.keys(result.index.handoffs ?? {}).length;
|
|
2730
|
+
const summary = {
|
|
2731
|
+
knowledgeCount: result.index.knowledge.length,
|
|
2732
|
+
handoffCount,
|
|
2733
|
+
hash: result.index.contentHash.slice(0, 8)
|
|
2734
|
+
};
|
|
2735
|
+
opts.onRebuild?.(summary);
|
|
2736
|
+
process.stdout.write(
|
|
2737
|
+
`[ak-docs] indexed ${summary.knowledgeCount} docs, ${summary.handoffCount} handoffs (${summary.hash}\u2026)
|
|
2738
|
+
`
|
|
2739
|
+
);
|
|
2740
|
+
} catch (error) {
|
|
2741
|
+
process.stderr.write(
|
|
2742
|
+
`[ak-docs] index failed: ${error instanceof Error ? error.message : String(error)}
|
|
2743
|
+
`
|
|
2744
|
+
);
|
|
2745
|
+
} finally {
|
|
2746
|
+
running = false;
|
|
2747
|
+
}
|
|
2748
|
+
}, debounceMs);
|
|
2749
|
+
};
|
|
2750
|
+
rebuild();
|
|
2751
|
+
for (const dir of collectWatchRoots(opts.root, opts.config, opts.configPath)) {
|
|
2752
|
+
watch(dir, { recursive: true }, (_event, filename) => {
|
|
2753
|
+
if (!filename || !WATCH_PATTERN.test(filename)) return;
|
|
2754
|
+
rebuild();
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
const configDir = resolve6(opts.root);
|
|
2758
|
+
if (existsSync10(configDir)) {
|
|
2759
|
+
watch(configDir, (_event, filename) => {
|
|
2760
|
+
if (!filename || !/doc-bridge\.config/.test(filename)) return;
|
|
2761
|
+
rebuild();
|
|
2762
|
+
});
|
|
2763
|
+
}
|
|
2764
|
+
process.stdout.write(
|
|
2765
|
+
`[ak-docs] watching ${collectWatchRoots(opts.root, opts.config, opts.configPath).join(", ") || opts.root} (Ctrl+C to stop)
|
|
2766
|
+
`
|
|
2767
|
+
);
|
|
2768
|
+
return new Promise((resolvePromise) => {
|
|
2769
|
+
const onSignal = () => resolvePromise(0);
|
|
2770
|
+
process.once("SIGINT", onSignal);
|
|
2771
|
+
process.once("SIGTERM", onSignal);
|
|
2772
|
+
});
|
|
2773
|
+
};
|
|
2774
|
+
|
|
2775
|
+
// src/memory/github-pr.ts
|
|
2776
|
+
import { execFileSync, spawnSync } from "child_process";
|
|
2777
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
2778
|
+
import { join as join15 } from "path";
|
|
2779
|
+
var run = (cmd, args, cwd) => {
|
|
2780
|
+
const result = spawnSync(cmd, [...args], { cwd, encoding: "utf8" });
|
|
2781
|
+
const out = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
2782
|
+
return { ok: result.status === 0, out };
|
|
2783
|
+
};
|
|
2784
|
+
var hasGh = () => run("gh", ["--version"], process.cwd()).ok;
|
|
2785
|
+
var slug = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2786
|
+
var defaultPromotionDraftPath = (root) => join15(root, ".doc-bridge", "drafts", `memory-promotion-${slug()}.md`);
|
|
2787
|
+
var writePromotionDraft = (root, draft, path) => {
|
|
2788
|
+
const draftPath = path ?? defaultPromotionDraftPath(root);
|
|
2789
|
+
mkdirSync3(join15(root, ".doc-bridge", "drafts"), { recursive: true });
|
|
2790
|
+
writeFileSync3(draftPath, `${draft.body}
|
|
2791
|
+
`, "utf8");
|
|
2792
|
+
return draftPath;
|
|
2793
|
+
};
|
|
2794
|
+
var promoteMemoryToGithubPr = (root, draft, options = {}) => {
|
|
2795
|
+
if (!draft.ok && !options.force) {
|
|
2796
|
+
return {
|
|
2797
|
+
ok: false,
|
|
2798
|
+
dryRun: Boolean(options.dryRun),
|
|
2799
|
+
draftPath: "",
|
|
2800
|
+
branch: "",
|
|
2801
|
+
commands: [],
|
|
2802
|
+
message: "Safety scan blocked promotion. Fix findings or pass --force to draft anyway."
|
|
2803
|
+
};
|
|
2804
|
+
}
|
|
2805
|
+
const branch = options.branch ?? `doc-bridge/memory-promotion-${slug()}`;
|
|
2806
|
+
const draftPath = writePromotionDraft(root, draft);
|
|
2807
|
+
const relDraft = draftPath.startsWith(`${root}/`) ? draftPath.slice(root.length + 1) : draftPath;
|
|
2808
|
+
const commands = [
|
|
2809
|
+
`git checkout -b ${branch}`,
|
|
2810
|
+
`git add ${relDraft}`,
|
|
2811
|
+
`git commit -m "draft: doc-bridge memory promotion"`,
|
|
2812
|
+
`git push -u origin ${branch}`,
|
|
2813
|
+
`gh pr create --draft --title "${draft.title}" --body-file ${relDraft}${options.base ? ` --base ${options.base}` : ""}`
|
|
2814
|
+
];
|
|
2815
|
+
if (options.dryRun) {
|
|
2816
|
+
return {
|
|
2817
|
+
ok: true,
|
|
2818
|
+
dryRun: true,
|
|
2819
|
+
draftPath,
|
|
2820
|
+
branch,
|
|
2821
|
+
commands,
|
|
2822
|
+
message: `Wrote draft to ${relDraft}. Run the printed git/gh commands to open a draft PR.`
|
|
2823
|
+
};
|
|
2824
|
+
}
|
|
2825
|
+
if (!existsSync11(join15(root, ".git"))) {
|
|
2826
|
+
return {
|
|
2827
|
+
ok: false,
|
|
2828
|
+
dryRun: false,
|
|
2829
|
+
draftPath,
|
|
2830
|
+
branch,
|
|
2831
|
+
commands,
|
|
2832
|
+
message: "Not a git repository. Commit the draft manually or run with --dry-run."
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
if (!hasGh()) {
|
|
2836
|
+
return {
|
|
2837
|
+
ok: false,
|
|
2838
|
+
dryRun: false,
|
|
2839
|
+
draftPath,
|
|
2840
|
+
branch,
|
|
2841
|
+
commands,
|
|
2842
|
+
message: "GitHub CLI (gh) not found. Install gh or use --dry-run for local draft + commands."
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2845
|
+
const auth = run("gh", ["auth", "status"], root);
|
|
2846
|
+
if (!auth.ok) {
|
|
2847
|
+
return {
|
|
2848
|
+
ok: false,
|
|
2849
|
+
dryRun: false,
|
|
2850
|
+
draftPath,
|
|
2851
|
+
branch,
|
|
2852
|
+
commands,
|
|
2853
|
+
message: `gh is not authenticated. Run: gh auth login
|
|
2854
|
+
${auth.out}`
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
const checkout = run("git", ["checkout", "-b", branch], root);
|
|
2858
|
+
if (!checkout.ok) {
|
|
2859
|
+
return {
|
|
2860
|
+
ok: false,
|
|
2861
|
+
dryRun: false,
|
|
2862
|
+
draftPath,
|
|
2863
|
+
branch,
|
|
2864
|
+
commands,
|
|
2865
|
+
message: `git checkout failed: ${checkout.out}`
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
run("git", ["add", relDraft], root);
|
|
2869
|
+
const commit = run("git", ["commit", "-m", "draft: doc-bridge memory promotion"], root);
|
|
2870
|
+
if (!commit.ok) {
|
|
2871
|
+
return {
|
|
2872
|
+
ok: false,
|
|
2873
|
+
dryRun: false,
|
|
2874
|
+
draftPath,
|
|
2875
|
+
branch,
|
|
2876
|
+
commands,
|
|
2877
|
+
message: `git commit failed: ${commit.out}`
|
|
2878
|
+
};
|
|
2879
|
+
}
|
|
2880
|
+
const push = run("git", ["push", "-u", "origin", branch], root);
|
|
2881
|
+
if (!push.ok) {
|
|
2882
|
+
return {
|
|
2883
|
+
ok: false,
|
|
2884
|
+
dryRun: false,
|
|
2885
|
+
draftPath,
|
|
2886
|
+
branch,
|
|
2887
|
+
commands,
|
|
2888
|
+
message: `git push failed: ${push.out}`
|
|
2889
|
+
};
|
|
2890
|
+
}
|
|
2891
|
+
const prArgs = [
|
|
2892
|
+
"pr",
|
|
2893
|
+
"create",
|
|
2894
|
+
"--draft",
|
|
2895
|
+
"--title",
|
|
2896
|
+
draft.title,
|
|
2897
|
+
"--body-file",
|
|
2898
|
+
relDraft,
|
|
2899
|
+
...options.base ? ["--base", options.base] : []
|
|
2900
|
+
];
|
|
2901
|
+
let prUrl = "";
|
|
2902
|
+
try {
|
|
2903
|
+
prUrl = execFileSync("gh", prArgs, { cwd: root, encoding: "utf8" }).trim();
|
|
2904
|
+
} catch (error) {
|
|
2905
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2906
|
+
return {
|
|
2907
|
+
ok: false,
|
|
2908
|
+
dryRun: false,
|
|
2909
|
+
draftPath,
|
|
2910
|
+
branch,
|
|
2911
|
+
commands,
|
|
2912
|
+
message: `gh pr create failed: ${message}`
|
|
2913
|
+
};
|
|
2914
|
+
}
|
|
2915
|
+
return {
|
|
2916
|
+
ok: true,
|
|
2917
|
+
dryRun: false,
|
|
2918
|
+
draftPath,
|
|
2919
|
+
branch,
|
|
2920
|
+
prUrl,
|
|
2921
|
+
...prUrl ? { previewUrl: prUrl } : {},
|
|
2922
|
+
commands,
|
|
2923
|
+
message: prUrl ? `Draft PR opened: ${prUrl}` : "Draft PR created."
|
|
2924
|
+
};
|
|
2925
|
+
};
|
|
2926
|
+
|
|
2381
2927
|
// src/federation/llms.ts
|
|
2382
|
-
import { existsSync as
|
|
2383
|
-
import { resolve as
|
|
2928
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
2929
|
+
import { resolve as resolve7 } from "path";
|
|
2384
2930
|
var tokenize2 = (value) => value.toLowerCase().split(/[^a-z0-9]+/).filter((token) => token.length >= 2);
|
|
2385
2931
|
var scoreText = (query, text) => {
|
|
2386
2932
|
const hay = text.toLowerCase();
|
|
@@ -2395,9 +2941,9 @@ var defaultFetchText = async (url) => {
|
|
|
2395
2941
|
var sourceText = async (root, source, fetchText) => {
|
|
2396
2942
|
try {
|
|
2397
2943
|
if (/^https?:\/\//.test(source)) return await fetchText(source);
|
|
2398
|
-
const path =
|
|
2399
|
-
if (!
|
|
2400
|
-
return
|
|
2944
|
+
const path = resolve7(root, source);
|
|
2945
|
+
if (!existsSync12(path)) return null;
|
|
2946
|
+
return readFileSync14(path, "utf8");
|
|
2401
2947
|
} catch {
|
|
2402
2948
|
return null;
|
|
2403
2949
|
}
|
|
@@ -2482,11 +3028,11 @@ var retrieveHybridChunks = async (root, config, index, query, options = {}) => {
|
|
|
2482
3028
|
};
|
|
2483
3029
|
|
|
2484
3030
|
// src/intelligence/rag.ts
|
|
2485
|
-
import { readFileSync as
|
|
2486
|
-
import { join as
|
|
3031
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
3032
|
+
import { join as join17 } from "path";
|
|
2487
3033
|
|
|
2488
3034
|
// src/intelligence/adapter.ts
|
|
2489
|
-
import { join as
|
|
3035
|
+
import { join as join16 } from "path";
|
|
2490
3036
|
|
|
2491
3037
|
// src/intelligence/peers.ts
|
|
2492
3038
|
var PeerMissingError = class extends Error {
|
|
@@ -2601,7 +3147,7 @@ var resolveIntelligenceRuntime = async (config) => {
|
|
|
2601
3147
|
}
|
|
2602
3148
|
return { adapter, embed, provider, ...model ? { model } : {} };
|
|
2603
3149
|
};
|
|
2604
|
-
var defaultVectorStorePath = (root) =>
|
|
3150
|
+
var defaultVectorStorePath = (root) => join16(root, ".doc-bridge", "vectors");
|
|
2605
3151
|
|
|
2606
3152
|
// src/intelligence/rag.ts
|
|
2607
3153
|
var loadDocuments = (root, index, sources) => {
|
|
@@ -2609,10 +3155,10 @@ var loadDocuments = (root, index, sources) => {
|
|
|
2609
3155
|
const docs = [];
|
|
2610
3156
|
if (includeAgent) {
|
|
2611
3157
|
for (const entry of index.knowledge) {
|
|
2612
|
-
const abs =
|
|
3158
|
+
const abs = join17(root, entry.path);
|
|
2613
3159
|
let content = "";
|
|
2614
3160
|
try {
|
|
2615
|
-
content =
|
|
3161
|
+
content = readFileSync15(abs, "utf8");
|
|
2616
3162
|
} catch {
|
|
2617
3163
|
content = [entry.title, entry.description].filter(Boolean).join("\n\n");
|
|
2618
3164
|
}
|
|
@@ -2630,7 +3176,7 @@ var createDocBridgeRag = async (root, config, index) => {
|
|
|
2630
3176
|
const { embed } = await resolveIntelligenceRuntime(config);
|
|
2631
3177
|
const ragMod = await importPeer("@agentskit/rag");
|
|
2632
3178
|
const memoryMod = await importPeer("@agentskit/memory");
|
|
2633
|
-
const storePath = typeof config.intelligence?.retriever?.options?.storePath === "string" ?
|
|
3179
|
+
const storePath = typeof config.intelligence?.retriever?.options?.storePath === "string" ? join17(root, config.intelligence.retriever.options.storePath) : defaultVectorStorePath(root);
|
|
2634
3180
|
const store = memoryMod.fileVectorMemory({ path: storePath });
|
|
2635
3181
|
const rag = ragMod.createRAG({
|
|
2636
3182
|
embed,
|
|
@@ -2763,17 +3309,140 @@ var startInkChat = async (root, config, index) => {
|
|
|
2763
3309
|
throw wrapIntelligenceError(error);
|
|
2764
3310
|
}
|
|
2765
3311
|
};
|
|
3312
|
+
|
|
3313
|
+
// src/playbook/doc-bridge-pattern.ts
|
|
3314
|
+
var DOC_BRIDGE_PATTERN_ID = "doc-bridge-pattern";
|
|
3315
|
+
var DOC_BRIDGE_PATTERN_META = {
|
|
3316
|
+
id: DOC_BRIDGE_PATTERN_ID,
|
|
3317
|
+
title: "Doc Bridge Pattern",
|
|
3318
|
+
slug: "pillars/ai-collaboration/doc-bridge-pattern",
|
|
3319
|
+
license: "CC-BY-4.0",
|
|
3320
|
+
visibility: "public",
|
|
3321
|
+
playbookUrl: "https://playbook.agentskit.io/patterns/doc-bridge-pattern",
|
|
3322
|
+
npmPackage: "@agentskit/doc-bridge",
|
|
3323
|
+
cli: "ak-docs"
|
|
3324
|
+
};
|
|
3325
|
+
var docBridgePatternMarkdown = () => `---
|
|
3326
|
+
type: pattern
|
|
3327
|
+
id: ${DOC_BRIDGE_PATTERN_ID}
|
|
3328
|
+
purpose: Route coding agents to the correct package, checks, and human docs in any monorepo.
|
|
3329
|
+
owner: AgentsKit
|
|
3330
|
+
license: CC-BY-4.0
|
|
3331
|
+
visibility: public
|
|
3332
|
+
tags: [agents, documentation, monorepo, handoff, mcp]
|
|
3333
|
+
---
|
|
3334
|
+
|
|
3335
|
+
# Doc Bridge Pattern
|
|
3336
|
+
|
|
3337
|
+
**AgentHandoff for your monorepo** \u2014 deterministic routing so agents edit the right roots, run the right checks, and stay linked to human documentation.
|
|
3338
|
+
|
|
3339
|
+
## Problem
|
|
3340
|
+
|
|
3341
|
+
Coding agents guess package ownership. They edit sibling modules, run repo-wide tests, and ship changes without a bridge to human-facing guides. Wikis and RAG explain concepts but weakly answer *where to act*.
|
|
3342
|
+
|
|
3343
|
+
## Solution (three artifacts)
|
|
3344
|
+
|
|
3345
|
+
| Artifact | Role |
|
|
3346
|
+
|----------|------|
|
|
3347
|
+
| **AgentHandoff** | JSON handoff: \`startHere\`, \`editRoots\`, \`checks\`, \`humanDoc\`, \`bridge\` |
|
|
3348
|
+
| **DocBridgeIndex** | Deterministic index + \`contentHash\` for CI freshness gates |
|
|
3349
|
+
| **Self-describe** | \`llms.txt\`, \`capabilities.json\` for discovery |
|
|
3350
|
+
|
|
3351
|
+
## Four loops
|
|
3352
|
+
|
|
3353
|
+
| Loop | Command | Outcome |
|
|
3354
|
+
|------|---------|---------|
|
|
3355
|
+
| **Act** | \`ak-docs query package <id> --agent\` | Agent edits only \`editRoots\` |
|
|
3356
|
+
| **Bridge** | \`ak-docs bootstrap agent-docs\` | Link agent corpus \u2194 human site |
|
|
3357
|
+
| **Learn** | \`ak-docs memory promote --pr\` | HITL draft PR from agent memory |
|
|
3358
|
+
| **Explain** | \`ak-docs ask "<question>"\` | Local consult + handoff preview |
|
|
3359
|
+
|
|
3360
|
+
## 60-second proof
|
|
3361
|
+
|
|
3362
|
+
\`\`\`bash
|
|
3363
|
+
npm i -D @agentskit/doc-bridge
|
|
3364
|
+
npx ak-docs demo --text
|
|
3365
|
+
ak-docs mcp install --cursor
|
|
3366
|
+
\`\`\`
|
|
3367
|
+
|
|
3368
|
+
## AgentHandoff example
|
|
3369
|
+
|
|
3370
|
+
\`\`\`json
|
|
3371
|
+
{
|
|
3372
|
+
"type": "agent-handoff",
|
|
3373
|
+
"startHere": "docs/for-agents/packages/auth.md",
|
|
3374
|
+
"editRoots": ["packages/auth"],
|
|
3375
|
+
"checks": ["pnpm --filter @demo/auth test"],
|
|
3376
|
+
"humanDoc": "/docs/guides/auth",
|
|
3377
|
+
"bridge": { "humanDoc": "linked" }
|
|
3378
|
+
}
|
|
3379
|
+
\`\`\`
|
|
3380
|
+
|
|
3381
|
+
When \`humanDoc\` is missing, handoffs surface \`bridge.action: "ak-docs bootstrap agent-docs"\` \u2014 a feature, not a silent gap.
|
|
3382
|
+
|
|
3383
|
+
## MCP contract
|
|
3384
|
+
|
|
3385
|
+
Agents call \`handoff.resolve\` before editing \`packages/*\`:
|
|
3386
|
+
|
|
3387
|
+
1. Read \`startHere\`
|
|
3388
|
+
2. Stay inside \`editRoots\`
|
|
3389
|
+
3. Run every \`checks\` command
|
|
3390
|
+
4. Link \`humanDoc\` or escalate missing bridge
|
|
3391
|
+
|
|
3392
|
+
## CI gate
|
|
3393
|
+
|
|
3394
|
+
\`\`\`yaml
|
|
3395
|
+
- uses: AgentsKit-io/doc-bridge@v1.0.0
|
|
3396
|
+
\`\`\`
|
|
3397
|
+
|
|
3398
|
+
Or: \`ak-docs index && ak-docs gate run\` \u2014 stale index fails the PR.
|
|
3399
|
+
|
|
3400
|
+
## Coverage metric
|
|
3401
|
+
|
|
3402
|
+
\`\`\`bash
|
|
3403
|
+
ak-docs doctor --text
|
|
3404
|
+
ak-docs doctor --badge
|
|
3405
|
+
\`\`\`
|
|
3406
|
+
|
|
3407
|
+
Teams track handoff % and human-bridge % daily.
|
|
3408
|
+
|
|
3409
|
+
## When to use
|
|
3410
|
+
|
|
3411
|
+
- pnpm/npm monorepos with real package ownership
|
|
3412
|
+
- Fumadocus / Docusaurus human sites + dense agent corpus
|
|
3413
|
+
- Cursor / Claude / Codex agents that should not guess edit roots
|
|
3414
|
+
|
|
3415
|
+
## When not to use
|
|
3416
|
+
|
|
3417
|
+
- Single-file repos with no ownership boundaries (AGENTS.md may suffice)
|
|
3418
|
+
- Hosted doc chat as primary product (doc-bridge is routing + bridge, not SaaS chat)
|
|
3419
|
+
|
|
3420
|
+
## References
|
|
3421
|
+
|
|
3422
|
+
- npm: https://www.npmjs.com/package/@agentskit/doc-bridge
|
|
3423
|
+
- repo: https://github.com/AgentsKit-io/doc-bridge
|
|
3424
|
+
- skill: https://github.com/AgentsKit-io/doc-bridge/blob/master/docs/skills/doc-bridge.md
|
|
3425
|
+
- landing: https://agentskit-io.github.io/doc-bridge/
|
|
3426
|
+
`;
|
|
3427
|
+
var docBridgePatternPayload = () => ({
|
|
3428
|
+
...DOC_BRIDGE_PATTERN_META,
|
|
3429
|
+
format: "okf-pattern-v1",
|
|
3430
|
+
body: docBridgePatternMarkdown()
|
|
3431
|
+
});
|
|
2766
3432
|
export {
|
|
2767
3433
|
AgentHandoffLegacySchema,
|
|
2768
3434
|
AgentHandoffV1JsonSchema,
|
|
2769
3435
|
AgentHandoffV1Schema,
|
|
2770
3436
|
AgentSearchV1Schema,
|
|
2771
3437
|
ConfigNotFoundError,
|
|
3438
|
+
DOC_BRIDGE_PATTERN_ID,
|
|
3439
|
+
DOC_BRIDGE_PATTERN_META,
|
|
2772
3440
|
DocBridgeConfigV1Schema,
|
|
2773
3441
|
DocBridgeIndexV1JsonSchema,
|
|
2774
3442
|
DocBridgeIndexV1Schema,
|
|
2775
3443
|
DocBridgeJsonSchemas,
|
|
2776
3444
|
HANDOFF_SCHEMA_VERSION,
|
|
3445
|
+
HandoffBridgeSchema,
|
|
2777
3446
|
HandoffTargetTypeSchema,
|
|
2778
3447
|
INDEX_SCHEMA_VERSION,
|
|
2779
3448
|
IndexNotFoundError,
|
|
@@ -2792,17 +3461,26 @@ export {
|
|
|
2792
3461
|
collectPackages,
|
|
2793
3462
|
createDocBridgeRag,
|
|
2794
3463
|
createDocBridgeRetriever,
|
|
3464
|
+
defaultPromotionDraftPath,
|
|
2795
3465
|
defineConfig,
|
|
3466
|
+
docBridgePatternMarkdown,
|
|
3467
|
+
docBridgePatternPayload,
|
|
3468
|
+
doctorBadgeMetrics,
|
|
2796
3469
|
draftMemoryPromotion,
|
|
3470
|
+
formatDoctorBadgeJson,
|
|
3471
|
+
formatDoctorBadgeMarkdown,
|
|
3472
|
+
formatDoctorText,
|
|
2797
3473
|
handleMcpRequest,
|
|
2798
3474
|
indexFilePath,
|
|
2799
3475
|
ingestAgentMemory,
|
|
2800
3476
|
ingestCursorRules,
|
|
2801
3477
|
ingestMemoryCandidates,
|
|
3478
|
+
installMcpConfig,
|
|
2802
3479
|
layer1InstallHint,
|
|
2803
3480
|
loadConfig,
|
|
2804
3481
|
loadDocBridgeIndex,
|
|
2805
3482
|
loadFederatedChunks,
|
|
3483
|
+
mcpSnippet,
|
|
2806
3484
|
normalizeAgentHandoff,
|
|
2807
3485
|
parseAgentHandoff,
|
|
2808
3486
|
parseAgentSearch,
|
|
@@ -2811,12 +3489,14 @@ export {
|
|
|
2811
3489
|
parseLlmsTxtLinks,
|
|
2812
3490
|
parseMemoryCandidate,
|
|
2813
3491
|
projectRootFromConfigPath,
|
|
3492
|
+
promoteMemoryToGithubPr,
|
|
2814
3493
|
resolveGateIds,
|
|
2815
3494
|
resolveProjectRoot,
|
|
2816
3495
|
resolveRoot,
|
|
2817
3496
|
retrieveDocBridgeChunks,
|
|
2818
3497
|
retrieveHybridChunks,
|
|
2819
3498
|
runChatOnce,
|
|
3499
|
+
runDoctor,
|
|
2820
3500
|
runGate,
|
|
2821
3501
|
runGates,
|
|
2822
3502
|
runQuery,
|
|
@@ -2827,6 +3507,8 @@ export {
|
|
|
2827
3507
|
searchIndex,
|
|
2828
3508
|
sha256NormalizedV1,
|
|
2829
3509
|
startInkChat,
|
|
2830
|
-
startMcpStdioServer
|
|
3510
|
+
startMcpStdioServer,
|
|
3511
|
+
watchDocBridgeIndex,
|
|
3512
|
+
writePromotionDraft
|
|
2831
3513
|
};
|
|
2832
3514
|
//# sourceMappingURL=index.js.map
|