@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
@@ -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) => {
@@ -2377,9 +2401,752 @@ var draftMemoryPromotion = (classifications) => {
2377
2401
  };
2378
2402
  };
2379
2403
 
2404
+ // src/memory/github-pr.ts
2405
+ import { execFileSync, spawnSync } from "child_process";
2406
+ import { existsSync as existsSync10, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
2407
+ import { join as join15 } from "path";
2408
+ var run = (cmd, args, cwd) => {
2409
+ const result = spawnSync(cmd, [...args], { cwd, encoding: "utf8" });
2410
+ const out = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
2411
+ return { ok: result.status === 0, out };
2412
+ };
2413
+ var hasGh = () => run("gh", ["--version"], process.cwd()).ok;
2414
+ var slug = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
2415
+ var defaultPromotionDraftPath = (root) => join15(root, ".doc-bridge", "drafts", `memory-promotion-${slug()}.md`);
2416
+ var writePromotionDraft = (root, draft, path) => {
2417
+ const draftPath = path ?? defaultPromotionDraftPath(root);
2418
+ mkdirSync2(join15(root, ".doc-bridge", "drafts"), { recursive: true });
2419
+ writeFileSync2(draftPath, `${draft.body}
2420
+ `, "utf8");
2421
+ return draftPath;
2422
+ };
2423
+ var promoteMemoryToGithubPr = (root, draft, options = {}) => {
2424
+ if (!draft.ok && !options.force) {
2425
+ return {
2426
+ ok: false,
2427
+ dryRun: Boolean(options.dryRun),
2428
+ draftPath: "",
2429
+ branch: "",
2430
+ commands: [],
2431
+ message: "Safety scan blocked promotion. Fix findings or pass --force to draft anyway."
2432
+ };
2433
+ }
2434
+ const branch = options.branch ?? `doc-bridge/memory-promotion-${slug()}`;
2435
+ const draftPath = writePromotionDraft(root, draft);
2436
+ const relDraft = draftPath.startsWith(`${root}/`) ? draftPath.slice(root.length + 1) : draftPath;
2437
+ const commands = [
2438
+ `git checkout -b ${branch}`,
2439
+ `git add ${relDraft}`,
2440
+ `git commit -m "draft: doc-bridge memory promotion"`,
2441
+ `git push -u origin ${branch}`,
2442
+ `gh pr create --draft --title "${draft.title}" --body-file ${relDraft}${options.base ? ` --base ${options.base}` : ""}`
2443
+ ];
2444
+ if (options.dryRun) {
2445
+ return {
2446
+ ok: true,
2447
+ dryRun: true,
2448
+ draftPath,
2449
+ branch,
2450
+ commands,
2451
+ message: `Wrote draft to ${relDraft}. Run the printed git/gh commands to open a draft PR.`
2452
+ };
2453
+ }
2454
+ if (!existsSync10(join15(root, ".git"))) {
2455
+ return {
2456
+ ok: false,
2457
+ dryRun: false,
2458
+ draftPath,
2459
+ branch,
2460
+ commands,
2461
+ message: "Not a git repository. Commit the draft manually or run with --dry-run."
2462
+ };
2463
+ }
2464
+ if (!hasGh()) {
2465
+ return {
2466
+ ok: false,
2467
+ dryRun: false,
2468
+ draftPath,
2469
+ branch,
2470
+ commands,
2471
+ message: "GitHub CLI (gh) not found. Install gh or use --dry-run for local draft + commands."
2472
+ };
2473
+ }
2474
+ const auth = run("gh", ["auth", "status"], root);
2475
+ if (!auth.ok) {
2476
+ return {
2477
+ ok: false,
2478
+ dryRun: false,
2479
+ draftPath,
2480
+ branch,
2481
+ commands,
2482
+ message: `gh is not authenticated. Run: gh auth login
2483
+ ${auth.out}`
2484
+ };
2485
+ }
2486
+ const checkout = run("git", ["checkout", "-b", branch], root);
2487
+ if (!checkout.ok) {
2488
+ return {
2489
+ ok: false,
2490
+ dryRun: false,
2491
+ draftPath,
2492
+ branch,
2493
+ commands,
2494
+ message: `git checkout failed: ${checkout.out}`
2495
+ };
2496
+ }
2497
+ run("git", ["add", relDraft], root);
2498
+ const commit = run("git", ["commit", "-m", "draft: doc-bridge memory promotion"], root);
2499
+ if (!commit.ok) {
2500
+ return {
2501
+ ok: false,
2502
+ dryRun: false,
2503
+ draftPath,
2504
+ branch,
2505
+ commands,
2506
+ message: `git commit failed: ${commit.out}`
2507
+ };
2508
+ }
2509
+ const push = run("git", ["push", "-u", "origin", branch], root);
2510
+ if (!push.ok) {
2511
+ return {
2512
+ ok: false,
2513
+ dryRun: false,
2514
+ draftPath,
2515
+ branch,
2516
+ commands,
2517
+ message: `git push failed: ${push.out}`
2518
+ };
2519
+ }
2520
+ const prArgs = [
2521
+ "pr",
2522
+ "create",
2523
+ "--draft",
2524
+ "--title",
2525
+ draft.title,
2526
+ "--body-file",
2527
+ relDraft,
2528
+ ...options.base ? ["--base", options.base] : []
2529
+ ];
2530
+ let prUrl = "";
2531
+ try {
2532
+ prUrl = execFileSync("gh", prArgs, { cwd: root, encoding: "utf8" }).trim();
2533
+ } catch (error) {
2534
+ const message = error instanceof Error ? error.message : String(error);
2535
+ return {
2536
+ ok: false,
2537
+ dryRun: false,
2538
+ draftPath,
2539
+ branch,
2540
+ commands,
2541
+ message: `gh pr create failed: ${message}`
2542
+ };
2543
+ }
2544
+ return {
2545
+ ok: true,
2546
+ dryRun: false,
2547
+ draftPath,
2548
+ branch,
2549
+ prUrl,
2550
+ ...prUrl ? { previewUrl: prUrl } : {},
2551
+ commands,
2552
+ message: prUrl ? `Draft PR opened: ${prUrl}` : "Draft PR created."
2553
+ };
2554
+ };
2555
+
2556
+ // src/index-builder/watch-index.ts
2557
+ import { existsSync as existsSync11, watch } from "fs";
2558
+ import { dirname as dirname4, resolve as resolve5 } from "path";
2559
+ var WATCH_PATTERN = /\.(md|mdx|json|ya?ml|mdc)$/i;
2560
+ var collectWatchRoots = (root, config, configPath) => {
2561
+ const roots = /* @__PURE__ */ new Set();
2562
+ roots.add(resolve5(root, config.corpus.agent.root));
2563
+ const humanSources = config.corpus.human ? Array.isArray(config.corpus.human) ? config.corpus.human : [config.corpus.human] : [];
2564
+ for (const source of humanSources) {
2565
+ const humanOpts = source.options ?? {};
2566
+ for (const key of ["contentDir", "docsDir", "root"]) {
2567
+ const value = humanOpts[key];
2568
+ if (typeof value === "string" && value.length) roots.add(resolve5(root, value));
2569
+ }
2570
+ }
2571
+ if (configPath) roots.add(dirname4(resolve5(configPath)));
2572
+ return [...roots].filter((dir) => existsSync11(dir));
2573
+ };
2574
+ var watchDocBridgeIndex = (opts) => {
2575
+ const debounceMs = opts.debounceMs ?? 350;
2576
+ let timer;
2577
+ let running = false;
2578
+ const rebuild = () => {
2579
+ if (timer) clearTimeout(timer);
2580
+ timer = setTimeout(() => {
2581
+ if (running) return;
2582
+ running = true;
2583
+ try {
2584
+ const result = buildDocBridgeIndex({ root: opts.root, config: opts.config });
2585
+ const handoffCount = Object.keys(result.index.handoffs ?? {}).length;
2586
+ const summary = {
2587
+ knowledgeCount: result.index.knowledge.length,
2588
+ handoffCount,
2589
+ hash: result.index.contentHash.slice(0, 8)
2590
+ };
2591
+ opts.onRebuild?.(summary);
2592
+ process.stdout.write(
2593
+ `[ak-docs] indexed ${summary.knowledgeCount} docs, ${summary.handoffCount} handoffs (${summary.hash}\u2026)
2594
+ `
2595
+ );
2596
+ } catch (error) {
2597
+ process.stderr.write(
2598
+ `[ak-docs] index failed: ${error instanceof Error ? error.message : String(error)}
2599
+ `
2600
+ );
2601
+ } finally {
2602
+ running = false;
2603
+ }
2604
+ }, debounceMs);
2605
+ };
2606
+ rebuild();
2607
+ for (const dir of collectWatchRoots(opts.root, opts.config, opts.configPath)) {
2608
+ watch(dir, { recursive: true }, (_event, filename) => {
2609
+ if (!filename || !WATCH_PATTERN.test(filename)) return;
2610
+ rebuild();
2611
+ });
2612
+ }
2613
+ const configDir = resolve5(opts.root);
2614
+ if (existsSync11(configDir)) {
2615
+ watch(configDir, (_event, filename) => {
2616
+ if (!filename || !/doc-bridge\.config/.test(filename)) return;
2617
+ rebuild();
2618
+ });
2619
+ }
2620
+ process.stdout.write(
2621
+ `[ak-docs] watching ${collectWatchRoots(opts.root, opts.config, opts.configPath).join(", ") || opts.root} (Ctrl+C to stop)
2622
+ `
2623
+ );
2624
+ return new Promise((resolvePromise) => {
2625
+ const onSignal = () => resolvePromise(0);
2626
+ process.once("SIGINT", onSignal);
2627
+ process.once("SIGTERM", onSignal);
2628
+ });
2629
+ };
2630
+
2631
+ // src/doctor/badge.ts
2632
+ var doctorBadgeMetrics = (report) => {
2633
+ const { packages } = report.coverage;
2634
+ const handoffPct = packages.total > 0 ? Math.round(packages.withAgentDoc / packages.total * 100) : 0;
2635
+ const bridgePct = packages.total > 0 ? Math.round(packages.withHumanDoc / packages.total * 100) : 0;
2636
+ return {
2637
+ handoffPct,
2638
+ bridgePct,
2639
+ score: report.score,
2640
+ grade: report.grade,
2641
+ packages: packages.total
2642
+ };
2643
+ };
2644
+ var badgeColor = (pct) => {
2645
+ if (pct >= 80) return "2ea44f";
2646
+ if (pct >= 50) return "dbab09";
2647
+ return "cb2431";
2648
+ };
2649
+ var formatDoctorBadgeMarkdown = (metrics) => {
2650
+ const handoff = `handoff_coverage-${metrics.handoffPct}%25-${badgeColor(metrics.handoffPct)}`;
2651
+ const bridge = `human_bridge-${metrics.bridgePct}%25-${badgeColor(metrics.bridgePct)}`;
2652
+ return [
2653
+ `![handoff coverage](https://img.shields.io/badge/${handoff}?style=flat-square)`,
2654
+ `![human bridge](https://img.shields.io/badge/${bridge}?style=flat-square)`,
2655
+ `![doc-bridge score](https://img.shields.io/badge/doc--bridge_score-${metrics.score}%2F100-${badgeColor(metrics.score)}?style=flat-square)`
2656
+ ].join(" ");
2657
+ };
2658
+ var formatDoctorBadgeJson = (metrics) => JSON.stringify(
2659
+ {
2660
+ schemaVersion: 1,
2661
+ ...metrics,
2662
+ markdown: formatDoctorBadgeMarkdown(metrics),
2663
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2664
+ },
2665
+ null,
2666
+ 2
2667
+ );
2668
+
2669
+ // src/playbook/doc-bridge-pattern.ts
2670
+ var DOC_BRIDGE_PATTERN_ID = "doc-bridge-pattern";
2671
+ var DOC_BRIDGE_PATTERN_META = {
2672
+ id: DOC_BRIDGE_PATTERN_ID,
2673
+ title: "Doc Bridge Pattern",
2674
+ slug: "pillars/ai-collaboration/doc-bridge-pattern",
2675
+ license: "CC-BY-4.0",
2676
+ visibility: "public",
2677
+ playbookUrl: "https://playbook.agentskit.io/patterns/doc-bridge-pattern",
2678
+ npmPackage: "@agentskit/doc-bridge",
2679
+ cli: "ak-docs"
2680
+ };
2681
+ var docBridgePatternMarkdown = () => `---
2682
+ type: pattern
2683
+ id: ${DOC_BRIDGE_PATTERN_ID}
2684
+ purpose: Route coding agents to the correct package, checks, and human docs in any monorepo.
2685
+ owner: AgentsKit
2686
+ license: CC-BY-4.0
2687
+ visibility: public
2688
+ tags: [agents, documentation, monorepo, handoff, mcp]
2689
+ ---
2690
+
2691
+ # Doc Bridge Pattern
2692
+
2693
+ **AgentHandoff for your monorepo** \u2014 deterministic routing so agents edit the right roots, run the right checks, and stay linked to human documentation.
2694
+
2695
+ ## Problem
2696
+
2697
+ 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*.
2698
+
2699
+ ## Solution (three artifacts)
2700
+
2701
+ | Artifact | Role |
2702
+ |----------|------|
2703
+ | **AgentHandoff** | JSON handoff: \`startHere\`, \`editRoots\`, \`checks\`, \`humanDoc\`, \`bridge\` |
2704
+ | **DocBridgeIndex** | Deterministic index + \`contentHash\` for CI freshness gates |
2705
+ | **Self-describe** | \`llms.txt\`, \`capabilities.json\` for discovery |
2706
+
2707
+ ## Four loops
2708
+
2709
+ | Loop | Command | Outcome |
2710
+ |------|---------|---------|
2711
+ | **Act** | \`ak-docs query package <id> --agent\` | Agent edits only \`editRoots\` |
2712
+ | **Bridge** | \`ak-docs bootstrap agent-docs\` | Link agent corpus \u2194 human site |
2713
+ | **Learn** | \`ak-docs memory promote --pr\` | HITL draft PR from agent memory |
2714
+ | **Explain** | \`ak-docs ask "<question>"\` | Local consult + handoff preview |
2715
+
2716
+ ## 60-second proof
2717
+
2718
+ \`\`\`bash
2719
+ npm i -D @agentskit/doc-bridge
2720
+ npx ak-docs demo --text
2721
+ ak-docs mcp install --cursor
2722
+ \`\`\`
2723
+
2724
+ ## AgentHandoff example
2725
+
2726
+ \`\`\`json
2727
+ {
2728
+ "type": "agent-handoff",
2729
+ "startHere": "docs/for-agents/packages/auth.md",
2730
+ "editRoots": ["packages/auth"],
2731
+ "checks": ["pnpm --filter @demo/auth test"],
2732
+ "humanDoc": "/docs/guides/auth",
2733
+ "bridge": { "humanDoc": "linked" }
2734
+ }
2735
+ \`\`\`
2736
+
2737
+ When \`humanDoc\` is missing, handoffs surface \`bridge.action: "ak-docs bootstrap agent-docs"\` \u2014 a feature, not a silent gap.
2738
+
2739
+ ## MCP contract
2740
+
2741
+ Agents call \`handoff.resolve\` before editing \`packages/*\`:
2742
+
2743
+ 1. Read \`startHere\`
2744
+ 2. Stay inside \`editRoots\`
2745
+ 3. Run every \`checks\` command
2746
+ 4. Link \`humanDoc\` or escalate missing bridge
2747
+
2748
+ ## CI gate
2749
+
2750
+ \`\`\`yaml
2751
+ - uses: AgentsKit-io/doc-bridge@v1.0.0
2752
+ \`\`\`
2753
+
2754
+ Or: \`ak-docs index && ak-docs gate run\` \u2014 stale index fails the PR.
2755
+
2756
+ ## Coverage metric
2757
+
2758
+ \`\`\`bash
2759
+ ak-docs doctor --text
2760
+ ak-docs doctor --badge
2761
+ \`\`\`
2762
+
2763
+ Teams track handoff % and human-bridge % daily.
2764
+
2765
+ ## When to use
2766
+
2767
+ - pnpm/npm monorepos with real package ownership
2768
+ - Fumadocus / Docusaurus human sites + dense agent corpus
2769
+ - Cursor / Claude / Codex agents that should not guess edit roots
2770
+
2771
+ ## When not to use
2772
+
2773
+ - Single-file repos with no ownership boundaries (AGENTS.md may suffice)
2774
+ - Hosted doc chat as primary product (doc-bridge is routing + bridge, not SaaS chat)
2775
+
2776
+ ## References
2777
+
2778
+ - npm: https://www.npmjs.com/package/@agentskit/doc-bridge
2779
+ - repo: https://github.com/AgentsKit-io/doc-bridge
2780
+ - skill: https://github.com/AgentsKit-io/doc-bridge/blob/master/docs/skills/doc-bridge.md
2781
+ - landing: https://agentskit-io.github.io/doc-bridge/
2782
+ `;
2783
+ var docBridgePatternPayload = () => ({
2784
+ ...DOC_BRIDGE_PATTERN_META,
2785
+ format: "okf-pattern-v1",
2786
+ body: docBridgePatternMarkdown()
2787
+ });
2788
+
2789
+ // src/cli/demo.ts
2790
+ import { cpSync, existsSync as existsSync13, mkdtempSync, readFileSync as readFileSync15, rmSync } from "fs";
2791
+ import { tmpdir } from "os";
2792
+ import { dirname as dirname6, join as join18, resolve as resolve7 } from "path";
2793
+ import { fileURLToPath } from "url";
2794
+
2795
+ // src/mcp/install.ts
2796
+ import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync14, writeFileSync as writeFileSync3 } from "fs";
2797
+ import { homedir } from "os";
2798
+ import { dirname as dirname5, join as join17, resolve as resolve6 } from "path";
2799
+ var SERVER_NAME = "ak-docs";
2800
+ var mcpServerEntry = (root) => ({
2801
+ command: "npx",
2802
+ args: ["ak-docs", "mcp"],
2803
+ cwd: root
2804
+ });
2805
+ var readJson = (path) => {
2806
+ if (!existsSync12(path)) return {};
2807
+ try {
2808
+ const parsed = JSON.parse(readFileSync14(path, "utf8"));
2809
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
2810
+ } catch {
2811
+ return {};
2812
+ }
2813
+ };
2814
+ var writeJson = (path, value) => {
2815
+ mkdirSync3(dirname5(path), { recursive: true });
2816
+ writeFileSync3(path, `${JSON.stringify(value, null, 2)}
2817
+ `, "utf8");
2818
+ };
2819
+ var resolveTargetPath = (target, root) => {
2820
+ if (target === "cursor") return resolve6(root, ".cursor", "mcp.json");
2821
+ return join17(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
2822
+ };
2823
+ var installMcpConfig = (root, target) => {
2824
+ const configPath = resolveTargetPath(target, root);
2825
+ const created = !existsSync12(configPath);
2826
+ const existing = readJson(configPath);
2827
+ const servers = existing.mcpServers && typeof existing.mcpServers === "object" && !Array.isArray(existing.mcpServers) ? { ...existing.mcpServers } : {};
2828
+ servers[SERVER_NAME] = mcpServerEntry(root);
2829
+ writeJson(configPath, { ...existing, mcpServers: servers });
2830
+ const nextSteps = target === "cursor" ? [
2831
+ "Restart Cursor or reload MCP servers",
2832
+ "Paste the doc-bridge skill into Cursor rules (see docs/skills/doc-bridge.md)",
2833
+ "Before editing packages/* call handoff.resolve"
2834
+ ] : [
2835
+ "Restart Claude Desktop",
2836
+ "Run ak-docs index after doc changes"
2837
+ ];
2838
+ return {
2839
+ ok: true,
2840
+ target,
2841
+ configPath,
2842
+ created,
2843
+ serverName: SERVER_NAME,
2844
+ nextSteps
2845
+ };
2846
+ };
2847
+ var mcpSnippet = (root) => JSON.stringify({ mcpServers: { [SERVER_NAME]: mcpServerEntry(root) } }, null, 2);
2848
+
2849
+ // src/cli/demo.ts
2850
+ var packageRoot = resolve7(dirname6(fileURLToPath(import.meta.url)), "..", "..");
2851
+ var fixturePath = (fixture) => {
2852
+ if (fixture === "monorepo") {
2853
+ return join18(packageRoot, "examples", "demo-monorepo");
2854
+ }
2855
+ return join18(packageRoot, "examples", "demo-example");
2856
+ };
2857
+ var formatHandoffText = (handoff) => {
2858
+ 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)";
2859
+ return [
2860
+ `target: ${handoff.target.id} (${handoff.target.path ?? "n/a"})`,
2861
+ `start: ${handoff.startHere}`,
2862
+ `edit: ${handoff.editRoots.join(", ")}`,
2863
+ `checks: ${handoff.checks.length ? handoff.checks.join(" \xB7 ") : "(none)"}`,
2864
+ bridge,
2865
+ ...handoff.notes.length ? [`notes: ${handoff.notes[0]}`] : []
2866
+ ];
2867
+ };
2868
+ var runDemo = (root, config, fixture = "example", options = {}) => {
2869
+ const targetPackage = fixture === "monorepo" ? "auth" : "example";
2870
+ const fixtureDir = fixturePath(fixture);
2871
+ if (options.copyFixture && existsSync13(fixtureDir)) {
2872
+ for (const rel of ["doc-bridge.config.json", "docs", "packages", "pnpm-workspace.yaml", "package.json"]) {
2873
+ const src = join18(fixtureDir, rel);
2874
+ if (!existsSync13(src)) continue;
2875
+ const dest = join18(root, rel);
2876
+ cpSync(src, dest, { recursive: true });
2877
+ }
2878
+ }
2879
+ const gateBeforeStale = runGates(root, config, ["index-freshness"]);
2880
+ const beforeGate = gateBeforeStale.results[0] ?? { ok: false, message: "index-freshness unavailable" };
2881
+ buildDocBridgeIndex({ root, config });
2882
+ const index = loadDocBridgeIndex(root, config);
2883
+ const handoff = runQuery(index, config, {
2884
+ kind: "package",
2885
+ id: targetPackage,
2886
+ agent: true
2887
+ });
2888
+ const gateAfter = runGates(root, config, ["index-freshness"]);
2889
+ const afterGate = gateAfter.results[0] ?? { ok: true, message: "Index is fresh" };
2890
+ return {
2891
+ ok: afterGate.ok,
2892
+ fixture,
2893
+ handoff,
2894
+ gateBefore: { ok: beforeGate.ok, message: beforeGate.message },
2895
+ gateAfter: { ok: afterGate.ok, message: afterGate.message },
2896
+ mcpSnippet: mcpSnippet(root),
2897
+ nextCommands: [
2898
+ `ak-docs query package ${targetPackage} --agent`,
2899
+ "ak-docs doctor --text",
2900
+ "ak-docs mcp install --cursor",
2901
+ 'ak-docs ask "where do I change auth?"'
2902
+ ]
2903
+ };
2904
+ };
2905
+ var formatDemoText = (result) => {
2906
+ const lines = [
2907
+ "ak-docs demo \u2014 AgentHandoff in 60s",
2908
+ "\u2550".repeat(44),
2909
+ "",
2910
+ "Before (agent guesses package)",
2911
+ ' \u2717 edits packages/billing when task mentions "auth"',
2912
+ " \u2717 runs repo-wide test instead of package checks",
2913
+ "",
2914
+ "After (handoff.resolve / query --agent)",
2915
+ ...formatHandoffText(result.handoff).map((line) => ` \u2713 ${line}`),
2916
+ "",
2917
+ `Gate: ${result.gateBefore.ok ? "green" : "red"} \u2192 ${result.gateAfter.ok ? "green" : "red"}`,
2918
+ ` before: ${result.gateBefore.message}`,
2919
+ ` after: ${result.gateAfter.message}`,
2920
+ "",
2921
+ "MCP snippet (.cursor/mcp.json)",
2922
+ ...result.mcpSnippet.split("\n").map((line) => ` ${line}`),
2923
+ "",
2924
+ "Next",
2925
+ ...result.nextCommands.map((cmd) => ` \u2192 ${cmd}`)
2926
+ ];
2927
+ return lines;
2928
+ };
2929
+ var withDemoWorkspace = (fixture, fn) => {
2930
+ const dir = mkdtempSync(join18(tmpdir(), "ak-docs-demo-"));
2931
+ try {
2932
+ const fixtureDir = fixturePath(fixture);
2933
+ if (!existsSync13(fixtureDir)) {
2934
+ throw new Error(`Demo fixture "${fixture}" not found at ${fixtureDir}`);
2935
+ }
2936
+ cpSync(fixtureDir, dir, { recursive: true });
2937
+ const config = JSON.parse(readFileSync15(join18(dir, "doc-bridge.config.json"), "utf8"));
2938
+ return fn(dir, config);
2939
+ } finally {
2940
+ rmSync(dir, { recursive: true, force: true });
2941
+ }
2942
+ };
2943
+
2944
+ // src/doctor/run-doctor.ts
2945
+ var gradeForScore = (score) => {
2946
+ if (score >= 90) return "A";
2947
+ if (score >= 75) return "B";
2948
+ if (score >= 60) return "C";
2949
+ if (score >= 40) return "D";
2950
+ return "F";
2951
+ };
2952
+ var agentDocPaths = (index) => {
2953
+ const paths = /* @__PURE__ */ new Set();
2954
+ for (const owner of Object.values(index.lookup?.ownership ?? {})) {
2955
+ if (owner.agentDoc) paths.add(owner.agentDoc);
2956
+ }
2957
+ for (const handoff of Object.values(index.handoffs ?? {})) {
2958
+ if (handoff.startHere) paths.add(handoff.startHere);
2959
+ }
2960
+ return paths;
2961
+ };
2962
+ var computeScore = (coverage) => {
2963
+ let score = 0;
2964
+ if (coverage.freshness.hasIndex) score += 15;
2965
+ if (coverage.freshness.ok) score += 15;
2966
+ const { total, withAgentDoc, withHumanDoc } = coverage.packages;
2967
+ if (total > 0) {
2968
+ score += Math.round(withAgentDoc / total * 35);
2969
+ score += Math.round(withHumanDoc / total * 20);
2970
+ } else if (coverage.agentDocs.indexed > 0) {
2971
+ score += 35;
2972
+ }
2973
+ if (coverage.gates.ok) score += 15;
2974
+ else {
2975
+ const passed = coverage.gates.results.filter((gate) => gate.ok).length;
2976
+ const totalGates = coverage.gates.results.length || 1;
2977
+ score += Math.round(passed / totalGates * 10);
2978
+ }
2979
+ return Math.min(100, Math.max(0, score));
2980
+ };
2981
+ var buildIssues = (coverage) => {
2982
+ const issues = [];
2983
+ if (!coverage.freshness.hasIndex) {
2984
+ issues.push({
2985
+ severity: "error",
2986
+ code: "index-missing",
2987
+ message: "No doc-bridge index found.",
2988
+ action: "ak-docs index"
2989
+ });
2990
+ } else if (!coverage.freshness.ok) {
2991
+ issues.push({
2992
+ severity: "error",
2993
+ code: "index-stale",
2994
+ message: coverage.freshness.message,
2995
+ action: "ak-docs index"
2996
+ });
2997
+ }
2998
+ for (const id of coverage.packages.missingAgentDoc) {
2999
+ issues.push({
3000
+ severity: "warn",
3001
+ code: "missing-agent-doc",
3002
+ message: `Package "${id}" has no dedicated agent doc.`,
3003
+ action: `ak-docs init --scaffold-workspaces # or edit docs/for-agents/packages/${id}.md`
3004
+ });
3005
+ }
3006
+ for (const id of coverage.packages.missingHumanDoc) {
3007
+ issues.push({
3008
+ severity: "info",
3009
+ code: "missing-human-doc",
3010
+ message: `Package "${id}" has no linked human guide.`,
3011
+ action: "ak-docs bootstrap agent-docs"
3012
+ });
3013
+ }
3014
+ for (const gate of coverage.gates.results.filter((result) => !result.ok)) {
3015
+ issues.push({
3016
+ severity: gate.id === "index-freshness" ? "error" : "warn",
3017
+ code: `gate-${gate.id}`,
3018
+ message: gate.message,
3019
+ action: gate.id === "index-freshness" ? "ak-docs index" : "ak-docs gate run"
3020
+ });
3021
+ }
3022
+ return issues;
3023
+ };
3024
+ var buildNextActions = (issues, coverage) => {
3025
+ const actions = /* @__PURE__ */ new Set();
3026
+ for (const issue of issues) {
3027
+ if (issue.action) actions.add(issue.action);
3028
+ }
3029
+ if (!coverage.freshness.hasIndex || !coverage.freshness.ok) {
3030
+ actions.add("ak-docs index");
3031
+ }
3032
+ if (coverage.packages.missingHumanDoc.length) {
3033
+ actions.add("ak-docs bootstrap agent-docs");
3034
+ }
3035
+ if (coverage.packages.total > 0) {
3036
+ const sample = coverage.packages.missingAgentDoc[0] ?? coverage.packages.missingHumanDoc[0];
3037
+ if (sample) actions.add(`ak-docs query package ${sample} --agent`);
3038
+ }
3039
+ if (!actions.size) {
3040
+ actions.add("ak-docs mcp install --cursor");
3041
+ actions.add("ak-docs gate run");
3042
+ }
3043
+ return [...actions].slice(0, 6);
3044
+ };
3045
+ var runDoctor = (root, config) => {
3046
+ let index;
3047
+ let hasIndex = true;
3048
+ let freshnessOk = false;
3049
+ let freshnessMessage = "Index is fresh";
3050
+ try {
3051
+ index = loadDocBridgeIndex(root, config);
3052
+ const next = buildDocBridgeIndex({ root, config, write: false }).index.contentHash;
3053
+ freshnessOk = index.contentHash === next;
3054
+ freshnessMessage = freshnessOk ? "Index is fresh" : "Index is stale. Run: ak-docs index";
3055
+ } catch (error) {
3056
+ if (error instanceof IndexNotFoundError) {
3057
+ hasIndex = false;
3058
+ freshnessOk = false;
3059
+ freshnessMessage = error.message;
3060
+ index = buildDocBridgeIndex({ root, config, write: false }).index;
3061
+ } else {
3062
+ throw error;
3063
+ }
3064
+ }
3065
+ const ownership = Object.entries(index.lookup?.ownership ?? {});
3066
+ const missingAgentDoc = ownership.filter(([, owner]) => !owner.agentDoc || owner.agentDoc === config.corpus.agent.index).map(([id]) => id);
3067
+ const missingHumanDoc = ownership.filter(([, owner]) => !owner.humanDoc).map(([id]) => id);
3068
+ const indexedPaths = new Set(index.knowledge.map((entry) => entry.path));
3069
+ const expectedAgentDocs = agentDocPaths(index);
3070
+ const unindexed = [...expectedAgentDocs].filter((path) => !indexedPaths.has(path));
3071
+ const corpusDocs = scanAgentCorpus(root, config).filter(
3072
+ (doc) => doc.path !== config.corpus.agent.index
3073
+ );
3074
+ const gates = runGates(root, config);
3075
+ const coverage = {
3076
+ packages: {
3077
+ total: ownership.length,
3078
+ withAgentDoc: ownership.length - missingAgentDoc.length,
3079
+ withHumanDoc: ownership.length - missingHumanDoc.length,
3080
+ missingAgentDoc,
3081
+ missingHumanDoc
3082
+ },
3083
+ agentDocs: {
3084
+ total: corpusDocs.length,
3085
+ indexed: corpusDocs.filter((doc) => indexedPaths.has(doc.path)).length,
3086
+ unindexed: corpusDocs.filter((doc) => !indexedPaths.has(doc.path)).map((doc) => doc.path)
3087
+ },
3088
+ freshness: {
3089
+ ok: freshnessOk,
3090
+ message: freshnessMessage,
3091
+ hasIndex
3092
+ },
3093
+ gates
3094
+ };
3095
+ const issues = buildIssues(coverage);
3096
+ const score = computeScore(coverage);
3097
+ const nextActions = buildNextActions(issues, coverage);
3098
+ const report = {
3099
+ ok: issues.every((issue) => issue.severity !== "error") && gates.ok,
3100
+ score,
3101
+ grade: gradeForScore(score),
3102
+ coverage,
3103
+ badge: { handoffPct: 0, bridgePct: 0, score, grade: gradeForScore(score), packages: 0 },
3104
+ issues,
3105
+ nextActions
3106
+ };
3107
+ return { ...report, badge: doctorBadgeMetrics(report) };
3108
+ };
3109
+ var formatDoctorText = (report) => {
3110
+ const { coverage } = report;
3111
+ const handoffPct = coverage.packages.total > 0 ? Math.round(coverage.packages.withAgentDoc / coverage.packages.total * 100) : 0;
3112
+ const humanPct = coverage.packages.total > 0 ? Math.round(coverage.packages.withHumanDoc / coverage.packages.total * 100) : 0;
3113
+ const lines = [
3114
+ "doc-bridge doctor",
3115
+ "\u2500".repeat(40),
3116
+ `Score: ${report.score}/100 (${report.grade})`,
3117
+ "",
3118
+ "Coverage",
3119
+ ` Packages: ${coverage.packages.total}`,
3120
+ ` Agent docs: ${coverage.packages.withAgentDoc}/${coverage.packages.total} (${handoffPct}% handoff-ready)`,
3121
+ ` Human guides: ${coverage.packages.withHumanDoc}/${coverage.packages.total} (${humanPct}% bridged)`,
3122
+ ` Corpus indexed: ${coverage.agentDocs.indexed}/${coverage.agentDocs.total} agent docs`,
3123
+ ` Index freshness: ${coverage.freshness.ok ? "fresh" : "stale or missing"}`,
3124
+ ` Gates: ${coverage.gates.results.filter((g) => g.ok).length}/${coverage.gates.results.length} passing`,
3125
+ ` Badge: handoff ${report.badge.handoffPct}% \xB7 bridge ${report.badge.bridgePct}%`
3126
+ ];
3127
+ if (coverage.packages.missingHumanDoc.length) {
3128
+ lines.push("", "Missing humanDoc (bridge gap)", ...coverage.packages.missingHumanDoc.map((id) => ` \u2022 ${id}`));
3129
+ }
3130
+ if (coverage.packages.missingAgentDoc.length) {
3131
+ lines.push("", "Missing agent doc", ...coverage.packages.missingAgentDoc.map((id) => ` \u2022 ${id}`));
3132
+ }
3133
+ if (report.issues.length) {
3134
+ lines.push("", "Issues");
3135
+ for (const issue of report.issues.slice(0, 8)) {
3136
+ const icon = issue.severity === "error" ? "\u2717" : issue.severity === "warn" ? "!" : "\xB7";
3137
+ lines.push(` ${icon} [${issue.code}] ${issue.message}`);
3138
+ }
3139
+ if (report.issues.length > 8) {
3140
+ lines.push(` \u2026 +${report.issues.length - 8} more`);
3141
+ }
3142
+ }
3143
+ lines.push("", "Next actions", ...report.nextActions.map((action) => ` \u2192 ${action}`));
3144
+ return lines;
3145
+ };
3146
+
2380
3147
  // src/mcp/server.ts
2381
- import { readFileSync as readFileSync14, realpathSync } from "fs";
2382
- import { relative, resolve as resolve5 } from "path";
3148
+ import { readFileSync as readFileSync16, realpathSync } from "fs";
3149
+ import { relative, resolve as resolve8 } from "path";
2383
3150
  import { z as z5, ZodError } from "zod";
2384
3151
  var MCP_TOOLS = [
2385
3152
  {
@@ -2486,7 +3253,7 @@ var findDocPath = (index, args) => {
2486
3253
  };
2487
3254
  var resolveDocPath = (root, relPath) => {
2488
3255
  const rootAbs = realpathSync.native(root);
2489
- const unresolved = resolve5(rootAbs, relPath);
3256
+ const unresolved = resolve8(rootAbs, relPath);
2490
3257
  const unresolvedRel = relative(rootAbs, unresolved);
2491
3258
  if (unresolvedRel.startsWith("..")) throw new Error("doc.get path escapes project root");
2492
3259
  const abs = realpathSync.native(unresolved);
@@ -2524,7 +3291,7 @@ var handleMcpRequest = (ctx, request) => {
2524
3291
  }
2525
3292
  if (name === "doc.get") {
2526
3293
  const relPath = findDocPath(index(), parseToolArgs("doc.get", DocGetArgsSchema, args));
2527
- return textResult(readFileSync14(resolveDocPath(ctx.root, relPath), "utf8"));
3294
+ return textResult(readFileSync16(resolveDocPath(ctx.root, relPath), "utf8"));
2528
3295
  }
2529
3296
  if (name === "gate.status") return textResult(runGates(ctx.root, ctx.config));
2530
3297
  if (name === "retriever.query") {
@@ -2606,14 +3373,17 @@ var usage = `ak-docs \u2014 human\u2194agent documentation bridge (@agentskit/do
2606
3373
 
2607
3374
  Core (no API key):
2608
3375
  ak-docs init [--demo] [--scaffold-workspaces]
2609
- ak-docs index
3376
+ ak-docs demo [--fixture example|monorepo] [--text] [--in-project]
3377
+ ak-docs doctor [--text] [--badge] [--write-badge]
3378
+ ak-docs index [--watch]
2610
3379
  ak-docs query <package|ownership|intent|change> <id> [--agent] [--text]
2611
3380
  ak-docs search <term> [--agent] [--text]
2612
3381
  ak-docs list <packages|intents|changes|knowledge> [--text]
2613
3382
  ak-docs ask [question] local consult (no LLM)
2614
3383
  ak-docs gate run [gate-id]
2615
3384
  ak-docs mcp
2616
- ak-docs memory ingest|classify|promote
3385
+ ak-docs mcp install --cursor | --claude
3386
+ ak-docs memory ingest|classify|promote [--pr] [--dry-run]
2617
3387
  ak-docs bootstrap agent-docs
2618
3388
  ak-docs validate-config | validate-handoff <file>
2619
3389
 
@@ -2625,7 +3395,7 @@ Intelligence (optional AgentsKit peers):
2625
3395
  Advanced / ecosystem:
2626
3396
  ak-docs retrieve <query>
2627
3397
  ak-docs registry topology
2628
- ak-docs playbook draft
3398
+ ak-docs playbook draft | pattern [--text]
2629
3399
 
2630
3400
  Global flags:
2631
3401
  -h, --help --version
@@ -2666,6 +3436,8 @@ var parseArgs = (argv) => {
2666
3436
  else if (positional[0] === "index") command = "index";
2667
3437
  else if (positional[0] === "gate") command = "gate";
2668
3438
  else if (positional[0] === "mcp") command = "mcp";
3439
+ else if (positional[0] === "doctor") command = "doctor";
3440
+ else if (positional[0] === "demo") command = "demo";
2669
3441
  else if (positional[0] === "query") command = "query";
2670
3442
  else if (positional[0] === "search") command = "search";
2671
3443
  else if (positional[0] === "retrieve") command = "retrieve";
@@ -2675,7 +3447,7 @@ var parseArgs = (argv) => {
2675
3447
  else if (positional[0] === "list") command = "list";
2676
3448
  return { command, flags, configPath, positional };
2677
3449
  };
2678
- var writeJson = (payload) => {
3450
+ var writeJson2 = (payload) => {
2679
3451
  process.stdout.write(`${JSON.stringify(payload, null, 2)}
2680
3452
  `);
2681
3453
  };
@@ -2723,13 +3495,33 @@ var writeTextSearch = (term, matches) => {
2723
3495
  ...matches.length ? matches.map(formatSearchMatch) : [" (none)"]
2724
3496
  ]);
2725
3497
  };
2726
- var writeAsk = (question, matches, index) => {
3498
+ var handoffSummaryLines = (index, config, ownerId) => {
3499
+ try {
3500
+ const handoff = runQuery(index, config, { kind: "ownership", id: ownerId, agent: true });
3501
+ if (!handoff || typeof handoff !== "object" || !("editRoots" in handoff)) return [];
3502
+ const payload = handoff;
3503
+ 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)";
3504
+ return [
3505
+ "",
3506
+ "Handoff preview",
3507
+ ` start: ${payload.startHere ?? "(unknown)"}`,
3508
+ ` edit: ${(payload.editRoots ?? []).join(", ") || "(none)"}`,
3509
+ ` checks: ${(payload.checks ?? []).join(" \xB7 ") || "(none)"}`,
3510
+ ` ${bridgeLine}`
3511
+ ];
3512
+ } catch {
3513
+ return [];
3514
+ }
3515
+ };
3516
+ var writeAsk = (question, matches, index, config) => {
2727
3517
  const owner = matches.find((match) => match.type === "ownership") ?? matches.find((match) => Boolean(index.lookup?.ownership?.[match.id]));
2728
3518
  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";
3519
+ const ownerId = best && (best.type === "ownership" || index.lookup?.ownership?.[best.id]) ? best.id : void 0;
3520
+ const bestQuery = ownerId ? `ak-docs query ownership ${ownerId} --agent` : "ak-docs list knowledge --text";
2730
3521
  writeLines([
2731
3522
  `Question: ${question}`,
2732
3523
  best ? `Best match: ${best.type} ${best.id} (${best.path})` : "Best match: none",
3524
+ ...ownerId ? handoffSummaryLines(index, config, ownerId) : [],
2733
3525
  "",
2734
3526
  "Matches:",
2735
3527
  ...matches.length ? matches.slice(0, 5).map(formatSearchMatch) : [" No local matches. Try: ak-docs search <term>"],
@@ -2737,20 +3529,21 @@ var writeAsk = (question, matches, index) => {
2737
3529
  "Next commands:",
2738
3530
  ...best ? [
2739
3531
  `ak-docs search "${question}" --agent`,
2740
- bestQuery
2741
- ] : ["ak-docs list knowledge --text"]
3532
+ bestQuery,
3533
+ ...ownerId ? [`ak-docs doctor --text`] : []
3534
+ ] : ["ak-docs list knowledge --text", "ak-docs doctor --text"]
2742
3535
  ]);
2743
3536
  };
2744
3537
  var readIndexedDoc = (root, config, idOrPath) => {
2745
3538
  const index = loadDocBridgeIndex(root, config);
2746
3539
  const entry = index.knowledge.find((doc) => doc.id === idOrPath || doc.path === idOrPath);
2747
3540
  if (!entry) throw new Error(`Unknown indexed doc "${idOrPath}". Try: search ${idOrPath}`);
2748
- const abs = resolve6(root, entry.path);
2749
- const rootAbs = resolve6(root);
3541
+ const abs = resolve9(root, entry.path);
3542
+ const rootAbs = resolve9(root);
2750
3543
  if (abs !== rootAbs && !abs.startsWith(`${rootAbs}/`)) {
2751
3544
  throw new Error(`Indexed doc escapes project root: ${entry.path}`);
2752
3545
  }
2753
- return readFileSync15(abs, "utf8");
3546
+ return readFileSync17(abs, "utf8");
2754
3547
  };
2755
3548
  var runAskRepl = async (root, config) => {
2756
3549
  const index = loadDocBridgeIndex(root, config);
@@ -2769,12 +3562,12 @@ var runAskRepl = async (root, config) => {
2769
3562
  process.stdout.write(`${readIndexedDoc(root, config, value).slice(0, 8e3)}
2770
3563
  `);
2771
3564
  } else if (command === "resolve") {
2772
- writeJson(runQuery(index, config, { kind: "ownership", id: value, agent: true }));
3565
+ writeJson2(runQuery(index, config, { kind: "ownership", id: value, agent: true }));
2773
3566
  } else if (command === "gate") {
2774
3567
  const gateId = value || void 0;
2775
- writeJson(runGates(root, config, gateId ? [gateId] : void 0));
3568
+ writeJson2(runGates(root, config, gateId ? [gateId] : void 0));
2776
3569
  } else {
2777
- writeAsk(line, searchIndex(index, line, 8), index);
3570
+ writeAsk(line, searchIndex(index, line, 8), index, config);
2778
3571
  }
2779
3572
  } catch (error) {
2780
3573
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -2826,9 +3619,9 @@ var diagnosticNextCommands = (config, result) => {
2826
3619
  ];
2827
3620
  };
2828
3621
  var writeIfMissing = (path, contents) => {
2829
- if (existsSync10(path)) return false;
2830
- mkdirSync2(dirname4(path), { recursive: true });
2831
- writeFileSync2(path, contents, "utf8");
3622
+ if (existsSync14(path)) return false;
3623
+ mkdirSync5(dirname7(path), { recursive: true });
3624
+ writeFileSync4(path, contents, "utf8");
2832
3625
  return true;
2833
3626
  };
2834
3627
  var demoOwnership = {
@@ -2931,7 +3724,7 @@ var scaffoldWorkspaceDocs = (root, config) => {
2931
3724
  const created = [];
2932
3725
  const skipped = [];
2933
3726
  for (const pkg of discoverPnpmPackages(root, config)) {
2934
- const path = resolve6(root, config.corpus.agent.root, "packages", `${pkg.id}.md`);
3727
+ const path = resolve9(root, config.corpus.agent.root, "packages", `${pkg.id}.md`);
2935
3728
  if (writeIfMissing(path, workspaceDocDraft(pkg.id, pkg.path))) created.push(path);
2936
3729
  else skipped.push(path);
2937
3730
  }
@@ -2941,11 +3734,11 @@ var bootstrapAgentDocs = (root, config) => {
2941
3734
  const created = [];
2942
3735
  const skipped = [];
2943
3736
  for (const doc of scanHumanDocRecords(root, config)) {
2944
- const raw = readFileSync15(doc.path, "utf8");
3737
+ const raw = readFileSync17(doc.path, "utf8");
2945
3738
  const body = raw.replace(/^---\n[\s\S]*?\n---\n?/, "");
2946
3739
  const title = firstHeading(body) ?? doc.id;
2947
3740
  const description = firstParagraph(body);
2948
- const draftPath = resolve6(root, config.corpus.agent.root, "human", `${doc.id}.md`);
3741
+ const draftPath = resolve9(root, config.corpus.agent.root, "human", `${doc.id}.md`);
2949
3742
  const draft = [
2950
3743
  "---",
2951
3744
  "type: knowledge",
@@ -2994,7 +3787,7 @@ var runCli = (argv) => {
2994
3787
  configPath ? { explicitPath: configPath } : {}
2995
3788
  );
2996
3789
  parseDocBridgeConfig(config);
2997
- writeJson({ ok: true, path, schemaVersion: config.schemaVersion });
3790
+ writeJson2({ ok: true, path, schemaVersion: config.schemaVersion });
2998
3791
  return 0;
2999
3792
  } catch (error) {
3000
3793
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3009,10 +3802,10 @@ var runCli = (argv) => {
3009
3802
  return 1;
3010
3803
  }
3011
3804
  try {
3012
- const abs = resolve6(file);
3013
- const raw = readFileSync15(abs, "utf8");
3805
+ const abs = resolve9(file);
3806
+ const raw = readFileSync17(abs, "utf8");
3014
3807
  const handoff = parseAgentHandoff(JSON.parse(raw));
3015
- writeJson({ ok: true, handoff });
3808
+ writeJson2({ ok: true, handoff });
3016
3809
  return 0;
3017
3810
  } catch (error) {
3018
3811
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3023,10 +3816,10 @@ var runCli = (argv) => {
3023
3816
  if (command === "init") {
3024
3817
  const root = process.cwd();
3025
3818
  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");
3819
+ const configFile = resolve9(root, configPath ?? "doc-bridge.config.json");
3820
+ const docsIndex = resolve9(root, "docs/for-agents/INDEX.md");
3821
+ const exampleDoc = resolve9(root, "docs/for-agents/packages/example.md");
3822
+ const agentsMd = resolve9(root, "AGENTS.md");
3030
3823
  const configWritten = writeIfMissing(configFile, initConfigContents(configFile, withDemo));
3031
3824
  const indexWritten = writeIfMissing(
3032
3825
  docsIndex,
@@ -3034,9 +3827,9 @@ var runCli = (argv) => {
3034
3827
  );
3035
3828
  const exampleWritten = withDemo ? writeIfMissing(exampleDoc, exampleAgentDoc) : false;
3036
3829
  const agentsWritten = writeIfMissing(agentsMd, agentsMdSnippet);
3037
- if (withDemo) writeIfMissing(resolve6(root, "src/.gitkeep"), "");
3830
+ if (withDemo) writeIfMissing(resolve9(root, "src/.gitkeep"), "");
3038
3831
  const scaffold = flags.has("--scaffold-workspaces") ? scaffoldWorkspaceDocs(root, loadProject(configFile).config) : void 0;
3039
- writeJson({
3832
+ writeJson2({
3040
3833
  ok: true,
3041
3834
  configPath: configFile,
3042
3835
  demo: withDemo,
@@ -3060,7 +3853,7 @@ var runCli = (argv) => {
3060
3853
  try {
3061
3854
  const { config, root } = loadProject(configPath);
3062
3855
  const result = bootstrapAgentDocs(root, config);
3063
- writeJson({ ok: true, ...result });
3856
+ writeJson2({ ok: true, ...result });
3064
3857
  return 0;
3065
3858
  } catch (error) {
3066
3859
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3070,24 +3863,43 @@ var runCli = (argv) => {
3070
3863
  }
3071
3864
  if (command === "memory") {
3072
3865
  if (!["ingest", "classify", "promote"].includes(positional[1] ?? "")) {
3073
- process.stderr.write("Usage: ak-docs memory <ingest|classify|promote> [--config <path>]\n");
3866
+ process.stderr.write(
3867
+ "Usage: ak-docs memory <ingest|classify|promote> [--pr] [--dry-run] [--force] [--config <path>]\n"
3868
+ );
3074
3869
  return 1;
3075
3870
  }
3076
3871
  try {
3077
3872
  const { config, root } = loadProject(configPath);
3078
3873
  const candidates = ingestMemoryCandidates(root);
3079
3874
  if (positional[1] === "ingest") {
3080
- writeJson({ ok: true, count: candidates.length, candidates });
3875
+ writeJson2({ ok: true, count: candidates.length, candidates });
3081
3876
  return 0;
3082
3877
  }
3083
3878
  const index = loadDocBridgeIndex(root, config);
3084
3879
  const classifications = classifyMemoryCandidates(candidates, index);
3085
3880
  if (positional[1] === "classify") {
3086
- writeJson({ ok: true, count: classifications.length, classifications });
3881
+ writeJson2({ ok: true, count: classifications.length, classifications });
3087
3882
  return 0;
3088
3883
  }
3089
3884
  const draft = draftMemoryPromotion(classifications);
3090
- writeJson(draft);
3885
+ if (flags.has("--pr") || flags.has("--github")) {
3886
+ const pr = promoteMemoryToGithubPr(root, draft, {
3887
+ dryRun: flags.has("--dry-run"),
3888
+ force: flags.has("--force")
3889
+ });
3890
+ if (flags.has("--text")) {
3891
+ writeLines([
3892
+ pr.message,
3893
+ ...pr.draftPath ? [`Draft: ${pr.draftPath}`] : [],
3894
+ ...pr.prUrl ? [`PR: ${pr.prUrl}`] : [],
3895
+ ...pr.commands.length ? ["", "Commands:", ...pr.commands.map((cmd) => ` ${cmd}`)] : []
3896
+ ]);
3897
+ } else {
3898
+ writeJson2({ ...draft, pr });
3899
+ }
3900
+ return pr.ok ? 0 : 1;
3901
+ }
3902
+ writeJson2(draft);
3091
3903
  return draft.ok ? 0 : 1;
3092
3904
  } catch (error) {
3093
3905
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3100,22 +3912,35 @@ var runCli = (argv) => {
3100
3912
  process.stderr.write("Usage: ak-docs registry topology\n");
3101
3913
  return 1;
3102
3914
  }
3103
- writeJson(registryTopology());
3915
+ writeJson2(registryTopology());
3104
3916
  return 0;
3105
3917
  }
3106
3918
  if (command === "playbook") {
3107
- if (positional[1] !== "draft") {
3108
- process.stderr.write("Usage: ak-docs playbook draft [--config <path>]\n");
3919
+ const action = positional[1];
3920
+ if (action === "pattern") {
3921
+ const payload = docBridgePatternPayload();
3922
+ if (flags.has("--text") || wantsTextOutput(flags, { schemaVersion: 1, corpus: { agent: { root: "docs" } } })) {
3923
+ process.stdout.write(`${docBridgePatternMarkdown()}
3924
+ `);
3925
+ } else {
3926
+ writeJson2(payload);
3927
+ }
3928
+ return 0;
3929
+ }
3930
+ if (action !== "draft") {
3931
+ process.stderr.write("Usage: ak-docs playbook draft | pattern [--text] [--config <path>]\n");
3109
3932
  return 1;
3110
3933
  }
3111
3934
  try {
3112
3935
  const { config, root } = loadProject(configPath);
3113
3936
  const index = loadDocBridgeIndex(root, config);
3114
3937
  const draft = draftMemoryPromotion(classifyMemoryCandidates(ingestMemoryCandidates(root), index));
3115
- writeJson({
3938
+ writeJson2({
3116
3939
  ...draft,
3117
3940
  title: "Draft Playbook feedback promotion",
3118
- pattern: "Doc Bridge Pattern"
3941
+ pattern: "Doc Bridge Pattern",
3942
+ patternDoc: "docs/playbook/doc-bridge-pattern.md",
3943
+ exportCommand: "ak-docs playbook pattern --text"
3119
3944
  });
3120
3945
  return 0;
3121
3946
  } catch (error) {
@@ -3126,11 +3951,18 @@ var runCli = (argv) => {
3126
3951
  }
3127
3952
  if (command === "index") {
3128
3953
  try {
3129
- const { config, root } = loadProject(configPath);
3954
+ const { config, root, configPath: loadedConfigPath } = loadProject(configPath);
3955
+ if (flags.has("--watch")) {
3956
+ return watchDocBridgeIndex({
3957
+ root,
3958
+ config,
3959
+ configPath: loadedConfigPath
3960
+ });
3961
+ }
3130
3962
  const result = buildDocBridgeIndex({ root, config });
3131
3963
  const diagnostics = indexDiagnostics(config, result);
3132
3964
  const handoffCount = Object.keys(result.index.handoffs ?? {}).length;
3133
- writeJson({
3965
+ writeJson2({
3134
3966
  ok: true,
3135
3967
  indexPath: result.indexPath,
3136
3968
  ...result.llmsTxtPath ? { llmsTxtPath: result.llmsTxtPath } : {},
@@ -3166,7 +3998,7 @@ var runCli = (argv) => {
3166
3998
  try {
3167
3999
  const { config, root } = loadProject(configPath);
3168
4000
  const result = runGates(root, config, gateId ? [gateId] : void 0);
3169
- writeJson(result);
4001
+ writeJson2(result);
3170
4002
  return result.ok ? 0 : 1;
3171
4003
  } catch (error) {
3172
4004
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3175,12 +4007,98 @@ var runCli = (argv) => {
3175
4007
  }
3176
4008
  }
3177
4009
  if (command === "mcp") {
4010
+ if (positional[1] === "install") {
4011
+ const target = flags.has("--claude") ? "claude" : flags.has("--cursor") ? "cursor" : void 0;
4012
+ if (!target) {
4013
+ process.stderr.write("Usage: ak-docs mcp install --cursor | --claude\n");
4014
+ return 1;
4015
+ }
4016
+ try {
4017
+ const { config, root } = loadProject(configPath);
4018
+ const result = installMcpConfig(root, target);
4019
+ if (wantsTextOutput(flags, config)) {
4020
+ writeLines([
4021
+ `Installed MCP server "${result.serverName}" for ${result.target}`,
4022
+ `Config: ${result.configPath}`,
4023
+ ...result.created ? ["Created new config file"] : ["Merged into existing config"],
4024
+ "",
4025
+ "Next steps:",
4026
+ ...result.nextSteps.map((step) => ` \u2192 ${step}`)
4027
+ ]);
4028
+ } else {
4029
+ writeJson2({ ...result, snippet: mcpSnippet(root) });
4030
+ }
4031
+ return 0;
4032
+ } catch (error) {
4033
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
4034
+ `);
4035
+ return 1;
4036
+ }
4037
+ }
3178
4038
  try {
3179
4039
  const { config, root } = loadProject(configPath);
3180
4040
  startMcpStdioServer({ root, config });
3181
4041
  return void 0;
3182
4042
  } catch (error) {
3183
4043
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
4044
+ `);
4045
+ return 1;
4046
+ }
4047
+ }
4048
+ if (command === "doctor") {
4049
+ try {
4050
+ const { config, root } = loadProject(configPath);
4051
+ const report = runDoctor(root, config);
4052
+ if (flags.has("--write-badge")) {
4053
+ const badgePath = resolve9(root, ".doc-bridge", "coverage-badge.json");
4054
+ mkdirSync5(dirname7(badgePath), { recursive: true });
4055
+ writeFileSync4(badgePath, `${formatDoctorBadgeJson(report.badge)}
4056
+ `, "utf8");
4057
+ }
4058
+ if (flags.has("--badge")) {
4059
+ writeLines([formatDoctorBadgeMarkdown(report.badge)]);
4060
+ return 0;
4061
+ }
4062
+ if (wantsTextOutput(flags, config)) writeLines(formatDoctorText(report));
4063
+ else writeJson2(report);
4064
+ return report.ok ? 0 : 1;
4065
+ } catch (error) {
4066
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
4067
+ `);
4068
+ return 1;
4069
+ }
4070
+ }
4071
+ if (command === "demo") {
4072
+ const resolveDemoFixture = () => {
4073
+ const fixtureFlagIndex = positional.indexOf("--fixture");
4074
+ if (fixtureFlagIndex >= 0) {
4075
+ const value = positional[fixtureFlagIndex + 1];
4076
+ if (value === "monorepo" || value === "example") return value;
4077
+ }
4078
+ if (flags.has("--monorepo") || positional.includes("monorepo")) return "monorepo";
4079
+ if (positional[1] === "monorepo") return "monorepo";
4080
+ return "example";
4081
+ };
4082
+ const resolvedFixture = resolveDemoFixture();
4083
+ try {
4084
+ const useProject = flags.has("--in-project") || flags.has("--copy-fixture");
4085
+ if (!useProject) {
4086
+ const result2 = withDemoWorkspace(
4087
+ resolvedFixture,
4088
+ (root2, config2) => runDemo(root2, config2, resolvedFixture)
4089
+ );
4090
+ const textMode = flags.has("--text") || !flags.has("--json") && !flags.has("--agent");
4091
+ if (textMode) writeLines(formatDemoText(result2));
4092
+ else writeJson2(result2);
4093
+ return result2.ok ? 0 : 1;
4094
+ }
4095
+ const { config, root } = loadProject(configPath);
4096
+ const result = runDemo(root, config, resolvedFixture, { copyFixture: flags.has("--copy-fixture") });
4097
+ if (wantsTextOutput(flags, config)) writeLines(formatDemoText(result));
4098
+ else writeJson2(result);
4099
+ return result.ok ? 0 : 1;
4100
+ } catch (error) {
4101
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
3184
4102
  `);
3185
4103
  return 1;
3186
4104
  }
@@ -3202,7 +4120,7 @@ var runCli = (argv) => {
3202
4120
  const index = loadDocBridgeIndex(root, config);
3203
4121
  const result = runQuery(index, config, { kind, id, agent: flags.has("--agent") });
3204
4122
  if (wantsTextOutput(flags, config)) writeTextQuery(result);
3205
- else writeJson(result);
4123
+ else writeJson2(result);
3206
4124
  return 0;
3207
4125
  } catch (error) {
3208
4126
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3221,11 +4139,11 @@ var runCli = (argv) => {
3221
4139
  const index = loadDocBridgeIndex(root, config);
3222
4140
  if (flags.has("--agent")) {
3223
4141
  const result = runQuery(index, config, { kind: "search", term, agent: true });
3224
- writeJson(result);
4142
+ writeJson2(result);
3225
4143
  } else {
3226
4144
  const matches = searchIndex(index, term);
3227
4145
  if (wantsTextOutput(flags, config)) writeTextSearch(term, matches);
3228
- else writeJson({ term, count: matches.length, matches });
4146
+ else writeJson2({ term, count: matches.length, matches });
3229
4147
  }
3230
4148
  return 0;
3231
4149
  } catch (error) {
@@ -3249,7 +4167,7 @@ var runCli = (argv) => {
3249
4167
  try {
3250
4168
  const { config, root } = loadProject(configPath);
3251
4169
  const index = loadDocBridgeIndex(root, config);
3252
- writeJson({ query, chunks: await retrieveHybridChunks(root, config, index, query) });
4170
+ writeJson2({ query, chunks: await retrieveHybridChunks(root, config, index, query) });
3253
4171
  return 0;
3254
4172
  } catch (error) {
3255
4173
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3271,7 +4189,7 @@ var runCli = (argv) => {
3271
4189
  const rag = await createDocBridgeRag(root, config, index);
3272
4190
  if (action === "ingest") {
3273
4191
  const result = await rag.ingest();
3274
- writeJson({ ok: true, ...result });
4192
+ writeJson2({ ok: true, ...result });
3275
4193
  return 0;
3276
4194
  }
3277
4195
  const query = positional.slice(2).join(" ").trim();
@@ -3280,7 +4198,7 @@ var runCli = (argv) => {
3280
4198
  return 1;
3281
4199
  }
3282
4200
  const hits = await rag.search(query);
3283
- writeJson({ query, count: hits.length, hits });
4201
+ writeJson2({ query, count: hits.length, hits });
3284
4202
  return 0;
3285
4203
  } catch (error) {
3286
4204
  if (error instanceof PeerMissingError) {
@@ -3362,7 +4280,7 @@ Install peers: ${layer1InstallHint()}
3362
4280
  return 1;
3363
4281
  }
3364
4282
  const index = loadDocBridgeIndex(root, config);
3365
- writeAsk(question, searchIndex(index, question, 8), index);
4283
+ writeAsk(question, searchIndex(index, question, 8), index, config);
3366
4284
  return 0;
3367
4285
  } catch (error) {
3368
4286
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
@@ -3382,19 +4300,19 @@ Install peers: ${layer1InstallHint()}
3382
4300
  if (kind === "packages") {
3383
4301
  const items2 = index.lookup?.packages ?? [];
3384
4302
  if (wantsTextOutput(flags, config)) writeLines(items2);
3385
- else writeJson({ kind, items: items2 });
4303
+ else writeJson2({ kind, items: items2 });
3386
4304
  return 0;
3387
4305
  }
3388
4306
  if (kind === "intents") {
3389
4307
  const items2 = Object.keys(index.lookup?.intents ?? {});
3390
4308
  if (wantsTextOutput(flags, config)) writeLines(items2);
3391
- else writeJson({ kind, items: items2 });
4309
+ else writeJson2({ kind, items: items2 });
3392
4310
  return 0;
3393
4311
  }
3394
4312
  if (kind === "changes") {
3395
4313
  const items2 = Object.keys(index.lookup?.changes ?? {});
3396
4314
  if (wantsTextOutput(flags, config)) writeLines(items2);
3397
- else writeJson({ kind, items: items2 });
4315
+ else writeJson2({ kind, items: items2 });
3398
4316
  return 0;
3399
4317
  }
3400
4318
  const items = index.knowledge.map((entry) => ({
@@ -3405,7 +4323,7 @@ Install peers: ${layer1InstallHint()}
3405
4323
  if (wantsTextOutput(flags, config)) {
3406
4324
  writeLines(items.map((item) => [item.id, item.path, item.title].join(" ")));
3407
4325
  } else {
3408
- writeJson({ kind, items });
4326
+ writeJson2({ kind, items });
3409
4327
  }
3410
4328
  return 0;
3411
4329
  } catch (error) {