@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.
- package/CHANGELOG.md +59 -0
- package/README.md +139 -57
- package/action.yml +78 -0
- package/dist/cli/program.js +1122 -114
- package/dist/cli/program.js.map +1 -1
- package/dist/index.d.ts +258 -28
- package/dist/index.js +824 -54
- package/dist/index.js.map +1 -1
- package/docs/DOGFOOD-ROUND2.md +142 -0
- package/docs/DOGFOOD-ROUND3.md +74 -0
- package/docs/POSITIONING.md +2 -0
- package/docs/RELEASE.md +5 -3
- package/docs/chat-and-rag.md +2 -0
- package/docs/getting-started.md +14 -1
- package/docs/landing/index.html +299 -0
- package/docs/mcp.md +10 -1
- package/docs/ollama-demo.md +64 -0
- package/docs/playbook/doc-bridge-pattern.md +114 -0
- package/docs/recipes/index-pipeline.md +89 -0
- package/docs/skills/doc-bridge.md +64 -0
- package/docs/spec/cli.md +6 -0
- package/docs/spec/playbook-feedback.md +12 -4
- package/examples/demo-example/doc-bridge.config.json +23 -0
- package/examples/demo-example/docs/for-agents/INDEX.md +3 -0
- package/examples/demo-example/docs/for-agents/packages/example.md +14 -0
- package/examples/demo-example/package.json +7 -0
- package/examples/demo-example/src/.gitkeep +0 -0
- package/examples/demo-monorepo/doc-bridge.config.json +37 -0
- package/examples/demo-monorepo/docs/for-agents/INDEX.md +4 -0
- package/examples/demo-monorepo/docs/for-agents/packages/auth.md +22 -0
- package/examples/demo-monorepo/docs/for-agents/packages/billing.md +19 -0
- package/examples/demo-monorepo/docs/human/guides/auth.md +7 -0
- package/examples/demo-monorepo/package.json +7 -0
- package/examples/demo-monorepo/packages/auth/package.json +8 -0
- package/examples/demo-monorepo/packages/billing/package.json +7 -0
- package/examples/demo-monorepo/pnpm-workspace.yaml +2 -0
- package/examples/ollama-chat.config.ts +42 -0
- package/package.json +12 -9
- package/src/cli/demo.ts +143 -0
- package/src/cli/program.ts +220 -26
- package/src/doctor/badge.ts +53 -0
- package/src/doctor/run-doctor.ts +286 -0
- package/src/federation/llms.ts +20 -11
- package/src/index-builder/build-handoffs.ts +35 -2
- package/src/index-builder/scan-corpus.ts +5 -1
- package/src/index-builder/watch-index.ts +94 -0
- package/src/index.ts +24 -0
- package/src/lib/markdown.ts +31 -4
- package/src/mcp/install.ts +84 -0
- package/src/memory/github-pr.ts +190 -0
- package/src/playbook/doc-bridge-pattern.ts +121 -0
- package/src/query/query.ts +19 -1
- package/src/query/search.ts +85 -17
- package/src/schemas/agent-handoff.ts +11 -0
- package/src/schemas/doc-bridge-index.ts +3 -1
- package/src/schemas/json-schemas.ts +2 -1
- package/src/version.ts +1 -1
package/dist/cli/program.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/cli/program.ts
|
|
2
|
-
import { existsSync as
|
|
3
|
-
import { dirname as
|
|
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
|
|
@@ -469,7 +469,7 @@ var firstHeading = (markdown) => {
|
|
|
469
469
|
}
|
|
470
470
|
return void 0;
|
|
471
471
|
};
|
|
472
|
-
var firstParagraph = (markdown) => {
|
|
472
|
+
var firstParagraph = (markdown, maxLen = 400) => {
|
|
473
473
|
const { body } = parseFrontmatter(markdown);
|
|
474
474
|
const lines = body.split("\n");
|
|
475
475
|
const buf = [];
|
|
@@ -481,11 +481,27 @@ var firstParagraph = (markdown) => {
|
|
|
481
481
|
}
|
|
482
482
|
if (t.startsWith("#")) continue;
|
|
483
483
|
if (t.startsWith("---")) continue;
|
|
484
|
+
if (t.startsWith("```")) break;
|
|
485
|
+
if (t.startsWith("|") || t.startsWith("- [") || t.startsWith("* [")) {
|
|
486
|
+
if (buf.length) break;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
484
489
|
buf.push(t);
|
|
485
|
-
if (buf.join(" ").length
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
|
|
490
|
+
if (buf.join(" ").length >= maxLen) break;
|
|
491
|
+
}
|
|
492
|
+
let text = buf.join(" ").replace(/\s+/g, " ").trim();
|
|
493
|
+
if (!text) return void 0;
|
|
494
|
+
if (text.length <= maxLen) return text;
|
|
495
|
+
const sliced = text.slice(0, maxLen);
|
|
496
|
+
const sentenceEnd = Math.max(sliced.lastIndexOf(". "), sliced.lastIndexOf("! "), sliced.lastIndexOf("? "));
|
|
497
|
+
if (sentenceEnd > maxLen * 0.4) return sliced.slice(0, sentenceEnd + 1).trim();
|
|
498
|
+
const wordEnd = sliced.lastIndexOf(" ");
|
|
499
|
+
return (wordEnd > 0 ? sliced.slice(0, wordEnd) : sliced).trim();
|
|
500
|
+
};
|
|
501
|
+
var extractSearchBody = (markdown, maxLen = 6e3) => {
|
|
502
|
+
const { body } = parseFrontmatter(markdown);
|
|
503
|
+
const text = body.replace(/```[\s\S]*?```/g, " ").replace(/!\[[^\]]*\]\([^)]+\)/g, " ").replace(/\[[^\]]*\]\([^)]+\)/g, " ").replace(/^#+\s+/gm, "").replace(/[|>*_`#]/g, " ").replace(/\s+/g, " ").trim();
|
|
504
|
+
return text.length > maxLen ? text.slice(0, maxLen) : text;
|
|
489
505
|
};
|
|
490
506
|
var slugFromPath = (relPath) => {
|
|
491
507
|
const base = relPath.replace(/\.mdx?$/, "");
|
|
@@ -543,7 +559,9 @@ var scanAgentCorpus = (root, config) => {
|
|
|
543
559
|
const { data: frontmatter } = parseFrontmatter(raw);
|
|
544
560
|
const id = frontmatterString(frontmatter, "id") ?? frontmatterString(frontmatter, "package") ?? slugFromPath(relToCorpus);
|
|
545
561
|
const title = firstHeading(raw) ?? id;
|
|
546
|
-
const
|
|
562
|
+
const purpose = frontmatterString(frontmatter, "purpose");
|
|
563
|
+
const description = purpose ?? firstParagraph(raw, 400);
|
|
564
|
+
const body = extractSearchBody(raw);
|
|
547
565
|
return {
|
|
548
566
|
id,
|
|
549
567
|
type: "agent-doc",
|
|
@@ -552,7 +570,8 @@ var scanAgentCorpus = (root, config) => {
|
|
|
552
570
|
absPath: abs,
|
|
553
571
|
relPath,
|
|
554
572
|
frontmatter,
|
|
555
|
-
...description ? { description } : {}
|
|
573
|
+
...description ? { description } : {},
|
|
574
|
+
...body ? { body } : {}
|
|
556
575
|
};
|
|
557
576
|
});
|
|
558
577
|
};
|
|
@@ -685,11 +704,19 @@ var resolveHumanDoc = (packageId, override, fmHuman, humanDocs = {}) => {
|
|
|
685
704
|
packageId,
|
|
686
705
|
packageId.replace(/^@[^/]+\//, ""),
|
|
687
706
|
packageId.replace(/^os-/, ""),
|
|
688
|
-
packageId.replace(/-pattern$/, "")
|
|
707
|
+
packageId.replace(/-pattern$/, ""),
|
|
708
|
+
`packages/${packageId}`,
|
|
709
|
+
`reference/packages/${packageId}`,
|
|
710
|
+
`packages/${packageId}/index`
|
|
689
711
|
];
|
|
690
712
|
for (const alias of aliases) {
|
|
691
713
|
if (humanDocs[alias]) return humanDocs[alias];
|
|
692
714
|
}
|
|
715
|
+
for (const [key, url] of Object.entries(humanDocs)) {
|
|
716
|
+
if (key === packageId || key.endsWith(`/${packageId}`) || key.endsWith(`/${packageId}/index`) || key.endsWith(`-${packageId}`)) {
|
|
717
|
+
return url;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
693
720
|
return void 0;
|
|
694
721
|
};
|
|
695
722
|
var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root = process.cwd()) => {
|
|
@@ -729,6 +756,11 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
|
|
|
729
756
|
...agentDoc ? { agentDoc } : {}
|
|
730
757
|
};
|
|
731
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;
|
|
732
764
|
handoffs[pkg.id] = {
|
|
733
765
|
type: "agent-handoff",
|
|
734
766
|
schemaVersion: 1,
|
|
@@ -747,7 +779,11 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
|
|
|
747
779
|
editRoots: [record.path],
|
|
748
780
|
checks: [...record.checks],
|
|
749
781
|
...record.humanDoc ? { humanDoc: record.humanDoc } : {},
|
|
750
|
-
|
|
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
|
+
]
|
|
751
787
|
};
|
|
752
788
|
}
|
|
753
789
|
const intents = {};
|
|
@@ -775,7 +811,7 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
|
|
|
775
811
|
};
|
|
776
812
|
|
|
777
813
|
// src/version.ts
|
|
778
|
-
var PACKAGE_VERSION = "
|
|
814
|
+
var PACKAGE_VERSION = "1.0.0";
|
|
779
815
|
|
|
780
816
|
// src/index-builder/capabilities.ts
|
|
781
817
|
var renderCapabilitiesJson = (config, index, paths) => {
|
|
@@ -874,10 +910,10 @@ var docId = (relToHumanRoot, raw) => {
|
|
|
874
910
|
return meta.package ?? meta.module ?? meta.id ?? slugFromPath(relToHumanRoot);
|
|
875
911
|
};
|
|
876
912
|
var routeSlug = (relToHumanRoot, opts) => relToHumanRoot.split("/").filter((part) => !opts?.stripGroups || !/^\(.+\)$/.test(part)).join("/").replace(/(?:^|\/)index\.mdx?$/, "").replace(/\.mdx?$/, "");
|
|
877
|
-
var humanUrl = (
|
|
878
|
-
if (typeof urlPrefix !== "string" || !urlPrefix) return
|
|
913
|
+
var humanUrl = (slug2, urlPrefix) => {
|
|
914
|
+
if (typeof urlPrefix !== "string" || !urlPrefix) return slug2;
|
|
879
915
|
const prefix = urlPrefix.replace(/\/$/, "");
|
|
880
|
-
return
|
|
916
|
+
return slug2 ? `${prefix}/${slug2.replace(/^\//, "")}` : prefix;
|
|
881
917
|
};
|
|
882
918
|
var scanMarkdownDocs = (root, humanRoot, options) => {
|
|
883
919
|
const out = [];
|
|
@@ -908,9 +944,9 @@ var docusaurusFileSlug = (relPath) => {
|
|
|
908
944
|
return routeSlug(relPath);
|
|
909
945
|
};
|
|
910
946
|
var docusaurusSlug = (relPath, raw) => {
|
|
911
|
-
const
|
|
912
|
-
if (!
|
|
913
|
-
return
|
|
947
|
+
const slug2 = parseFrontmatter2(raw).slug;
|
|
948
|
+
if (!slug2) return docusaurusFileSlug(relPath);
|
|
949
|
+
return slug2.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "");
|
|
914
950
|
};
|
|
915
951
|
var docusaurusSidebarId = (relPath, raw) => {
|
|
916
952
|
const id = parseFrontmatter2(raw).id;
|
|
@@ -1252,19 +1288,58 @@ import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
|
|
|
1252
1288
|
import { resolve as resolve3 } from "path";
|
|
1253
1289
|
|
|
1254
1290
|
// src/query/search.ts
|
|
1255
|
-
var tokenize = (value) => value.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2);
|
|
1291
|
+
var tokenize = (value) => value.toLowerCase().split(/[^a-z0-9@/_-]+/).filter((t) => t.length >= 2);
|
|
1292
|
+
var PACKAGE_INTENT = /\b(package|module|pkg|edit|change|where|owns?|ownership|handoff|start)\b/i;
|
|
1293
|
+
var scoreHay = (tokens, hay, weight = 1) => {
|
|
1294
|
+
let score = 0;
|
|
1295
|
+
for (const token of tokens) {
|
|
1296
|
+
if (!hay.includes(token)) continue;
|
|
1297
|
+
score += token.length * weight;
|
|
1298
|
+
if (new RegExp(`(?:^|[^a-z0-9])${token}(?:[^a-z0-9]|$)`).test(hay)) {
|
|
1299
|
+
score += token.length;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
return score;
|
|
1303
|
+
};
|
|
1304
|
+
var identityBoost = (id, path, tokens, term) => {
|
|
1305
|
+
const idLower = id.toLowerCase();
|
|
1306
|
+
const termLower = term.toLowerCase().trim();
|
|
1307
|
+
const base = path.split("/").pop()?.replace(/\.mdx?$/i, "").toLowerCase() ?? "";
|
|
1308
|
+
let boost = 0;
|
|
1309
|
+
if (idLower === termLower || base === termLower) boost += 200;
|
|
1310
|
+
if (tokens.length === 1 && (idLower === tokens[0] || base === tokens[0])) boost += 200;
|
|
1311
|
+
for (const token of tokens) {
|
|
1312
|
+
if (idLower === token) boost += 120;
|
|
1313
|
+
else if (idLower.startsWith(`${token}-`) || idLower.endsWith(`-${token}`)) boost += 40;
|
|
1314
|
+
else if (idLower.includes(token) && idLower.length <= token.length + 4) boost += 30;
|
|
1315
|
+
if (base === token) boost += 100;
|
|
1316
|
+
}
|
|
1317
|
+
if (tokens.includes(idLower)) boost += Math.max(0, 40 - idLower.length);
|
|
1318
|
+
return boost;
|
|
1319
|
+
};
|
|
1320
|
+
var preferOwnership = (term) => PACKAGE_INTENT.test(term) || /^(where|how).*(edit|change|package|module)/i.test(term);
|
|
1256
1321
|
var searchIndex = (index, term, limit = 20) => {
|
|
1257
1322
|
const tokens = tokenize(term);
|
|
1258
1323
|
if (!tokens.length) return [];
|
|
1259
|
-
const
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1324
|
+
const wantOwnership = preferOwnership(term);
|
|
1325
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
1326
|
+
const consider = (match) => {
|
|
1327
|
+
const key = match.path;
|
|
1328
|
+
const existing = byPath.get(key);
|
|
1329
|
+
if (!existing) {
|
|
1330
|
+
byPath.set(key, match);
|
|
1331
|
+
return;
|
|
1265
1332
|
}
|
|
1333
|
+
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;
|
|
1334
|
+
if (prefer) byPath.set(key, match);
|
|
1335
|
+
};
|
|
1336
|
+
for (const entry of index.knowledge) {
|
|
1337
|
+
const body = entry.body ?? "";
|
|
1338
|
+
const hay = `${entry.id} ${entry.title} ${entry.description ?? ""} ${entry.path} ${body}`.toLowerCase();
|
|
1339
|
+
let score = scoreHay(tokens, hay, 1);
|
|
1340
|
+
score += identityBoost(entry.id, entry.path, tokens, term);
|
|
1266
1341
|
if (score > 0) {
|
|
1267
|
-
|
|
1342
|
+
consider({
|
|
1268
1343
|
type: "knowledge",
|
|
1269
1344
|
id: entry.id,
|
|
1270
1345
|
path: entry.path,
|
|
@@ -1274,22 +1349,31 @@ var searchIndex = (index, term, limit = 20) => {
|
|
|
1274
1349
|
}
|
|
1275
1350
|
}
|
|
1276
1351
|
for (const [id, owner] of Object.entries(index.lookup?.ownership ?? {})) {
|
|
1277
|
-
const
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1352
|
+
const path = owner.agentDoc ?? owner.path;
|
|
1353
|
+
const hay = `${id} ${owner.path} ${owner.purpose ?? ""} ${owner.group ?? ""} ${owner.agentDoc ?? ""} ${owner.humanDoc ?? ""}`.toLowerCase();
|
|
1354
|
+
let score = scoreHay(tokens, hay, 2);
|
|
1355
|
+
score += identityBoost(id, path, tokens, term);
|
|
1356
|
+
if (wantOwnership) score += 25;
|
|
1357
|
+
score += 15;
|
|
1282
1358
|
if (score > 0) {
|
|
1283
|
-
|
|
1359
|
+
consider({
|
|
1284
1360
|
type: "ownership",
|
|
1285
1361
|
id,
|
|
1286
|
-
path
|
|
1362
|
+
path,
|
|
1287
1363
|
...owner.purpose ? { summary: owner.purpose } : {},
|
|
1288
1364
|
score
|
|
1289
1365
|
});
|
|
1290
1366
|
}
|
|
1291
1367
|
}
|
|
1292
|
-
return
|
|
1368
|
+
return [...byPath.values()].sort((a, b) => {
|
|
1369
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
1370
|
+
const aExact = tokens.includes(a.id.toLowerCase()) ? 1 : 0;
|
|
1371
|
+
const bExact = tokens.includes(b.id.toLowerCase()) ? 1 : 0;
|
|
1372
|
+
if (bExact !== aExact) return bExact - aExact;
|
|
1373
|
+
if (a.type === "ownership" && b.type !== "ownership") return -1;
|
|
1374
|
+
if (b.type === "ownership" && a.type !== "ownership") return 1;
|
|
1375
|
+
return a.id.localeCompare(b.id);
|
|
1376
|
+
}).slice(0, limit);
|
|
1293
1377
|
};
|
|
1294
1378
|
|
|
1295
1379
|
// src/retriever/doc-bridge-retriever.ts
|
|
@@ -1328,10 +1412,14 @@ var defaultFetchText = async (url) => {
|
|
|
1328
1412
|
return res.text();
|
|
1329
1413
|
};
|
|
1330
1414
|
var sourceText = async (root, source, fetchText) => {
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1415
|
+
try {
|
|
1416
|
+
if (/^https?:\/\//.test(source)) return await fetchText(source);
|
|
1417
|
+
const path = resolve3(root, source);
|
|
1418
|
+
if (!existsSync7(path)) return null;
|
|
1419
|
+
return readFileSync9(path, "utf8");
|
|
1420
|
+
} catch {
|
|
1421
|
+
return null;
|
|
1422
|
+
}
|
|
1335
1423
|
};
|
|
1336
1424
|
var sameOrigin = (base, target) => {
|
|
1337
1425
|
if (!/^https?:\/\//.test(base) || !/^https?:\/\//.test(target)) return true;
|
|
@@ -1370,22 +1458,28 @@ var chunksFromMarkdown = (property, raw, sourceUrl) => {
|
|
|
1370
1458
|
var loadFederatedChunks = async (root, config, options = {}) => {
|
|
1371
1459
|
const fetchText = options.fetchText ?? defaultFetchText;
|
|
1372
1460
|
const chunks = [];
|
|
1461
|
+
const warnings = [];
|
|
1373
1462
|
for (const source of config.federation?.sources ?? []) {
|
|
1374
1463
|
if (source.includeInRetriever === false || !source.llmsTxt) continue;
|
|
1375
1464
|
const llms = await sourceText(root, source.llmsTxt, fetchText);
|
|
1465
|
+
if (!llms) {
|
|
1466
|
+
warnings.push(`federation source skipped (unavailable): ${source.id} \u2192 ${source.llmsTxt}`);
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1376
1469
|
chunks.push(...chunksFromMarkdown(source.id, llms, source.llmsTxt));
|
|
1377
1470
|
const links = parseLlmsTxtLinks(llms);
|
|
1378
1471
|
for (const link of links) {
|
|
1379
1472
|
const url = !/^https?:\/\//.test(link.url) && source.rawBaseUrl ? `${source.rawBaseUrl.replace(/\/$/, "")}/${link.url.replace(/^\//, "")}` : link.url;
|
|
1380
1473
|
if (!/\.(md|txt)(?:$|\?)/.test(url)) continue;
|
|
1381
1474
|
if (!sameOrigin(source.llmsTxt, url)) continue;
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
chunks.push(...chunksFromMarkdown(source.id, raw, url));
|
|
1385
|
-
} catch {
|
|
1386
|
-
}
|
|
1475
|
+
const raw = await sourceText(root, url, fetchText);
|
|
1476
|
+
if (raw) chunks.push(...chunksFromMarkdown(source.id, raw, url));
|
|
1387
1477
|
}
|
|
1388
1478
|
}
|
|
1479
|
+
if (warnings.length && process.stderr.isTTY) {
|
|
1480
|
+
for (const w of warnings) process.stderr.write(`${w}
|
|
1481
|
+
`);
|
|
1482
|
+
}
|
|
1389
1483
|
return chunks;
|
|
1390
1484
|
};
|
|
1391
1485
|
var retrieveHybridChunks = async (root, config, index, query, options = {}) => {
|
|
@@ -1434,6 +1528,11 @@ var HandoffTargetSchema = z2.object({
|
|
|
1434
1528
|
group: z2.string().min(1).max(128).optional(),
|
|
1435
1529
|
layer: z2.string().min(1).max(32).optional()
|
|
1436
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();
|
|
1437
1536
|
var AgentHandoffV1Schema = z2.object({
|
|
1438
1537
|
type: z2.literal("agent-handoff"),
|
|
1439
1538
|
schemaVersion: z2.literal(HANDOFF_SCHEMA_VERSION).default(HANDOFF_SCHEMA_VERSION),
|
|
@@ -1444,6 +1543,7 @@ var AgentHandoffV1Schema = z2.object({
|
|
|
1444
1543
|
editRoots: z2.array(z2.string().min(1).max(512)).max(32),
|
|
1445
1544
|
checks: z2.array(z2.string().min(1).max(256)).max(32),
|
|
1446
1545
|
humanDoc: z2.string().min(1).max(512).nullable().optional(),
|
|
1546
|
+
bridge: HandoffBridgeSchema.optional(),
|
|
1447
1547
|
playbookPatterns: z2.array(z2.string().url()).max(16).optional(),
|
|
1448
1548
|
notes: z2.array(z2.string().min(1).max(1024)).max(16)
|
|
1449
1549
|
}).strict();
|
|
@@ -1485,7 +1585,9 @@ var KnowledgeEntrySchema = z3.object({
|
|
|
1485
1585
|
type: z3.string().min(1).max(128),
|
|
1486
1586
|
title: z3.string().min(1).max(256),
|
|
1487
1587
|
path: z3.string().min(1).max(512),
|
|
1488
|
-
description: z3.string().max(
|
|
1588
|
+
description: z3.string().max(2048).optional(),
|
|
1589
|
+
/** Flattened body excerpt for full-text search (not for display). */
|
|
1590
|
+
body: z3.string().max(8e3).optional(),
|
|
1489
1591
|
links: z3.array(z3.string().min(1).max(512)).max(64).optional(),
|
|
1490
1592
|
tags: z3.array(z3.string().min(1).max(64)).max(32).optional()
|
|
1491
1593
|
}).strict();
|
|
@@ -1802,6 +1904,11 @@ var handoffForPackage = (index, id, config) => {
|
|
|
1802
1904
|
if (fromIndex) return normalizeAgentHandoff(fromIndex);
|
|
1803
1905
|
const owner = index.lookup?.ownership?.[id];
|
|
1804
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;
|
|
1805
1912
|
return normalizeAgentHandoff({
|
|
1806
1913
|
type: "agent-handoff",
|
|
1807
1914
|
source: config.index?.outFile ?? ".doc-bridge/index.json",
|
|
@@ -1817,7 +1924,11 @@ var handoffForPackage = (index, id, config) => {
|
|
|
1817
1924
|
editRoots: [owner.path],
|
|
1818
1925
|
checks: [...owner.checks],
|
|
1819
1926
|
...owner.humanDoc ? { humanDoc: owner.humanDoc } : {},
|
|
1820
|
-
|
|
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
|
+
]
|
|
1821
1932
|
});
|
|
1822
1933
|
};
|
|
1823
1934
|
var runQuery = (index, config, req) => {
|
|
@@ -2290,9 +2401,752 @@ var draftMemoryPromotion = (classifications) => {
|
|
|
2290
2401
|
};
|
|
2291
2402
|
};
|
|
2292
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
|
+
``,
|
|
2654
|
+
``,
|
|
2655
|
+
`}?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
|
+
|
|
2293
3147
|
// src/mcp/server.ts
|
|
2294
|
-
import { readFileSync as
|
|
2295
|
-
import { relative, resolve as
|
|
3148
|
+
import { readFileSync as readFileSync16, realpathSync } from "fs";
|
|
3149
|
+
import { relative, resolve as resolve8 } from "path";
|
|
2296
3150
|
import { z as z5, ZodError } from "zod";
|
|
2297
3151
|
var MCP_TOOLS = [
|
|
2298
3152
|
{
|
|
@@ -2399,7 +3253,7 @@ var findDocPath = (index, args) => {
|
|
|
2399
3253
|
};
|
|
2400
3254
|
var resolveDocPath = (root, relPath) => {
|
|
2401
3255
|
const rootAbs = realpathSync.native(root);
|
|
2402
|
-
const unresolved =
|
|
3256
|
+
const unresolved = resolve8(rootAbs, relPath);
|
|
2403
3257
|
const unresolvedRel = relative(rootAbs, unresolved);
|
|
2404
3258
|
if (unresolvedRel.startsWith("..")) throw new Error("doc.get path escapes project root");
|
|
2405
3259
|
const abs = realpathSync.native(unresolved);
|
|
@@ -2437,7 +3291,7 @@ var handleMcpRequest = (ctx, request) => {
|
|
|
2437
3291
|
}
|
|
2438
3292
|
if (name === "doc.get") {
|
|
2439
3293
|
const relPath = findDocPath(index(), parseToolArgs("doc.get", DocGetArgsSchema, args));
|
|
2440
|
-
return textResult(
|
|
3294
|
+
return textResult(readFileSync16(resolveDocPath(ctx.root, relPath), "utf8"));
|
|
2441
3295
|
}
|
|
2442
3296
|
if (name === "gate.status") return textResult(runGates(ctx.root, ctx.config));
|
|
2443
3297
|
if (name === "retriever.query") {
|
|
@@ -2519,14 +3373,17 @@ var usage = `ak-docs \u2014 human\u2194agent documentation bridge (@agentskit/do
|
|
|
2519
3373
|
|
|
2520
3374
|
Core (no API key):
|
|
2521
3375
|
ak-docs init [--demo] [--scaffold-workspaces]
|
|
2522
|
-
ak-docs
|
|
3376
|
+
ak-docs demo [--fixture example|monorepo] [--text] [--in-project]
|
|
3377
|
+
ak-docs doctor [--text] [--badge] [--write-badge]
|
|
3378
|
+
ak-docs index [--watch]
|
|
2523
3379
|
ak-docs query <package|ownership|intent|change> <id> [--agent] [--text]
|
|
2524
3380
|
ak-docs search <term> [--agent] [--text]
|
|
2525
3381
|
ak-docs list <packages|intents|changes|knowledge> [--text]
|
|
2526
3382
|
ak-docs ask [question] local consult (no LLM)
|
|
2527
3383
|
ak-docs gate run [gate-id]
|
|
2528
3384
|
ak-docs mcp
|
|
2529
|
-
ak-docs
|
|
3385
|
+
ak-docs mcp install --cursor | --claude
|
|
3386
|
+
ak-docs memory ingest|classify|promote [--pr] [--dry-run]
|
|
2530
3387
|
ak-docs bootstrap agent-docs
|
|
2531
3388
|
ak-docs validate-config | validate-handoff <file>
|
|
2532
3389
|
|
|
@@ -2538,7 +3395,7 @@ Intelligence (optional AgentsKit peers):
|
|
|
2538
3395
|
Advanced / ecosystem:
|
|
2539
3396
|
ak-docs retrieve <query>
|
|
2540
3397
|
ak-docs registry topology
|
|
2541
|
-
ak-docs playbook draft
|
|
3398
|
+
ak-docs playbook draft | pattern [--text]
|
|
2542
3399
|
|
|
2543
3400
|
Global flags:
|
|
2544
3401
|
-h, --help --version
|
|
@@ -2579,6 +3436,8 @@ var parseArgs = (argv) => {
|
|
|
2579
3436
|
else if (positional[0] === "index") command = "index";
|
|
2580
3437
|
else if (positional[0] === "gate") command = "gate";
|
|
2581
3438
|
else if (positional[0] === "mcp") command = "mcp";
|
|
3439
|
+
else if (positional[0] === "doctor") command = "doctor";
|
|
3440
|
+
else if (positional[0] === "demo") command = "demo";
|
|
2582
3441
|
else if (positional[0] === "query") command = "query";
|
|
2583
3442
|
else if (positional[0] === "search") command = "search";
|
|
2584
3443
|
else if (positional[0] === "retrieve") command = "retrieve";
|
|
@@ -2588,7 +3447,7 @@ var parseArgs = (argv) => {
|
|
|
2588
3447
|
else if (positional[0] === "list") command = "list";
|
|
2589
3448
|
return { command, flags, configPath, positional };
|
|
2590
3449
|
};
|
|
2591
|
-
var
|
|
3450
|
+
var writeJson2 = (payload) => {
|
|
2592
3451
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}
|
|
2593
3452
|
`);
|
|
2594
3453
|
};
|
|
@@ -2614,53 +3473,77 @@ var writeTextQuery = (payload) => {
|
|
|
2614
3473
|
writeLines([
|
|
2615
3474
|
`Search: ${String(data.term ?? "")}`,
|
|
2616
3475
|
`Matches: ${String(data.count ?? matches.length)}`,
|
|
2617
|
-
...matches.map(
|
|
2618
|
-
(match) =>
|
|
2619
|
-
)
|
|
3476
|
+
...matches.length ? matches.map(
|
|
3477
|
+
(match) => formatSearchMatch(match)
|
|
3478
|
+
) : [" (none)"]
|
|
2620
3479
|
]);
|
|
2621
3480
|
return;
|
|
2622
3481
|
}
|
|
2623
3482
|
writeLines([textValue(result.data)]);
|
|
2624
3483
|
};
|
|
3484
|
+
var formatSearchMatch = (match) => {
|
|
3485
|
+
const summary = match.summary ? match.summary.replace(/\s+/g, " ").slice(0, 100) : "";
|
|
3486
|
+
const score = typeof match.score === "number" ? ` score=${match.score}` : "";
|
|
3487
|
+
return ` [${match.type}] ${match.id}${score}
|
|
3488
|
+
${match.path}${summary ? `
|
|
3489
|
+
${summary}` : ""}`;
|
|
3490
|
+
};
|
|
2625
3491
|
var writeTextSearch = (term, matches) => {
|
|
2626
3492
|
writeLines([
|
|
2627
3493
|
`Search: ${term}`,
|
|
2628
3494
|
`Matches: ${matches.length}`,
|
|
2629
|
-
...matches.map(
|
|
2630
|
-
(match) => [match.type, match.id, match.path, match.summary].filter(Boolean).join(" ")
|
|
2631
|
-
)
|
|
3495
|
+
...matches.length ? matches.map(formatSearchMatch) : [" (none)"]
|
|
2632
3496
|
]);
|
|
2633
3497
|
};
|
|
2634
|
-
var
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
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) => {
|
|
3517
|
+
const owner = matches.find((match) => match.type === "ownership") ?? matches.find((match) => Boolean(index.lookup?.ownership?.[match.id]));
|
|
3518
|
+
const best = owner ?? matches[0];
|
|
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";
|
|
2638
3521
|
writeLines([
|
|
2639
3522
|
`Question: ${question}`,
|
|
2640
3523
|
best ? `Best match: ${best.type} ${best.id} (${best.path})` : "Best match: none",
|
|
3524
|
+
...ownerId ? handoffSummaryLines(index, config, ownerId) : [],
|
|
2641
3525
|
"",
|
|
2642
3526
|
"Matches:",
|
|
2643
|
-
...matches.length ? matches.slice(0, 5).map(
|
|
2644
|
-
(match) => [match.type, match.id, match.path, match.summary].filter(Boolean).join(" ")
|
|
2645
|
-
) : ["No local matches. Try: ak-docs search <term>"],
|
|
3527
|
+
...matches.length ? matches.slice(0, 5).map(formatSearchMatch) : [" No local matches. Try: ak-docs search <term>"],
|
|
2646
3528
|
"",
|
|
2647
3529
|
"Next commands:",
|
|
2648
3530
|
...best ? [
|
|
2649
3531
|
`ak-docs search "${question}" --agent`,
|
|
2650
|
-
bestQuery
|
|
2651
|
-
|
|
3532
|
+
bestQuery,
|
|
3533
|
+
...ownerId ? [`ak-docs doctor --text`] : []
|
|
3534
|
+
] : ["ak-docs list knowledge --text", "ak-docs doctor --text"]
|
|
2652
3535
|
]);
|
|
2653
3536
|
};
|
|
2654
3537
|
var readIndexedDoc = (root, config, idOrPath) => {
|
|
2655
3538
|
const index = loadDocBridgeIndex(root, config);
|
|
2656
3539
|
const entry = index.knowledge.find((doc) => doc.id === idOrPath || doc.path === idOrPath);
|
|
2657
3540
|
if (!entry) throw new Error(`Unknown indexed doc "${idOrPath}". Try: search ${idOrPath}`);
|
|
2658
|
-
const abs =
|
|
2659
|
-
const rootAbs =
|
|
3541
|
+
const abs = resolve9(root, entry.path);
|
|
3542
|
+
const rootAbs = resolve9(root);
|
|
2660
3543
|
if (abs !== rootAbs && !abs.startsWith(`${rootAbs}/`)) {
|
|
2661
3544
|
throw new Error(`Indexed doc escapes project root: ${entry.path}`);
|
|
2662
3545
|
}
|
|
2663
|
-
return
|
|
3546
|
+
return readFileSync17(abs, "utf8");
|
|
2664
3547
|
};
|
|
2665
3548
|
var runAskRepl = async (root, config) => {
|
|
2666
3549
|
const index = loadDocBridgeIndex(root, config);
|
|
@@ -2679,12 +3562,12 @@ var runAskRepl = async (root, config) => {
|
|
|
2679
3562
|
process.stdout.write(`${readIndexedDoc(root, config, value).slice(0, 8e3)}
|
|
2680
3563
|
`);
|
|
2681
3564
|
} else if (command === "resolve") {
|
|
2682
|
-
|
|
3565
|
+
writeJson2(runQuery(index, config, { kind: "ownership", id: value, agent: true }));
|
|
2683
3566
|
} else if (command === "gate") {
|
|
2684
3567
|
const gateId = value || void 0;
|
|
2685
|
-
|
|
3568
|
+
writeJson2(runGates(root, config, gateId ? [gateId] : void 0));
|
|
2686
3569
|
} else {
|
|
2687
|
-
writeAsk(line, searchIndex(index, line, 8), index);
|
|
3570
|
+
writeAsk(line, searchIndex(index, line, 8), index, config);
|
|
2688
3571
|
}
|
|
2689
3572
|
} catch (error) {
|
|
2690
3573
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -2736,9 +3619,9 @@ var diagnosticNextCommands = (config, result) => {
|
|
|
2736
3619
|
];
|
|
2737
3620
|
};
|
|
2738
3621
|
var writeIfMissing = (path, contents) => {
|
|
2739
|
-
if (
|
|
2740
|
-
|
|
2741
|
-
|
|
3622
|
+
if (existsSync14(path)) return false;
|
|
3623
|
+
mkdirSync5(dirname7(path), { recursive: true });
|
|
3624
|
+
writeFileSync4(path, contents, "utf8");
|
|
2742
3625
|
return true;
|
|
2743
3626
|
};
|
|
2744
3627
|
var demoOwnership = {
|
|
@@ -2841,7 +3724,7 @@ var scaffoldWorkspaceDocs = (root, config) => {
|
|
|
2841
3724
|
const created = [];
|
|
2842
3725
|
const skipped = [];
|
|
2843
3726
|
for (const pkg of discoverPnpmPackages(root, config)) {
|
|
2844
|
-
const path =
|
|
3727
|
+
const path = resolve9(root, config.corpus.agent.root, "packages", `${pkg.id}.md`);
|
|
2845
3728
|
if (writeIfMissing(path, workspaceDocDraft(pkg.id, pkg.path))) created.push(path);
|
|
2846
3729
|
else skipped.push(path);
|
|
2847
3730
|
}
|
|
@@ -2851,11 +3734,11 @@ var bootstrapAgentDocs = (root, config) => {
|
|
|
2851
3734
|
const created = [];
|
|
2852
3735
|
const skipped = [];
|
|
2853
3736
|
for (const doc of scanHumanDocRecords(root, config)) {
|
|
2854
|
-
const raw =
|
|
3737
|
+
const raw = readFileSync17(doc.path, "utf8");
|
|
2855
3738
|
const body = raw.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
2856
3739
|
const title = firstHeading(body) ?? doc.id;
|
|
2857
3740
|
const description = firstParagraph(body);
|
|
2858
|
-
const draftPath =
|
|
3741
|
+
const draftPath = resolve9(root, config.corpus.agent.root, "human", `${doc.id}.md`);
|
|
2859
3742
|
const draft = [
|
|
2860
3743
|
"---",
|
|
2861
3744
|
"type: knowledge",
|
|
@@ -2904,7 +3787,7 @@ var runCli = (argv) => {
|
|
|
2904
3787
|
configPath ? { explicitPath: configPath } : {}
|
|
2905
3788
|
);
|
|
2906
3789
|
parseDocBridgeConfig(config);
|
|
2907
|
-
|
|
3790
|
+
writeJson2({ ok: true, path, schemaVersion: config.schemaVersion });
|
|
2908
3791
|
return 0;
|
|
2909
3792
|
} catch (error) {
|
|
2910
3793
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -2919,10 +3802,10 @@ var runCli = (argv) => {
|
|
|
2919
3802
|
return 1;
|
|
2920
3803
|
}
|
|
2921
3804
|
try {
|
|
2922
|
-
const abs =
|
|
2923
|
-
const raw =
|
|
3805
|
+
const abs = resolve9(file);
|
|
3806
|
+
const raw = readFileSync17(abs, "utf8");
|
|
2924
3807
|
const handoff = parseAgentHandoff(JSON.parse(raw));
|
|
2925
|
-
|
|
3808
|
+
writeJson2({ ok: true, handoff });
|
|
2926
3809
|
return 0;
|
|
2927
3810
|
} catch (error) {
|
|
2928
3811
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -2933,10 +3816,10 @@ var runCli = (argv) => {
|
|
|
2933
3816
|
if (command === "init") {
|
|
2934
3817
|
const root = process.cwd();
|
|
2935
3818
|
const withDemo = flags.has("--demo") || !flags.has("--no-demo");
|
|
2936
|
-
const configFile =
|
|
2937
|
-
const docsIndex =
|
|
2938
|
-
const exampleDoc =
|
|
2939
|
-
const agentsMd =
|
|
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");
|
|
2940
3823
|
const configWritten = writeIfMissing(configFile, initConfigContents(configFile, withDemo));
|
|
2941
3824
|
const indexWritten = writeIfMissing(
|
|
2942
3825
|
docsIndex,
|
|
@@ -2944,9 +3827,9 @@ var runCli = (argv) => {
|
|
|
2944
3827
|
);
|
|
2945
3828
|
const exampleWritten = withDemo ? writeIfMissing(exampleDoc, exampleAgentDoc) : false;
|
|
2946
3829
|
const agentsWritten = writeIfMissing(agentsMd, agentsMdSnippet);
|
|
2947
|
-
if (withDemo) writeIfMissing(
|
|
3830
|
+
if (withDemo) writeIfMissing(resolve9(root, "src/.gitkeep"), "");
|
|
2948
3831
|
const scaffold = flags.has("--scaffold-workspaces") ? scaffoldWorkspaceDocs(root, loadProject(configFile).config) : void 0;
|
|
2949
|
-
|
|
3832
|
+
writeJson2({
|
|
2950
3833
|
ok: true,
|
|
2951
3834
|
configPath: configFile,
|
|
2952
3835
|
demo: withDemo,
|
|
@@ -2970,7 +3853,7 @@ var runCli = (argv) => {
|
|
|
2970
3853
|
try {
|
|
2971
3854
|
const { config, root } = loadProject(configPath);
|
|
2972
3855
|
const result = bootstrapAgentDocs(root, config);
|
|
2973
|
-
|
|
3856
|
+
writeJson2({ ok: true, ...result });
|
|
2974
3857
|
return 0;
|
|
2975
3858
|
} catch (error) {
|
|
2976
3859
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -2980,24 +3863,43 @@ var runCli = (argv) => {
|
|
|
2980
3863
|
}
|
|
2981
3864
|
if (command === "memory") {
|
|
2982
3865
|
if (!["ingest", "classify", "promote"].includes(positional[1] ?? "")) {
|
|
2983
|
-
process.stderr.write(
|
|
3866
|
+
process.stderr.write(
|
|
3867
|
+
"Usage: ak-docs memory <ingest|classify|promote> [--pr] [--dry-run] [--force] [--config <path>]\n"
|
|
3868
|
+
);
|
|
2984
3869
|
return 1;
|
|
2985
3870
|
}
|
|
2986
3871
|
try {
|
|
2987
3872
|
const { config, root } = loadProject(configPath);
|
|
2988
3873
|
const candidates = ingestMemoryCandidates(root);
|
|
2989
3874
|
if (positional[1] === "ingest") {
|
|
2990
|
-
|
|
3875
|
+
writeJson2({ ok: true, count: candidates.length, candidates });
|
|
2991
3876
|
return 0;
|
|
2992
3877
|
}
|
|
2993
3878
|
const index = loadDocBridgeIndex(root, config);
|
|
2994
3879
|
const classifications = classifyMemoryCandidates(candidates, index);
|
|
2995
3880
|
if (positional[1] === "classify") {
|
|
2996
|
-
|
|
3881
|
+
writeJson2({ ok: true, count: classifications.length, classifications });
|
|
2997
3882
|
return 0;
|
|
2998
3883
|
}
|
|
2999
3884
|
const draft = draftMemoryPromotion(classifications);
|
|
3000
|
-
|
|
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);
|
|
3001
3903
|
return draft.ok ? 0 : 1;
|
|
3002
3904
|
} catch (error) {
|
|
3003
3905
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -3010,22 +3912,35 @@ var runCli = (argv) => {
|
|
|
3010
3912
|
process.stderr.write("Usage: ak-docs registry topology\n");
|
|
3011
3913
|
return 1;
|
|
3012
3914
|
}
|
|
3013
|
-
|
|
3915
|
+
writeJson2(registryTopology());
|
|
3014
3916
|
return 0;
|
|
3015
3917
|
}
|
|
3016
3918
|
if (command === "playbook") {
|
|
3017
|
-
|
|
3018
|
-
|
|
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");
|
|
3019
3932
|
return 1;
|
|
3020
3933
|
}
|
|
3021
3934
|
try {
|
|
3022
3935
|
const { config, root } = loadProject(configPath);
|
|
3023
3936
|
const index = loadDocBridgeIndex(root, config);
|
|
3024
3937
|
const draft = draftMemoryPromotion(classifyMemoryCandidates(ingestMemoryCandidates(root), index));
|
|
3025
|
-
|
|
3938
|
+
writeJson2({
|
|
3026
3939
|
...draft,
|
|
3027
3940
|
title: "Draft Playbook feedback promotion",
|
|
3028
|
-
pattern: "Doc Bridge Pattern"
|
|
3941
|
+
pattern: "Doc Bridge Pattern",
|
|
3942
|
+
patternDoc: "docs/playbook/doc-bridge-pattern.md",
|
|
3943
|
+
exportCommand: "ak-docs playbook pattern --text"
|
|
3029
3944
|
});
|
|
3030
3945
|
return 0;
|
|
3031
3946
|
} catch (error) {
|
|
@@ -3036,11 +3951,18 @@ var runCli = (argv) => {
|
|
|
3036
3951
|
}
|
|
3037
3952
|
if (command === "index") {
|
|
3038
3953
|
try {
|
|
3039
|
-
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
|
+
}
|
|
3040
3962
|
const result = buildDocBridgeIndex({ root, config });
|
|
3041
3963
|
const diagnostics = indexDiagnostics(config, result);
|
|
3042
3964
|
const handoffCount = Object.keys(result.index.handoffs ?? {}).length;
|
|
3043
|
-
|
|
3965
|
+
writeJson2({
|
|
3044
3966
|
ok: true,
|
|
3045
3967
|
indexPath: result.indexPath,
|
|
3046
3968
|
...result.llmsTxtPath ? { llmsTxtPath: result.llmsTxtPath } : {},
|
|
@@ -3076,7 +3998,7 @@ var runCli = (argv) => {
|
|
|
3076
3998
|
try {
|
|
3077
3999
|
const { config, root } = loadProject(configPath);
|
|
3078
4000
|
const result = runGates(root, config, gateId ? [gateId] : void 0);
|
|
3079
|
-
|
|
4001
|
+
writeJson2(result);
|
|
3080
4002
|
return result.ok ? 0 : 1;
|
|
3081
4003
|
} catch (error) {
|
|
3082
4004
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -3085,12 +4007,98 @@ var runCli = (argv) => {
|
|
|
3085
4007
|
}
|
|
3086
4008
|
}
|
|
3087
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
|
+
}
|
|
3088
4038
|
try {
|
|
3089
4039
|
const { config, root } = loadProject(configPath);
|
|
3090
4040
|
startMcpStdioServer({ root, config });
|
|
3091
4041
|
return void 0;
|
|
3092
4042
|
} catch (error) {
|
|
3093
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)}
|
|
3094
4102
|
`);
|
|
3095
4103
|
return 1;
|
|
3096
4104
|
}
|
|
@@ -3112,7 +4120,7 @@ var runCli = (argv) => {
|
|
|
3112
4120
|
const index = loadDocBridgeIndex(root, config);
|
|
3113
4121
|
const result = runQuery(index, config, { kind, id, agent: flags.has("--agent") });
|
|
3114
4122
|
if (wantsTextOutput(flags, config)) writeTextQuery(result);
|
|
3115
|
-
else
|
|
4123
|
+
else writeJson2(result);
|
|
3116
4124
|
return 0;
|
|
3117
4125
|
} catch (error) {
|
|
3118
4126
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -3131,11 +4139,11 @@ var runCli = (argv) => {
|
|
|
3131
4139
|
const index = loadDocBridgeIndex(root, config);
|
|
3132
4140
|
if (flags.has("--agent")) {
|
|
3133
4141
|
const result = runQuery(index, config, { kind: "search", term, agent: true });
|
|
3134
|
-
|
|
4142
|
+
writeJson2(result);
|
|
3135
4143
|
} else {
|
|
3136
4144
|
const matches = searchIndex(index, term);
|
|
3137
4145
|
if (wantsTextOutput(flags, config)) writeTextSearch(term, matches);
|
|
3138
|
-
else
|
|
4146
|
+
else writeJson2({ term, count: matches.length, matches });
|
|
3139
4147
|
}
|
|
3140
4148
|
return 0;
|
|
3141
4149
|
} catch (error) {
|
|
@@ -3159,7 +4167,7 @@ var runCli = (argv) => {
|
|
|
3159
4167
|
try {
|
|
3160
4168
|
const { config, root } = loadProject(configPath);
|
|
3161
4169
|
const index = loadDocBridgeIndex(root, config);
|
|
3162
|
-
|
|
4170
|
+
writeJson2({ query, chunks: await retrieveHybridChunks(root, config, index, query) });
|
|
3163
4171
|
return 0;
|
|
3164
4172
|
} catch (error) {
|
|
3165
4173
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -3181,7 +4189,7 @@ var runCli = (argv) => {
|
|
|
3181
4189
|
const rag = await createDocBridgeRag(root, config, index);
|
|
3182
4190
|
if (action === "ingest") {
|
|
3183
4191
|
const result = await rag.ingest();
|
|
3184
|
-
|
|
4192
|
+
writeJson2({ ok: true, ...result });
|
|
3185
4193
|
return 0;
|
|
3186
4194
|
}
|
|
3187
4195
|
const query = positional.slice(2).join(" ").trim();
|
|
@@ -3190,7 +4198,7 @@ var runCli = (argv) => {
|
|
|
3190
4198
|
return 1;
|
|
3191
4199
|
}
|
|
3192
4200
|
const hits = await rag.search(query);
|
|
3193
|
-
|
|
4201
|
+
writeJson2({ query, count: hits.length, hits });
|
|
3194
4202
|
return 0;
|
|
3195
4203
|
} catch (error) {
|
|
3196
4204
|
if (error instanceof PeerMissingError) {
|
|
@@ -3272,7 +4280,7 @@ Install peers: ${layer1InstallHint()}
|
|
|
3272
4280
|
return 1;
|
|
3273
4281
|
}
|
|
3274
4282
|
const index = loadDocBridgeIndex(root, config);
|
|
3275
|
-
writeAsk(question, searchIndex(index, question, 8), index);
|
|
4283
|
+
writeAsk(question, searchIndex(index, question, 8), index, config);
|
|
3276
4284
|
return 0;
|
|
3277
4285
|
} catch (error) {
|
|
3278
4286
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -3292,19 +4300,19 @@ Install peers: ${layer1InstallHint()}
|
|
|
3292
4300
|
if (kind === "packages") {
|
|
3293
4301
|
const items2 = index.lookup?.packages ?? [];
|
|
3294
4302
|
if (wantsTextOutput(flags, config)) writeLines(items2);
|
|
3295
|
-
else
|
|
4303
|
+
else writeJson2({ kind, items: items2 });
|
|
3296
4304
|
return 0;
|
|
3297
4305
|
}
|
|
3298
4306
|
if (kind === "intents") {
|
|
3299
4307
|
const items2 = Object.keys(index.lookup?.intents ?? {});
|
|
3300
4308
|
if (wantsTextOutput(flags, config)) writeLines(items2);
|
|
3301
|
-
else
|
|
4309
|
+
else writeJson2({ kind, items: items2 });
|
|
3302
4310
|
return 0;
|
|
3303
4311
|
}
|
|
3304
4312
|
if (kind === "changes") {
|
|
3305
4313
|
const items2 = Object.keys(index.lookup?.changes ?? {});
|
|
3306
4314
|
if (wantsTextOutput(flags, config)) writeLines(items2);
|
|
3307
|
-
else
|
|
4315
|
+
else writeJson2({ kind, items: items2 });
|
|
3308
4316
|
return 0;
|
|
3309
4317
|
}
|
|
3310
4318
|
const items = index.knowledge.map((entry) => ({
|
|
@@ -3315,7 +4323,7 @@ Install peers: ${layer1InstallHint()}
|
|
|
3315
4323
|
if (wantsTextOutput(flags, config)) {
|
|
3316
4324
|
writeLines(items.map((item) => [item.id, item.path, item.title].join(" ")));
|
|
3317
4325
|
} else {
|
|
3318
|
-
|
|
4326
|
+
writeJson2({ kind, items });
|
|
3319
4327
|
}
|
|
3320
4328
|
return 0;
|
|
3321
4329
|
} catch (error) {
|