@agentskit/doc-bridge 0.1.0-alpha.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/README.md +139 -57
- package/action.yml +78 -0
- package/dist/cli/program.js +987 -69
- package/dist/cli/program.js.map +1 -1
- package/dist/index.d.ts +238 -26
- package/dist/index.js +704 -22
- package/dist/index.js.map +1 -1
- 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 +5 -2
- package/src/cli/demo.ts +143 -0
- package/src/cli/program.ts +194 -14
- package/src/doctor/badge.ts +53 -0
- package/src/doctor/run-doctor.ts +286 -0
- package/src/index-builder/build-handoffs.ts +19 -1
- package/src/index-builder/watch-index.ts +94 -0
- package/src/index.ts +24 -0
- 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/schemas/agent-handoff.ts +11 -0
- 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
|
|
@@ -756,6 +756,11 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
|
|
|
756
756
|
...agentDoc ? { agentDoc } : {}
|
|
757
757
|
};
|
|
758
758
|
ownership[pkg.id] = record;
|
|
759
|
+
const bridge = record.humanDoc ? /^https?:\/\//.test(record.humanDoc) ? { humanDoc: "external" } : { humanDoc: "linked" } : config.corpus.human ? {
|
|
760
|
+
humanDoc: "missing",
|
|
761
|
+
action: "ak-docs bootstrap agent-docs",
|
|
762
|
+
bootstrap: `docs/for-agents/human/${pkg.id}.md`
|
|
763
|
+
} : void 0;
|
|
759
764
|
handoffs[pkg.id] = {
|
|
760
765
|
type: "agent-handoff",
|
|
761
766
|
schemaVersion: 1,
|
|
@@ -774,7 +779,11 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
|
|
|
774
779
|
editRoots: [record.path],
|
|
775
780
|
checks: [...record.checks],
|
|
776
781
|
...record.humanDoc ? { humanDoc: record.humanDoc } : {},
|
|
777
|
-
|
|
782
|
+
...bridge ? { bridge } : {},
|
|
783
|
+
notes: [
|
|
784
|
+
...record.purpose ? [record.purpose] : [],
|
|
785
|
+
...!record.humanDoc && config.corpus.human ? [`Human guide missing for ${pkg.id}. Run: ak-docs bootstrap agent-docs`] : []
|
|
786
|
+
]
|
|
778
787
|
};
|
|
779
788
|
}
|
|
780
789
|
const intents = {};
|
|
@@ -802,7 +811,7 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
|
|
|
802
811
|
};
|
|
803
812
|
|
|
804
813
|
// src/version.ts
|
|
805
|
-
var PACKAGE_VERSION = "
|
|
814
|
+
var PACKAGE_VERSION = "1.0.0";
|
|
806
815
|
|
|
807
816
|
// src/index-builder/capabilities.ts
|
|
808
817
|
var renderCapabilitiesJson = (config, index, paths) => {
|
|
@@ -901,10 +910,10 @@ var docId = (relToHumanRoot, raw) => {
|
|
|
901
910
|
return meta.package ?? meta.module ?? meta.id ?? slugFromPath(relToHumanRoot);
|
|
902
911
|
};
|
|
903
912
|
var routeSlug = (relToHumanRoot, opts) => relToHumanRoot.split("/").filter((part) => !opts?.stripGroups || !/^\(.+\)$/.test(part)).join("/").replace(/(?:^|\/)index\.mdx?$/, "").replace(/\.mdx?$/, "");
|
|
904
|
-
var humanUrl = (
|
|
905
|
-
if (typeof urlPrefix !== "string" || !urlPrefix) return
|
|
913
|
+
var humanUrl = (slug2, urlPrefix) => {
|
|
914
|
+
if (typeof urlPrefix !== "string" || !urlPrefix) return slug2;
|
|
906
915
|
const prefix = urlPrefix.replace(/\/$/, "");
|
|
907
|
-
return
|
|
916
|
+
return slug2 ? `${prefix}/${slug2.replace(/^\//, "")}` : prefix;
|
|
908
917
|
};
|
|
909
918
|
var scanMarkdownDocs = (root, humanRoot, options) => {
|
|
910
919
|
const out = [];
|
|
@@ -935,9 +944,9 @@ var docusaurusFileSlug = (relPath) => {
|
|
|
935
944
|
return routeSlug(relPath);
|
|
936
945
|
};
|
|
937
946
|
var docusaurusSlug = (relPath, raw) => {
|
|
938
|
-
const
|
|
939
|
-
if (!
|
|
940
|
-
return
|
|
947
|
+
const slug2 = parseFrontmatter2(raw).slug;
|
|
948
|
+
if (!slug2) return docusaurusFileSlug(relPath);
|
|
949
|
+
return slug2.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "");
|
|
941
950
|
};
|
|
942
951
|
var docusaurusSidebarId = (relPath, raw) => {
|
|
943
952
|
const id = parseFrontmatter2(raw).id;
|
|
@@ -1519,6 +1528,11 @@ var HandoffTargetSchema = z2.object({
|
|
|
1519
1528
|
group: z2.string().min(1).max(128).optional(),
|
|
1520
1529
|
layer: z2.string().min(1).max(32).optional()
|
|
1521
1530
|
}).strict();
|
|
1531
|
+
var HandoffBridgeSchema = z2.object({
|
|
1532
|
+
humanDoc: z2.enum(["linked", "missing", "external"]),
|
|
1533
|
+
action: z2.string().min(1).max(256).optional(),
|
|
1534
|
+
bootstrap: z2.string().min(1).max(256).optional()
|
|
1535
|
+
}).strict();
|
|
1522
1536
|
var AgentHandoffV1Schema = z2.object({
|
|
1523
1537
|
type: z2.literal("agent-handoff"),
|
|
1524
1538
|
schemaVersion: z2.literal(HANDOFF_SCHEMA_VERSION).default(HANDOFF_SCHEMA_VERSION),
|
|
@@ -1529,6 +1543,7 @@ var AgentHandoffV1Schema = z2.object({
|
|
|
1529
1543
|
editRoots: z2.array(z2.string().min(1).max(512)).max(32),
|
|
1530
1544
|
checks: z2.array(z2.string().min(1).max(256)).max(32),
|
|
1531
1545
|
humanDoc: z2.string().min(1).max(512).nullable().optional(),
|
|
1546
|
+
bridge: HandoffBridgeSchema.optional(),
|
|
1532
1547
|
playbookPatterns: z2.array(z2.string().url()).max(16).optional(),
|
|
1533
1548
|
notes: z2.array(z2.string().min(1).max(1024)).max(16)
|
|
1534
1549
|
}).strict();
|
|
@@ -1889,6 +1904,11 @@ var handoffForPackage = (index, id, config) => {
|
|
|
1889
1904
|
if (fromIndex) return normalizeAgentHandoff(fromIndex);
|
|
1890
1905
|
const owner = index.lookup?.ownership?.[id];
|
|
1891
1906
|
if (!owner) throw new Error(`Unknown package/ownership id "${id}". Try: ak-docs list packages`);
|
|
1907
|
+
const bridge = owner.humanDoc ? /^https?:\/\//.test(owner.humanDoc) ? { humanDoc: "external" } : { humanDoc: "linked" } : config.corpus.human ? {
|
|
1908
|
+
humanDoc: "missing",
|
|
1909
|
+
action: "ak-docs bootstrap agent-docs",
|
|
1910
|
+
bootstrap: `docs/for-agents/human/${id}.md`
|
|
1911
|
+
} : void 0;
|
|
1892
1912
|
return normalizeAgentHandoff({
|
|
1893
1913
|
type: "agent-handoff",
|
|
1894
1914
|
source: config.index?.outFile ?? ".doc-bridge/index.json",
|
|
@@ -1904,7 +1924,11 @@ var handoffForPackage = (index, id, config) => {
|
|
|
1904
1924
|
editRoots: [owner.path],
|
|
1905
1925
|
checks: [...owner.checks],
|
|
1906
1926
|
...owner.humanDoc ? { humanDoc: owner.humanDoc } : {},
|
|
1907
|
-
|
|
1927
|
+
...bridge ? { bridge } : {},
|
|
1928
|
+
notes: [
|
|
1929
|
+
...owner.purpose ? [owner.purpose] : [],
|
|
1930
|
+
...!owner.humanDoc && config.corpus.human ? [`Human guide missing for ${id}. Run: ak-docs bootstrap agent-docs`] : []
|
|
1931
|
+
]
|
|
1908
1932
|
});
|
|
1909
1933
|
};
|
|
1910
1934
|
var runQuery = (index, config, req) => {
|
|
@@ -2377,9 +2401,752 @@ var draftMemoryPromotion = (classifications) => {
|
|
|
2377
2401
|
};
|
|
2378
2402
|
};
|
|
2379
2403
|
|
|
2404
|
+
// src/memory/github-pr.ts
|
|
2405
|
+
import { execFileSync, spawnSync } from "child_process";
|
|
2406
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2407
|
+
import { join as join15 } from "path";
|
|
2408
|
+
var run = (cmd, args, cwd) => {
|
|
2409
|
+
const result = spawnSync(cmd, [...args], { cwd, encoding: "utf8" });
|
|
2410
|
+
const out = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
2411
|
+
return { ok: result.status === 0, out };
|
|
2412
|
+
};
|
|
2413
|
+
var hasGh = () => run("gh", ["--version"], process.cwd()).ok;
|
|
2414
|
+
var slug = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2415
|
+
var defaultPromotionDraftPath = (root) => join15(root, ".doc-bridge", "drafts", `memory-promotion-${slug()}.md`);
|
|
2416
|
+
var writePromotionDraft = (root, draft, path) => {
|
|
2417
|
+
const draftPath = path ?? defaultPromotionDraftPath(root);
|
|
2418
|
+
mkdirSync2(join15(root, ".doc-bridge", "drafts"), { recursive: true });
|
|
2419
|
+
writeFileSync2(draftPath, `${draft.body}
|
|
2420
|
+
`, "utf8");
|
|
2421
|
+
return draftPath;
|
|
2422
|
+
};
|
|
2423
|
+
var promoteMemoryToGithubPr = (root, draft, options = {}) => {
|
|
2424
|
+
if (!draft.ok && !options.force) {
|
|
2425
|
+
return {
|
|
2426
|
+
ok: false,
|
|
2427
|
+
dryRun: Boolean(options.dryRun),
|
|
2428
|
+
draftPath: "",
|
|
2429
|
+
branch: "",
|
|
2430
|
+
commands: [],
|
|
2431
|
+
message: "Safety scan blocked promotion. Fix findings or pass --force to draft anyway."
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
2434
|
+
const branch = options.branch ?? `doc-bridge/memory-promotion-${slug()}`;
|
|
2435
|
+
const draftPath = writePromotionDraft(root, draft);
|
|
2436
|
+
const relDraft = draftPath.startsWith(`${root}/`) ? draftPath.slice(root.length + 1) : draftPath;
|
|
2437
|
+
const commands = [
|
|
2438
|
+
`git checkout -b ${branch}`,
|
|
2439
|
+
`git add ${relDraft}`,
|
|
2440
|
+
`git commit -m "draft: doc-bridge memory promotion"`,
|
|
2441
|
+
`git push -u origin ${branch}`,
|
|
2442
|
+
`gh pr create --draft --title "${draft.title}" --body-file ${relDraft}${options.base ? ` --base ${options.base}` : ""}`
|
|
2443
|
+
];
|
|
2444
|
+
if (options.dryRun) {
|
|
2445
|
+
return {
|
|
2446
|
+
ok: true,
|
|
2447
|
+
dryRun: true,
|
|
2448
|
+
draftPath,
|
|
2449
|
+
branch,
|
|
2450
|
+
commands,
|
|
2451
|
+
message: `Wrote draft to ${relDraft}. Run the printed git/gh commands to open a draft PR.`
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2454
|
+
if (!existsSync10(join15(root, ".git"))) {
|
|
2455
|
+
return {
|
|
2456
|
+
ok: false,
|
|
2457
|
+
dryRun: false,
|
|
2458
|
+
draftPath,
|
|
2459
|
+
branch,
|
|
2460
|
+
commands,
|
|
2461
|
+
message: "Not a git repository. Commit the draft manually or run with --dry-run."
|
|
2462
|
+
};
|
|
2463
|
+
}
|
|
2464
|
+
if (!hasGh()) {
|
|
2465
|
+
return {
|
|
2466
|
+
ok: false,
|
|
2467
|
+
dryRun: false,
|
|
2468
|
+
draftPath,
|
|
2469
|
+
branch,
|
|
2470
|
+
commands,
|
|
2471
|
+
message: "GitHub CLI (gh) not found. Install gh or use --dry-run for local draft + commands."
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
const auth = run("gh", ["auth", "status"], root);
|
|
2475
|
+
if (!auth.ok) {
|
|
2476
|
+
return {
|
|
2477
|
+
ok: false,
|
|
2478
|
+
dryRun: false,
|
|
2479
|
+
draftPath,
|
|
2480
|
+
branch,
|
|
2481
|
+
commands,
|
|
2482
|
+
message: `gh is not authenticated. Run: gh auth login
|
|
2483
|
+
${auth.out}`
|
|
2484
|
+
};
|
|
2485
|
+
}
|
|
2486
|
+
const checkout = run("git", ["checkout", "-b", branch], root);
|
|
2487
|
+
if (!checkout.ok) {
|
|
2488
|
+
return {
|
|
2489
|
+
ok: false,
|
|
2490
|
+
dryRun: false,
|
|
2491
|
+
draftPath,
|
|
2492
|
+
branch,
|
|
2493
|
+
commands,
|
|
2494
|
+
message: `git checkout failed: ${checkout.out}`
|
|
2495
|
+
};
|
|
2496
|
+
}
|
|
2497
|
+
run("git", ["add", relDraft], root);
|
|
2498
|
+
const commit = run("git", ["commit", "-m", "draft: doc-bridge memory promotion"], root);
|
|
2499
|
+
if (!commit.ok) {
|
|
2500
|
+
return {
|
|
2501
|
+
ok: false,
|
|
2502
|
+
dryRun: false,
|
|
2503
|
+
draftPath,
|
|
2504
|
+
branch,
|
|
2505
|
+
commands,
|
|
2506
|
+
message: `git commit failed: ${commit.out}`
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
const push = run("git", ["push", "-u", "origin", branch], root);
|
|
2510
|
+
if (!push.ok) {
|
|
2511
|
+
return {
|
|
2512
|
+
ok: false,
|
|
2513
|
+
dryRun: false,
|
|
2514
|
+
draftPath,
|
|
2515
|
+
branch,
|
|
2516
|
+
commands,
|
|
2517
|
+
message: `git push failed: ${push.out}`
|
|
2518
|
+
};
|
|
2519
|
+
}
|
|
2520
|
+
const prArgs = [
|
|
2521
|
+
"pr",
|
|
2522
|
+
"create",
|
|
2523
|
+
"--draft",
|
|
2524
|
+
"--title",
|
|
2525
|
+
draft.title,
|
|
2526
|
+
"--body-file",
|
|
2527
|
+
relDraft,
|
|
2528
|
+
...options.base ? ["--base", options.base] : []
|
|
2529
|
+
];
|
|
2530
|
+
let prUrl = "";
|
|
2531
|
+
try {
|
|
2532
|
+
prUrl = execFileSync("gh", prArgs, { cwd: root, encoding: "utf8" }).trim();
|
|
2533
|
+
} catch (error) {
|
|
2534
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2535
|
+
return {
|
|
2536
|
+
ok: false,
|
|
2537
|
+
dryRun: false,
|
|
2538
|
+
draftPath,
|
|
2539
|
+
branch,
|
|
2540
|
+
commands,
|
|
2541
|
+
message: `gh pr create failed: ${message}`
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
return {
|
|
2545
|
+
ok: true,
|
|
2546
|
+
dryRun: false,
|
|
2547
|
+
draftPath,
|
|
2548
|
+
branch,
|
|
2549
|
+
prUrl,
|
|
2550
|
+
...prUrl ? { previewUrl: prUrl } : {},
|
|
2551
|
+
commands,
|
|
2552
|
+
message: prUrl ? `Draft PR opened: ${prUrl}` : "Draft PR created."
|
|
2553
|
+
};
|
|
2554
|
+
};
|
|
2555
|
+
|
|
2556
|
+
// src/index-builder/watch-index.ts
|
|
2557
|
+
import { existsSync as existsSync11, watch } from "fs";
|
|
2558
|
+
import { dirname as dirname4, resolve as resolve5 } from "path";
|
|
2559
|
+
var WATCH_PATTERN = /\.(md|mdx|json|ya?ml|mdc)$/i;
|
|
2560
|
+
var collectWatchRoots = (root, config, configPath) => {
|
|
2561
|
+
const roots = /* @__PURE__ */ new Set();
|
|
2562
|
+
roots.add(resolve5(root, config.corpus.agent.root));
|
|
2563
|
+
const humanSources = config.corpus.human ? Array.isArray(config.corpus.human) ? config.corpus.human : [config.corpus.human] : [];
|
|
2564
|
+
for (const source of humanSources) {
|
|
2565
|
+
const humanOpts = source.options ?? {};
|
|
2566
|
+
for (const key of ["contentDir", "docsDir", "root"]) {
|
|
2567
|
+
const value = humanOpts[key];
|
|
2568
|
+
if (typeof value === "string" && value.length) roots.add(resolve5(root, value));
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
if (configPath) roots.add(dirname4(resolve5(configPath)));
|
|
2572
|
+
return [...roots].filter((dir) => existsSync11(dir));
|
|
2573
|
+
};
|
|
2574
|
+
var watchDocBridgeIndex = (opts) => {
|
|
2575
|
+
const debounceMs = opts.debounceMs ?? 350;
|
|
2576
|
+
let timer;
|
|
2577
|
+
let running = false;
|
|
2578
|
+
const rebuild = () => {
|
|
2579
|
+
if (timer) clearTimeout(timer);
|
|
2580
|
+
timer = setTimeout(() => {
|
|
2581
|
+
if (running) return;
|
|
2582
|
+
running = true;
|
|
2583
|
+
try {
|
|
2584
|
+
const result = buildDocBridgeIndex({ root: opts.root, config: opts.config });
|
|
2585
|
+
const handoffCount = Object.keys(result.index.handoffs ?? {}).length;
|
|
2586
|
+
const summary = {
|
|
2587
|
+
knowledgeCount: result.index.knowledge.length,
|
|
2588
|
+
handoffCount,
|
|
2589
|
+
hash: result.index.contentHash.slice(0, 8)
|
|
2590
|
+
};
|
|
2591
|
+
opts.onRebuild?.(summary);
|
|
2592
|
+
process.stdout.write(
|
|
2593
|
+
`[ak-docs] indexed ${summary.knowledgeCount} docs, ${summary.handoffCount} handoffs (${summary.hash}\u2026)
|
|
2594
|
+
`
|
|
2595
|
+
);
|
|
2596
|
+
} catch (error) {
|
|
2597
|
+
process.stderr.write(
|
|
2598
|
+
`[ak-docs] index failed: ${error instanceof Error ? error.message : String(error)}
|
|
2599
|
+
`
|
|
2600
|
+
);
|
|
2601
|
+
} finally {
|
|
2602
|
+
running = false;
|
|
2603
|
+
}
|
|
2604
|
+
}, debounceMs);
|
|
2605
|
+
};
|
|
2606
|
+
rebuild();
|
|
2607
|
+
for (const dir of collectWatchRoots(opts.root, opts.config, opts.configPath)) {
|
|
2608
|
+
watch(dir, { recursive: true }, (_event, filename) => {
|
|
2609
|
+
if (!filename || !WATCH_PATTERN.test(filename)) return;
|
|
2610
|
+
rebuild();
|
|
2611
|
+
});
|
|
2612
|
+
}
|
|
2613
|
+
const configDir = resolve5(opts.root);
|
|
2614
|
+
if (existsSync11(configDir)) {
|
|
2615
|
+
watch(configDir, (_event, filename) => {
|
|
2616
|
+
if (!filename || !/doc-bridge\.config/.test(filename)) return;
|
|
2617
|
+
rebuild();
|
|
2618
|
+
});
|
|
2619
|
+
}
|
|
2620
|
+
process.stdout.write(
|
|
2621
|
+
`[ak-docs] watching ${collectWatchRoots(opts.root, opts.config, opts.configPath).join(", ") || opts.root} (Ctrl+C to stop)
|
|
2622
|
+
`
|
|
2623
|
+
);
|
|
2624
|
+
return new Promise((resolvePromise) => {
|
|
2625
|
+
const onSignal = () => resolvePromise(0);
|
|
2626
|
+
process.once("SIGINT", onSignal);
|
|
2627
|
+
process.once("SIGTERM", onSignal);
|
|
2628
|
+
});
|
|
2629
|
+
};
|
|
2630
|
+
|
|
2631
|
+
// src/doctor/badge.ts
|
|
2632
|
+
var doctorBadgeMetrics = (report) => {
|
|
2633
|
+
const { packages } = report.coverage;
|
|
2634
|
+
const handoffPct = packages.total > 0 ? Math.round(packages.withAgentDoc / packages.total * 100) : 0;
|
|
2635
|
+
const bridgePct = packages.total > 0 ? Math.round(packages.withHumanDoc / packages.total * 100) : 0;
|
|
2636
|
+
return {
|
|
2637
|
+
handoffPct,
|
|
2638
|
+
bridgePct,
|
|
2639
|
+
score: report.score,
|
|
2640
|
+
grade: report.grade,
|
|
2641
|
+
packages: packages.total
|
|
2642
|
+
};
|
|
2643
|
+
};
|
|
2644
|
+
var badgeColor = (pct) => {
|
|
2645
|
+
if (pct >= 80) return "2ea44f";
|
|
2646
|
+
if (pct >= 50) return "dbab09";
|
|
2647
|
+
return "cb2431";
|
|
2648
|
+
};
|
|
2649
|
+
var formatDoctorBadgeMarkdown = (metrics) => {
|
|
2650
|
+
const handoff = `handoff_coverage-${metrics.handoffPct}%25-${badgeColor(metrics.handoffPct)}`;
|
|
2651
|
+
const bridge = `human_bridge-${metrics.bridgePct}%25-${badgeColor(metrics.bridgePct)}`;
|
|
2652
|
+
return [
|
|
2653
|
+
``,
|
|
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
|
+
|
|
2380
3147
|
// src/mcp/server.ts
|
|
2381
|
-
import { readFileSync as
|
|
2382
|
-
import { relative, resolve as
|
|
3148
|
+
import { readFileSync as readFileSync16, realpathSync } from "fs";
|
|
3149
|
+
import { relative, resolve as resolve8 } from "path";
|
|
2383
3150
|
import { z as z5, ZodError } from "zod";
|
|
2384
3151
|
var MCP_TOOLS = [
|
|
2385
3152
|
{
|
|
@@ -2486,7 +3253,7 @@ var findDocPath = (index, args) => {
|
|
|
2486
3253
|
};
|
|
2487
3254
|
var resolveDocPath = (root, relPath) => {
|
|
2488
3255
|
const rootAbs = realpathSync.native(root);
|
|
2489
|
-
const unresolved =
|
|
3256
|
+
const unresolved = resolve8(rootAbs, relPath);
|
|
2490
3257
|
const unresolvedRel = relative(rootAbs, unresolved);
|
|
2491
3258
|
if (unresolvedRel.startsWith("..")) throw new Error("doc.get path escapes project root");
|
|
2492
3259
|
const abs = realpathSync.native(unresolved);
|
|
@@ -2524,7 +3291,7 @@ var handleMcpRequest = (ctx, request) => {
|
|
|
2524
3291
|
}
|
|
2525
3292
|
if (name === "doc.get") {
|
|
2526
3293
|
const relPath = findDocPath(index(), parseToolArgs("doc.get", DocGetArgsSchema, args));
|
|
2527
|
-
return textResult(
|
|
3294
|
+
return textResult(readFileSync16(resolveDocPath(ctx.root, relPath), "utf8"));
|
|
2528
3295
|
}
|
|
2529
3296
|
if (name === "gate.status") return textResult(runGates(ctx.root, ctx.config));
|
|
2530
3297
|
if (name === "retriever.query") {
|
|
@@ -2606,14 +3373,17 @@ var usage = `ak-docs \u2014 human\u2194agent documentation bridge (@agentskit/do
|
|
|
2606
3373
|
|
|
2607
3374
|
Core (no API key):
|
|
2608
3375
|
ak-docs init [--demo] [--scaffold-workspaces]
|
|
2609
|
-
ak-docs
|
|
3376
|
+
ak-docs demo [--fixture example|monorepo] [--text] [--in-project]
|
|
3377
|
+
ak-docs doctor [--text] [--badge] [--write-badge]
|
|
3378
|
+
ak-docs index [--watch]
|
|
2610
3379
|
ak-docs query <package|ownership|intent|change> <id> [--agent] [--text]
|
|
2611
3380
|
ak-docs search <term> [--agent] [--text]
|
|
2612
3381
|
ak-docs list <packages|intents|changes|knowledge> [--text]
|
|
2613
3382
|
ak-docs ask [question] local consult (no LLM)
|
|
2614
3383
|
ak-docs gate run [gate-id]
|
|
2615
3384
|
ak-docs mcp
|
|
2616
|
-
ak-docs
|
|
3385
|
+
ak-docs mcp install --cursor | --claude
|
|
3386
|
+
ak-docs memory ingest|classify|promote [--pr] [--dry-run]
|
|
2617
3387
|
ak-docs bootstrap agent-docs
|
|
2618
3388
|
ak-docs validate-config | validate-handoff <file>
|
|
2619
3389
|
|
|
@@ -2625,7 +3395,7 @@ Intelligence (optional AgentsKit peers):
|
|
|
2625
3395
|
Advanced / ecosystem:
|
|
2626
3396
|
ak-docs retrieve <query>
|
|
2627
3397
|
ak-docs registry topology
|
|
2628
|
-
ak-docs playbook draft
|
|
3398
|
+
ak-docs playbook draft | pattern [--text]
|
|
2629
3399
|
|
|
2630
3400
|
Global flags:
|
|
2631
3401
|
-h, --help --version
|
|
@@ -2666,6 +3436,8 @@ var parseArgs = (argv) => {
|
|
|
2666
3436
|
else if (positional[0] === "index") command = "index";
|
|
2667
3437
|
else if (positional[0] === "gate") command = "gate";
|
|
2668
3438
|
else if (positional[0] === "mcp") command = "mcp";
|
|
3439
|
+
else if (positional[0] === "doctor") command = "doctor";
|
|
3440
|
+
else if (positional[0] === "demo") command = "demo";
|
|
2669
3441
|
else if (positional[0] === "query") command = "query";
|
|
2670
3442
|
else if (positional[0] === "search") command = "search";
|
|
2671
3443
|
else if (positional[0] === "retrieve") command = "retrieve";
|
|
@@ -2675,7 +3447,7 @@ var parseArgs = (argv) => {
|
|
|
2675
3447
|
else if (positional[0] === "list") command = "list";
|
|
2676
3448
|
return { command, flags, configPath, positional };
|
|
2677
3449
|
};
|
|
2678
|
-
var
|
|
3450
|
+
var writeJson2 = (payload) => {
|
|
2679
3451
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}
|
|
2680
3452
|
`);
|
|
2681
3453
|
};
|
|
@@ -2723,13 +3495,33 @@ var writeTextSearch = (term, matches) => {
|
|
|
2723
3495
|
...matches.length ? matches.map(formatSearchMatch) : [" (none)"]
|
|
2724
3496
|
]);
|
|
2725
3497
|
};
|
|
2726
|
-
var
|
|
3498
|
+
var handoffSummaryLines = (index, config, ownerId) => {
|
|
3499
|
+
try {
|
|
3500
|
+
const handoff = runQuery(index, config, { kind: "ownership", id: ownerId, agent: true });
|
|
3501
|
+
if (!handoff || typeof handoff !== "object" || !("editRoots" in handoff)) return [];
|
|
3502
|
+
const payload = handoff;
|
|
3503
|
+
const bridgeLine = payload.bridge?.humanDoc === "missing" ? `Bridge: human guide missing \u2192 ${payload.bridge.action ?? "ak-docs bootstrap agent-docs"}` : payload.humanDoc ? `Bridge: ${payload.humanDoc}` : "Bridge: (no human plugin configured)";
|
|
3504
|
+
return [
|
|
3505
|
+
"",
|
|
3506
|
+
"Handoff preview",
|
|
3507
|
+
` start: ${payload.startHere ?? "(unknown)"}`,
|
|
3508
|
+
` edit: ${(payload.editRoots ?? []).join(", ") || "(none)"}`,
|
|
3509
|
+
` checks: ${(payload.checks ?? []).join(" \xB7 ") || "(none)"}`,
|
|
3510
|
+
` ${bridgeLine}`
|
|
3511
|
+
];
|
|
3512
|
+
} catch {
|
|
3513
|
+
return [];
|
|
3514
|
+
}
|
|
3515
|
+
};
|
|
3516
|
+
var writeAsk = (question, matches, index, config) => {
|
|
2727
3517
|
const owner = matches.find((match) => match.type === "ownership") ?? matches.find((match) => Boolean(index.lookup?.ownership?.[match.id]));
|
|
2728
3518
|
const best = owner ?? matches[0];
|
|
2729
|
-
const
|
|
3519
|
+
const ownerId = best && (best.type === "ownership" || index.lookup?.ownership?.[best.id]) ? best.id : void 0;
|
|
3520
|
+
const bestQuery = ownerId ? `ak-docs query ownership ${ownerId} --agent` : "ak-docs list knowledge --text";
|
|
2730
3521
|
writeLines([
|
|
2731
3522
|
`Question: ${question}`,
|
|
2732
3523
|
best ? `Best match: ${best.type} ${best.id} (${best.path})` : "Best match: none",
|
|
3524
|
+
...ownerId ? handoffSummaryLines(index, config, ownerId) : [],
|
|
2733
3525
|
"",
|
|
2734
3526
|
"Matches:",
|
|
2735
3527
|
...matches.length ? matches.slice(0, 5).map(formatSearchMatch) : [" No local matches. Try: ak-docs search <term>"],
|
|
@@ -2737,20 +3529,21 @@ var writeAsk = (question, matches, index) => {
|
|
|
2737
3529
|
"Next commands:",
|
|
2738
3530
|
...best ? [
|
|
2739
3531
|
`ak-docs search "${question}" --agent`,
|
|
2740
|
-
bestQuery
|
|
2741
|
-
|
|
3532
|
+
bestQuery,
|
|
3533
|
+
...ownerId ? [`ak-docs doctor --text`] : []
|
|
3534
|
+
] : ["ak-docs list knowledge --text", "ak-docs doctor --text"]
|
|
2742
3535
|
]);
|
|
2743
3536
|
};
|
|
2744
3537
|
var readIndexedDoc = (root, config, idOrPath) => {
|
|
2745
3538
|
const index = loadDocBridgeIndex(root, config);
|
|
2746
3539
|
const entry = index.knowledge.find((doc) => doc.id === idOrPath || doc.path === idOrPath);
|
|
2747
3540
|
if (!entry) throw new Error(`Unknown indexed doc "${idOrPath}". Try: search ${idOrPath}`);
|
|
2748
|
-
const abs =
|
|
2749
|
-
const rootAbs =
|
|
3541
|
+
const abs = resolve9(root, entry.path);
|
|
3542
|
+
const rootAbs = resolve9(root);
|
|
2750
3543
|
if (abs !== rootAbs && !abs.startsWith(`${rootAbs}/`)) {
|
|
2751
3544
|
throw new Error(`Indexed doc escapes project root: ${entry.path}`);
|
|
2752
3545
|
}
|
|
2753
|
-
return
|
|
3546
|
+
return readFileSync17(abs, "utf8");
|
|
2754
3547
|
};
|
|
2755
3548
|
var runAskRepl = async (root, config) => {
|
|
2756
3549
|
const index = loadDocBridgeIndex(root, config);
|
|
@@ -2769,12 +3562,12 @@ var runAskRepl = async (root, config) => {
|
|
|
2769
3562
|
process.stdout.write(`${readIndexedDoc(root, config, value).slice(0, 8e3)}
|
|
2770
3563
|
`);
|
|
2771
3564
|
} else if (command === "resolve") {
|
|
2772
|
-
|
|
3565
|
+
writeJson2(runQuery(index, config, { kind: "ownership", id: value, agent: true }));
|
|
2773
3566
|
} else if (command === "gate") {
|
|
2774
3567
|
const gateId = value || void 0;
|
|
2775
|
-
|
|
3568
|
+
writeJson2(runGates(root, config, gateId ? [gateId] : void 0));
|
|
2776
3569
|
} else {
|
|
2777
|
-
writeAsk(line, searchIndex(index, line, 8), index);
|
|
3570
|
+
writeAsk(line, searchIndex(index, line, 8), index, config);
|
|
2778
3571
|
}
|
|
2779
3572
|
} catch (error) {
|
|
2780
3573
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -2826,9 +3619,9 @@ var diagnosticNextCommands = (config, result) => {
|
|
|
2826
3619
|
];
|
|
2827
3620
|
};
|
|
2828
3621
|
var writeIfMissing = (path, contents) => {
|
|
2829
|
-
if (
|
|
2830
|
-
|
|
2831
|
-
|
|
3622
|
+
if (existsSync14(path)) return false;
|
|
3623
|
+
mkdirSync5(dirname7(path), { recursive: true });
|
|
3624
|
+
writeFileSync4(path, contents, "utf8");
|
|
2832
3625
|
return true;
|
|
2833
3626
|
};
|
|
2834
3627
|
var demoOwnership = {
|
|
@@ -2931,7 +3724,7 @@ var scaffoldWorkspaceDocs = (root, config) => {
|
|
|
2931
3724
|
const created = [];
|
|
2932
3725
|
const skipped = [];
|
|
2933
3726
|
for (const pkg of discoverPnpmPackages(root, config)) {
|
|
2934
|
-
const path =
|
|
3727
|
+
const path = resolve9(root, config.corpus.agent.root, "packages", `${pkg.id}.md`);
|
|
2935
3728
|
if (writeIfMissing(path, workspaceDocDraft(pkg.id, pkg.path))) created.push(path);
|
|
2936
3729
|
else skipped.push(path);
|
|
2937
3730
|
}
|
|
@@ -2941,11 +3734,11 @@ var bootstrapAgentDocs = (root, config) => {
|
|
|
2941
3734
|
const created = [];
|
|
2942
3735
|
const skipped = [];
|
|
2943
3736
|
for (const doc of scanHumanDocRecords(root, config)) {
|
|
2944
|
-
const raw =
|
|
3737
|
+
const raw = readFileSync17(doc.path, "utf8");
|
|
2945
3738
|
const body = raw.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
2946
3739
|
const title = firstHeading(body) ?? doc.id;
|
|
2947
3740
|
const description = firstParagraph(body);
|
|
2948
|
-
const draftPath =
|
|
3741
|
+
const draftPath = resolve9(root, config.corpus.agent.root, "human", `${doc.id}.md`);
|
|
2949
3742
|
const draft = [
|
|
2950
3743
|
"---",
|
|
2951
3744
|
"type: knowledge",
|
|
@@ -2994,7 +3787,7 @@ var runCli = (argv) => {
|
|
|
2994
3787
|
configPath ? { explicitPath: configPath } : {}
|
|
2995
3788
|
);
|
|
2996
3789
|
parseDocBridgeConfig(config);
|
|
2997
|
-
|
|
3790
|
+
writeJson2({ ok: true, path, schemaVersion: config.schemaVersion });
|
|
2998
3791
|
return 0;
|
|
2999
3792
|
} catch (error) {
|
|
3000
3793
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -3009,10 +3802,10 @@ var runCli = (argv) => {
|
|
|
3009
3802
|
return 1;
|
|
3010
3803
|
}
|
|
3011
3804
|
try {
|
|
3012
|
-
const abs =
|
|
3013
|
-
const raw =
|
|
3805
|
+
const abs = resolve9(file);
|
|
3806
|
+
const raw = readFileSync17(abs, "utf8");
|
|
3014
3807
|
const handoff = parseAgentHandoff(JSON.parse(raw));
|
|
3015
|
-
|
|
3808
|
+
writeJson2({ ok: true, handoff });
|
|
3016
3809
|
return 0;
|
|
3017
3810
|
} catch (error) {
|
|
3018
3811
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -3023,10 +3816,10 @@ var runCli = (argv) => {
|
|
|
3023
3816
|
if (command === "init") {
|
|
3024
3817
|
const root = process.cwd();
|
|
3025
3818
|
const withDemo = flags.has("--demo") || !flags.has("--no-demo");
|
|
3026
|
-
const configFile =
|
|
3027
|
-
const docsIndex =
|
|
3028
|
-
const exampleDoc =
|
|
3029
|
-
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");
|
|
3030
3823
|
const configWritten = writeIfMissing(configFile, initConfigContents(configFile, withDemo));
|
|
3031
3824
|
const indexWritten = writeIfMissing(
|
|
3032
3825
|
docsIndex,
|
|
@@ -3034,9 +3827,9 @@ var runCli = (argv) => {
|
|
|
3034
3827
|
);
|
|
3035
3828
|
const exampleWritten = withDemo ? writeIfMissing(exampleDoc, exampleAgentDoc) : false;
|
|
3036
3829
|
const agentsWritten = writeIfMissing(agentsMd, agentsMdSnippet);
|
|
3037
|
-
if (withDemo) writeIfMissing(
|
|
3830
|
+
if (withDemo) writeIfMissing(resolve9(root, "src/.gitkeep"), "");
|
|
3038
3831
|
const scaffold = flags.has("--scaffold-workspaces") ? scaffoldWorkspaceDocs(root, loadProject(configFile).config) : void 0;
|
|
3039
|
-
|
|
3832
|
+
writeJson2({
|
|
3040
3833
|
ok: true,
|
|
3041
3834
|
configPath: configFile,
|
|
3042
3835
|
demo: withDemo,
|
|
@@ -3060,7 +3853,7 @@ var runCli = (argv) => {
|
|
|
3060
3853
|
try {
|
|
3061
3854
|
const { config, root } = loadProject(configPath);
|
|
3062
3855
|
const result = bootstrapAgentDocs(root, config);
|
|
3063
|
-
|
|
3856
|
+
writeJson2({ ok: true, ...result });
|
|
3064
3857
|
return 0;
|
|
3065
3858
|
} catch (error) {
|
|
3066
3859
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -3070,24 +3863,43 @@ var runCli = (argv) => {
|
|
|
3070
3863
|
}
|
|
3071
3864
|
if (command === "memory") {
|
|
3072
3865
|
if (!["ingest", "classify", "promote"].includes(positional[1] ?? "")) {
|
|
3073
|
-
process.stderr.write(
|
|
3866
|
+
process.stderr.write(
|
|
3867
|
+
"Usage: ak-docs memory <ingest|classify|promote> [--pr] [--dry-run] [--force] [--config <path>]\n"
|
|
3868
|
+
);
|
|
3074
3869
|
return 1;
|
|
3075
3870
|
}
|
|
3076
3871
|
try {
|
|
3077
3872
|
const { config, root } = loadProject(configPath);
|
|
3078
3873
|
const candidates = ingestMemoryCandidates(root);
|
|
3079
3874
|
if (positional[1] === "ingest") {
|
|
3080
|
-
|
|
3875
|
+
writeJson2({ ok: true, count: candidates.length, candidates });
|
|
3081
3876
|
return 0;
|
|
3082
3877
|
}
|
|
3083
3878
|
const index = loadDocBridgeIndex(root, config);
|
|
3084
3879
|
const classifications = classifyMemoryCandidates(candidates, index);
|
|
3085
3880
|
if (positional[1] === "classify") {
|
|
3086
|
-
|
|
3881
|
+
writeJson2({ ok: true, count: classifications.length, classifications });
|
|
3087
3882
|
return 0;
|
|
3088
3883
|
}
|
|
3089
3884
|
const draft = draftMemoryPromotion(classifications);
|
|
3090
|
-
|
|
3885
|
+
if (flags.has("--pr") || flags.has("--github")) {
|
|
3886
|
+
const pr = promoteMemoryToGithubPr(root, draft, {
|
|
3887
|
+
dryRun: flags.has("--dry-run"),
|
|
3888
|
+
force: flags.has("--force")
|
|
3889
|
+
});
|
|
3890
|
+
if (flags.has("--text")) {
|
|
3891
|
+
writeLines([
|
|
3892
|
+
pr.message,
|
|
3893
|
+
...pr.draftPath ? [`Draft: ${pr.draftPath}`] : [],
|
|
3894
|
+
...pr.prUrl ? [`PR: ${pr.prUrl}`] : [],
|
|
3895
|
+
...pr.commands.length ? ["", "Commands:", ...pr.commands.map((cmd) => ` ${cmd}`)] : []
|
|
3896
|
+
]);
|
|
3897
|
+
} else {
|
|
3898
|
+
writeJson2({ ...draft, pr });
|
|
3899
|
+
}
|
|
3900
|
+
return pr.ok ? 0 : 1;
|
|
3901
|
+
}
|
|
3902
|
+
writeJson2(draft);
|
|
3091
3903
|
return draft.ok ? 0 : 1;
|
|
3092
3904
|
} catch (error) {
|
|
3093
3905
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -3100,22 +3912,35 @@ var runCli = (argv) => {
|
|
|
3100
3912
|
process.stderr.write("Usage: ak-docs registry topology\n");
|
|
3101
3913
|
return 1;
|
|
3102
3914
|
}
|
|
3103
|
-
|
|
3915
|
+
writeJson2(registryTopology());
|
|
3104
3916
|
return 0;
|
|
3105
3917
|
}
|
|
3106
3918
|
if (command === "playbook") {
|
|
3107
|
-
|
|
3108
|
-
|
|
3919
|
+
const action = positional[1];
|
|
3920
|
+
if (action === "pattern") {
|
|
3921
|
+
const payload = docBridgePatternPayload();
|
|
3922
|
+
if (flags.has("--text") || wantsTextOutput(flags, { schemaVersion: 1, corpus: { agent: { root: "docs" } } })) {
|
|
3923
|
+
process.stdout.write(`${docBridgePatternMarkdown()}
|
|
3924
|
+
`);
|
|
3925
|
+
} else {
|
|
3926
|
+
writeJson2(payload);
|
|
3927
|
+
}
|
|
3928
|
+
return 0;
|
|
3929
|
+
}
|
|
3930
|
+
if (action !== "draft") {
|
|
3931
|
+
process.stderr.write("Usage: ak-docs playbook draft | pattern [--text] [--config <path>]\n");
|
|
3109
3932
|
return 1;
|
|
3110
3933
|
}
|
|
3111
3934
|
try {
|
|
3112
3935
|
const { config, root } = loadProject(configPath);
|
|
3113
3936
|
const index = loadDocBridgeIndex(root, config);
|
|
3114
3937
|
const draft = draftMemoryPromotion(classifyMemoryCandidates(ingestMemoryCandidates(root), index));
|
|
3115
|
-
|
|
3938
|
+
writeJson2({
|
|
3116
3939
|
...draft,
|
|
3117
3940
|
title: "Draft Playbook feedback promotion",
|
|
3118
|
-
pattern: "Doc Bridge Pattern"
|
|
3941
|
+
pattern: "Doc Bridge Pattern",
|
|
3942
|
+
patternDoc: "docs/playbook/doc-bridge-pattern.md",
|
|
3943
|
+
exportCommand: "ak-docs playbook pattern --text"
|
|
3119
3944
|
});
|
|
3120
3945
|
return 0;
|
|
3121
3946
|
} catch (error) {
|
|
@@ -3126,11 +3951,18 @@ var runCli = (argv) => {
|
|
|
3126
3951
|
}
|
|
3127
3952
|
if (command === "index") {
|
|
3128
3953
|
try {
|
|
3129
|
-
const { config, root } = loadProject(configPath);
|
|
3954
|
+
const { config, root, configPath: loadedConfigPath } = loadProject(configPath);
|
|
3955
|
+
if (flags.has("--watch")) {
|
|
3956
|
+
return watchDocBridgeIndex({
|
|
3957
|
+
root,
|
|
3958
|
+
config,
|
|
3959
|
+
configPath: loadedConfigPath
|
|
3960
|
+
});
|
|
3961
|
+
}
|
|
3130
3962
|
const result = buildDocBridgeIndex({ root, config });
|
|
3131
3963
|
const diagnostics = indexDiagnostics(config, result);
|
|
3132
3964
|
const handoffCount = Object.keys(result.index.handoffs ?? {}).length;
|
|
3133
|
-
|
|
3965
|
+
writeJson2({
|
|
3134
3966
|
ok: true,
|
|
3135
3967
|
indexPath: result.indexPath,
|
|
3136
3968
|
...result.llmsTxtPath ? { llmsTxtPath: result.llmsTxtPath } : {},
|
|
@@ -3166,7 +3998,7 @@ var runCli = (argv) => {
|
|
|
3166
3998
|
try {
|
|
3167
3999
|
const { config, root } = loadProject(configPath);
|
|
3168
4000
|
const result = runGates(root, config, gateId ? [gateId] : void 0);
|
|
3169
|
-
|
|
4001
|
+
writeJson2(result);
|
|
3170
4002
|
return result.ok ? 0 : 1;
|
|
3171
4003
|
} catch (error) {
|
|
3172
4004
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -3175,12 +4007,98 @@ var runCli = (argv) => {
|
|
|
3175
4007
|
}
|
|
3176
4008
|
}
|
|
3177
4009
|
if (command === "mcp") {
|
|
4010
|
+
if (positional[1] === "install") {
|
|
4011
|
+
const target = flags.has("--claude") ? "claude" : flags.has("--cursor") ? "cursor" : void 0;
|
|
4012
|
+
if (!target) {
|
|
4013
|
+
process.stderr.write("Usage: ak-docs mcp install --cursor | --claude\n");
|
|
4014
|
+
return 1;
|
|
4015
|
+
}
|
|
4016
|
+
try {
|
|
4017
|
+
const { config, root } = loadProject(configPath);
|
|
4018
|
+
const result = installMcpConfig(root, target);
|
|
4019
|
+
if (wantsTextOutput(flags, config)) {
|
|
4020
|
+
writeLines([
|
|
4021
|
+
`Installed MCP server "${result.serverName}" for ${result.target}`,
|
|
4022
|
+
`Config: ${result.configPath}`,
|
|
4023
|
+
...result.created ? ["Created new config file"] : ["Merged into existing config"],
|
|
4024
|
+
"",
|
|
4025
|
+
"Next steps:",
|
|
4026
|
+
...result.nextSteps.map((step) => ` \u2192 ${step}`)
|
|
4027
|
+
]);
|
|
4028
|
+
} else {
|
|
4029
|
+
writeJson2({ ...result, snippet: mcpSnippet(root) });
|
|
4030
|
+
}
|
|
4031
|
+
return 0;
|
|
4032
|
+
} catch (error) {
|
|
4033
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
4034
|
+
`);
|
|
4035
|
+
return 1;
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
3178
4038
|
try {
|
|
3179
4039
|
const { config, root } = loadProject(configPath);
|
|
3180
4040
|
startMcpStdioServer({ root, config });
|
|
3181
4041
|
return void 0;
|
|
3182
4042
|
} catch (error) {
|
|
3183
4043
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
4044
|
+
`);
|
|
4045
|
+
return 1;
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
if (command === "doctor") {
|
|
4049
|
+
try {
|
|
4050
|
+
const { config, root } = loadProject(configPath);
|
|
4051
|
+
const report = runDoctor(root, config);
|
|
4052
|
+
if (flags.has("--write-badge")) {
|
|
4053
|
+
const badgePath = resolve9(root, ".doc-bridge", "coverage-badge.json");
|
|
4054
|
+
mkdirSync5(dirname7(badgePath), { recursive: true });
|
|
4055
|
+
writeFileSync4(badgePath, `${formatDoctorBadgeJson(report.badge)}
|
|
4056
|
+
`, "utf8");
|
|
4057
|
+
}
|
|
4058
|
+
if (flags.has("--badge")) {
|
|
4059
|
+
writeLines([formatDoctorBadgeMarkdown(report.badge)]);
|
|
4060
|
+
return 0;
|
|
4061
|
+
}
|
|
4062
|
+
if (wantsTextOutput(flags, config)) writeLines(formatDoctorText(report));
|
|
4063
|
+
else writeJson2(report);
|
|
4064
|
+
return report.ok ? 0 : 1;
|
|
4065
|
+
} catch (error) {
|
|
4066
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
4067
|
+
`);
|
|
4068
|
+
return 1;
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
if (command === "demo") {
|
|
4072
|
+
const resolveDemoFixture = () => {
|
|
4073
|
+
const fixtureFlagIndex = positional.indexOf("--fixture");
|
|
4074
|
+
if (fixtureFlagIndex >= 0) {
|
|
4075
|
+
const value = positional[fixtureFlagIndex + 1];
|
|
4076
|
+
if (value === "monorepo" || value === "example") return value;
|
|
4077
|
+
}
|
|
4078
|
+
if (flags.has("--monorepo") || positional.includes("monorepo")) return "monorepo";
|
|
4079
|
+
if (positional[1] === "monorepo") return "monorepo";
|
|
4080
|
+
return "example";
|
|
4081
|
+
};
|
|
4082
|
+
const resolvedFixture = resolveDemoFixture();
|
|
4083
|
+
try {
|
|
4084
|
+
const useProject = flags.has("--in-project") || flags.has("--copy-fixture");
|
|
4085
|
+
if (!useProject) {
|
|
4086
|
+
const result2 = withDemoWorkspace(
|
|
4087
|
+
resolvedFixture,
|
|
4088
|
+
(root2, config2) => runDemo(root2, config2, resolvedFixture)
|
|
4089
|
+
);
|
|
4090
|
+
const textMode = flags.has("--text") || !flags.has("--json") && !flags.has("--agent");
|
|
4091
|
+
if (textMode) writeLines(formatDemoText(result2));
|
|
4092
|
+
else writeJson2(result2);
|
|
4093
|
+
return result2.ok ? 0 : 1;
|
|
4094
|
+
}
|
|
4095
|
+
const { config, root } = loadProject(configPath);
|
|
4096
|
+
const result = runDemo(root, config, resolvedFixture, { copyFixture: flags.has("--copy-fixture") });
|
|
4097
|
+
if (wantsTextOutput(flags, config)) writeLines(formatDemoText(result));
|
|
4098
|
+
else writeJson2(result);
|
|
4099
|
+
return result.ok ? 0 : 1;
|
|
4100
|
+
} catch (error) {
|
|
4101
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
3184
4102
|
`);
|
|
3185
4103
|
return 1;
|
|
3186
4104
|
}
|
|
@@ -3202,7 +4120,7 @@ var runCli = (argv) => {
|
|
|
3202
4120
|
const index = loadDocBridgeIndex(root, config);
|
|
3203
4121
|
const result = runQuery(index, config, { kind, id, agent: flags.has("--agent") });
|
|
3204
4122
|
if (wantsTextOutput(flags, config)) writeTextQuery(result);
|
|
3205
|
-
else
|
|
4123
|
+
else writeJson2(result);
|
|
3206
4124
|
return 0;
|
|
3207
4125
|
} catch (error) {
|
|
3208
4126
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -3221,11 +4139,11 @@ var runCli = (argv) => {
|
|
|
3221
4139
|
const index = loadDocBridgeIndex(root, config);
|
|
3222
4140
|
if (flags.has("--agent")) {
|
|
3223
4141
|
const result = runQuery(index, config, { kind: "search", term, agent: true });
|
|
3224
|
-
|
|
4142
|
+
writeJson2(result);
|
|
3225
4143
|
} else {
|
|
3226
4144
|
const matches = searchIndex(index, term);
|
|
3227
4145
|
if (wantsTextOutput(flags, config)) writeTextSearch(term, matches);
|
|
3228
|
-
else
|
|
4146
|
+
else writeJson2({ term, count: matches.length, matches });
|
|
3229
4147
|
}
|
|
3230
4148
|
return 0;
|
|
3231
4149
|
} catch (error) {
|
|
@@ -3249,7 +4167,7 @@ var runCli = (argv) => {
|
|
|
3249
4167
|
try {
|
|
3250
4168
|
const { config, root } = loadProject(configPath);
|
|
3251
4169
|
const index = loadDocBridgeIndex(root, config);
|
|
3252
|
-
|
|
4170
|
+
writeJson2({ query, chunks: await retrieveHybridChunks(root, config, index, query) });
|
|
3253
4171
|
return 0;
|
|
3254
4172
|
} catch (error) {
|
|
3255
4173
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -3271,7 +4189,7 @@ var runCli = (argv) => {
|
|
|
3271
4189
|
const rag = await createDocBridgeRag(root, config, index);
|
|
3272
4190
|
if (action === "ingest") {
|
|
3273
4191
|
const result = await rag.ingest();
|
|
3274
|
-
|
|
4192
|
+
writeJson2({ ok: true, ...result });
|
|
3275
4193
|
return 0;
|
|
3276
4194
|
}
|
|
3277
4195
|
const query = positional.slice(2).join(" ").trim();
|
|
@@ -3280,7 +4198,7 @@ var runCli = (argv) => {
|
|
|
3280
4198
|
return 1;
|
|
3281
4199
|
}
|
|
3282
4200
|
const hits = await rag.search(query);
|
|
3283
|
-
|
|
4201
|
+
writeJson2({ query, count: hits.length, hits });
|
|
3284
4202
|
return 0;
|
|
3285
4203
|
} catch (error) {
|
|
3286
4204
|
if (error instanceof PeerMissingError) {
|
|
@@ -3362,7 +4280,7 @@ Install peers: ${layer1InstallHint()}
|
|
|
3362
4280
|
return 1;
|
|
3363
4281
|
}
|
|
3364
4282
|
const index = loadDocBridgeIndex(root, config);
|
|
3365
|
-
writeAsk(question, searchIndex(index, question, 8), index);
|
|
4283
|
+
writeAsk(question, searchIndex(index, question, 8), index, config);
|
|
3366
4284
|
return 0;
|
|
3367
4285
|
} catch (error) {
|
|
3368
4286
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
@@ -3382,19 +4300,19 @@ Install peers: ${layer1InstallHint()}
|
|
|
3382
4300
|
if (kind === "packages") {
|
|
3383
4301
|
const items2 = index.lookup?.packages ?? [];
|
|
3384
4302
|
if (wantsTextOutput(flags, config)) writeLines(items2);
|
|
3385
|
-
else
|
|
4303
|
+
else writeJson2({ kind, items: items2 });
|
|
3386
4304
|
return 0;
|
|
3387
4305
|
}
|
|
3388
4306
|
if (kind === "intents") {
|
|
3389
4307
|
const items2 = Object.keys(index.lookup?.intents ?? {});
|
|
3390
4308
|
if (wantsTextOutput(flags, config)) writeLines(items2);
|
|
3391
|
-
else
|
|
4309
|
+
else writeJson2({ kind, items: items2 });
|
|
3392
4310
|
return 0;
|
|
3393
4311
|
}
|
|
3394
4312
|
if (kind === "changes") {
|
|
3395
4313
|
const items2 = Object.keys(index.lookup?.changes ?? {});
|
|
3396
4314
|
if (wantsTextOutput(flags, config)) writeLines(items2);
|
|
3397
|
-
else
|
|
4315
|
+
else writeJson2({ kind, items: items2 });
|
|
3398
4316
|
return 0;
|
|
3399
4317
|
}
|
|
3400
4318
|
const items = index.knowledge.map((entry) => ({
|
|
@@ -3405,7 +4323,7 @@ Install peers: ${layer1InstallHint()}
|
|
|
3405
4323
|
if (wantsTextOutput(flags, config)) {
|
|
3406
4324
|
writeLines(items.map((item) => [item.id, item.path, item.title].join(" ")));
|
|
3407
4325
|
} else {
|
|
3408
|
-
|
|
4326
|
+
writeJson2({ kind, items });
|
|
3409
4327
|
}
|
|
3410
4328
|
return 0;
|
|
3411
4329
|
} catch (error) {
|