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