buddy-agent-harness 0.15.0 → 0.16.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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/dist/cli.mjs +203 -52
- package/dist/index.d.mts +35 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +159 -8
- package/dist/index.mjs.map +1 -1
- package/dist/skill-scripts/doctor.mjs +18 -6
- package/dist/skill-scripts/init.mjs +18 -6
- package/dist/skill-scripts/mcp.mjs +18 -6
- package/dist/skill-scripts/reference.mjs +18 -6
- package/llms.txt +27 -26
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/doctor-buddy-agent-harness/SKILL.md +1 -1
- package/skills/doctor-buddy-agent-harness/scripts/doctor.mjs +18 -6
- package/skills/init-buddy-agent-harness/SKILL.md +1 -1
- package/skills/init-buddy-agent-harness/scripts/doctor.mjs +18 -6
- package/skills/init-buddy-agent-harness/scripts/init.mjs +18 -6
- package/skills/reference/SKILL.md +5 -3
- package/skills/reference/references/create.md +15 -4
- package/skills/reference/scripts/reference.mjs +18 -6
- package/skills/repair/SKILL.md +1 -1
- package/skills/repair/scripts/doctor.mjs +18 -6
- package/skills/repair/scripts/mcp.mjs +18 -6
- package/src/governance-overrides/governance-overrides.ts +32 -4
- package/src/governance-overrides/governance.command.ts +3 -0
- package/src/references/create-reference.ts +57 -0
- package/src/references/reference-document.ts +7 -1
- package/src/references/reference.command.ts +132 -10
package/dist/index.mjs
CHANGED
|
@@ -2088,6 +2088,11 @@ function stripMergeComments(body) {
|
|
|
2088
2088
|
const inFence = fenceTracker();
|
|
2089
2089
|
return body.split("\n").filter((line) => inFence(line) || !mergeCommentPattern.test(line)).join("\n");
|
|
2090
2090
|
}
|
|
2091
|
+
/** Each heading with a single `#` outside a code fence, as written. */
|
|
2092
|
+
function titleHeadings(body) {
|
|
2093
|
+
const inFence = fenceTracker();
|
|
2094
|
+
return body.split("\n").filter((line) => !inFence(line) && headingPattern.exec(line)?.[1] === "#");
|
|
2095
|
+
}
|
|
2091
2096
|
function sectionKey(title) {
|
|
2092
2097
|
return title.trim().replace(/\s+/g, " ").toLowerCase();
|
|
2093
2098
|
}
|
|
@@ -2513,6 +2518,7 @@ function searchReferences(query, layers, options = {}) {
|
|
|
2513
2518
|
}
|
|
2514
2519
|
//#endregion
|
|
2515
2520
|
//#region src/governance-overrides/governance-overrides.ts
|
|
2521
|
+
/** @deprecated Use `managedReferencesDir`. Removed in the next major version. */
|
|
2516
2522
|
function managedGovernancesDir(platform, programData) {
|
|
2517
2523
|
if (platform === "darwin") return "/Library/Application Support/BuddyAgentHarness/governances";
|
|
2518
2524
|
if (platform === "win32") return join(programData || "C:\\ProgramData", "BuddyAgentHarness", "governances");
|
|
@@ -2521,6 +2527,8 @@ function managedGovernancesDir(platform, programData) {
|
|
|
2521
2527
|
/**
|
|
2522
2528
|
* Still read below the layer above, so a machine already using it keeps working; reported as
|
|
2523
2529
|
* deprecated so an admin knows to move it.
|
|
2530
|
+
*
|
|
2531
|
+
* @deprecated Use `managedReferencesDir`. Removed in the next major version.
|
|
2524
2532
|
*/
|
|
2525
2533
|
function deprecatedManagedGovernancesDir(platform, programData) {
|
|
2526
2534
|
if (platform === "darwin") return "/Library/Application Support/UniPlugin/governances";
|
|
@@ -2546,6 +2554,7 @@ function packageGovernancesDir(from = fileURLToPath(import.meta.url)) {
|
|
|
2546
2554
|
dir = parent;
|
|
2547
2555
|
}
|
|
2548
2556
|
}
|
|
2557
|
+
/** @deprecated Use `referenceLayers`. Removed in the next major version. */
|
|
2549
2558
|
function governanceLayers({ root, home, platform, programData }) {
|
|
2550
2559
|
return [
|
|
2551
2560
|
{
|
|
@@ -2573,6 +2582,8 @@ function governanceLayers({ root, home, platform, programData }) {
|
|
|
2573
2582
|
/**
|
|
2574
2583
|
* Filters by scope rather than slicing the list, so a layer added later can't quietly become an
|
|
2575
2584
|
* override by position.
|
|
2585
|
+
*
|
|
2586
|
+
* @deprecated Use `referenceLayers`. Removed in the next major version.
|
|
2576
2587
|
*/
|
|
2577
2588
|
function overrideLayers(layers) {
|
|
2578
2589
|
return layers.filter((layer) => layer.scope !== "package");
|
|
@@ -2581,6 +2592,8 @@ const namePattern = /^[a-z0-9]+(?:[-.][a-z0-9]+)*$/i;
|
|
|
2581
2592
|
/**
|
|
2582
2593
|
* Rejects rather than sanitizes — silently answering a different question is how untrusted input
|
|
2583
2594
|
* reads a file outside the layer.
|
|
2595
|
+
*
|
|
2596
|
+
* @deprecated Use `parseReferenceName`. Removed in the next major version.
|
|
2584
2597
|
*/
|
|
2585
2598
|
function parseGovernanceName(value) {
|
|
2586
2599
|
if (value.endsWith(".md")) throw new Error(`"${value}" names a file. Ask for the governance by name, without the .md extension.`);
|
|
@@ -2602,7 +2615,11 @@ function documentNames(dir) {
|
|
|
2602
2615
|
return [];
|
|
2603
2616
|
}
|
|
2604
2617
|
}
|
|
2605
|
-
/**
|
|
2618
|
+
/**
|
|
2619
|
+
* The first layer that holds the name wins; the rest are not read.
|
|
2620
|
+
*
|
|
2621
|
+
* @deprecated Use `resolveReference`. Removed in the next major version.
|
|
2622
|
+
*/
|
|
2606
2623
|
function resolveGovernance(name, layers) {
|
|
2607
2624
|
for (const { scope, dir } of layers) {
|
|
2608
2625
|
const path = join(dir, `${name}.md`);
|
|
@@ -2615,7 +2632,11 @@ function resolveGovernance(name, layers) {
|
|
|
2615
2632
|
};
|
|
2616
2633
|
}
|
|
2617
2634
|
}
|
|
2618
|
-
/**
|
|
2635
|
+
/**
|
|
2636
|
+
* Every name any layer holds, each reported at the layer that would win, sorted by name.
|
|
2637
|
+
*
|
|
2638
|
+
* @deprecated Use `listReferences`. Removed in the next major version.
|
|
2639
|
+
*/
|
|
2619
2640
|
function listGovernances(layers) {
|
|
2620
2641
|
const found = /* @__PURE__ */ new Map();
|
|
2621
2642
|
for (const { scope, dir } of layers) for (const name of documentNames(dir)) if (!found.has(name)) found.set(name, {
|
|
@@ -3580,12 +3601,61 @@ const governanceShowCommand = command({
|
|
|
3580
3601
|
}
|
|
3581
3602
|
}
|
|
3582
3603
|
});
|
|
3604
|
+
/** @deprecated Use `referenceCommand`. Removed in the next major version. */
|
|
3583
3605
|
const governanceCommand = command({
|
|
3584
3606
|
name: "governance",
|
|
3585
3607
|
description: "Deprecated: use `reference`. Read the governance documents the project, user, and machine-wide override layers hold. Read-only.",
|
|
3586
3608
|
commands: [governanceListCommand, governanceShowCommand]
|
|
3587
3609
|
});
|
|
3588
3610
|
//#endregion
|
|
3611
|
+
//#region src/references/create-reference.ts
|
|
3612
|
+
/** No `#` title: `merge-sections` matches by heading path, so a title would hold every section under it. */
|
|
3613
|
+
const defaultTemplate = `---
|
|
3614
|
+
description: What this reference covers, and when an agent should read it.
|
|
3615
|
+
tags: [topic]
|
|
3616
|
+
---
|
|
3617
|
+
|
|
3618
|
+
## Overview
|
|
3619
|
+
|
|
3620
|
+
Write the guidance here. Give each topic its own top-level \`##\` section.
|
|
3621
|
+
`;
|
|
3622
|
+
function readTemplate(path) {
|
|
3623
|
+
if (path === void 0) return parseTemplate(defaultTemplate, "the default template");
|
|
3624
|
+
let content;
|
|
3625
|
+
try {
|
|
3626
|
+
content = readFileSync(path, "utf8");
|
|
3627
|
+
} catch (error) {
|
|
3628
|
+
throw new Error(`cannot read the template ${path}: ${error.message}`);
|
|
3629
|
+
}
|
|
3630
|
+
return parseTemplate(content, `the template ${path}`);
|
|
3631
|
+
}
|
|
3632
|
+
function parseTemplate(content, label) {
|
|
3633
|
+
let mapping = true;
|
|
3634
|
+
const { metadata, body } = parseDocument(content, () => {
|
|
3635
|
+
mapping = false;
|
|
3636
|
+
});
|
|
3637
|
+
if (!mapping) throw new Error(`${label} has frontmatter that is not a YAML mapping.`);
|
|
3638
|
+
return {
|
|
3639
|
+
content,
|
|
3640
|
+
metadata,
|
|
3641
|
+
body
|
|
3642
|
+
};
|
|
3643
|
+
}
|
|
3644
|
+
/** Adds one line and changes nothing else, so the result still reads as the template it came from. */
|
|
3645
|
+
function withMergeSections(content) {
|
|
3646
|
+
const match = frontmatterPattern.exec(content);
|
|
3647
|
+
if (!match) return `---\nmerge: merge-sections\n---\n\n${content}`;
|
|
3648
|
+
const open = match[0].indexOf("\n") + 1;
|
|
3649
|
+
const newline = match[0][open - 2] === "\r" ? "\r\n" : "\n";
|
|
3650
|
+
const end = open + match[1].length;
|
|
3651
|
+
return `${content.slice(0, end)}${newline}merge: merge-sections${content.slice(end)}`;
|
|
3652
|
+
}
|
|
3653
|
+
function templateWarnings({ metadata, body }) {
|
|
3654
|
+
const warnings = titleHeadings(body).map((heading) => `"${heading.trim()}" is a heading with one hash; merge-sections matches by heading path, so an override would replace or duplicate the whole document. Use top-level ## sections.`);
|
|
3655
|
+
if (typeof metadata["description"] !== "string" || !metadata["description"].trim()) warnings.push("the reference has no `description` in its frontmatter; `list` and `search` show it blank.");
|
|
3656
|
+
return warnings;
|
|
3657
|
+
}
|
|
3658
|
+
//#endregion
|
|
3589
3659
|
//#region src/references/reference.command.ts
|
|
3590
3660
|
const rootOption = {
|
|
3591
3661
|
description: "Directory the project tier is read from, and each folder above it up to the repository root. Defaults to the current directory.",
|
|
@@ -3604,6 +3674,12 @@ function fail(error, fallback) {
|
|
|
3604
3674
|
process.stderr.write(`error: ${error instanceof Error ? error.message : fallback}\n`);
|
|
3605
3675
|
return exitCodes.error;
|
|
3606
3676
|
}
|
|
3677
|
+
function traceOf(resolved, home) {
|
|
3678
|
+
return resolved.trace.map(({ description: _, ...step }) => ({
|
|
3679
|
+
...step,
|
|
3680
|
+
path: collapseHome(home, step.path)
|
|
3681
|
+
}));
|
|
3682
|
+
}
|
|
3607
3683
|
function showEntry(resolved, home, suggestions, trace) {
|
|
3608
3684
|
const entry = {
|
|
3609
3685
|
name: resolved.name,
|
|
@@ -3625,10 +3701,7 @@ function showEntry(resolved, home, suggestions, trace) {
|
|
|
3625
3701
|
if (resolved.status === "missing") entry.suggestions = suggestions;
|
|
3626
3702
|
if (resolved.status === "ambiguous") entry.plugins = resolved.plugins;
|
|
3627
3703
|
entry.warnings = resolved.warnings;
|
|
3628
|
-
if (trace) entry.trace = resolved
|
|
3629
|
-
...step,
|
|
3630
|
-
path: collapseHome(home, step.path)
|
|
3631
|
-
}));
|
|
3704
|
+
if (trace) entry.trace = traceOf(resolved, home);
|
|
3632
3705
|
return entry;
|
|
3633
3706
|
}
|
|
3634
3707
|
function missReason(entry) {
|
|
@@ -3850,14 +3923,92 @@ const referenceWhereCommand = command({
|
|
|
3850
3923
|
}
|
|
3851
3924
|
}
|
|
3852
3925
|
});
|
|
3926
|
+
function parseScope(value) {
|
|
3927
|
+
if (value !== "project" && value !== "user") throw new Error("--scope must be project or user.");
|
|
3928
|
+
return value;
|
|
3929
|
+
}
|
|
3930
|
+
const referenceCreateCommand = command({
|
|
3931
|
+
name: "create",
|
|
3932
|
+
description: "Start a new reference in the project or user tier from a template, marked merge-sections when it overrides a copy below. Never overwrites.",
|
|
3933
|
+
arguments: [{
|
|
3934
|
+
name: "name",
|
|
3935
|
+
description: "Reference name, without the `.md` extension.",
|
|
3936
|
+
type: z.string()
|
|
3937
|
+
}],
|
|
3938
|
+
options: {
|
|
3939
|
+
root: rootOption,
|
|
3940
|
+
template: {
|
|
3941
|
+
description: "File whose text is written as it is, frontmatter included. Defaults to a built-in template of top-level `##` sections with no `#` title.",
|
|
3942
|
+
type: z.optional(z.string())
|
|
3943
|
+
},
|
|
3944
|
+
scope: {
|
|
3945
|
+
description: "Tier to write: project (default), `.agents/references/` at the root, or user, `~/.agents/references/`.",
|
|
3946
|
+
type: z.optional(z.string()),
|
|
3947
|
+
default: "project"
|
|
3948
|
+
},
|
|
3949
|
+
"dry-run": {
|
|
3950
|
+
description: "Print the target path and the exact content, and write nothing.",
|
|
3951
|
+
type: z.optional(z.boolean())
|
|
3952
|
+
},
|
|
3953
|
+
format: {
|
|
3954
|
+
description: "Output format: text (default) writes the path, then the content or the trace; toon and json return an object.",
|
|
3955
|
+
type: z.optional(z.string()),
|
|
3956
|
+
default: "text"
|
|
3957
|
+
}
|
|
3958
|
+
},
|
|
3959
|
+
async run(args) {
|
|
3960
|
+
try {
|
|
3961
|
+
const format = parseFormat(args.format);
|
|
3962
|
+
const name = parseReferenceName(args.name);
|
|
3963
|
+
if (name.plugin !== void 0) throw new Error(`an override is written under the bare name, which every tier resolves; ask for "${name.name}".`);
|
|
3964
|
+
const scope = parseScope(args.scope);
|
|
3965
|
+
const template = readTemplate(args.template === void 0 ? void 0 : resolve(args.template));
|
|
3966
|
+
const home = homedir();
|
|
3967
|
+
const dir = scope === "project" ? projectReferencesDir(resolve(args.root ?? process.cwd())) : join(home, ".agents", "references");
|
|
3968
|
+
const target = join(dir, `${name.name}.md`);
|
|
3969
|
+
const display = (path) => collapseHome(home, path);
|
|
3970
|
+
const layers = await layersFor(args, home);
|
|
3971
|
+
const { trace } = resolveReference(name, layers, { display });
|
|
3972
|
+
const at = layers.findIndex((layer) => layer.dir === dir);
|
|
3973
|
+
if (trace[at]?.found) throw new Error(`${display(trace[at].path)} already holds "${name.name}"; change it with the reference skill's Update mode.`);
|
|
3974
|
+
const shadow = trace.slice(0, at).find((step) => step.found && step.merge === "first-wins");
|
|
3975
|
+
if (shadow) throw new Error(`the ${shadow.tier} copy ${display(shadow.path)} is first-wins, so nothing would read a new ${scope} file; change that copy instead.`);
|
|
3976
|
+
const content = trace.slice(at + 1).some((step) => step.found) && template.metadata["merge"] === void 0 ? withMergeSections(template.content) : template.content;
|
|
3977
|
+
const warnings = templateWarnings(template);
|
|
3978
|
+
const report = {
|
|
3979
|
+
name: name.name,
|
|
3980
|
+
scope,
|
|
3981
|
+
path: display(target),
|
|
3982
|
+
dryRun: Boolean(args["dry-run"]),
|
|
3983
|
+
content,
|
|
3984
|
+
warnings
|
|
3985
|
+
};
|
|
3986
|
+
if (!report.dryRun) {
|
|
3987
|
+
mkdirSync(dir, { recursive: true });
|
|
3988
|
+
writeFileSync(target, content, { flag: "wx" });
|
|
3989
|
+
report.trace = traceOf(resolveReference(name, layers, { display }), home);
|
|
3990
|
+
}
|
|
3991
|
+
if (format !== "text") writeResult(report, format);
|
|
3992
|
+
else {
|
|
3993
|
+
for (const warning of warnings) process.stderr.write(`warning: ${warning}\n`);
|
|
3994
|
+
if (report.trace) process.stdout.write(`${report.path}\n\n${renderText({ trace: report.trace })}\n`);
|
|
3995
|
+
else writeDocument(`${report.path}\n\n${content}`);
|
|
3996
|
+
}
|
|
3997
|
+
return exitCodes.success;
|
|
3998
|
+
} catch (error) {
|
|
3999
|
+
return fail(error, "Reference creation failed.");
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
});
|
|
3853
4003
|
const referenceCommand = command({
|
|
3854
4004
|
name: "reference",
|
|
3855
|
-
description: "Read on-demand reference documents by name, layered across the managed, project, user, and plugin tiers
|
|
4005
|
+
description: "Read on-demand reference documents by name, layered across the managed, project, user, and plugin tiers, and start a new one in the project or user tier.",
|
|
3856
4006
|
commands: [
|
|
3857
4007
|
referenceShowCommand,
|
|
3858
4008
|
referenceListCommand,
|
|
3859
4009
|
referenceSearchCommand,
|
|
3860
|
-
referenceWhereCommand
|
|
4010
|
+
referenceWhereCommand,
|
|
4011
|
+
referenceCreateCommand
|
|
3861
4012
|
]
|
|
3862
4013
|
});
|
|
3863
4014
|
//#endregion
|