@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.
Files changed (50) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +139 -57
  3. package/action.yml +78 -0
  4. package/dist/cli/program.js +987 -69
  5. package/dist/cli/program.js.map +1 -1
  6. package/dist/index.d.ts +238 -26
  7. package/dist/index.js +704 -22
  8. package/dist/index.js.map +1 -1
  9. package/docs/DOGFOOD-ROUND3.md +74 -0
  10. package/docs/POSITIONING.md +2 -0
  11. package/docs/RELEASE.md +5 -3
  12. package/docs/chat-and-rag.md +2 -0
  13. package/docs/getting-started.md +14 -1
  14. package/docs/landing/index.html +299 -0
  15. package/docs/mcp.md +10 -1
  16. package/docs/ollama-demo.md +64 -0
  17. package/docs/playbook/doc-bridge-pattern.md +114 -0
  18. package/docs/recipes/index-pipeline.md +89 -0
  19. package/docs/skills/doc-bridge.md +64 -0
  20. package/docs/spec/cli.md +6 -0
  21. package/docs/spec/playbook-feedback.md +12 -4
  22. package/examples/demo-example/doc-bridge.config.json +23 -0
  23. package/examples/demo-example/docs/for-agents/INDEX.md +3 -0
  24. package/examples/demo-example/docs/for-agents/packages/example.md +14 -0
  25. package/examples/demo-example/package.json +7 -0
  26. package/examples/demo-example/src/.gitkeep +0 -0
  27. package/examples/demo-monorepo/doc-bridge.config.json +37 -0
  28. package/examples/demo-monorepo/docs/for-agents/INDEX.md +4 -0
  29. package/examples/demo-monorepo/docs/for-agents/packages/auth.md +22 -0
  30. package/examples/demo-monorepo/docs/for-agents/packages/billing.md +19 -0
  31. package/examples/demo-monorepo/docs/human/guides/auth.md +7 -0
  32. package/examples/demo-monorepo/package.json +7 -0
  33. package/examples/demo-monorepo/packages/auth/package.json +8 -0
  34. package/examples/demo-monorepo/packages/billing/package.json +7 -0
  35. package/examples/demo-monorepo/pnpm-workspace.yaml +2 -0
  36. package/examples/ollama-chat.config.ts +42 -0
  37. package/package.json +5 -2
  38. package/src/cli/demo.ts +143 -0
  39. package/src/cli/program.ts +194 -14
  40. package/src/doctor/badge.ts +53 -0
  41. package/src/doctor/run-doctor.ts +286 -0
  42. package/src/index-builder/build-handoffs.ts +19 -1
  43. package/src/index-builder/watch-index.ts +94 -0
  44. package/src/index.ts +24 -0
  45. package/src/mcp/install.ts +84 -0
  46. package/src/memory/github-pr.ts +190 -0
  47. package/src/playbook/doc-bridge-pattern.ts +121 -0
  48. package/src/query/query.ts +19 -1
  49. package/src/schemas/agent-handoff.ts +11 -0
  50. 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
- notes: record.purpose ? [record.purpose] : []
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 = "0.1.0-alpha.3";
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 = (slug, urlPrefix) => {
1260
- if (typeof urlPrefix !== "string" || !urlPrefix) return slug;
1274
+ var humanUrl = (slug2, urlPrefix) => {
1275
+ if (typeof urlPrefix !== "string" || !urlPrefix) return slug2;
1261
1276
  const prefix = urlPrefix.replace(/\/$/, "");
1262
- return slug ? `${prefix}/${slug.replace(/^\//, "")}` : prefix;
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 slug = parseFrontmatter2(raw).slug;
1294
- if (!slug) return docusaurusFileSlug(relPath);
1295
- return slug.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "");
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
- notes: owner.purpose ? [owner.purpose] : []
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
+ `![handoff coverage](https://img.shields.io/badge/${handoff}?style=flat-square)`,
2482
+ `![human bridge](https://img.shields.io/badge/${bridge}?style=flat-square)`,
2483
+ `![doc-bridge score](https://img.shields.io/badge/doc--bridge_score-${metrics.score}%2F100-${badgeColor(metrics.score)}?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 existsSync9, readFileSync as readFileSync13 } from "fs";
2383
- import { resolve as resolve5 } from "path";
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 = resolve5(root, source);
2399
- if (!existsSync9(path)) return null;
2400
- return readFileSync13(path, "utf8");
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 readFileSync14 } from "fs";
2486
- import { join as join14 } from "path";
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 join13 } from "path";
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) => join13(root, ".doc-bridge", "vectors");
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 = join14(root, entry.path);
3158
+ const abs = join17(root, entry.path);
2613
3159
  let content = "";
2614
3160
  try {
2615
- content = readFileSync14(abs, "utf8");
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" ? join14(root, config.intelligence.retriever.options.storePath) : defaultVectorStorePath(root);
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