@agentskit/doc-bridge 0.1.0-alpha.3 → 1.0.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.
Files changed (52) 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 +1000 -70
  5. package/dist/cli/program.js.map +1 -1
  6. package/dist/index.d.ts +238 -26
  7. package/dist/index.js +717 -23
  8. package/dist/index.js.map +1 -1
  9. package/docs/DOGFOOD-ROUND3.md +74 -0
  10. package/docs/DOGFOOD-V1.md +84 -0
  11. package/docs/POSITIONING.md +2 -0
  12. package/docs/RELEASE.md +5 -3
  13. package/docs/chat-and-rag.md +2 -0
  14. package/docs/getting-started.md +14 -1
  15. package/docs/landing/index.html +299 -0
  16. package/docs/mcp.md +10 -1
  17. package/docs/ollama-demo.md +64 -0
  18. package/docs/playbook/doc-bridge-pattern.md +114 -0
  19. package/docs/recipes/index-pipeline.md +89 -0
  20. package/docs/skills/doc-bridge.md +64 -0
  21. package/docs/spec/cli.md +6 -0
  22. package/docs/spec/playbook-feedback.md +12 -4
  23. package/examples/demo-example/doc-bridge.config.json +23 -0
  24. package/examples/demo-example/docs/for-agents/INDEX.md +3 -0
  25. package/examples/demo-example/docs/for-agents/packages/example.md +14 -0
  26. package/examples/demo-example/package.json +7 -0
  27. package/examples/demo-example/src/.gitkeep +0 -0
  28. package/examples/demo-monorepo/doc-bridge.config.json +37 -0
  29. package/examples/demo-monorepo/docs/for-agents/INDEX.md +4 -0
  30. package/examples/demo-monorepo/docs/for-agents/packages/auth.md +22 -0
  31. package/examples/demo-monorepo/docs/for-agents/packages/billing.md +19 -0
  32. package/examples/demo-monorepo/docs/human/guides/auth.md +7 -0
  33. package/examples/demo-monorepo/package.json +7 -0
  34. package/examples/demo-monorepo/packages/auth/package.json +8 -0
  35. package/examples/demo-monorepo/packages/billing/package.json +7 -0
  36. package/examples/demo-monorepo/pnpm-workspace.yaml +2 -0
  37. package/examples/ollama-chat.config.ts +42 -0
  38. package/package.json +7 -4
  39. package/src/cli/demo.ts +143 -0
  40. package/src/cli/program.ts +194 -14
  41. package/src/doctor/badge.ts +53 -0
  42. package/src/doctor/run-doctor.ts +286 -0
  43. package/src/index-builder/build-handoffs.ts +19 -1
  44. package/src/index-builder/watch-index.ts +94 -0
  45. package/src/index.ts +24 -0
  46. package/src/intelligence/adapter.ts +14 -1
  47. package/src/mcp/install.ts +84 -0
  48. package/src/memory/github-pr.ts +190 -0
  49. package/src/playbook/doc-bridge-pattern.ts +121 -0
  50. package/src/query/query.ts +19 -1
  51. package/src/schemas/agent-handoff.ts +11 -0
  52. package/src/version.ts +1 -1
@@ -1,6 +1,6 @@
1
1
  // src/cli/program.ts
2
- import { existsSync as existsSync10, mkdirSync as mkdirSync2, readFileSync as readFileSync15, writeFileSync as writeFileSync2 } from "fs";
3
- import { dirname as dirname4, resolve as resolve6 } from "path";
2
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync17, writeFileSync as writeFileSync4 } from "fs";
3
+ import { dirname as dirname7, resolve as resolve9 } from "path";
4
4
  import { createInterface } from "readline/promises";
5
5
 
6
6
  // src/config/load-config.ts
@@ -756,6 +756,11 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
756
756
  ...agentDoc ? { agentDoc } : {}
757
757
  };
758
758
  ownership[pkg.id] = record;
759
+ const bridge = record.humanDoc ? /^https?:\/\//.test(record.humanDoc) ? { humanDoc: "external" } : { humanDoc: "linked" } : config.corpus.human ? {
760
+ humanDoc: "missing",
761
+ action: "ak-docs bootstrap agent-docs",
762
+ bootstrap: `docs/for-agents/human/${pkg.id}.md`
763
+ } : void 0;
759
764
  handoffs[pkg.id] = {
760
765
  type: "agent-handoff",
761
766
  schemaVersion: 1,
@@ -774,7 +779,11 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
774
779
  editRoots: [record.path],
775
780
  checks: [...record.checks],
776
781
  ...record.humanDoc ? { humanDoc: record.humanDoc } : {},
777
- notes: record.purpose ? [record.purpose] : []
782
+ ...bridge ? { bridge } : {},
783
+ notes: [
784
+ ...record.purpose ? [record.purpose] : [],
785
+ ...!record.humanDoc && config.corpus.human ? [`Human guide missing for ${pkg.id}. Run: ak-docs bootstrap agent-docs`] : []
786
+ ]
778
787
  };
779
788
  }
780
789
  const intents = {};
@@ -802,7 +811,7 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
802
811
  };
803
812
 
804
813
  // src/version.ts
805
- var PACKAGE_VERSION = "0.1.0-alpha.3";
814
+ var PACKAGE_VERSION = "1.0.0";
806
815
 
807
816
  // src/index-builder/capabilities.ts
808
817
  var renderCapabilitiesJson = (config, index, paths) => {
@@ -901,10 +910,10 @@ var docId = (relToHumanRoot, raw) => {
901
910
  return meta.package ?? meta.module ?? meta.id ?? slugFromPath(relToHumanRoot);
902
911
  };
903
912
  var routeSlug = (relToHumanRoot, opts) => relToHumanRoot.split("/").filter((part) => !opts?.stripGroups || !/^\(.+\)$/.test(part)).join("/").replace(/(?:^|\/)index\.mdx?$/, "").replace(/\.mdx?$/, "");
904
- var humanUrl = (slug, urlPrefix) => {
905
- if (typeof urlPrefix !== "string" || !urlPrefix) return slug;
913
+ var humanUrl = (slug2, urlPrefix) => {
914
+ if (typeof urlPrefix !== "string" || !urlPrefix) return slug2;
906
915
  const prefix = urlPrefix.replace(/\/$/, "");
907
- return slug ? `${prefix}/${slug.replace(/^\//, "")}` : prefix;
916
+ return slug2 ? `${prefix}/${slug2.replace(/^\//, "")}` : prefix;
908
917
  };
909
918
  var scanMarkdownDocs = (root, humanRoot, options) => {
910
919
  const out = [];
@@ -935,9 +944,9 @@ var docusaurusFileSlug = (relPath) => {
935
944
  return routeSlug(relPath);
936
945
  };
937
946
  var docusaurusSlug = (relPath, raw) => {
938
- const slug = parseFrontmatter2(raw).slug;
939
- if (!slug) return docusaurusFileSlug(relPath);
940
- return slug.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "");
947
+ const slug2 = parseFrontmatter2(raw).slug;
948
+ if (!slug2) return docusaurusFileSlug(relPath);
949
+ return slug2.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "");
941
950
  };
942
951
  var docusaurusSidebarId = (relPath, raw) => {
943
952
  const id = parseFrontmatter2(raw).id;
@@ -1519,6 +1528,11 @@ var HandoffTargetSchema = z2.object({
1519
1528
  group: z2.string().min(1).max(128).optional(),
1520
1529
  layer: z2.string().min(1).max(32).optional()
1521
1530
  }).strict();
1531
+ var HandoffBridgeSchema = z2.object({
1532
+ humanDoc: z2.enum(["linked", "missing", "external"]),
1533
+ action: z2.string().min(1).max(256).optional(),
1534
+ bootstrap: z2.string().min(1).max(256).optional()
1535
+ }).strict();
1522
1536
  var AgentHandoffV1Schema = z2.object({
1523
1537
  type: z2.literal("agent-handoff"),
1524
1538
  schemaVersion: z2.literal(HANDOFF_SCHEMA_VERSION).default(HANDOFF_SCHEMA_VERSION),
@@ -1529,6 +1543,7 @@ var AgentHandoffV1Schema = z2.object({
1529
1543
  editRoots: z2.array(z2.string().min(1).max(512)).max(32),
1530
1544
  checks: z2.array(z2.string().min(1).max(256)).max(32),
1531
1545
  humanDoc: z2.string().min(1).max(512).nullable().optional(),
1546
+ bridge: HandoffBridgeSchema.optional(),
1532
1547
  playbookPatterns: z2.array(z2.string().url()).max(16).optional(),
1533
1548
  notes: z2.array(z2.string().min(1).max(1024)).max(16)
1534
1549
  }).strict();
@@ -1889,6 +1904,11 @@ var handoffForPackage = (index, id, config) => {
1889
1904
  if (fromIndex) return normalizeAgentHandoff(fromIndex);
1890
1905
  const owner = index.lookup?.ownership?.[id];
1891
1906
  if (!owner) throw new Error(`Unknown package/ownership id "${id}". Try: ak-docs list packages`);
1907
+ const bridge = owner.humanDoc ? /^https?:\/\//.test(owner.humanDoc) ? { humanDoc: "external" } : { humanDoc: "linked" } : config.corpus.human ? {
1908
+ humanDoc: "missing",
1909
+ action: "ak-docs bootstrap agent-docs",
1910
+ bootstrap: `docs/for-agents/human/${id}.md`
1911
+ } : void 0;
1892
1912
  return normalizeAgentHandoff({
1893
1913
  type: "agent-handoff",
1894
1914
  source: config.index?.outFile ?? ".doc-bridge/index.json",
@@ -1904,7 +1924,11 @@ var handoffForPackage = (index, id, config) => {
1904
1924
  editRoots: [owner.path],
1905
1925
  checks: [...owner.checks],
1906
1926
  ...owner.humanDoc ? { humanDoc: owner.humanDoc } : {},
1907
- notes: owner.purpose ? [owner.purpose] : []
1927
+ ...bridge ? { bridge } : {},
1928
+ notes: [
1929
+ ...owner.purpose ? [owner.purpose] : [],
1930
+ ...!owner.humanDoc && config.corpus.human ? [`Human guide missing for ${id}. Run: ak-docs bootstrap agent-docs`] : []
1931
+ ]
1908
1932
  });
1909
1933
  };
1910
1934
  var runQuery = (index, config, req) => {
@@ -2039,6 +2063,18 @@ var readEnv = (name) => {
2039
2063
  const value = process.env[name];
2040
2064
  return value && value.length > 0 ? value : void 0;
2041
2065
  };
2066
+ var providerDefaultApiKeyEnv = (provider) => {
2067
+ switch (provider) {
2068
+ case "openai":
2069
+ return "OPENAI_API_KEY";
2070
+ case "anthropic":
2071
+ return "ANTHROPIC_API_KEY";
2072
+ case "openrouter":
2073
+ return "OPENROUTER_API_KEY";
2074
+ default:
2075
+ return void 0;
2076
+ }
2077
+ };
2042
2078
  var resolveIntelligenceRuntime = async (config) => {
2043
2079
  if (!config.intelligence?.enabled) {
2044
2080
  throw new Error("Intelligence is disabled. Set intelligence.enabled: true in doc-bridge config.");
@@ -2050,7 +2086,7 @@ var resolveIntelligenceRuntime = async (config) => {
2050
2086
  const adapters = await importPeer("@agentskit/adapters");
2051
2087
  const provider = adapterCfg.provider;
2052
2088
  const model = adapterCfg.model;
2053
- const apiKey = readEnv(adapterCfg.apiKeyEnv) ?? readEnv("OPENAI_API_KEY") ?? readEnv("ANTHROPIC_API_KEY");
2089
+ const apiKey = readEnv(adapterCfg.apiKeyEnv) ?? readEnv(providerDefaultApiKeyEnv(provider));
2054
2090
  const baseUrl = adapterCfg.baseUrl;
2055
2091
  let adapter;
2056
2092
  let embed;
@@ -2377,9 +2413,752 @@ var draftMemoryPromotion = (classifications) => {
2377
2413
  };
2378
2414
  };
2379
2415
 
2416
+ // src/memory/github-pr.ts
2417
+ import { execFileSync, spawnSync } from "child_process";
2418
+ import { existsSync as existsSync10, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
2419
+ import { join as join15 } from "path";
2420
+ var run = (cmd, args, cwd) => {
2421
+ const result = spawnSync(cmd, [...args], { cwd, encoding: "utf8" });
2422
+ const out = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
2423
+ return { ok: result.status === 0, out };
2424
+ };
2425
+ var hasGh = () => run("gh", ["--version"], process.cwd()).ok;
2426
+ var slug = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
2427
+ var defaultPromotionDraftPath = (root) => join15(root, ".doc-bridge", "drafts", `memory-promotion-${slug()}.md`);
2428
+ var writePromotionDraft = (root, draft, path) => {
2429
+ const draftPath = path ?? defaultPromotionDraftPath(root);
2430
+ mkdirSync2(join15(root, ".doc-bridge", "drafts"), { recursive: true });
2431
+ writeFileSync2(draftPath, `${draft.body}
2432
+ `, "utf8");
2433
+ return draftPath;
2434
+ };
2435
+ var promoteMemoryToGithubPr = (root, draft, options = {}) => {
2436
+ if (!draft.ok && !options.force) {
2437
+ return {
2438
+ ok: false,
2439
+ dryRun: Boolean(options.dryRun),
2440
+ draftPath: "",
2441
+ branch: "",
2442
+ commands: [],
2443
+ message: "Safety scan blocked promotion. Fix findings or pass --force to draft anyway."
2444
+ };
2445
+ }
2446
+ const branch = options.branch ?? `doc-bridge/memory-promotion-${slug()}`;
2447
+ const draftPath = writePromotionDraft(root, draft);
2448
+ const relDraft = draftPath.startsWith(`${root}/`) ? draftPath.slice(root.length + 1) : draftPath;
2449
+ const commands = [
2450
+ `git checkout -b ${branch}`,
2451
+ `git add ${relDraft}`,
2452
+ `git commit -m "draft: doc-bridge memory promotion"`,
2453
+ `git push -u origin ${branch}`,
2454
+ `gh pr create --draft --title "${draft.title}" --body-file ${relDraft}${options.base ? ` --base ${options.base}` : ""}`
2455
+ ];
2456
+ if (options.dryRun) {
2457
+ return {
2458
+ ok: true,
2459
+ dryRun: true,
2460
+ draftPath,
2461
+ branch,
2462
+ commands,
2463
+ message: `Wrote draft to ${relDraft}. Run the printed git/gh commands to open a draft PR.`
2464
+ };
2465
+ }
2466
+ if (!existsSync10(join15(root, ".git"))) {
2467
+ return {
2468
+ ok: false,
2469
+ dryRun: false,
2470
+ draftPath,
2471
+ branch,
2472
+ commands,
2473
+ message: "Not a git repository. Commit the draft manually or run with --dry-run."
2474
+ };
2475
+ }
2476
+ if (!hasGh()) {
2477
+ return {
2478
+ ok: false,
2479
+ dryRun: false,
2480
+ draftPath,
2481
+ branch,
2482
+ commands,
2483
+ message: "GitHub CLI (gh) not found. Install gh or use --dry-run for local draft + commands."
2484
+ };
2485
+ }
2486
+ const auth = run("gh", ["auth", "status"], root);
2487
+ if (!auth.ok) {
2488
+ return {
2489
+ ok: false,
2490
+ dryRun: false,
2491
+ draftPath,
2492
+ branch,
2493
+ commands,
2494
+ message: `gh is not authenticated. Run: gh auth login
2495
+ ${auth.out}`
2496
+ };
2497
+ }
2498
+ const checkout = run("git", ["checkout", "-b", branch], root);
2499
+ if (!checkout.ok) {
2500
+ return {
2501
+ ok: false,
2502
+ dryRun: false,
2503
+ draftPath,
2504
+ branch,
2505
+ commands,
2506
+ message: `git checkout failed: ${checkout.out}`
2507
+ };
2508
+ }
2509
+ run("git", ["add", relDraft], root);
2510
+ const commit = run("git", ["commit", "-m", "draft: doc-bridge memory promotion"], root);
2511
+ if (!commit.ok) {
2512
+ return {
2513
+ ok: false,
2514
+ dryRun: false,
2515
+ draftPath,
2516
+ branch,
2517
+ commands,
2518
+ message: `git commit failed: ${commit.out}`
2519
+ };
2520
+ }
2521
+ const push = run("git", ["push", "-u", "origin", branch], root);
2522
+ if (!push.ok) {
2523
+ return {
2524
+ ok: false,
2525
+ dryRun: false,
2526
+ draftPath,
2527
+ branch,
2528
+ commands,
2529
+ message: `git push failed: ${push.out}`
2530
+ };
2531
+ }
2532
+ const prArgs = [
2533
+ "pr",
2534
+ "create",
2535
+ "--draft",
2536
+ "--title",
2537
+ draft.title,
2538
+ "--body-file",
2539
+ relDraft,
2540
+ ...options.base ? ["--base", options.base] : []
2541
+ ];
2542
+ let prUrl = "";
2543
+ try {
2544
+ prUrl = execFileSync("gh", prArgs, { cwd: root, encoding: "utf8" }).trim();
2545
+ } catch (error) {
2546
+ const message = error instanceof Error ? error.message : String(error);
2547
+ return {
2548
+ ok: false,
2549
+ dryRun: false,
2550
+ draftPath,
2551
+ branch,
2552
+ commands,
2553
+ message: `gh pr create failed: ${message}`
2554
+ };
2555
+ }
2556
+ return {
2557
+ ok: true,
2558
+ dryRun: false,
2559
+ draftPath,
2560
+ branch,
2561
+ prUrl,
2562
+ ...prUrl ? { previewUrl: prUrl } : {},
2563
+ commands,
2564
+ message: prUrl ? `Draft PR opened: ${prUrl}` : "Draft PR created."
2565
+ };
2566
+ };
2567
+
2568
+ // src/index-builder/watch-index.ts
2569
+ import { existsSync as existsSync11, watch } from "fs";
2570
+ import { dirname as dirname4, resolve as resolve5 } from "path";
2571
+ var WATCH_PATTERN = /\.(md|mdx|json|ya?ml|mdc)$/i;
2572
+ var collectWatchRoots = (root, config, configPath) => {
2573
+ const roots = /* @__PURE__ */ new Set();
2574
+ roots.add(resolve5(root, config.corpus.agent.root));
2575
+ const humanSources = config.corpus.human ? Array.isArray(config.corpus.human) ? config.corpus.human : [config.corpus.human] : [];
2576
+ for (const source of humanSources) {
2577
+ const humanOpts = source.options ?? {};
2578
+ for (const key of ["contentDir", "docsDir", "root"]) {
2579
+ const value = humanOpts[key];
2580
+ if (typeof value === "string" && value.length) roots.add(resolve5(root, value));
2581
+ }
2582
+ }
2583
+ if (configPath) roots.add(dirname4(resolve5(configPath)));
2584
+ return [...roots].filter((dir) => existsSync11(dir));
2585
+ };
2586
+ var watchDocBridgeIndex = (opts) => {
2587
+ const debounceMs = opts.debounceMs ?? 350;
2588
+ let timer;
2589
+ let running = false;
2590
+ const rebuild = () => {
2591
+ if (timer) clearTimeout(timer);
2592
+ timer = setTimeout(() => {
2593
+ if (running) return;
2594
+ running = true;
2595
+ try {
2596
+ const result = buildDocBridgeIndex({ root: opts.root, config: opts.config });
2597
+ const handoffCount = Object.keys(result.index.handoffs ?? {}).length;
2598
+ const summary = {
2599
+ knowledgeCount: result.index.knowledge.length,
2600
+ handoffCount,
2601
+ hash: result.index.contentHash.slice(0, 8)
2602
+ };
2603
+ opts.onRebuild?.(summary);
2604
+ process.stdout.write(
2605
+ `[ak-docs] indexed ${summary.knowledgeCount} docs, ${summary.handoffCount} handoffs (${summary.hash}\u2026)
2606
+ `
2607
+ );
2608
+ } catch (error) {
2609
+ process.stderr.write(
2610
+ `[ak-docs] index failed: ${error instanceof Error ? error.message : String(error)}
2611
+ `
2612
+ );
2613
+ } finally {
2614
+ running = false;
2615
+ }
2616
+ }, debounceMs);
2617
+ };
2618
+ rebuild();
2619
+ for (const dir of collectWatchRoots(opts.root, opts.config, opts.configPath)) {
2620
+ watch(dir, { recursive: true }, (_event, filename) => {
2621
+ if (!filename || !WATCH_PATTERN.test(filename)) return;
2622
+ rebuild();
2623
+ });
2624
+ }
2625
+ const configDir = resolve5(opts.root);
2626
+ if (existsSync11(configDir)) {
2627
+ watch(configDir, (_event, filename) => {
2628
+ if (!filename || !/doc-bridge\.config/.test(filename)) return;
2629
+ rebuild();
2630
+ });
2631
+ }
2632
+ process.stdout.write(
2633
+ `[ak-docs] watching ${collectWatchRoots(opts.root, opts.config, opts.configPath).join(", ") || opts.root} (Ctrl+C to stop)
2634
+ `
2635
+ );
2636
+ return new Promise((resolvePromise) => {
2637
+ const onSignal = () => resolvePromise(0);
2638
+ process.once("SIGINT", onSignal);
2639
+ process.once("SIGTERM", onSignal);
2640
+ });
2641
+ };
2642
+
2643
+ // src/doctor/badge.ts
2644
+ var doctorBadgeMetrics = (report) => {
2645
+ const { packages } = report.coverage;
2646
+ const handoffPct = packages.total > 0 ? Math.round(packages.withAgentDoc / packages.total * 100) : 0;
2647
+ const bridgePct = packages.total > 0 ? Math.round(packages.withHumanDoc / packages.total * 100) : 0;
2648
+ return {
2649
+ handoffPct,
2650
+ bridgePct,
2651
+ score: report.score,
2652
+ grade: report.grade,
2653
+ packages: packages.total
2654
+ };
2655
+ };
2656
+ var badgeColor = (pct) => {
2657
+ if (pct >= 80) return "2ea44f";
2658
+ if (pct >= 50) return "dbab09";
2659
+ return "cb2431";
2660
+ };
2661
+ var formatDoctorBadgeMarkdown = (metrics) => {
2662
+ const handoff = `handoff_coverage-${metrics.handoffPct}%25-${badgeColor(metrics.handoffPct)}`;
2663
+ const bridge = `human_bridge-${metrics.bridgePct}%25-${badgeColor(metrics.bridgePct)}`;
2664
+ return [
2665
+ `![handoff coverage](https://img.shields.io/badge/${handoff}?style=flat-square)`,
2666
+ `![human bridge](https://img.shields.io/badge/${bridge}?style=flat-square)`,
2667
+ `![doc-bridge score](https://img.shields.io/badge/doc--bridge_score-${metrics.score}%2F100-${badgeColor(metrics.score)}?style=flat-square)`
2668
+ ].join(" ");
2669
+ };
2670
+ var formatDoctorBadgeJson = (metrics) => JSON.stringify(
2671
+ {
2672
+ schemaVersion: 1,
2673
+ ...metrics,
2674
+ markdown: formatDoctorBadgeMarkdown(metrics),
2675
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2676
+ },
2677
+ null,
2678
+ 2
2679
+ );
2680
+
2681
+ // src/playbook/doc-bridge-pattern.ts
2682
+ var DOC_BRIDGE_PATTERN_ID = "doc-bridge-pattern";
2683
+ var DOC_BRIDGE_PATTERN_META = {
2684
+ id: DOC_BRIDGE_PATTERN_ID,
2685
+ title: "Doc Bridge Pattern",
2686
+ slug: "pillars/ai-collaboration/doc-bridge-pattern",
2687
+ license: "CC-BY-4.0",
2688
+ visibility: "public",
2689
+ playbookUrl: "https://playbook.agentskit.io/patterns/doc-bridge-pattern",
2690
+ npmPackage: "@agentskit/doc-bridge",
2691
+ cli: "ak-docs"
2692
+ };
2693
+ var docBridgePatternMarkdown = () => `---
2694
+ type: pattern
2695
+ id: ${DOC_BRIDGE_PATTERN_ID}
2696
+ purpose: Route coding agents to the correct package, checks, and human docs in any monorepo.
2697
+ owner: AgentsKit
2698
+ license: CC-BY-4.0
2699
+ visibility: public
2700
+ tags: [agents, documentation, monorepo, handoff, mcp]
2701
+ ---
2702
+
2703
+ # Doc Bridge Pattern
2704
+
2705
+ **AgentHandoff for your monorepo** \u2014 deterministic routing so agents edit the right roots, run the right checks, and stay linked to human documentation.
2706
+
2707
+ ## Problem
2708
+
2709
+ 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*.
2710
+
2711
+ ## Solution (three artifacts)
2712
+
2713
+ | Artifact | Role |
2714
+ |----------|------|
2715
+ | **AgentHandoff** | JSON handoff: \`startHere\`, \`editRoots\`, \`checks\`, \`humanDoc\`, \`bridge\` |
2716
+ | **DocBridgeIndex** | Deterministic index + \`contentHash\` for CI freshness gates |
2717
+ | **Self-describe** | \`llms.txt\`, \`capabilities.json\` for discovery |
2718
+
2719
+ ## Four loops
2720
+
2721
+ | Loop | Command | Outcome |
2722
+ |------|---------|---------|
2723
+ | **Act** | \`ak-docs query package <id> --agent\` | Agent edits only \`editRoots\` |
2724
+ | **Bridge** | \`ak-docs bootstrap agent-docs\` | Link agent corpus \u2194 human site |
2725
+ | **Learn** | \`ak-docs memory promote --pr\` | HITL draft PR from agent memory |
2726
+ | **Explain** | \`ak-docs ask "<question>"\` | Local consult + handoff preview |
2727
+
2728
+ ## 60-second proof
2729
+
2730
+ \`\`\`bash
2731
+ npm i -D @agentskit/doc-bridge
2732
+ npx ak-docs demo --text
2733
+ ak-docs mcp install --cursor
2734
+ \`\`\`
2735
+
2736
+ ## AgentHandoff example
2737
+
2738
+ \`\`\`json
2739
+ {
2740
+ "type": "agent-handoff",
2741
+ "startHere": "docs/for-agents/packages/auth.md",
2742
+ "editRoots": ["packages/auth"],
2743
+ "checks": ["pnpm --filter @demo/auth test"],
2744
+ "humanDoc": "/docs/guides/auth",
2745
+ "bridge": { "humanDoc": "linked" }
2746
+ }
2747
+ \`\`\`
2748
+
2749
+ When \`humanDoc\` is missing, handoffs surface \`bridge.action: "ak-docs bootstrap agent-docs"\` \u2014 a feature, not a silent gap.
2750
+
2751
+ ## MCP contract
2752
+
2753
+ Agents call \`handoff.resolve\` before editing \`packages/*\`:
2754
+
2755
+ 1. Read \`startHere\`
2756
+ 2. Stay inside \`editRoots\`
2757
+ 3. Run every \`checks\` command
2758
+ 4. Link \`humanDoc\` or escalate missing bridge
2759
+
2760
+ ## CI gate
2761
+
2762
+ \`\`\`yaml
2763
+ - uses: AgentsKit-io/doc-bridge@v1.0.0
2764
+ \`\`\`
2765
+
2766
+ Or: \`ak-docs index && ak-docs gate run\` \u2014 stale index fails the PR.
2767
+
2768
+ ## Coverage metric
2769
+
2770
+ \`\`\`bash
2771
+ ak-docs doctor --text
2772
+ ak-docs doctor --badge
2773
+ \`\`\`
2774
+
2775
+ Teams track handoff % and human-bridge % daily.
2776
+
2777
+ ## When to use
2778
+
2779
+ - pnpm/npm monorepos with real package ownership
2780
+ - Fumadocus / Docusaurus human sites + dense agent corpus
2781
+ - Cursor / Claude / Codex agents that should not guess edit roots
2782
+
2783
+ ## When not to use
2784
+
2785
+ - Single-file repos with no ownership boundaries (AGENTS.md may suffice)
2786
+ - Hosted doc chat as primary product (doc-bridge is routing + bridge, not SaaS chat)
2787
+
2788
+ ## References
2789
+
2790
+ - npm: https://www.npmjs.com/package/@agentskit/doc-bridge
2791
+ - repo: https://github.com/AgentsKit-io/doc-bridge
2792
+ - skill: https://github.com/AgentsKit-io/doc-bridge/blob/master/docs/skills/doc-bridge.md
2793
+ - landing: https://agentskit-io.github.io/doc-bridge/
2794
+ `;
2795
+ var docBridgePatternPayload = () => ({
2796
+ ...DOC_BRIDGE_PATTERN_META,
2797
+ format: "okf-pattern-v1",
2798
+ body: docBridgePatternMarkdown()
2799
+ });
2800
+
2801
+ // src/cli/demo.ts
2802
+ import { cpSync, existsSync as existsSync13, mkdtempSync, readFileSync as readFileSync15, rmSync } from "fs";
2803
+ import { tmpdir } from "os";
2804
+ import { dirname as dirname6, join as join18, resolve as resolve7 } from "path";
2805
+ import { fileURLToPath } from "url";
2806
+
2807
+ // src/mcp/install.ts
2808
+ import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync14, writeFileSync as writeFileSync3 } from "fs";
2809
+ import { homedir } from "os";
2810
+ import { dirname as dirname5, join as join17, resolve as resolve6 } from "path";
2811
+ var SERVER_NAME = "ak-docs";
2812
+ var mcpServerEntry = (root) => ({
2813
+ command: "npx",
2814
+ args: ["ak-docs", "mcp"],
2815
+ cwd: root
2816
+ });
2817
+ var readJson = (path) => {
2818
+ if (!existsSync12(path)) return {};
2819
+ try {
2820
+ const parsed = JSON.parse(readFileSync14(path, "utf8"));
2821
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
2822
+ } catch {
2823
+ return {};
2824
+ }
2825
+ };
2826
+ var writeJson = (path, value) => {
2827
+ mkdirSync3(dirname5(path), { recursive: true });
2828
+ writeFileSync3(path, `${JSON.stringify(value, null, 2)}
2829
+ `, "utf8");
2830
+ };
2831
+ var resolveTargetPath = (target, root) => {
2832
+ if (target === "cursor") return resolve6(root, ".cursor", "mcp.json");
2833
+ return join17(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
2834
+ };
2835
+ var installMcpConfig = (root, target) => {
2836
+ const configPath = resolveTargetPath(target, root);
2837
+ const created = !existsSync12(configPath);
2838
+ const existing = readJson(configPath);
2839
+ const servers = existing.mcpServers && typeof existing.mcpServers === "object" && !Array.isArray(existing.mcpServers) ? { ...existing.mcpServers } : {};
2840
+ servers[SERVER_NAME] = mcpServerEntry(root);
2841
+ writeJson(configPath, { ...existing, mcpServers: servers });
2842
+ const nextSteps = target === "cursor" ? [
2843
+ "Restart Cursor or reload MCP servers",
2844
+ "Paste the doc-bridge skill into Cursor rules (see docs/skills/doc-bridge.md)",
2845
+ "Before editing packages/* call handoff.resolve"
2846
+ ] : [
2847
+ "Restart Claude Desktop",
2848
+ "Run ak-docs index after doc changes"
2849
+ ];
2850
+ return {
2851
+ ok: true,
2852
+ target,
2853
+ configPath,
2854
+ created,
2855
+ serverName: SERVER_NAME,
2856
+ nextSteps
2857
+ };
2858
+ };
2859
+ var mcpSnippet = (root) => JSON.stringify({ mcpServers: { [SERVER_NAME]: mcpServerEntry(root) } }, null, 2);
2860
+
2861
+ // src/cli/demo.ts
2862
+ var packageRoot = resolve7(dirname6(fileURLToPath(import.meta.url)), "..", "..");
2863
+ var fixturePath = (fixture) => {
2864
+ if (fixture === "monorepo") {
2865
+ return join18(packageRoot, "examples", "demo-monorepo");
2866
+ }
2867
+ return join18(packageRoot, "examples", "demo-example");
2868
+ };
2869
+ var formatHandoffText = (handoff) => {
2870
+ const bridge = handoff.bridge?.humanDoc === "missing" ? `human guide: missing \u2192 ${handoff.bridge.action ?? "ak-docs bootstrap agent-docs"}` : handoff.humanDoc ? `human guide: ${handoff.humanDoc}` : "human guide: (none)";
2871
+ return [
2872
+ `target: ${handoff.target.id} (${handoff.target.path ?? "n/a"})`,
2873
+ `start: ${handoff.startHere}`,
2874
+ `edit: ${handoff.editRoots.join(", ")}`,
2875
+ `checks: ${handoff.checks.length ? handoff.checks.join(" \xB7 ") : "(none)"}`,
2876
+ bridge,
2877
+ ...handoff.notes.length ? [`notes: ${handoff.notes[0]}`] : []
2878
+ ];
2879
+ };
2880
+ var runDemo = (root, config, fixture = "example", options = {}) => {
2881
+ const targetPackage = fixture === "monorepo" ? "auth" : "example";
2882
+ const fixtureDir = fixturePath(fixture);
2883
+ if (options.copyFixture && existsSync13(fixtureDir)) {
2884
+ for (const rel of ["doc-bridge.config.json", "docs", "packages", "pnpm-workspace.yaml", "package.json"]) {
2885
+ const src = join18(fixtureDir, rel);
2886
+ if (!existsSync13(src)) continue;
2887
+ const dest = join18(root, rel);
2888
+ cpSync(src, dest, { recursive: true });
2889
+ }
2890
+ }
2891
+ const gateBeforeStale = runGates(root, config, ["index-freshness"]);
2892
+ const beforeGate = gateBeforeStale.results[0] ?? { ok: false, message: "index-freshness unavailable" };
2893
+ buildDocBridgeIndex({ root, config });
2894
+ const index = loadDocBridgeIndex(root, config);
2895
+ const handoff = runQuery(index, config, {
2896
+ kind: "package",
2897
+ id: targetPackage,
2898
+ agent: true
2899
+ });
2900
+ const gateAfter = runGates(root, config, ["index-freshness"]);
2901
+ const afterGate = gateAfter.results[0] ?? { ok: true, message: "Index is fresh" };
2902
+ return {
2903
+ ok: afterGate.ok,
2904
+ fixture,
2905
+ handoff,
2906
+ gateBefore: { ok: beforeGate.ok, message: beforeGate.message },
2907
+ gateAfter: { ok: afterGate.ok, message: afterGate.message },
2908
+ mcpSnippet: mcpSnippet(root),
2909
+ nextCommands: [
2910
+ `ak-docs query package ${targetPackage} --agent`,
2911
+ "ak-docs doctor --text",
2912
+ "ak-docs mcp install --cursor",
2913
+ 'ak-docs ask "where do I change auth?"'
2914
+ ]
2915
+ };
2916
+ };
2917
+ var formatDemoText = (result) => {
2918
+ const lines = [
2919
+ "ak-docs demo \u2014 AgentHandoff in 60s",
2920
+ "\u2550".repeat(44),
2921
+ "",
2922
+ "Before (agent guesses package)",
2923
+ ' \u2717 edits packages/billing when task mentions "auth"',
2924
+ " \u2717 runs repo-wide test instead of package checks",
2925
+ "",
2926
+ "After (handoff.resolve / query --agent)",
2927
+ ...formatHandoffText(result.handoff).map((line) => ` \u2713 ${line}`),
2928
+ "",
2929
+ `Gate: ${result.gateBefore.ok ? "green" : "red"} \u2192 ${result.gateAfter.ok ? "green" : "red"}`,
2930
+ ` before: ${result.gateBefore.message}`,
2931
+ ` after: ${result.gateAfter.message}`,
2932
+ "",
2933
+ "MCP snippet (.cursor/mcp.json)",
2934
+ ...result.mcpSnippet.split("\n").map((line) => ` ${line}`),
2935
+ "",
2936
+ "Next",
2937
+ ...result.nextCommands.map((cmd) => ` \u2192 ${cmd}`)
2938
+ ];
2939
+ return lines;
2940
+ };
2941
+ var withDemoWorkspace = (fixture, fn) => {
2942
+ const dir = mkdtempSync(join18(tmpdir(), "ak-docs-demo-"));
2943
+ try {
2944
+ const fixtureDir = fixturePath(fixture);
2945
+ if (!existsSync13(fixtureDir)) {
2946
+ throw new Error(`Demo fixture "${fixture}" not found at ${fixtureDir}`);
2947
+ }
2948
+ cpSync(fixtureDir, dir, { recursive: true });
2949
+ const config = JSON.parse(readFileSync15(join18(dir, "doc-bridge.config.json"), "utf8"));
2950
+ return fn(dir, config);
2951
+ } finally {
2952
+ rmSync(dir, { recursive: true, force: true });
2953
+ }
2954
+ };
2955
+
2956
+ // src/doctor/run-doctor.ts
2957
+ var gradeForScore = (score) => {
2958
+ if (score >= 90) return "A";
2959
+ if (score >= 75) return "B";
2960
+ if (score >= 60) return "C";
2961
+ if (score >= 40) return "D";
2962
+ return "F";
2963
+ };
2964
+ var agentDocPaths = (index) => {
2965
+ const paths = /* @__PURE__ */ new Set();
2966
+ for (const owner of Object.values(index.lookup?.ownership ?? {})) {
2967
+ if (owner.agentDoc) paths.add(owner.agentDoc);
2968
+ }
2969
+ for (const handoff of Object.values(index.handoffs ?? {})) {
2970
+ if (handoff.startHere) paths.add(handoff.startHere);
2971
+ }
2972
+ return paths;
2973
+ };
2974
+ var computeScore = (coverage) => {
2975
+ let score = 0;
2976
+ if (coverage.freshness.hasIndex) score += 15;
2977
+ if (coverage.freshness.ok) score += 15;
2978
+ const { total, withAgentDoc, withHumanDoc } = coverage.packages;
2979
+ if (total > 0) {
2980
+ score += Math.round(withAgentDoc / total * 35);
2981
+ score += Math.round(withHumanDoc / total * 20);
2982
+ } else if (coverage.agentDocs.indexed > 0) {
2983
+ score += 35;
2984
+ }
2985
+ if (coverage.gates.ok) score += 15;
2986
+ else {
2987
+ const passed = coverage.gates.results.filter((gate) => gate.ok).length;
2988
+ const totalGates = coverage.gates.results.length || 1;
2989
+ score += Math.round(passed / totalGates * 10);
2990
+ }
2991
+ return Math.min(100, Math.max(0, score));
2992
+ };
2993
+ var buildIssues = (coverage) => {
2994
+ const issues = [];
2995
+ if (!coverage.freshness.hasIndex) {
2996
+ issues.push({
2997
+ severity: "error",
2998
+ code: "index-missing",
2999
+ message: "No doc-bridge index found.",
3000
+ action: "ak-docs index"
3001
+ });
3002
+ } else if (!coverage.freshness.ok) {
3003
+ issues.push({
3004
+ severity: "error",
3005
+ code: "index-stale",
3006
+ message: coverage.freshness.message,
3007
+ action: "ak-docs index"
3008
+ });
3009
+ }
3010
+ for (const id of coverage.packages.missingAgentDoc) {
3011
+ issues.push({
3012
+ severity: "warn",
3013
+ code: "missing-agent-doc",
3014
+ message: `Package "${id}" has no dedicated agent doc.`,
3015
+ action: `ak-docs init --scaffold-workspaces # or edit docs/for-agents/packages/${id}.md`
3016
+ });
3017
+ }
3018
+ for (const id of coverage.packages.missingHumanDoc) {
3019
+ issues.push({
3020
+ severity: "info",
3021
+ code: "missing-human-doc",
3022
+ message: `Package "${id}" has no linked human guide.`,
3023
+ action: "ak-docs bootstrap agent-docs"
3024
+ });
3025
+ }
3026
+ for (const gate of coverage.gates.results.filter((result) => !result.ok)) {
3027
+ issues.push({
3028
+ severity: gate.id === "index-freshness" ? "error" : "warn",
3029
+ code: `gate-${gate.id}`,
3030
+ message: gate.message,
3031
+ action: gate.id === "index-freshness" ? "ak-docs index" : "ak-docs gate run"
3032
+ });
3033
+ }
3034
+ return issues;
3035
+ };
3036
+ var buildNextActions = (issues, coverage) => {
3037
+ const actions = /* @__PURE__ */ new Set();
3038
+ for (const issue of issues) {
3039
+ if (issue.action) actions.add(issue.action);
3040
+ }
3041
+ if (!coverage.freshness.hasIndex || !coverage.freshness.ok) {
3042
+ actions.add("ak-docs index");
3043
+ }
3044
+ if (coverage.packages.missingHumanDoc.length) {
3045
+ actions.add("ak-docs bootstrap agent-docs");
3046
+ }
3047
+ if (coverage.packages.total > 0) {
3048
+ const sample = coverage.packages.missingAgentDoc[0] ?? coverage.packages.missingHumanDoc[0];
3049
+ if (sample) actions.add(`ak-docs query package ${sample} --agent`);
3050
+ }
3051
+ if (!actions.size) {
3052
+ actions.add("ak-docs mcp install --cursor");
3053
+ actions.add("ak-docs gate run");
3054
+ }
3055
+ return [...actions].slice(0, 6);
3056
+ };
3057
+ var runDoctor = (root, config) => {
3058
+ let index;
3059
+ let hasIndex = true;
3060
+ let freshnessOk = false;
3061
+ let freshnessMessage = "Index is fresh";
3062
+ try {
3063
+ index = loadDocBridgeIndex(root, config);
3064
+ const next = buildDocBridgeIndex({ root, config, write: false }).index.contentHash;
3065
+ freshnessOk = index.contentHash === next;
3066
+ freshnessMessage = freshnessOk ? "Index is fresh" : "Index is stale. Run: ak-docs index";
3067
+ } catch (error) {
3068
+ if (error instanceof IndexNotFoundError) {
3069
+ hasIndex = false;
3070
+ freshnessOk = false;
3071
+ freshnessMessage = error.message;
3072
+ index = buildDocBridgeIndex({ root, config, write: false }).index;
3073
+ } else {
3074
+ throw error;
3075
+ }
3076
+ }
3077
+ const ownership = Object.entries(index.lookup?.ownership ?? {});
3078
+ const missingAgentDoc = ownership.filter(([, owner]) => !owner.agentDoc || owner.agentDoc === config.corpus.agent.index).map(([id]) => id);
3079
+ const missingHumanDoc = ownership.filter(([, owner]) => !owner.humanDoc).map(([id]) => id);
3080
+ const indexedPaths = new Set(index.knowledge.map((entry) => entry.path));
3081
+ const expectedAgentDocs = agentDocPaths(index);
3082
+ const unindexed = [...expectedAgentDocs].filter((path) => !indexedPaths.has(path));
3083
+ const corpusDocs = scanAgentCorpus(root, config).filter(
3084
+ (doc) => doc.path !== config.corpus.agent.index
3085
+ );
3086
+ const gates = runGates(root, config);
3087
+ const coverage = {
3088
+ packages: {
3089
+ total: ownership.length,
3090
+ withAgentDoc: ownership.length - missingAgentDoc.length,
3091
+ withHumanDoc: ownership.length - missingHumanDoc.length,
3092
+ missingAgentDoc,
3093
+ missingHumanDoc
3094
+ },
3095
+ agentDocs: {
3096
+ total: corpusDocs.length,
3097
+ indexed: corpusDocs.filter((doc) => indexedPaths.has(doc.path)).length,
3098
+ unindexed: corpusDocs.filter((doc) => !indexedPaths.has(doc.path)).map((doc) => doc.path)
3099
+ },
3100
+ freshness: {
3101
+ ok: freshnessOk,
3102
+ message: freshnessMessage,
3103
+ hasIndex
3104
+ },
3105
+ gates
3106
+ };
3107
+ const issues = buildIssues(coverage);
3108
+ const score = computeScore(coverage);
3109
+ const nextActions = buildNextActions(issues, coverage);
3110
+ const report = {
3111
+ ok: issues.every((issue) => issue.severity !== "error") && gates.ok,
3112
+ score,
3113
+ grade: gradeForScore(score),
3114
+ coverage,
3115
+ badge: { handoffPct: 0, bridgePct: 0, score, grade: gradeForScore(score), packages: 0 },
3116
+ issues,
3117
+ nextActions
3118
+ };
3119
+ return { ...report, badge: doctorBadgeMetrics(report) };
3120
+ };
3121
+ var formatDoctorText = (report) => {
3122
+ const { coverage } = report;
3123
+ const handoffPct = coverage.packages.total > 0 ? Math.round(coverage.packages.withAgentDoc / coverage.packages.total * 100) : 0;
3124
+ const humanPct = coverage.packages.total > 0 ? Math.round(coverage.packages.withHumanDoc / coverage.packages.total * 100) : 0;
3125
+ const lines = [
3126
+ "doc-bridge doctor",
3127
+ "\u2500".repeat(40),
3128
+ `Score: ${report.score}/100 (${report.grade})`,
3129
+ "",
3130
+ "Coverage",
3131
+ ` Packages: ${coverage.packages.total}`,
3132
+ ` Agent docs: ${coverage.packages.withAgentDoc}/${coverage.packages.total} (${handoffPct}% handoff-ready)`,
3133
+ ` Human guides: ${coverage.packages.withHumanDoc}/${coverage.packages.total} (${humanPct}% bridged)`,
3134
+ ` Corpus indexed: ${coverage.agentDocs.indexed}/${coverage.agentDocs.total} agent docs`,
3135
+ ` Index freshness: ${coverage.freshness.ok ? "fresh" : "stale or missing"}`,
3136
+ ` Gates: ${coverage.gates.results.filter((g) => g.ok).length}/${coverage.gates.results.length} passing`,
3137
+ ` Badge: handoff ${report.badge.handoffPct}% \xB7 bridge ${report.badge.bridgePct}%`
3138
+ ];
3139
+ if (coverage.packages.missingHumanDoc.length) {
3140
+ lines.push("", "Missing humanDoc (bridge gap)", ...coverage.packages.missingHumanDoc.map((id) => ` \u2022 ${id}`));
3141
+ }
3142
+ if (coverage.packages.missingAgentDoc.length) {
3143
+ lines.push("", "Missing agent doc", ...coverage.packages.missingAgentDoc.map((id) => ` \u2022 ${id}`));
3144
+ }
3145
+ if (report.issues.length) {
3146
+ lines.push("", "Issues");
3147
+ for (const issue of report.issues.slice(0, 8)) {
3148
+ const icon = issue.severity === "error" ? "\u2717" : issue.severity === "warn" ? "!" : "\xB7";
3149
+ lines.push(` ${icon} [${issue.code}] ${issue.message}`);
3150
+ }
3151
+ if (report.issues.length > 8) {
3152
+ lines.push(` \u2026 +${report.issues.length - 8} more`);
3153
+ }
3154
+ }
3155
+ lines.push("", "Next actions", ...report.nextActions.map((action) => ` \u2192 ${action}`));
3156
+ return lines;
3157
+ };
3158
+
2380
3159
  // src/mcp/server.ts
2381
- import { readFileSync as readFileSync14, realpathSync } from "fs";
2382
- import { relative, resolve as resolve5 } from "path";
3160
+ import { readFileSync as readFileSync16, realpathSync } from "fs";
3161
+ import { relative, resolve as resolve8 } from "path";
2383
3162
  import { z as z5, ZodError } from "zod";
2384
3163
  var MCP_TOOLS = [
2385
3164
  {
@@ -2486,7 +3265,7 @@ var findDocPath = (index, args) => {
2486
3265
  };
2487
3266
  var resolveDocPath = (root, relPath) => {
2488
3267
  const rootAbs = realpathSync.native(root);
2489
- const unresolved = resolve5(rootAbs, relPath);
3268
+ const unresolved = resolve8(rootAbs, relPath);
2490
3269
  const unresolvedRel = relative(rootAbs, unresolved);
2491
3270
  if (unresolvedRel.startsWith("..")) throw new Error("doc.get path escapes project root");
2492
3271
  const abs = realpathSync.native(unresolved);
@@ -2524,7 +3303,7 @@ var handleMcpRequest = (ctx, request) => {
2524
3303
  }
2525
3304
  if (name === "doc.get") {
2526
3305
  const relPath = findDocPath(index(), parseToolArgs("doc.get", DocGetArgsSchema, args));
2527
- return textResult(readFileSync14(resolveDocPath(ctx.root, relPath), "utf8"));
3306
+ return textResult(readFileSync16(resolveDocPath(ctx.root, relPath), "utf8"));
2528
3307
  }
2529
3308
  if (name === "gate.status") return textResult(runGates(ctx.root, ctx.config));
2530
3309
  if (name === "retriever.query") {
@@ -2606,14 +3385,17 @@ var usage = `ak-docs \u2014 human\u2194agent documentation bridge (@agentskit/do
2606
3385
 
2607
3386
  Core (no API key):
2608
3387
  ak-docs init [--demo] [--scaffold-workspaces]
2609
- ak-docs index
3388
+ ak-docs demo [--fixture example|monorepo] [--text] [--in-project]
3389
+ ak-docs doctor [--text] [--badge] [--write-badge]
3390
+ ak-docs index [--watch]
2610
3391
  ak-docs query <package|ownership|intent|change> <id> [--agent] [--text]
2611
3392
  ak-docs search <term> [--agent] [--text]
2612
3393
  ak-docs list <packages|intents|changes|knowledge> [--text]
2613
3394
  ak-docs ask [question] local consult (no LLM)
2614
3395
  ak-docs gate run [gate-id]
2615
3396
  ak-docs mcp
2616
- ak-docs memory ingest|classify|promote
3397
+ ak-docs mcp install --cursor | --claude
3398
+ ak-docs memory ingest|classify|promote [--pr] [--dry-run]
2617
3399
  ak-docs bootstrap agent-docs
2618
3400
  ak-docs validate-config | validate-handoff <file>
2619
3401
 
@@ -2625,7 +3407,7 @@ Intelligence (optional AgentsKit peers):
2625
3407
  Advanced / ecosystem:
2626
3408
  ak-docs retrieve <query>
2627
3409
  ak-docs registry topology
2628
- ak-docs playbook draft
3410
+ ak-docs playbook draft | pattern [--text]
2629
3411
 
2630
3412
  Global flags:
2631
3413
  -h, --help --version
@@ -2666,6 +3448,8 @@ var parseArgs = (argv) => {
2666
3448
  else if (positional[0] === "index") command = "index";
2667
3449
  else if (positional[0] === "gate") command = "gate";
2668
3450
  else if (positional[0] === "mcp") command = "mcp";
3451
+ else if (positional[0] === "doctor") command = "doctor";
3452
+ else if (positional[0] === "demo") command = "demo";
2669
3453
  else if (positional[0] === "query") command = "query";
2670
3454
  else if (positional[0] === "search") command = "search";
2671
3455
  else if (positional[0] === "retrieve") command = "retrieve";
@@ -2675,7 +3459,7 @@ var parseArgs = (argv) => {
2675
3459
  else if (positional[0] === "list") command = "list";
2676
3460
  return { command, flags, configPath, positional };
2677
3461
  };
2678
- var writeJson = (payload) => {
3462
+ var writeJson2 = (payload) => {
2679
3463
  process.stdout.write(`${JSON.stringify(payload, null, 2)}
2680
3464
  `);
2681
3465
  };
@@ -2723,13 +3507,33 @@ var writeTextSearch = (term, matches) => {
2723
3507
  ...matches.length ? matches.map(formatSearchMatch) : [" (none)"]
2724
3508
  ]);
2725
3509
  };
2726
- var writeAsk = (question, matches, index) => {
3510
+ var handoffSummaryLines = (index, config, ownerId) => {
3511
+ try {
3512
+ const handoff = runQuery(index, config, { kind: "ownership", id: ownerId, agent: true });
3513
+ if (!handoff || typeof handoff !== "object" || !("editRoots" in handoff)) return [];
3514
+ const payload = handoff;
3515
+ const bridgeLine = payload.bridge?.humanDoc === "missing" ? `Bridge: human guide missing \u2192 ${payload.bridge.action ?? "ak-docs bootstrap agent-docs"}` : payload.humanDoc ? `Bridge: ${payload.humanDoc}` : "Bridge: (no human plugin configured)";
3516
+ return [
3517
+ "",
3518
+ "Handoff preview",
3519
+ ` start: ${payload.startHere ?? "(unknown)"}`,
3520
+ ` edit: ${(payload.editRoots ?? []).join(", ") || "(none)"}`,
3521
+ ` checks: ${(payload.checks ?? []).join(" \xB7 ") || "(none)"}`,
3522
+ ` ${bridgeLine}`
3523
+ ];
3524
+ } catch {
3525
+ return [];
3526
+ }
3527
+ };
3528
+ var writeAsk = (question, matches, index, config) => {
2727
3529
  const owner = matches.find((match) => match.type === "ownership") ?? matches.find((match) => Boolean(index.lookup?.ownership?.[match.id]));
2728
3530
  const best = owner ?? matches[0];
2729
- const bestQuery = best && (best.type === "ownership" || index.lookup?.ownership?.[best.id]) ? `ak-docs query ownership ${best.id} --agent` : "ak-docs list knowledge --text";
3531
+ const ownerId = best && (best.type === "ownership" || index.lookup?.ownership?.[best.id]) ? best.id : void 0;
3532
+ const bestQuery = ownerId ? `ak-docs query ownership ${ownerId} --agent` : "ak-docs list knowledge --text";
2730
3533
  writeLines([
2731
3534
  `Question: ${question}`,
2732
3535
  best ? `Best match: ${best.type} ${best.id} (${best.path})` : "Best match: none",
3536
+ ...ownerId ? handoffSummaryLines(index, config, ownerId) : [],
2733
3537
  "",
2734
3538
  "Matches:",
2735
3539
  ...matches.length ? matches.slice(0, 5).map(formatSearchMatch) : [" No local matches. Try: ak-docs search <term>"],
@@ -2737,20 +3541,21 @@ var writeAsk = (question, matches, index) => {
2737
3541
  "Next commands:",
2738
3542
  ...best ? [
2739
3543
  `ak-docs search "${question}" --agent`,
2740
- bestQuery
2741
- ] : ["ak-docs list knowledge --text"]
3544
+ bestQuery,
3545
+ ...ownerId ? [`ak-docs doctor --text`] : []
3546
+ ] : ["ak-docs list knowledge --text", "ak-docs doctor --text"]
2742
3547
  ]);
2743
3548
  };
2744
3549
  var readIndexedDoc = (root, config, idOrPath) => {
2745
3550
  const index = loadDocBridgeIndex(root, config);
2746
3551
  const entry = index.knowledge.find((doc) => doc.id === idOrPath || doc.path === idOrPath);
2747
3552
  if (!entry) throw new Error(`Unknown indexed doc "${idOrPath}". Try: search ${idOrPath}`);
2748
- const abs = resolve6(root, entry.path);
2749
- const rootAbs = resolve6(root);
3553
+ const abs = resolve9(root, entry.path);
3554
+ const rootAbs = resolve9(root);
2750
3555
  if (abs !== rootAbs && !abs.startsWith(`${rootAbs}/`)) {
2751
3556
  throw new Error(`Indexed doc escapes project root: ${entry.path}`);
2752
3557
  }
2753
- return readFileSync15(abs, "utf8");
3558
+ return readFileSync17(abs, "utf8");
2754
3559
  };
2755
3560
  var runAskRepl = async (root, config) => {
2756
3561
  const index = loadDocBridgeIndex(root, config);
@@ -2769,12 +3574,12 @@ var runAskRepl = async (root, config) => {
2769
3574
  process.stdout.write(`${readIndexedDoc(root, config, value).slice(0, 8e3)}
2770
3575
  `);
2771
3576
  } else if (command === "resolve") {
2772
- writeJson(runQuery(index, config, { kind: "ownership", id: value, agent: true }));
3577
+ writeJson2(runQuery(index, config, { kind: "ownership", id: value, agent: true }));
2773
3578
  } else if (command === "gate") {
2774
3579
  const gateId = value || void 0;
2775
- writeJson(runGates(root, config, gateId ? [gateId] : void 0));
3580
+ writeJson2(runGates(root, config, gateId ? [gateId] : void 0));
2776
3581
  } else {
2777
- writeAsk(line, searchIndex(index, line, 8), index);
3582
+ writeAsk(line, searchIndex(index, line, 8), index, config);
2778
3583
  }
2779
3584
  } catch (error) {
2780
3585
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -2826,9 +3631,9 @@ var diagnosticNextCommands = (config, result) => {
2826
3631
  ];
2827
3632
  };
2828
3633
  var writeIfMissing = (path, contents) => {
2829
- if (existsSync10(path)) return false;
2830
- mkdirSync2(dirname4(path), { recursive: true });
2831
- writeFileSync2(path, contents, "utf8");
3634
+ if (existsSync14(path)) return false;
3635
+ mkdirSync5(dirname7(path), { recursive: true });
3636
+ writeFileSync4(path, contents, "utf8");
2832
3637
  return true;
2833
3638
  };
2834
3639
  var demoOwnership = {
@@ -2931,7 +3736,7 @@ var scaffoldWorkspaceDocs = (root, config) => {
2931
3736
  const created = [];
2932
3737
  const skipped = [];
2933
3738
  for (const pkg of discoverPnpmPackages(root, config)) {
2934
- const path = resolve6(root, config.corpus.agent.root, "packages", `${pkg.id}.md`);
3739
+ const path = resolve9(root, config.corpus.agent.root, "packages", `${pkg.id}.md`);
2935
3740
  if (writeIfMissing(path, workspaceDocDraft(pkg.id, pkg.path))) created.push(path);
2936
3741
  else skipped.push(path);
2937
3742
  }
@@ -2941,11 +3746,11 @@ var bootstrapAgentDocs = (root, config) => {
2941
3746
  const created = [];
2942
3747
  const skipped = [];
2943
3748
  for (const doc of scanHumanDocRecords(root, config)) {
2944
- const raw = readFileSync15(doc.path, "utf8");
3749
+ const raw = readFileSync17(doc.path, "utf8");
2945
3750
  const body = raw.replace(/^---\n[\s\S]*?\n---\n?/, "");
2946
3751
  const title = firstHeading(body) ?? doc.id;
2947
3752
  const description = firstParagraph(body);
2948
- const draftPath = resolve6(root, config.corpus.agent.root, "human", `${doc.id}.md`);
3753
+ const draftPath = resolve9(root, config.corpus.agent.root, "human", `${doc.id}.md`);
2949
3754
  const draft = [
2950
3755
  "---",
2951
3756
  "type: knowledge",
@@ -2994,7 +3799,7 @@ var runCli = (argv) => {
2994
3799
  configPath ? { explicitPath: configPath } : {}
2995
3800
  );
2996
3801
  parseDocBridgeConfig(config);
2997
- writeJson({ ok: true, path, schemaVersion: config.schemaVersion });
3802
+ writeJson2({ ok: true, path, schemaVersion: config.schemaVersion });
2998
3803
  return 0;
2999
3804
  } catch (error) {
3000
3805
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3009,10 +3814,10 @@ var runCli = (argv) => {
3009
3814
  return 1;
3010
3815
  }
3011
3816
  try {
3012
- const abs = resolve6(file);
3013
- const raw = readFileSync15(abs, "utf8");
3817
+ const abs = resolve9(file);
3818
+ const raw = readFileSync17(abs, "utf8");
3014
3819
  const handoff = parseAgentHandoff(JSON.parse(raw));
3015
- writeJson({ ok: true, handoff });
3820
+ writeJson2({ ok: true, handoff });
3016
3821
  return 0;
3017
3822
  } catch (error) {
3018
3823
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3023,10 +3828,10 @@ var runCli = (argv) => {
3023
3828
  if (command === "init") {
3024
3829
  const root = process.cwd();
3025
3830
  const withDemo = flags.has("--demo") || !flags.has("--no-demo");
3026
- const configFile = resolve6(root, configPath ?? "doc-bridge.config.json");
3027
- const docsIndex = resolve6(root, "docs/for-agents/INDEX.md");
3028
- const exampleDoc = resolve6(root, "docs/for-agents/packages/example.md");
3029
- const agentsMd = resolve6(root, "AGENTS.md");
3831
+ const configFile = resolve9(root, configPath ?? "doc-bridge.config.json");
3832
+ const docsIndex = resolve9(root, "docs/for-agents/INDEX.md");
3833
+ const exampleDoc = resolve9(root, "docs/for-agents/packages/example.md");
3834
+ const agentsMd = resolve9(root, "AGENTS.md");
3030
3835
  const configWritten = writeIfMissing(configFile, initConfigContents(configFile, withDemo));
3031
3836
  const indexWritten = writeIfMissing(
3032
3837
  docsIndex,
@@ -3034,9 +3839,9 @@ var runCli = (argv) => {
3034
3839
  );
3035
3840
  const exampleWritten = withDemo ? writeIfMissing(exampleDoc, exampleAgentDoc) : false;
3036
3841
  const agentsWritten = writeIfMissing(agentsMd, agentsMdSnippet);
3037
- if (withDemo) writeIfMissing(resolve6(root, "src/.gitkeep"), "");
3842
+ if (withDemo) writeIfMissing(resolve9(root, "src/.gitkeep"), "");
3038
3843
  const scaffold = flags.has("--scaffold-workspaces") ? scaffoldWorkspaceDocs(root, loadProject(configFile).config) : void 0;
3039
- writeJson({
3844
+ writeJson2({
3040
3845
  ok: true,
3041
3846
  configPath: configFile,
3042
3847
  demo: withDemo,
@@ -3060,7 +3865,7 @@ var runCli = (argv) => {
3060
3865
  try {
3061
3866
  const { config, root } = loadProject(configPath);
3062
3867
  const result = bootstrapAgentDocs(root, config);
3063
- writeJson({ ok: true, ...result });
3868
+ writeJson2({ ok: true, ...result });
3064
3869
  return 0;
3065
3870
  } catch (error) {
3066
3871
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3070,24 +3875,43 @@ var runCli = (argv) => {
3070
3875
  }
3071
3876
  if (command === "memory") {
3072
3877
  if (!["ingest", "classify", "promote"].includes(positional[1] ?? "")) {
3073
- process.stderr.write("Usage: ak-docs memory <ingest|classify|promote> [--config <path>]\n");
3878
+ process.stderr.write(
3879
+ "Usage: ak-docs memory <ingest|classify|promote> [--pr] [--dry-run] [--force] [--config <path>]\n"
3880
+ );
3074
3881
  return 1;
3075
3882
  }
3076
3883
  try {
3077
3884
  const { config, root } = loadProject(configPath);
3078
3885
  const candidates = ingestMemoryCandidates(root);
3079
3886
  if (positional[1] === "ingest") {
3080
- writeJson({ ok: true, count: candidates.length, candidates });
3887
+ writeJson2({ ok: true, count: candidates.length, candidates });
3081
3888
  return 0;
3082
3889
  }
3083
3890
  const index = loadDocBridgeIndex(root, config);
3084
3891
  const classifications = classifyMemoryCandidates(candidates, index);
3085
3892
  if (positional[1] === "classify") {
3086
- writeJson({ ok: true, count: classifications.length, classifications });
3893
+ writeJson2({ ok: true, count: classifications.length, classifications });
3087
3894
  return 0;
3088
3895
  }
3089
3896
  const draft = draftMemoryPromotion(classifications);
3090
- writeJson(draft);
3897
+ if (flags.has("--pr") || flags.has("--github")) {
3898
+ const pr = promoteMemoryToGithubPr(root, draft, {
3899
+ dryRun: flags.has("--dry-run"),
3900
+ force: flags.has("--force")
3901
+ });
3902
+ if (flags.has("--text")) {
3903
+ writeLines([
3904
+ pr.message,
3905
+ ...pr.draftPath ? [`Draft: ${pr.draftPath}`] : [],
3906
+ ...pr.prUrl ? [`PR: ${pr.prUrl}`] : [],
3907
+ ...pr.commands.length ? ["", "Commands:", ...pr.commands.map((cmd) => ` ${cmd}`)] : []
3908
+ ]);
3909
+ } else {
3910
+ writeJson2({ ...draft, pr });
3911
+ }
3912
+ return pr.ok ? 0 : 1;
3913
+ }
3914
+ writeJson2(draft);
3091
3915
  return draft.ok ? 0 : 1;
3092
3916
  } catch (error) {
3093
3917
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3100,22 +3924,35 @@ var runCli = (argv) => {
3100
3924
  process.stderr.write("Usage: ak-docs registry topology\n");
3101
3925
  return 1;
3102
3926
  }
3103
- writeJson(registryTopology());
3927
+ writeJson2(registryTopology());
3104
3928
  return 0;
3105
3929
  }
3106
3930
  if (command === "playbook") {
3107
- if (positional[1] !== "draft") {
3108
- process.stderr.write("Usage: ak-docs playbook draft [--config <path>]\n");
3931
+ const action = positional[1];
3932
+ if (action === "pattern") {
3933
+ const payload = docBridgePatternPayload();
3934
+ if (flags.has("--text") || wantsTextOutput(flags, { schemaVersion: 1, corpus: { agent: { root: "docs" } } })) {
3935
+ process.stdout.write(`${docBridgePatternMarkdown()}
3936
+ `);
3937
+ } else {
3938
+ writeJson2(payload);
3939
+ }
3940
+ return 0;
3941
+ }
3942
+ if (action !== "draft") {
3943
+ process.stderr.write("Usage: ak-docs playbook draft | pattern [--text] [--config <path>]\n");
3109
3944
  return 1;
3110
3945
  }
3111
3946
  try {
3112
3947
  const { config, root } = loadProject(configPath);
3113
3948
  const index = loadDocBridgeIndex(root, config);
3114
3949
  const draft = draftMemoryPromotion(classifyMemoryCandidates(ingestMemoryCandidates(root), index));
3115
- writeJson({
3950
+ writeJson2({
3116
3951
  ...draft,
3117
3952
  title: "Draft Playbook feedback promotion",
3118
- pattern: "Doc Bridge Pattern"
3953
+ pattern: "Doc Bridge Pattern",
3954
+ patternDoc: "docs/playbook/doc-bridge-pattern.md",
3955
+ exportCommand: "ak-docs playbook pattern --text"
3119
3956
  });
3120
3957
  return 0;
3121
3958
  } catch (error) {
@@ -3126,11 +3963,18 @@ var runCli = (argv) => {
3126
3963
  }
3127
3964
  if (command === "index") {
3128
3965
  try {
3129
- const { config, root } = loadProject(configPath);
3966
+ const { config, root, configPath: loadedConfigPath } = loadProject(configPath);
3967
+ if (flags.has("--watch")) {
3968
+ return watchDocBridgeIndex({
3969
+ root,
3970
+ config,
3971
+ configPath: loadedConfigPath
3972
+ });
3973
+ }
3130
3974
  const result = buildDocBridgeIndex({ root, config });
3131
3975
  const diagnostics = indexDiagnostics(config, result);
3132
3976
  const handoffCount = Object.keys(result.index.handoffs ?? {}).length;
3133
- writeJson({
3977
+ writeJson2({
3134
3978
  ok: true,
3135
3979
  indexPath: result.indexPath,
3136
3980
  ...result.llmsTxtPath ? { llmsTxtPath: result.llmsTxtPath } : {},
@@ -3166,7 +4010,7 @@ var runCli = (argv) => {
3166
4010
  try {
3167
4011
  const { config, root } = loadProject(configPath);
3168
4012
  const result = runGates(root, config, gateId ? [gateId] : void 0);
3169
- writeJson(result);
4013
+ writeJson2(result);
3170
4014
  return result.ok ? 0 : 1;
3171
4015
  } catch (error) {
3172
4016
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3175,12 +4019,98 @@ var runCli = (argv) => {
3175
4019
  }
3176
4020
  }
3177
4021
  if (command === "mcp") {
4022
+ if (positional[1] === "install") {
4023
+ const target = flags.has("--claude") ? "claude" : flags.has("--cursor") ? "cursor" : void 0;
4024
+ if (!target) {
4025
+ process.stderr.write("Usage: ak-docs mcp install --cursor | --claude\n");
4026
+ return 1;
4027
+ }
4028
+ try {
4029
+ const { config, root } = loadProject(configPath);
4030
+ const result = installMcpConfig(root, target);
4031
+ if (wantsTextOutput(flags, config)) {
4032
+ writeLines([
4033
+ `Installed MCP server "${result.serverName}" for ${result.target}`,
4034
+ `Config: ${result.configPath}`,
4035
+ ...result.created ? ["Created new config file"] : ["Merged into existing config"],
4036
+ "",
4037
+ "Next steps:",
4038
+ ...result.nextSteps.map((step) => ` \u2192 ${step}`)
4039
+ ]);
4040
+ } else {
4041
+ writeJson2({ ...result, snippet: mcpSnippet(root) });
4042
+ }
4043
+ return 0;
4044
+ } catch (error) {
4045
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
4046
+ `);
4047
+ return 1;
4048
+ }
4049
+ }
3178
4050
  try {
3179
4051
  const { config, root } = loadProject(configPath);
3180
4052
  startMcpStdioServer({ root, config });
3181
4053
  return void 0;
3182
4054
  } catch (error) {
3183
4055
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
4056
+ `);
4057
+ return 1;
4058
+ }
4059
+ }
4060
+ if (command === "doctor") {
4061
+ try {
4062
+ const { config, root } = loadProject(configPath);
4063
+ const report = runDoctor(root, config);
4064
+ if (flags.has("--write-badge")) {
4065
+ const badgePath = resolve9(root, ".doc-bridge", "coverage-badge.json");
4066
+ mkdirSync5(dirname7(badgePath), { recursive: true });
4067
+ writeFileSync4(badgePath, `${formatDoctorBadgeJson(report.badge)}
4068
+ `, "utf8");
4069
+ }
4070
+ if (flags.has("--badge")) {
4071
+ writeLines([formatDoctorBadgeMarkdown(report.badge)]);
4072
+ return 0;
4073
+ }
4074
+ if (wantsTextOutput(flags, config)) writeLines(formatDoctorText(report));
4075
+ else writeJson2(report);
4076
+ return report.ok ? 0 : 1;
4077
+ } catch (error) {
4078
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
4079
+ `);
4080
+ return 1;
4081
+ }
4082
+ }
4083
+ if (command === "demo") {
4084
+ const resolveDemoFixture = () => {
4085
+ const fixtureFlagIndex = positional.indexOf("--fixture");
4086
+ if (fixtureFlagIndex >= 0) {
4087
+ const value = positional[fixtureFlagIndex + 1];
4088
+ if (value === "monorepo" || value === "example") return value;
4089
+ }
4090
+ if (flags.has("--monorepo") || positional.includes("monorepo")) return "monorepo";
4091
+ if (positional[1] === "monorepo") return "monorepo";
4092
+ return "example";
4093
+ };
4094
+ const resolvedFixture = resolveDemoFixture();
4095
+ try {
4096
+ const useProject = flags.has("--in-project") || flags.has("--copy-fixture");
4097
+ if (!useProject) {
4098
+ const result2 = withDemoWorkspace(
4099
+ resolvedFixture,
4100
+ (root2, config2) => runDemo(root2, config2, resolvedFixture)
4101
+ );
4102
+ const textMode = flags.has("--text") || !flags.has("--json") && !flags.has("--agent");
4103
+ if (textMode) writeLines(formatDemoText(result2));
4104
+ else writeJson2(result2);
4105
+ return result2.ok ? 0 : 1;
4106
+ }
4107
+ const { config, root } = loadProject(configPath);
4108
+ const result = runDemo(root, config, resolvedFixture, { copyFixture: flags.has("--copy-fixture") });
4109
+ if (wantsTextOutput(flags, config)) writeLines(formatDemoText(result));
4110
+ else writeJson2(result);
4111
+ return result.ok ? 0 : 1;
4112
+ } catch (error) {
4113
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
3184
4114
  `);
3185
4115
  return 1;
3186
4116
  }
@@ -3202,7 +4132,7 @@ var runCli = (argv) => {
3202
4132
  const index = loadDocBridgeIndex(root, config);
3203
4133
  const result = runQuery(index, config, { kind, id, agent: flags.has("--agent") });
3204
4134
  if (wantsTextOutput(flags, config)) writeTextQuery(result);
3205
- else writeJson(result);
4135
+ else writeJson2(result);
3206
4136
  return 0;
3207
4137
  } catch (error) {
3208
4138
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3221,11 +4151,11 @@ var runCli = (argv) => {
3221
4151
  const index = loadDocBridgeIndex(root, config);
3222
4152
  if (flags.has("--agent")) {
3223
4153
  const result = runQuery(index, config, { kind: "search", term, agent: true });
3224
- writeJson(result);
4154
+ writeJson2(result);
3225
4155
  } else {
3226
4156
  const matches = searchIndex(index, term);
3227
4157
  if (wantsTextOutput(flags, config)) writeTextSearch(term, matches);
3228
- else writeJson({ term, count: matches.length, matches });
4158
+ else writeJson2({ term, count: matches.length, matches });
3229
4159
  }
3230
4160
  return 0;
3231
4161
  } catch (error) {
@@ -3249,7 +4179,7 @@ var runCli = (argv) => {
3249
4179
  try {
3250
4180
  const { config, root } = loadProject(configPath);
3251
4181
  const index = loadDocBridgeIndex(root, config);
3252
- writeJson({ query, chunks: await retrieveHybridChunks(root, config, index, query) });
4182
+ writeJson2({ query, chunks: await retrieveHybridChunks(root, config, index, query) });
3253
4183
  return 0;
3254
4184
  } catch (error) {
3255
4185
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3271,7 +4201,7 @@ var runCli = (argv) => {
3271
4201
  const rag = await createDocBridgeRag(root, config, index);
3272
4202
  if (action === "ingest") {
3273
4203
  const result = await rag.ingest();
3274
- writeJson({ ok: true, ...result });
4204
+ writeJson2({ ok: true, ...result });
3275
4205
  return 0;
3276
4206
  }
3277
4207
  const query = positional.slice(2).join(" ").trim();
@@ -3280,7 +4210,7 @@ var runCli = (argv) => {
3280
4210
  return 1;
3281
4211
  }
3282
4212
  const hits = await rag.search(query);
3283
- writeJson({ query, count: hits.length, hits });
4213
+ writeJson2({ query, count: hits.length, hits });
3284
4214
  return 0;
3285
4215
  } catch (error) {
3286
4216
  if (error instanceof PeerMissingError) {
@@ -3362,7 +4292,7 @@ Install peers: ${layer1InstallHint()}
3362
4292
  return 1;
3363
4293
  }
3364
4294
  const index = loadDocBridgeIndex(root, config);
3365
- writeAsk(question, searchIndex(index, question, 8), index);
4295
+ writeAsk(question, searchIndex(index, question, 8), index, config);
3366
4296
  return 0;
3367
4297
  } catch (error) {
3368
4298
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3382,19 +4312,19 @@ Install peers: ${layer1InstallHint()}
3382
4312
  if (kind === "packages") {
3383
4313
  const items2 = index.lookup?.packages ?? [];
3384
4314
  if (wantsTextOutput(flags, config)) writeLines(items2);
3385
- else writeJson({ kind, items: items2 });
4315
+ else writeJson2({ kind, items: items2 });
3386
4316
  return 0;
3387
4317
  }
3388
4318
  if (kind === "intents") {
3389
4319
  const items2 = Object.keys(index.lookup?.intents ?? {});
3390
4320
  if (wantsTextOutput(flags, config)) writeLines(items2);
3391
- else writeJson({ kind, items: items2 });
4321
+ else writeJson2({ kind, items: items2 });
3392
4322
  return 0;
3393
4323
  }
3394
4324
  if (kind === "changes") {
3395
4325
  const items2 = Object.keys(index.lookup?.changes ?? {});
3396
4326
  if (wantsTextOutput(flags, config)) writeLines(items2);
3397
- else writeJson({ kind, items: items2 });
4327
+ else writeJson2({ kind, items: items2 });
3398
4328
  return 0;
3399
4329
  }
3400
4330
  const items = index.knowledge.map((entry) => ({
@@ -3405,7 +4335,7 @@ Install peers: ${layer1InstallHint()}
3405
4335
  if (wantsTextOutput(flags, config)) {
3406
4336
  writeLines(items.map((item) => [item.id, item.path, item.title].join(" ")));
3407
4337
  } else {
3408
- writeJson({ kind, items });
4338
+ writeJson2({ kind, items });
3409
4339
  }
3410
4340
  return 0;
3411
4341
  } catch (error) {