@agentskit/doc-bridge 0.1.0-alpha.2 → 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 (57) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +139 -57
  3. package/action.yml +78 -0
  4. package/dist/cli/program.js +1122 -114
  5. package/dist/cli/program.js.map +1 -1
  6. package/dist/index.d.ts +258 -28
  7. package/dist/index.js +824 -54
  8. package/dist/index.js.map +1 -1
  9. package/docs/DOGFOOD-ROUND2.md +142 -0
  10. package/docs/DOGFOOD-ROUND3.md +74 -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 +12 -9
  39. package/src/cli/demo.ts +143 -0
  40. package/src/cli/program.ts +220 -26
  41. package/src/doctor/badge.ts +53 -0
  42. package/src/doctor/run-doctor.ts +286 -0
  43. package/src/federation/llms.ts +20 -11
  44. package/src/index-builder/build-handoffs.ts +35 -2
  45. package/src/index-builder/scan-corpus.ts +5 -1
  46. package/src/index-builder/watch-index.ts +94 -0
  47. package/src/index.ts +24 -0
  48. package/src/lib/markdown.ts +31 -4
  49. package/src/mcp/install.ts +84 -0
  50. package/src/memory/github-pr.ts +190 -0
  51. package/src/playbook/doc-bridge-pattern.ts +121 -0
  52. package/src/query/query.ts +19 -1
  53. package/src/query/search.ts +85 -17
  54. package/src/schemas/agent-handoff.ts +11 -0
  55. package/src/schemas/doc-bridge-index.ts +3 -1
  56. package/src/schemas/json-schemas.ts +2 -1
  57. package/src/version.ts +1 -1
package/dist/index.js CHANGED
@@ -372,6 +372,11 @@ var HandoffTargetSchema = z2.object({
372
372
  group: z2.string().min(1).max(128).optional(),
373
373
  layer: z2.string().min(1).max(32).optional()
374
374
  }).strict();
375
+ var HandoffBridgeSchema = z2.object({
376
+ humanDoc: z2.enum(["linked", "missing", "external"]),
377
+ action: z2.string().min(1).max(256).optional(),
378
+ bootstrap: z2.string().min(1).max(256).optional()
379
+ }).strict();
375
380
  var AgentHandoffV1Schema = z2.object({
376
381
  type: z2.literal("agent-handoff"),
377
382
  schemaVersion: z2.literal(HANDOFF_SCHEMA_VERSION).default(HANDOFF_SCHEMA_VERSION),
@@ -382,6 +387,7 @@ var AgentHandoffV1Schema = z2.object({
382
387
  editRoots: z2.array(z2.string().min(1).max(512)).max(32),
383
388
  checks: z2.array(z2.string().min(1).max(256)).max(32),
384
389
  humanDoc: z2.string().min(1).max(512).nullable().optional(),
390
+ bridge: HandoffBridgeSchema.optional(),
385
391
  playbookPatterns: z2.array(z2.string().url()).max(16).optional(),
386
392
  notes: z2.array(z2.string().min(1).max(1024)).max(16)
387
393
  }).strict();
@@ -423,7 +429,9 @@ var KnowledgeEntrySchema = z3.object({
423
429
  type: z3.string().min(1).max(128),
424
430
  title: z3.string().min(1).max(256),
425
431
  path: z3.string().min(1).max(512),
426
- description: z3.string().max(1024).optional(),
432
+ description: z3.string().max(2048).optional(),
433
+ /** Flattened body excerpt for full-text search (not for display). */
434
+ body: z3.string().max(8e3).optional(),
427
435
  links: z3.array(z3.string().min(1).max(512)).max(64).optional(),
428
436
  tags: z3.array(z3.string().min(1).max(64)).max(32).optional()
429
437
  }).strict();
@@ -628,7 +636,8 @@ var DocBridgeIndexV1JsonSchema = {
628
636
  type: { type: "string", minLength: 1, maxLength: 128 },
629
637
  title: { type: "string", minLength: 1, maxLength: 256 },
630
638
  path: { type: "string", minLength: 1, maxLength: 512 },
631
- description: { type: "string", maxLength: 1024 },
639
+ description: { type: "string", maxLength: 2048 },
640
+ body: { type: "string", maxLength: 8e3 },
632
641
  links: stringArray(64),
633
642
  tags: stringArray(32)
634
643
  }
@@ -821,7 +830,7 @@ var firstHeading = (markdown) => {
821
830
  }
822
831
  return void 0;
823
832
  };
824
- var firstParagraph = (markdown) => {
833
+ var firstParagraph = (markdown, maxLen = 400) => {
825
834
  const { body } = parseFrontmatter(markdown);
826
835
  const lines = body.split("\n");
827
836
  const buf = [];
@@ -833,11 +842,27 @@ var firstParagraph = (markdown) => {
833
842
  }
834
843
  if (t.startsWith("#")) continue;
835
844
  if (t.startsWith("---")) continue;
845
+ if (t.startsWith("```")) break;
846
+ if (t.startsWith("|") || t.startsWith("- [") || t.startsWith("* [")) {
847
+ if (buf.length) break;
848
+ continue;
849
+ }
836
850
  buf.push(t);
837
- if (buf.join(" ").length > 40) break;
838
- }
839
- const text = buf.join(" ").trim();
840
- return text || void 0;
851
+ if (buf.join(" ").length >= maxLen) break;
852
+ }
853
+ let text = buf.join(" ").replace(/\s+/g, " ").trim();
854
+ if (!text) return void 0;
855
+ if (text.length <= maxLen) return text;
856
+ const sliced = text.slice(0, maxLen);
857
+ const sentenceEnd = Math.max(sliced.lastIndexOf(". "), sliced.lastIndexOf("! "), sliced.lastIndexOf("? "));
858
+ if (sentenceEnd > maxLen * 0.4) return sliced.slice(0, sentenceEnd + 1).trim();
859
+ const wordEnd = sliced.lastIndexOf(" ");
860
+ return (wordEnd > 0 ? sliced.slice(0, wordEnd) : sliced).trim();
861
+ };
862
+ var extractSearchBody = (markdown, maxLen = 6e3) => {
863
+ const { body } = parseFrontmatter(markdown);
864
+ const text = body.replace(/```[\s\S]*?```/g, " ").replace(/!\[[^\]]*\]\([^)]+\)/g, " ").replace(/\[[^\]]*\]\([^)]+\)/g, " ").replace(/^#+\s+/gm, "").replace(/[|>*_`#]/g, " ").replace(/\s+/g, " ").trim();
865
+ return text.length > maxLen ? text.slice(0, maxLen) : text;
841
866
  };
842
867
  var slugFromPath = (relPath) => {
843
868
  const base = relPath.replace(/\.mdx?$/, "");
@@ -895,7 +920,9 @@ var scanAgentCorpus = (root, config) => {
895
920
  const { data: frontmatter } = parseFrontmatter(raw);
896
921
  const id = frontmatterString(frontmatter, "id") ?? frontmatterString(frontmatter, "package") ?? slugFromPath(relToCorpus);
897
922
  const title = firstHeading(raw) ?? id;
898
- const description = firstParagraph(raw);
923
+ const purpose = frontmatterString(frontmatter, "purpose");
924
+ const description = purpose ?? firstParagraph(raw, 400);
925
+ const body = extractSearchBody(raw);
899
926
  return {
900
927
  id,
901
928
  type: "agent-doc",
@@ -904,7 +931,8 @@ var scanAgentCorpus = (root, config) => {
904
931
  absPath: abs,
905
932
  relPath,
906
933
  frontmatter,
907
- ...description ? { description } : {}
934
+ ...description ? { description } : {},
935
+ ...body ? { body } : {}
908
936
  };
909
937
  });
910
938
  };
@@ -1037,11 +1065,19 @@ var resolveHumanDoc = (packageId, override, fmHuman, humanDocs = {}) => {
1037
1065
  packageId,
1038
1066
  packageId.replace(/^@[^/]+\//, ""),
1039
1067
  packageId.replace(/^os-/, ""),
1040
- packageId.replace(/-pattern$/, "")
1068
+ packageId.replace(/-pattern$/, ""),
1069
+ `packages/${packageId}`,
1070
+ `reference/packages/${packageId}`,
1071
+ `packages/${packageId}/index`
1041
1072
  ];
1042
1073
  for (const alias of aliases) {
1043
1074
  if (humanDocs[alias]) return humanDocs[alias];
1044
1075
  }
1076
+ for (const [key, url] of Object.entries(humanDocs)) {
1077
+ if (key === packageId || key.endsWith(`/${packageId}`) || key.endsWith(`/${packageId}/index`) || key.endsWith(`-${packageId}`)) {
1078
+ return url;
1079
+ }
1080
+ }
1045
1081
  return void 0;
1046
1082
  };
1047
1083
  var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root = process.cwd()) => {
@@ -1081,6 +1117,11 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
1081
1117
  ...agentDoc ? { agentDoc } : {}
1082
1118
  };
1083
1119
  ownership[pkg.id] = record;
1120
+ const bridge = record.humanDoc ? /^https?:\/\//.test(record.humanDoc) ? { humanDoc: "external" } : { humanDoc: "linked" } : config.corpus.human ? {
1121
+ humanDoc: "missing",
1122
+ action: "ak-docs bootstrap agent-docs",
1123
+ bootstrap: `docs/for-agents/human/${pkg.id}.md`
1124
+ } : void 0;
1084
1125
  handoffs[pkg.id] = {
1085
1126
  type: "agent-handoff",
1086
1127
  schemaVersion: 1,
@@ -1099,7 +1140,11 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
1099
1140
  editRoots: [record.path],
1100
1141
  checks: [...record.checks],
1101
1142
  ...record.humanDoc ? { humanDoc: record.humanDoc } : {},
1102
- notes: record.purpose ? [record.purpose] : []
1143
+ ...bridge ? { bridge } : {},
1144
+ notes: [
1145
+ ...record.purpose ? [record.purpose] : [],
1146
+ ...!record.humanDoc && config.corpus.human ? [`Human guide missing for ${pkg.id}. Run: ak-docs bootstrap agent-docs`] : []
1147
+ ]
1103
1148
  };
1104
1149
  }
1105
1150
  const intents = {};
@@ -1127,7 +1172,7 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
1127
1172
  };
1128
1173
 
1129
1174
  // src/version.ts
1130
- var PACKAGE_VERSION = "0.1.0-alpha.2";
1175
+ var PACKAGE_VERSION = "1.0.0";
1131
1176
 
1132
1177
  // src/index-builder/capabilities.ts
1133
1178
  var renderCapabilitiesJson = (config, index, paths) => {
@@ -1226,10 +1271,10 @@ var docId = (relToHumanRoot, raw) => {
1226
1271
  return meta.package ?? meta.module ?? meta.id ?? slugFromPath(relToHumanRoot);
1227
1272
  };
1228
1273
  var routeSlug = (relToHumanRoot, opts) => relToHumanRoot.split("/").filter((part) => !opts?.stripGroups || !/^\(.+\)$/.test(part)).join("/").replace(/(?:^|\/)index\.mdx?$/, "").replace(/\.mdx?$/, "");
1229
- var humanUrl = (slug, urlPrefix) => {
1230
- if (typeof urlPrefix !== "string" || !urlPrefix) return slug;
1274
+ var humanUrl = (slug2, urlPrefix) => {
1275
+ if (typeof urlPrefix !== "string" || !urlPrefix) return slug2;
1231
1276
  const prefix = urlPrefix.replace(/\/$/, "");
1232
- return slug ? `${prefix}/${slug.replace(/^\//, "")}` : prefix;
1277
+ return slug2 ? `${prefix}/${slug2.replace(/^\//, "")}` : prefix;
1233
1278
  };
1234
1279
  var scanMarkdownDocs = (root, humanRoot, options) => {
1235
1280
  const out = [];
@@ -1260,9 +1305,9 @@ var docusaurusFileSlug = (relPath) => {
1260
1305
  return routeSlug(relPath);
1261
1306
  };
1262
1307
  var docusaurusSlug = (relPath, raw) => {
1263
- const slug = parseFrontmatter2(raw).slug;
1264
- if (!slug) return docusaurusFileSlug(relPath);
1265
- return slug.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "");
1308
+ const slug2 = parseFrontmatter2(raw).slug;
1309
+ if (!slug2) return docusaurusFileSlug(relPath);
1310
+ return slug2.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "");
1266
1311
  };
1267
1312
  var docusaurusSidebarId = (relPath, raw) => {
1268
1313
  const id = parseFrontmatter2(raw).id;
@@ -1800,19 +1845,58 @@ import { relative, resolve as resolve4 } from "path";
1800
1845
  import { z as z5, ZodError } from "zod";
1801
1846
 
1802
1847
  // src/query/search.ts
1803
- var tokenize = (value) => value.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2);
1848
+ var tokenize = (value) => value.toLowerCase().split(/[^a-z0-9@/_-]+/).filter((t) => t.length >= 2);
1849
+ var PACKAGE_INTENT = /\b(package|module|pkg|edit|change|where|owns?|ownership|handoff|start)\b/i;
1850
+ var scoreHay = (tokens, hay, weight = 1) => {
1851
+ let score = 0;
1852
+ for (const token of tokens) {
1853
+ if (!hay.includes(token)) continue;
1854
+ score += token.length * weight;
1855
+ if (new RegExp(`(?:^|[^a-z0-9])${token}(?:[^a-z0-9]|$)`).test(hay)) {
1856
+ score += token.length;
1857
+ }
1858
+ }
1859
+ return score;
1860
+ };
1861
+ var identityBoost = (id, path, tokens, term) => {
1862
+ const idLower = id.toLowerCase();
1863
+ const termLower = term.toLowerCase().trim();
1864
+ const base = path.split("/").pop()?.replace(/\.mdx?$/i, "").toLowerCase() ?? "";
1865
+ let boost = 0;
1866
+ if (idLower === termLower || base === termLower) boost += 200;
1867
+ if (tokens.length === 1 && (idLower === tokens[0] || base === tokens[0])) boost += 200;
1868
+ for (const token of tokens) {
1869
+ if (idLower === token) boost += 120;
1870
+ else if (idLower.startsWith(`${token}-`) || idLower.endsWith(`-${token}`)) boost += 40;
1871
+ else if (idLower.includes(token) && idLower.length <= token.length + 4) boost += 30;
1872
+ if (base === token) boost += 100;
1873
+ }
1874
+ if (tokens.includes(idLower)) boost += Math.max(0, 40 - idLower.length);
1875
+ return boost;
1876
+ };
1877
+ var preferOwnership = (term) => PACKAGE_INTENT.test(term) || /^(where|how).*(edit|change|package|module)/i.test(term);
1804
1878
  var searchIndex = (index, term, limit = 20) => {
1805
1879
  const tokens = tokenize(term);
1806
1880
  if (!tokens.length) return [];
1807
- const matches = [];
1808
- for (const entry of index.knowledge) {
1809
- const hay = `${entry.id} ${entry.title} ${entry.description ?? ""} ${entry.path}`.toLowerCase();
1810
- let score = 0;
1811
- for (const token of tokens) {
1812
- if (hay.includes(token)) score += token.length;
1881
+ const wantOwnership = preferOwnership(term);
1882
+ const byPath = /* @__PURE__ */ new Map();
1883
+ const consider = (match) => {
1884
+ const key = match.path;
1885
+ const existing = byPath.get(key);
1886
+ if (!existing) {
1887
+ byPath.set(key, match);
1888
+ return;
1813
1889
  }
1890
+ const prefer = match.score > existing.score || match.score === existing.score && match.type === "ownership" && existing.type !== "ownership" || wantOwnership && match.type === "ownership" && existing.type !== "ownership" && match.score >= existing.score - 20;
1891
+ if (prefer) byPath.set(key, match);
1892
+ };
1893
+ for (const entry of index.knowledge) {
1894
+ const body = entry.body ?? "";
1895
+ const hay = `${entry.id} ${entry.title} ${entry.description ?? ""} ${entry.path} ${body}`.toLowerCase();
1896
+ let score = scoreHay(tokens, hay, 1);
1897
+ score += identityBoost(entry.id, entry.path, tokens, term);
1814
1898
  if (score > 0) {
1815
- matches.push({
1899
+ consider({
1816
1900
  type: "knowledge",
1817
1901
  id: entry.id,
1818
1902
  path: entry.path,
@@ -1822,22 +1906,31 @@ var searchIndex = (index, term, limit = 20) => {
1822
1906
  }
1823
1907
  }
1824
1908
  for (const [id, owner] of Object.entries(index.lookup?.ownership ?? {})) {
1825
- const hay = `${id} ${owner.path} ${owner.purpose ?? ""} ${owner.group ?? ""}`.toLowerCase();
1826
- let score = 0;
1827
- for (const token of tokens) {
1828
- if (hay.includes(token)) score += token.length * 2;
1829
- }
1909
+ const path = owner.agentDoc ?? owner.path;
1910
+ const hay = `${id} ${owner.path} ${owner.purpose ?? ""} ${owner.group ?? ""} ${owner.agentDoc ?? ""} ${owner.humanDoc ?? ""}`.toLowerCase();
1911
+ let score = scoreHay(tokens, hay, 2);
1912
+ score += identityBoost(id, path, tokens, term);
1913
+ if (wantOwnership) score += 25;
1914
+ score += 15;
1830
1915
  if (score > 0) {
1831
- matches.push({
1916
+ consider({
1832
1917
  type: "ownership",
1833
1918
  id,
1834
- path: owner.agentDoc ?? owner.path,
1919
+ path,
1835
1920
  ...owner.purpose ? { summary: owner.purpose } : {},
1836
1921
  score
1837
1922
  });
1838
1923
  }
1839
1924
  }
1840
- return matches.sort((a, b) => b.score - a.score).slice(0, limit);
1925
+ return [...byPath.values()].sort((a, b) => {
1926
+ if (b.score !== a.score) return b.score - a.score;
1927
+ const aExact = tokens.includes(a.id.toLowerCase()) ? 1 : 0;
1928
+ const bExact = tokens.includes(b.id.toLowerCase()) ? 1 : 0;
1929
+ if (bExact !== aExact) return bExact - aExact;
1930
+ if (a.type === "ownership" && b.type !== "ownership") return -1;
1931
+ if (b.type === "ownership" && a.type !== "ownership") return 1;
1932
+ return a.id.localeCompare(b.id);
1933
+ }).slice(0, limit);
1841
1934
  };
1842
1935
 
1843
1936
  // src/retriever/doc-bridge-retriever.ts
@@ -1984,6 +2077,11 @@ var handoffForPackage = (index, id, config) => {
1984
2077
  if (fromIndex) return normalizeAgentHandoff(fromIndex);
1985
2078
  const owner = index.lookup?.ownership?.[id];
1986
2079
  if (!owner) throw new Error(`Unknown package/ownership id "${id}". Try: ak-docs list packages`);
2080
+ const bridge = owner.humanDoc ? /^https?:\/\//.test(owner.humanDoc) ? { humanDoc: "external" } : { humanDoc: "linked" } : config.corpus.human ? {
2081
+ humanDoc: "missing",
2082
+ action: "ak-docs bootstrap agent-docs",
2083
+ bootstrap: `docs/for-agents/human/${id}.md`
2084
+ } : void 0;
1987
2085
  return normalizeAgentHandoff({
1988
2086
  type: "agent-handoff",
1989
2087
  source: config.index?.outFile ?? ".doc-bridge/index.json",
@@ -1999,7 +2097,11 @@ var handoffForPackage = (index, id, config) => {
1999
2097
  editRoots: [owner.path],
2000
2098
  checks: [...owner.checks],
2001
2099
  ...owner.humanDoc ? { humanDoc: owner.humanDoc } : {},
2002
- notes: owner.purpose ? [owner.purpose] : []
2100
+ ...bridge ? { bridge } : {},
2101
+ notes: [
2102
+ ...owner.purpose ? [owner.purpose] : [],
2103
+ ...!owner.humanDoc && config.corpus.human ? [`Human guide missing for ${id}. Run: ak-docs bootstrap agent-docs`] : []
2104
+ ]
2003
2105
  });
2004
2106
  };
2005
2107
  var runQuery = (index, config, req) => {
@@ -2300,9 +2402,531 @@ var startMcpStdioServer = (ctx) => {
2300
2402
  process.stdin.resume();
2301
2403
  };
2302
2404
 
2405
+ // src/mcp/install.ts
2406
+ import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync13, writeFileSync as writeFileSync2 } from "fs";
2407
+ import { homedir } from "os";
2408
+ import { dirname as dirname4, join as join13, resolve as resolve5 } from "path";
2409
+ var SERVER_NAME = "ak-docs";
2410
+ var mcpServerEntry = (root) => ({
2411
+ command: "npx",
2412
+ args: ["ak-docs", "mcp"],
2413
+ cwd: root
2414
+ });
2415
+ var readJson = (path) => {
2416
+ if (!existsSync9(path)) return {};
2417
+ try {
2418
+ const parsed = JSON.parse(readFileSync13(path, "utf8"));
2419
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
2420
+ } catch {
2421
+ return {};
2422
+ }
2423
+ };
2424
+ var writeJson = (path, value) => {
2425
+ mkdirSync2(dirname4(path), { recursive: true });
2426
+ writeFileSync2(path, `${JSON.stringify(value, null, 2)}
2427
+ `, "utf8");
2428
+ };
2429
+ var resolveTargetPath = (target, root) => {
2430
+ if (target === "cursor") return resolve5(root, ".cursor", "mcp.json");
2431
+ return join13(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
2432
+ };
2433
+ var installMcpConfig = (root, target) => {
2434
+ const configPath = resolveTargetPath(target, root);
2435
+ const created = !existsSync9(configPath);
2436
+ const existing = readJson(configPath);
2437
+ const servers = existing.mcpServers && typeof existing.mcpServers === "object" && !Array.isArray(existing.mcpServers) ? { ...existing.mcpServers } : {};
2438
+ servers[SERVER_NAME] = mcpServerEntry(root);
2439
+ writeJson(configPath, { ...existing, mcpServers: servers });
2440
+ const nextSteps = target === "cursor" ? [
2441
+ "Restart Cursor or reload MCP servers",
2442
+ "Paste the doc-bridge skill into Cursor rules (see docs/skills/doc-bridge.md)",
2443
+ "Before editing packages/* call handoff.resolve"
2444
+ ] : [
2445
+ "Restart Claude Desktop",
2446
+ "Run ak-docs index after doc changes"
2447
+ ];
2448
+ return {
2449
+ ok: true,
2450
+ target,
2451
+ configPath,
2452
+ created,
2453
+ serverName: SERVER_NAME,
2454
+ nextSteps
2455
+ };
2456
+ };
2457
+ var mcpSnippet = (root) => JSON.stringify({ mcpServers: { [SERVER_NAME]: mcpServerEntry(root) } }, null, 2);
2458
+
2459
+ // src/doctor/badge.ts
2460
+ var doctorBadgeMetrics = (report) => {
2461
+ const { packages } = report.coverage;
2462
+ const handoffPct = packages.total > 0 ? Math.round(packages.withAgentDoc / packages.total * 100) : 0;
2463
+ const bridgePct = packages.total > 0 ? Math.round(packages.withHumanDoc / packages.total * 100) : 0;
2464
+ return {
2465
+ handoffPct,
2466
+ bridgePct,
2467
+ score: report.score,
2468
+ grade: report.grade,
2469
+ packages: packages.total
2470
+ };
2471
+ };
2472
+ var badgeColor = (pct) => {
2473
+ if (pct >= 80) return "2ea44f";
2474
+ if (pct >= 50) return "dbab09";
2475
+ return "cb2431";
2476
+ };
2477
+ var formatDoctorBadgeMarkdown = (metrics) => {
2478
+ const handoff = `handoff_coverage-${metrics.handoffPct}%25-${badgeColor(metrics.handoffPct)}`;
2479
+ const bridge = `human_bridge-${metrics.bridgePct}%25-${badgeColor(metrics.bridgePct)}`;
2480
+ return [
2481
+ `![handoff coverage](https://img.shields.io/badge/${handoff}?style=flat-square)`,
2482
+ `![human bridge](https://img.shields.io/badge/${bridge}?style=flat-square)`,
2483
+ `![doc-bridge score](https://img.shields.io/badge/doc--bridge_score-${metrics.score}%2F100-${badgeColor(metrics.score)}?style=flat-square)`
2484
+ ].join(" ");
2485
+ };
2486
+ var formatDoctorBadgeJson = (metrics) => JSON.stringify(
2487
+ {
2488
+ schemaVersion: 1,
2489
+ ...metrics,
2490
+ markdown: formatDoctorBadgeMarkdown(metrics),
2491
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2492
+ },
2493
+ null,
2494
+ 2
2495
+ );
2496
+
2497
+ // src/doctor/run-doctor.ts
2498
+ var gradeForScore = (score) => {
2499
+ if (score >= 90) return "A";
2500
+ if (score >= 75) return "B";
2501
+ if (score >= 60) return "C";
2502
+ if (score >= 40) return "D";
2503
+ return "F";
2504
+ };
2505
+ var agentDocPaths = (index) => {
2506
+ const paths = /* @__PURE__ */ new Set();
2507
+ for (const owner of Object.values(index.lookup?.ownership ?? {})) {
2508
+ if (owner.agentDoc) paths.add(owner.agentDoc);
2509
+ }
2510
+ for (const handoff of Object.values(index.handoffs ?? {})) {
2511
+ if (handoff.startHere) paths.add(handoff.startHere);
2512
+ }
2513
+ return paths;
2514
+ };
2515
+ var computeScore = (coverage) => {
2516
+ let score = 0;
2517
+ if (coverage.freshness.hasIndex) score += 15;
2518
+ if (coverage.freshness.ok) score += 15;
2519
+ const { total, withAgentDoc, withHumanDoc } = coverage.packages;
2520
+ if (total > 0) {
2521
+ score += Math.round(withAgentDoc / total * 35);
2522
+ score += Math.round(withHumanDoc / total * 20);
2523
+ } else if (coverage.agentDocs.indexed > 0) {
2524
+ score += 35;
2525
+ }
2526
+ if (coverage.gates.ok) score += 15;
2527
+ else {
2528
+ const passed = coverage.gates.results.filter((gate) => gate.ok).length;
2529
+ const totalGates = coverage.gates.results.length || 1;
2530
+ score += Math.round(passed / totalGates * 10);
2531
+ }
2532
+ return Math.min(100, Math.max(0, score));
2533
+ };
2534
+ var buildIssues = (coverage) => {
2535
+ const issues = [];
2536
+ if (!coverage.freshness.hasIndex) {
2537
+ issues.push({
2538
+ severity: "error",
2539
+ code: "index-missing",
2540
+ message: "No doc-bridge index found.",
2541
+ action: "ak-docs index"
2542
+ });
2543
+ } else if (!coverage.freshness.ok) {
2544
+ issues.push({
2545
+ severity: "error",
2546
+ code: "index-stale",
2547
+ message: coverage.freshness.message,
2548
+ action: "ak-docs index"
2549
+ });
2550
+ }
2551
+ for (const id of coverage.packages.missingAgentDoc) {
2552
+ issues.push({
2553
+ severity: "warn",
2554
+ code: "missing-agent-doc",
2555
+ message: `Package "${id}" has no dedicated agent doc.`,
2556
+ action: `ak-docs init --scaffold-workspaces # or edit docs/for-agents/packages/${id}.md`
2557
+ });
2558
+ }
2559
+ for (const id of coverage.packages.missingHumanDoc) {
2560
+ issues.push({
2561
+ severity: "info",
2562
+ code: "missing-human-doc",
2563
+ message: `Package "${id}" has no linked human guide.`,
2564
+ action: "ak-docs bootstrap agent-docs"
2565
+ });
2566
+ }
2567
+ for (const gate of coverage.gates.results.filter((result) => !result.ok)) {
2568
+ issues.push({
2569
+ severity: gate.id === "index-freshness" ? "error" : "warn",
2570
+ code: `gate-${gate.id}`,
2571
+ message: gate.message,
2572
+ action: gate.id === "index-freshness" ? "ak-docs index" : "ak-docs gate run"
2573
+ });
2574
+ }
2575
+ return issues;
2576
+ };
2577
+ var buildNextActions = (issues, coverage) => {
2578
+ const actions = /* @__PURE__ */ new Set();
2579
+ for (const issue of issues) {
2580
+ if (issue.action) actions.add(issue.action);
2581
+ }
2582
+ if (!coverage.freshness.hasIndex || !coverage.freshness.ok) {
2583
+ actions.add("ak-docs index");
2584
+ }
2585
+ if (coverage.packages.missingHumanDoc.length) {
2586
+ actions.add("ak-docs bootstrap agent-docs");
2587
+ }
2588
+ if (coverage.packages.total > 0) {
2589
+ const sample = coverage.packages.missingAgentDoc[0] ?? coverage.packages.missingHumanDoc[0];
2590
+ if (sample) actions.add(`ak-docs query package ${sample} --agent`);
2591
+ }
2592
+ if (!actions.size) {
2593
+ actions.add("ak-docs mcp install --cursor");
2594
+ actions.add("ak-docs gate run");
2595
+ }
2596
+ return [...actions].slice(0, 6);
2597
+ };
2598
+ var runDoctor = (root, config) => {
2599
+ let index;
2600
+ let hasIndex = true;
2601
+ let freshnessOk = false;
2602
+ let freshnessMessage = "Index is fresh";
2603
+ try {
2604
+ index = loadDocBridgeIndex(root, config);
2605
+ const next = buildDocBridgeIndex({ root, config, write: false }).index.contentHash;
2606
+ freshnessOk = index.contentHash === next;
2607
+ freshnessMessage = freshnessOk ? "Index is fresh" : "Index is stale. Run: ak-docs index";
2608
+ } catch (error) {
2609
+ if (error instanceof IndexNotFoundError) {
2610
+ hasIndex = false;
2611
+ freshnessOk = false;
2612
+ freshnessMessage = error.message;
2613
+ index = buildDocBridgeIndex({ root, config, write: false }).index;
2614
+ } else {
2615
+ throw error;
2616
+ }
2617
+ }
2618
+ const ownership = Object.entries(index.lookup?.ownership ?? {});
2619
+ const missingAgentDoc = ownership.filter(([, owner]) => !owner.agentDoc || owner.agentDoc === config.corpus.agent.index).map(([id]) => id);
2620
+ const missingHumanDoc = ownership.filter(([, owner]) => !owner.humanDoc).map(([id]) => id);
2621
+ const indexedPaths = new Set(index.knowledge.map((entry) => entry.path));
2622
+ const expectedAgentDocs = agentDocPaths(index);
2623
+ const unindexed = [...expectedAgentDocs].filter((path) => !indexedPaths.has(path));
2624
+ const corpusDocs = scanAgentCorpus(root, config).filter(
2625
+ (doc) => doc.path !== config.corpus.agent.index
2626
+ );
2627
+ const gates = runGates(root, config);
2628
+ const coverage = {
2629
+ packages: {
2630
+ total: ownership.length,
2631
+ withAgentDoc: ownership.length - missingAgentDoc.length,
2632
+ withHumanDoc: ownership.length - missingHumanDoc.length,
2633
+ missingAgentDoc,
2634
+ missingHumanDoc
2635
+ },
2636
+ agentDocs: {
2637
+ total: corpusDocs.length,
2638
+ indexed: corpusDocs.filter((doc) => indexedPaths.has(doc.path)).length,
2639
+ unindexed: corpusDocs.filter((doc) => !indexedPaths.has(doc.path)).map((doc) => doc.path)
2640
+ },
2641
+ freshness: {
2642
+ ok: freshnessOk,
2643
+ message: freshnessMessage,
2644
+ hasIndex
2645
+ },
2646
+ gates
2647
+ };
2648
+ const issues = buildIssues(coverage);
2649
+ const score = computeScore(coverage);
2650
+ const nextActions = buildNextActions(issues, coverage);
2651
+ const report = {
2652
+ ok: issues.every((issue) => issue.severity !== "error") && gates.ok,
2653
+ score,
2654
+ grade: gradeForScore(score),
2655
+ coverage,
2656
+ badge: { handoffPct: 0, bridgePct: 0, score, grade: gradeForScore(score), packages: 0 },
2657
+ issues,
2658
+ nextActions
2659
+ };
2660
+ return { ...report, badge: doctorBadgeMetrics(report) };
2661
+ };
2662
+ var formatDoctorText = (report) => {
2663
+ const { coverage } = report;
2664
+ const handoffPct = coverage.packages.total > 0 ? Math.round(coverage.packages.withAgentDoc / coverage.packages.total * 100) : 0;
2665
+ const humanPct = coverage.packages.total > 0 ? Math.round(coverage.packages.withHumanDoc / coverage.packages.total * 100) : 0;
2666
+ const lines = [
2667
+ "doc-bridge doctor",
2668
+ "\u2500".repeat(40),
2669
+ `Score: ${report.score}/100 (${report.grade})`,
2670
+ "",
2671
+ "Coverage",
2672
+ ` Packages: ${coverage.packages.total}`,
2673
+ ` Agent docs: ${coverage.packages.withAgentDoc}/${coverage.packages.total} (${handoffPct}% handoff-ready)`,
2674
+ ` Human guides: ${coverage.packages.withHumanDoc}/${coverage.packages.total} (${humanPct}% bridged)`,
2675
+ ` Corpus indexed: ${coverage.agentDocs.indexed}/${coverage.agentDocs.total} agent docs`,
2676
+ ` Index freshness: ${coverage.freshness.ok ? "fresh" : "stale or missing"}`,
2677
+ ` Gates: ${coverage.gates.results.filter((g) => g.ok).length}/${coverage.gates.results.length} passing`,
2678
+ ` Badge: handoff ${report.badge.handoffPct}% \xB7 bridge ${report.badge.bridgePct}%`
2679
+ ];
2680
+ if (coverage.packages.missingHumanDoc.length) {
2681
+ lines.push("", "Missing humanDoc (bridge gap)", ...coverage.packages.missingHumanDoc.map((id) => ` \u2022 ${id}`));
2682
+ }
2683
+ if (coverage.packages.missingAgentDoc.length) {
2684
+ lines.push("", "Missing agent doc", ...coverage.packages.missingAgentDoc.map((id) => ` \u2022 ${id}`));
2685
+ }
2686
+ if (report.issues.length) {
2687
+ lines.push("", "Issues");
2688
+ for (const issue of report.issues.slice(0, 8)) {
2689
+ const icon = issue.severity === "error" ? "\u2717" : issue.severity === "warn" ? "!" : "\xB7";
2690
+ lines.push(` ${icon} [${issue.code}] ${issue.message}`);
2691
+ }
2692
+ if (report.issues.length > 8) {
2693
+ lines.push(` \u2026 +${report.issues.length - 8} more`);
2694
+ }
2695
+ }
2696
+ lines.push("", "Next actions", ...report.nextActions.map((action) => ` \u2192 ${action}`));
2697
+ return lines;
2698
+ };
2699
+
2700
+ // src/index-builder/watch-index.ts
2701
+ import { existsSync as existsSync10, watch } from "fs";
2702
+ import { dirname as dirname5, resolve as resolve6 } from "path";
2703
+ var WATCH_PATTERN = /\.(md|mdx|json|ya?ml|mdc)$/i;
2704
+ var collectWatchRoots = (root, config, configPath) => {
2705
+ const roots = /* @__PURE__ */ new Set();
2706
+ roots.add(resolve6(root, config.corpus.agent.root));
2707
+ const humanSources = config.corpus.human ? Array.isArray(config.corpus.human) ? config.corpus.human : [config.corpus.human] : [];
2708
+ for (const source of humanSources) {
2709
+ const humanOpts = source.options ?? {};
2710
+ for (const key of ["contentDir", "docsDir", "root"]) {
2711
+ const value = humanOpts[key];
2712
+ if (typeof value === "string" && value.length) roots.add(resolve6(root, value));
2713
+ }
2714
+ }
2715
+ if (configPath) roots.add(dirname5(resolve6(configPath)));
2716
+ return [...roots].filter((dir) => existsSync10(dir));
2717
+ };
2718
+ var watchDocBridgeIndex = (opts) => {
2719
+ const debounceMs = opts.debounceMs ?? 350;
2720
+ let timer;
2721
+ let running = false;
2722
+ const rebuild = () => {
2723
+ if (timer) clearTimeout(timer);
2724
+ timer = setTimeout(() => {
2725
+ if (running) return;
2726
+ running = true;
2727
+ try {
2728
+ const result = buildDocBridgeIndex({ root: opts.root, config: opts.config });
2729
+ const handoffCount = Object.keys(result.index.handoffs ?? {}).length;
2730
+ const summary = {
2731
+ knowledgeCount: result.index.knowledge.length,
2732
+ handoffCount,
2733
+ hash: result.index.contentHash.slice(0, 8)
2734
+ };
2735
+ opts.onRebuild?.(summary);
2736
+ process.stdout.write(
2737
+ `[ak-docs] indexed ${summary.knowledgeCount} docs, ${summary.handoffCount} handoffs (${summary.hash}\u2026)
2738
+ `
2739
+ );
2740
+ } catch (error) {
2741
+ process.stderr.write(
2742
+ `[ak-docs] index failed: ${error instanceof Error ? error.message : String(error)}
2743
+ `
2744
+ );
2745
+ } finally {
2746
+ running = false;
2747
+ }
2748
+ }, debounceMs);
2749
+ };
2750
+ rebuild();
2751
+ for (const dir of collectWatchRoots(opts.root, opts.config, opts.configPath)) {
2752
+ watch(dir, { recursive: true }, (_event, filename) => {
2753
+ if (!filename || !WATCH_PATTERN.test(filename)) return;
2754
+ rebuild();
2755
+ });
2756
+ }
2757
+ const configDir = resolve6(opts.root);
2758
+ if (existsSync10(configDir)) {
2759
+ watch(configDir, (_event, filename) => {
2760
+ if (!filename || !/doc-bridge\.config/.test(filename)) return;
2761
+ rebuild();
2762
+ });
2763
+ }
2764
+ process.stdout.write(
2765
+ `[ak-docs] watching ${collectWatchRoots(opts.root, opts.config, opts.configPath).join(", ") || opts.root} (Ctrl+C to stop)
2766
+ `
2767
+ );
2768
+ return new Promise((resolvePromise) => {
2769
+ const onSignal = () => resolvePromise(0);
2770
+ process.once("SIGINT", onSignal);
2771
+ process.once("SIGTERM", onSignal);
2772
+ });
2773
+ };
2774
+
2775
+ // src/memory/github-pr.ts
2776
+ import { execFileSync, spawnSync } from "child_process";
2777
+ import { existsSync as existsSync11, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
2778
+ import { join as join15 } from "path";
2779
+ var run = (cmd, args, cwd) => {
2780
+ const result = spawnSync(cmd, [...args], { cwd, encoding: "utf8" });
2781
+ const out = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
2782
+ return { ok: result.status === 0, out };
2783
+ };
2784
+ var hasGh = () => run("gh", ["--version"], process.cwd()).ok;
2785
+ var slug = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
2786
+ var defaultPromotionDraftPath = (root) => join15(root, ".doc-bridge", "drafts", `memory-promotion-${slug()}.md`);
2787
+ var writePromotionDraft = (root, draft, path) => {
2788
+ const draftPath = path ?? defaultPromotionDraftPath(root);
2789
+ mkdirSync3(join15(root, ".doc-bridge", "drafts"), { recursive: true });
2790
+ writeFileSync3(draftPath, `${draft.body}
2791
+ `, "utf8");
2792
+ return draftPath;
2793
+ };
2794
+ var promoteMemoryToGithubPr = (root, draft, options = {}) => {
2795
+ if (!draft.ok && !options.force) {
2796
+ return {
2797
+ ok: false,
2798
+ dryRun: Boolean(options.dryRun),
2799
+ draftPath: "",
2800
+ branch: "",
2801
+ commands: [],
2802
+ message: "Safety scan blocked promotion. Fix findings or pass --force to draft anyway."
2803
+ };
2804
+ }
2805
+ const branch = options.branch ?? `doc-bridge/memory-promotion-${slug()}`;
2806
+ const draftPath = writePromotionDraft(root, draft);
2807
+ const relDraft = draftPath.startsWith(`${root}/`) ? draftPath.slice(root.length + 1) : draftPath;
2808
+ const commands = [
2809
+ `git checkout -b ${branch}`,
2810
+ `git add ${relDraft}`,
2811
+ `git commit -m "draft: doc-bridge memory promotion"`,
2812
+ `git push -u origin ${branch}`,
2813
+ `gh pr create --draft --title "${draft.title}" --body-file ${relDraft}${options.base ? ` --base ${options.base}` : ""}`
2814
+ ];
2815
+ if (options.dryRun) {
2816
+ return {
2817
+ ok: true,
2818
+ dryRun: true,
2819
+ draftPath,
2820
+ branch,
2821
+ commands,
2822
+ message: `Wrote draft to ${relDraft}. Run the printed git/gh commands to open a draft PR.`
2823
+ };
2824
+ }
2825
+ if (!existsSync11(join15(root, ".git"))) {
2826
+ return {
2827
+ ok: false,
2828
+ dryRun: false,
2829
+ draftPath,
2830
+ branch,
2831
+ commands,
2832
+ message: "Not a git repository. Commit the draft manually or run with --dry-run."
2833
+ };
2834
+ }
2835
+ if (!hasGh()) {
2836
+ return {
2837
+ ok: false,
2838
+ dryRun: false,
2839
+ draftPath,
2840
+ branch,
2841
+ commands,
2842
+ message: "GitHub CLI (gh) not found. Install gh or use --dry-run for local draft + commands."
2843
+ };
2844
+ }
2845
+ const auth = run("gh", ["auth", "status"], root);
2846
+ if (!auth.ok) {
2847
+ return {
2848
+ ok: false,
2849
+ dryRun: false,
2850
+ draftPath,
2851
+ branch,
2852
+ commands,
2853
+ message: `gh is not authenticated. Run: gh auth login
2854
+ ${auth.out}`
2855
+ };
2856
+ }
2857
+ const checkout = run("git", ["checkout", "-b", branch], root);
2858
+ if (!checkout.ok) {
2859
+ return {
2860
+ ok: false,
2861
+ dryRun: false,
2862
+ draftPath,
2863
+ branch,
2864
+ commands,
2865
+ message: `git checkout failed: ${checkout.out}`
2866
+ };
2867
+ }
2868
+ run("git", ["add", relDraft], root);
2869
+ const commit = run("git", ["commit", "-m", "draft: doc-bridge memory promotion"], root);
2870
+ if (!commit.ok) {
2871
+ return {
2872
+ ok: false,
2873
+ dryRun: false,
2874
+ draftPath,
2875
+ branch,
2876
+ commands,
2877
+ message: `git commit failed: ${commit.out}`
2878
+ };
2879
+ }
2880
+ const push = run("git", ["push", "-u", "origin", branch], root);
2881
+ if (!push.ok) {
2882
+ return {
2883
+ ok: false,
2884
+ dryRun: false,
2885
+ draftPath,
2886
+ branch,
2887
+ commands,
2888
+ message: `git push failed: ${push.out}`
2889
+ };
2890
+ }
2891
+ const prArgs = [
2892
+ "pr",
2893
+ "create",
2894
+ "--draft",
2895
+ "--title",
2896
+ draft.title,
2897
+ "--body-file",
2898
+ relDraft,
2899
+ ...options.base ? ["--base", options.base] : []
2900
+ ];
2901
+ let prUrl = "";
2902
+ try {
2903
+ prUrl = execFileSync("gh", prArgs, { cwd: root, encoding: "utf8" }).trim();
2904
+ } catch (error) {
2905
+ const message = error instanceof Error ? error.message : String(error);
2906
+ return {
2907
+ ok: false,
2908
+ dryRun: false,
2909
+ draftPath,
2910
+ branch,
2911
+ commands,
2912
+ message: `gh pr create failed: ${message}`
2913
+ };
2914
+ }
2915
+ return {
2916
+ ok: true,
2917
+ dryRun: false,
2918
+ draftPath,
2919
+ branch,
2920
+ prUrl,
2921
+ ...prUrl ? { previewUrl: prUrl } : {},
2922
+ commands,
2923
+ message: prUrl ? `Draft PR opened: ${prUrl}` : "Draft PR created."
2924
+ };
2925
+ };
2926
+
2303
2927
  // src/federation/llms.ts
2304
- import { existsSync as existsSync9, readFileSync as readFileSync13 } from "fs";
2305
- import { resolve as resolve5 } from "path";
2928
+ import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
2929
+ import { resolve as resolve7 } from "path";
2306
2930
  var tokenize2 = (value) => value.toLowerCase().split(/[^a-z0-9]+/).filter((token) => token.length >= 2);
2307
2931
  var scoreText = (query, text) => {
2308
2932
  const hay = text.toLowerCase();
@@ -2315,10 +2939,14 @@ var defaultFetchText = async (url) => {
2315
2939
  return res.text();
2316
2940
  };
2317
2941
  var sourceText = async (root, source, fetchText) => {
2318
- if (/^https?:\/\//.test(source)) return fetchText(source);
2319
- const path = resolve5(root, source);
2320
- if (!existsSync9(path)) throw new Error(`Federation source not found: ${source}`);
2321
- return readFileSync13(path, "utf8");
2942
+ try {
2943
+ if (/^https?:\/\//.test(source)) return await fetchText(source);
2944
+ const path = resolve7(root, source);
2945
+ if (!existsSync12(path)) return null;
2946
+ return readFileSync14(path, "utf8");
2947
+ } catch {
2948
+ return null;
2949
+ }
2322
2950
  };
2323
2951
  var sameOrigin = (base, target) => {
2324
2952
  if (!/^https?:\/\//.test(base) || !/^https?:\/\//.test(target)) return true;
@@ -2357,22 +2985,28 @@ var chunksFromMarkdown = (property, raw, sourceUrl) => {
2357
2985
  var loadFederatedChunks = async (root, config, options = {}) => {
2358
2986
  const fetchText = options.fetchText ?? defaultFetchText;
2359
2987
  const chunks = [];
2988
+ const warnings = [];
2360
2989
  for (const source of config.federation?.sources ?? []) {
2361
2990
  if (source.includeInRetriever === false || !source.llmsTxt) continue;
2362
2991
  const llms = await sourceText(root, source.llmsTxt, fetchText);
2992
+ if (!llms) {
2993
+ warnings.push(`federation source skipped (unavailable): ${source.id} \u2192 ${source.llmsTxt}`);
2994
+ continue;
2995
+ }
2363
2996
  chunks.push(...chunksFromMarkdown(source.id, llms, source.llmsTxt));
2364
2997
  const links = parseLlmsTxtLinks(llms);
2365
2998
  for (const link of links) {
2366
2999
  const url = !/^https?:\/\//.test(link.url) && source.rawBaseUrl ? `${source.rawBaseUrl.replace(/\/$/, "")}/${link.url.replace(/^\//, "")}` : link.url;
2367
3000
  if (!/\.(md|txt)(?:$|\?)/.test(url)) continue;
2368
3001
  if (!sameOrigin(source.llmsTxt, url)) continue;
2369
- try {
2370
- const raw = await sourceText(root, url, fetchText);
2371
- chunks.push(...chunksFromMarkdown(source.id, raw, url));
2372
- } catch {
2373
- }
3002
+ const raw = await sourceText(root, url, fetchText);
3003
+ if (raw) chunks.push(...chunksFromMarkdown(source.id, raw, url));
2374
3004
  }
2375
3005
  }
3006
+ if (warnings.length && process.stderr.isTTY) {
3007
+ for (const w of warnings) process.stderr.write(`${w}
3008
+ `);
3009
+ }
2376
3010
  return chunks;
2377
3011
  };
2378
3012
  var retrieveHybridChunks = async (root, config, index, query, options = {}) => {
@@ -2394,11 +3028,11 @@ var retrieveHybridChunks = async (root, config, index, query, options = {}) => {
2394
3028
  };
2395
3029
 
2396
3030
  // src/intelligence/rag.ts
2397
- import { readFileSync as readFileSync14 } from "fs";
2398
- import { join as join14 } from "path";
3031
+ import { readFileSync as readFileSync15 } from "fs";
3032
+ import { join as join17 } from "path";
2399
3033
 
2400
3034
  // src/intelligence/adapter.ts
2401
- import { join as join13 } from "path";
3035
+ import { join as join16 } from "path";
2402
3036
 
2403
3037
  // src/intelligence/peers.ts
2404
3038
  var PeerMissingError = class extends Error {
@@ -2513,7 +3147,7 @@ var resolveIntelligenceRuntime = async (config) => {
2513
3147
  }
2514
3148
  return { adapter, embed, provider, ...model ? { model } : {} };
2515
3149
  };
2516
- var defaultVectorStorePath = (root) => join13(root, ".doc-bridge", "vectors");
3150
+ var defaultVectorStorePath = (root) => join16(root, ".doc-bridge", "vectors");
2517
3151
 
2518
3152
  // src/intelligence/rag.ts
2519
3153
  var loadDocuments = (root, index, sources) => {
@@ -2521,10 +3155,10 @@ var loadDocuments = (root, index, sources) => {
2521
3155
  const docs = [];
2522
3156
  if (includeAgent) {
2523
3157
  for (const entry of index.knowledge) {
2524
- const abs = join14(root, entry.path);
3158
+ const abs = join17(root, entry.path);
2525
3159
  let content = "";
2526
3160
  try {
2527
- content = readFileSync14(abs, "utf8");
3161
+ content = readFileSync15(abs, "utf8");
2528
3162
  } catch {
2529
3163
  content = [entry.title, entry.description].filter(Boolean).join("\n\n");
2530
3164
  }
@@ -2542,7 +3176,7 @@ var createDocBridgeRag = async (root, config, index) => {
2542
3176
  const { embed } = await resolveIntelligenceRuntime(config);
2543
3177
  const ragMod = await importPeer("@agentskit/rag");
2544
3178
  const memoryMod = await importPeer("@agentskit/memory");
2545
- const storePath = typeof config.intelligence?.retriever?.options?.storePath === "string" ? join14(root, config.intelligence.retriever.options.storePath) : defaultVectorStorePath(root);
3179
+ const storePath = typeof config.intelligence?.retriever?.options?.storePath === "string" ? join17(root, config.intelligence.retriever.options.storePath) : defaultVectorStorePath(root);
2546
3180
  const store = memoryMod.fileVectorMemory({ path: storePath });
2547
3181
  const rag = ragMod.createRAG({
2548
3182
  embed,
@@ -2675,17 +3309,140 @@ var startInkChat = async (root, config, index) => {
2675
3309
  throw wrapIntelligenceError(error);
2676
3310
  }
2677
3311
  };
3312
+
3313
+ // src/playbook/doc-bridge-pattern.ts
3314
+ var DOC_BRIDGE_PATTERN_ID = "doc-bridge-pattern";
3315
+ var DOC_BRIDGE_PATTERN_META = {
3316
+ id: DOC_BRIDGE_PATTERN_ID,
3317
+ title: "Doc Bridge Pattern",
3318
+ slug: "pillars/ai-collaboration/doc-bridge-pattern",
3319
+ license: "CC-BY-4.0",
3320
+ visibility: "public",
3321
+ playbookUrl: "https://playbook.agentskit.io/patterns/doc-bridge-pattern",
3322
+ npmPackage: "@agentskit/doc-bridge",
3323
+ cli: "ak-docs"
3324
+ };
3325
+ var docBridgePatternMarkdown = () => `---
3326
+ type: pattern
3327
+ id: ${DOC_BRIDGE_PATTERN_ID}
3328
+ purpose: Route coding agents to the correct package, checks, and human docs in any monorepo.
3329
+ owner: AgentsKit
3330
+ license: CC-BY-4.0
3331
+ visibility: public
3332
+ tags: [agents, documentation, monorepo, handoff, mcp]
3333
+ ---
3334
+
3335
+ # Doc Bridge Pattern
3336
+
3337
+ **AgentHandoff for your monorepo** \u2014 deterministic routing so agents edit the right roots, run the right checks, and stay linked to human documentation.
3338
+
3339
+ ## Problem
3340
+
3341
+ Coding agents guess package ownership. They edit sibling modules, run repo-wide tests, and ship changes without a bridge to human-facing guides. Wikis and RAG explain concepts but weakly answer *where to act*.
3342
+
3343
+ ## Solution (three artifacts)
3344
+
3345
+ | Artifact | Role |
3346
+ |----------|------|
3347
+ | **AgentHandoff** | JSON handoff: \`startHere\`, \`editRoots\`, \`checks\`, \`humanDoc\`, \`bridge\` |
3348
+ | **DocBridgeIndex** | Deterministic index + \`contentHash\` for CI freshness gates |
3349
+ | **Self-describe** | \`llms.txt\`, \`capabilities.json\` for discovery |
3350
+
3351
+ ## Four loops
3352
+
3353
+ | Loop | Command | Outcome |
3354
+ |------|---------|---------|
3355
+ | **Act** | \`ak-docs query package <id> --agent\` | Agent edits only \`editRoots\` |
3356
+ | **Bridge** | \`ak-docs bootstrap agent-docs\` | Link agent corpus \u2194 human site |
3357
+ | **Learn** | \`ak-docs memory promote --pr\` | HITL draft PR from agent memory |
3358
+ | **Explain** | \`ak-docs ask "<question>"\` | Local consult + handoff preview |
3359
+
3360
+ ## 60-second proof
3361
+
3362
+ \`\`\`bash
3363
+ npm i -D @agentskit/doc-bridge
3364
+ npx ak-docs demo --text
3365
+ ak-docs mcp install --cursor
3366
+ \`\`\`
3367
+
3368
+ ## AgentHandoff example
3369
+
3370
+ \`\`\`json
3371
+ {
3372
+ "type": "agent-handoff",
3373
+ "startHere": "docs/for-agents/packages/auth.md",
3374
+ "editRoots": ["packages/auth"],
3375
+ "checks": ["pnpm --filter @demo/auth test"],
3376
+ "humanDoc": "/docs/guides/auth",
3377
+ "bridge": { "humanDoc": "linked" }
3378
+ }
3379
+ \`\`\`
3380
+
3381
+ When \`humanDoc\` is missing, handoffs surface \`bridge.action: "ak-docs bootstrap agent-docs"\` \u2014 a feature, not a silent gap.
3382
+
3383
+ ## MCP contract
3384
+
3385
+ Agents call \`handoff.resolve\` before editing \`packages/*\`:
3386
+
3387
+ 1. Read \`startHere\`
3388
+ 2. Stay inside \`editRoots\`
3389
+ 3. Run every \`checks\` command
3390
+ 4. Link \`humanDoc\` or escalate missing bridge
3391
+
3392
+ ## CI gate
3393
+
3394
+ \`\`\`yaml
3395
+ - uses: AgentsKit-io/doc-bridge@v1.0.0
3396
+ \`\`\`
3397
+
3398
+ Or: \`ak-docs index && ak-docs gate run\` \u2014 stale index fails the PR.
3399
+
3400
+ ## Coverage metric
3401
+
3402
+ \`\`\`bash
3403
+ ak-docs doctor --text
3404
+ ak-docs doctor --badge
3405
+ \`\`\`
3406
+
3407
+ Teams track handoff % and human-bridge % daily.
3408
+
3409
+ ## When to use
3410
+
3411
+ - pnpm/npm monorepos with real package ownership
3412
+ - Fumadocus / Docusaurus human sites + dense agent corpus
3413
+ - Cursor / Claude / Codex agents that should not guess edit roots
3414
+
3415
+ ## When not to use
3416
+
3417
+ - Single-file repos with no ownership boundaries (AGENTS.md may suffice)
3418
+ - Hosted doc chat as primary product (doc-bridge is routing + bridge, not SaaS chat)
3419
+
3420
+ ## References
3421
+
3422
+ - npm: https://www.npmjs.com/package/@agentskit/doc-bridge
3423
+ - repo: https://github.com/AgentsKit-io/doc-bridge
3424
+ - skill: https://github.com/AgentsKit-io/doc-bridge/blob/master/docs/skills/doc-bridge.md
3425
+ - landing: https://agentskit-io.github.io/doc-bridge/
3426
+ `;
3427
+ var docBridgePatternPayload = () => ({
3428
+ ...DOC_BRIDGE_PATTERN_META,
3429
+ format: "okf-pattern-v1",
3430
+ body: docBridgePatternMarkdown()
3431
+ });
2678
3432
  export {
2679
3433
  AgentHandoffLegacySchema,
2680
3434
  AgentHandoffV1JsonSchema,
2681
3435
  AgentHandoffV1Schema,
2682
3436
  AgentSearchV1Schema,
2683
3437
  ConfigNotFoundError,
3438
+ DOC_BRIDGE_PATTERN_ID,
3439
+ DOC_BRIDGE_PATTERN_META,
2684
3440
  DocBridgeConfigV1Schema,
2685
3441
  DocBridgeIndexV1JsonSchema,
2686
3442
  DocBridgeIndexV1Schema,
2687
3443
  DocBridgeJsonSchemas,
2688
3444
  HANDOFF_SCHEMA_VERSION,
3445
+ HandoffBridgeSchema,
2689
3446
  HandoffTargetTypeSchema,
2690
3447
  INDEX_SCHEMA_VERSION,
2691
3448
  IndexNotFoundError,
@@ -2704,17 +3461,26 @@ export {
2704
3461
  collectPackages,
2705
3462
  createDocBridgeRag,
2706
3463
  createDocBridgeRetriever,
3464
+ defaultPromotionDraftPath,
2707
3465
  defineConfig,
3466
+ docBridgePatternMarkdown,
3467
+ docBridgePatternPayload,
3468
+ doctorBadgeMetrics,
2708
3469
  draftMemoryPromotion,
3470
+ formatDoctorBadgeJson,
3471
+ formatDoctorBadgeMarkdown,
3472
+ formatDoctorText,
2709
3473
  handleMcpRequest,
2710
3474
  indexFilePath,
2711
3475
  ingestAgentMemory,
2712
3476
  ingestCursorRules,
2713
3477
  ingestMemoryCandidates,
3478
+ installMcpConfig,
2714
3479
  layer1InstallHint,
2715
3480
  loadConfig,
2716
3481
  loadDocBridgeIndex,
2717
3482
  loadFederatedChunks,
3483
+ mcpSnippet,
2718
3484
  normalizeAgentHandoff,
2719
3485
  parseAgentHandoff,
2720
3486
  parseAgentSearch,
@@ -2723,12 +3489,14 @@ export {
2723
3489
  parseLlmsTxtLinks,
2724
3490
  parseMemoryCandidate,
2725
3491
  projectRootFromConfigPath,
3492
+ promoteMemoryToGithubPr,
2726
3493
  resolveGateIds,
2727
3494
  resolveProjectRoot,
2728
3495
  resolveRoot,
2729
3496
  retrieveDocBridgeChunks,
2730
3497
  retrieveHybridChunks,
2731
3498
  runChatOnce,
3499
+ runDoctor,
2732
3500
  runGate,
2733
3501
  runGates,
2734
3502
  runQuery,
@@ -2739,6 +3507,8 @@ export {
2739
3507
  searchIndex,
2740
3508
  sha256NormalizedV1,
2741
3509
  startInkChat,
2742
- startMcpStdioServer
3510
+ startMcpStdioServer,
3511
+ watchDocBridgeIndex,
3512
+ writePromotionDraft
2743
3513
  };
2744
3514
  //# sourceMappingURL=index.js.map