buddy-agent-harness 0.20.0 → 0.22.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 +2 -2
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/dist/cli.mjs +14193 -24818
- package/dist/index.d.mts +11 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +183 -772
- package/dist/index.mjs.map +1 -1
- package/dist/skill-scripts/doctor.mjs +122 -180
- package/dist/skill-scripts/init.mjs +122 -180
- package/dist/skill-scripts/mcp.mjs +122 -180
- package/llms.txt +11 -8
- package/package.json +4 -4
- package/plugin.json +2 -2
- package/skills/doctor-buddy-agent-harness/README.md +1 -1
- package/skills/doctor-buddy-agent-harness/SKILL.md +3 -3
- package/skills/doctor-buddy-agent-harness/references/bridges.md +2 -2
- package/skills/doctor-buddy-agent-harness/references/configuration.md +4 -4
- package/skills/doctor-buddy-agent-harness/references/instructions.md +6 -6
- package/skills/doctor-buddy-agent-harness/references/judgment/claude-code.md +64 -0
- package/skills/doctor-buddy-agent-harness/references/judgment/codex.md +19 -0
- package/skills/doctor-buddy-agent-harness/references/judgment/copilot-cli.md +16 -0
- package/skills/doctor-buddy-agent-harness/references/judgment/cursor.md +36 -0
- package/skills/doctor-buddy-agent-harness/references/judgment/devin-desktop.md +19 -0
- package/skills/doctor-buddy-agent-harness/references/judgment/gemini-cli.md +30 -0
- package/skills/doctor-buddy-agent-harness/references/mcp.md +4 -4
- package/skills/doctor-buddy-agent-harness/references/nonstandard.md +4 -4
- package/skills/doctor-buddy-agent-harness/scripts/doctor.mjs +122 -180
- package/skills/enhance/SKILL.md +5 -5
- package/skills/enhance/references/agents-md.md +150 -0
- package/skills/enhance/references/detection.md +36 -0
- package/skills/enhance/references/global-instructions.md +33 -0
- package/skills/enhance/scripts/doctor.mjs +122 -180
- package/skills/init-buddy-agent-harness/SKILL.md +1 -1
- package/skills/init-buddy-agent-harness/scripts/doctor.mjs +122 -180
- package/skills/init-buddy-agent-harness/scripts/init.mjs +122 -180
- package/skills/repair/SKILL.md +3 -3
- package/skills/repair/references/agents-md.md +150 -0
- package/skills/repair/references/classes.md +2 -2
- package/skills/repair/scripts/doctor.mjs +122 -180
- package/skills/repair/scripts/mcp.mjs +122 -180
- package/src/cli.ts +22 -4
- package/src/command-output/command-output.ts +15 -55
- package/src/dep-plugins/dep-plugins.command.ts +8 -14
- package/src/dep-plugins/dep-plugins.ts +1 -1
- package/src/dep-plugins/resolve.ts +1 -1
- package/src/diagnose-bridges/doctor-guidance.ts +64 -54
- package/src/diagnose-bridges/doctor.command.ts +12 -22
- package/src/governance-overrides/governance-overrides.ts +18 -8
- package/src/governance-overrides/governance.command.ts +19 -37
- package/src/harness-registry/harness-registry.ts +3 -4
- package/src/index.ts +5 -8
- package/src/initialize-harnesses/init.command.ts +9 -19
- package/src/initialize-harnesses/initialize-harnesses.ts +2 -2
- package/src/project-mcp/mcp.command.ts +14 -25
- package/src/reconcile-mcp/reconcile.command.ts +15 -26
- package/src/references/references.ts +6 -6
- package/dist/dist-ktNZ1sJo.mjs +0 -2966
- package/dist/esm-BMqwcD7W.mjs +0 -87
- package/dist/esm-CVacKtt6.mjs +0 -64
package/dist/index.mjs
CHANGED
|
@@ -1,614 +1,33 @@
|
|
|
1
|
-
import { declaredDependencies,
|
|
1
|
+
import { declaredDependencies, listReferences, listReferences as listReferences$1, packageDir, parseReferenceName, projectReferencesDir, referenceLayers as referenceLayers$1, referenceNames, resolveReference, searchReferences, skillsDirectories } from "cyber-agentkit";
|
|
2
2
|
import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { cli, command,
|
|
3
|
+
import { cli, command, t } from "clibuilder";
|
|
4
|
+
import { CliError, document, exitCodes, isCliError, printAnswer, result } from "clibuilder/axi";
|
|
4
5
|
import { homedir, platform } from "node:os";
|
|
5
6
|
import { basename, dirname, join, posix, relative, resolve, sep } from "node:path";
|
|
6
7
|
import { parse, stringify } from "smol-toml";
|
|
7
|
-
import { createReferenceCommand } from "
|
|
8
|
+
import { createReferenceCommand } from "cyber-agentkit/commands";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
9
10
|
import { execFileSync } from "node:child_process";
|
|
10
11
|
import { parseTOML } from "toml-eslint-parser";
|
|
11
12
|
import { findNodeAtLocation, parseTree } from "jsonc-parser";
|
|
12
|
-
//#region
|
|
13
|
-
const NULL_LITERAL = "null";
|
|
14
|
-
const DELIMITERS = {
|
|
15
|
-
comma: ",",
|
|
16
|
-
tab: " ",
|
|
17
|
-
pipe: "|"
|
|
18
|
-
};
|
|
19
|
-
const DEFAULT_DELIMITER = DELIMITERS.comma;
|
|
20
|
-
/**
|
|
21
|
-
* Escapes special characters in a string for encoding.
|
|
22
|
-
*
|
|
23
|
-
* @remarks
|
|
24
|
-
* Control characters outside `\n`, `\r`, `\t`, `\\`, and `"` are emitted as `\uXXXX`.
|
|
25
|
-
*/
|
|
26
|
-
function escapeString(value) {
|
|
27
|
-
return value.replace(/\\/g, `\\\\`).replace(/"/g, `\\"`).replace(/\n/g, `\\n`).replace(/\r/g, `\\r`).replace(/\t/g, `\\t`).replace(/[\u0000-\u001F]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
28
|
-
}
|
|
29
|
-
function isBooleanOrNullLiteral(token) {
|
|
30
|
-
return token === "true" || token === "false" || token === "null";
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Assigns an own data property without invoking inherited accessors.
|
|
34
|
-
*
|
|
35
|
-
* @remarks
|
|
36
|
-
* Plain assignment of `__proto__` would hit the `Object.prototype` setter and
|
|
37
|
-
* corrupt the prototype chain; `defineProperty` avoids that but is markedly
|
|
38
|
-
* slower, so every other key takes plain assignment.
|
|
39
|
-
*/
|
|
40
|
-
function setOwnProperty(target, key, value) {
|
|
41
|
-
if (key === "__proto__") {
|
|
42
|
-
Object.defineProperty(target, key, {
|
|
43
|
-
value,
|
|
44
|
-
enumerable: true,
|
|
45
|
-
writable: true,
|
|
46
|
-
configurable: true
|
|
47
|
-
});
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
target[key] = value;
|
|
51
|
-
}
|
|
52
|
-
const COMMENT_LINE_PATTERN = new RegExp(`(?:^?|\\n) *#`);
|
|
53
|
-
/**
|
|
54
|
-
* Pre-formatted string that the encoder emits verbatim at a primitive value
|
|
55
|
-
* position, bypassing quoting, escaping, and number/keyword detection.
|
|
56
|
-
*
|
|
57
|
-
* Returned from a replacer for an object or array value, it is ignored and
|
|
58
|
-
* the container is encoded normally.
|
|
59
|
-
*/
|
|
60
|
-
var RawString = class {
|
|
61
|
-
constructor(value) {
|
|
62
|
-
if (COMMENT_LINE_PATTERN.test(value)) throw new TypeError(`Raw string must not contain a line starting with "#": ${JSON.stringify(value)}`);
|
|
63
|
-
this.value = value;
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
function isRawString(value) {
|
|
67
|
-
return value instanceof RawString;
|
|
68
|
-
}
|
|
69
|
-
const SURROGATE_PATTERN = /[\uD800-\uDFFF]/;
|
|
70
|
-
function normalizeValue(value) {
|
|
71
|
-
if (value === null) return null;
|
|
72
|
-
if (isRawString(value)) return value;
|
|
73
|
-
if (typeof value === "object" && value !== null && "toJSON" in value && typeof value.toJSON === "function") {
|
|
74
|
-
const next = value.toJSON();
|
|
75
|
-
if (next !== value) return normalizeValue(next);
|
|
76
|
-
}
|
|
77
|
-
if (typeof value === "string") {
|
|
78
|
-
assertNoLoneSurrogate(value, "string value");
|
|
79
|
-
return value;
|
|
80
|
-
}
|
|
81
|
-
if (typeof value === "boolean") return value;
|
|
82
|
-
if (typeof value === "number") {
|
|
83
|
-
if (Object.is(value, -0)) return 0;
|
|
84
|
-
if (!Number.isFinite(value)) return null;
|
|
85
|
-
return value;
|
|
86
|
-
}
|
|
87
|
-
if (typeof value === "bigint") {
|
|
88
|
-
if (value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER) return Number(value);
|
|
89
|
-
return value.toString();
|
|
90
|
-
}
|
|
91
|
-
if (value instanceof Date) return value.toISOString();
|
|
92
|
-
if (Array.isArray(value)) return value.map(normalizeValue);
|
|
93
|
-
if (value instanceof Set) return Array.from(value).map(normalizeValue);
|
|
94
|
-
if (value instanceof Map) return Object.fromEntries(Array.from(value, ([k, v]) => [String(k), normalizeValue(v)]));
|
|
95
|
-
if (isPlainObject(value)) {
|
|
96
|
-
const encodedValues = {};
|
|
97
|
-
for (const key in value) if (Object.hasOwn(value, key)) {
|
|
98
|
-
assertNoLoneSurrogate(key, "object key");
|
|
99
|
-
setOwnProperty(encodedValues, key, normalizeValue(value[key]));
|
|
100
|
-
}
|
|
101
|
-
return encodedValues;
|
|
102
|
-
}
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
function assertNoLoneSurrogate(value, context) {
|
|
106
|
-
if (!SURROGATE_PATTERN.test(value)) return;
|
|
107
|
-
for (let index = 0; index < value.length; index++) {
|
|
108
|
-
const code = value.charCodeAt(index);
|
|
109
|
-
if (code < 55296 || code > 57343) continue;
|
|
110
|
-
const isHighSurrogate = code <= 56319;
|
|
111
|
-
const next = value.charCodeAt(index + 1);
|
|
112
|
-
if (isHighSurrogate && next >= 56320 && next <= 57343) {
|
|
113
|
-
index++;
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
throw new TypeError(`Cannot encode ${context} containing an unpaired surrogate U+${code.toString(16).toUpperCase()} at index ${index}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
function isJsonPrimitive(value) {
|
|
120
|
-
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
121
|
-
}
|
|
122
|
-
function isEncodablePrimitive(value) {
|
|
123
|
-
return isJsonPrimitive(value) || isRawString(value);
|
|
124
|
-
}
|
|
125
|
-
function isJsonArray(value) {
|
|
126
|
-
return Array.isArray(value);
|
|
127
|
-
}
|
|
128
|
-
function isJsonObject(value) {
|
|
129
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) && !isRawString(value);
|
|
130
|
-
}
|
|
131
|
-
function isEmptyObject(value) {
|
|
132
|
-
return Object.keys(value).length === 0;
|
|
133
|
-
}
|
|
134
|
-
function isPlainObject(value) {
|
|
135
|
-
if (value === null || typeof value !== "object") return false;
|
|
136
|
-
const prototype = Object.getPrototypeOf(value);
|
|
137
|
-
return prototype === null || prototype === Object.prototype;
|
|
138
|
-
}
|
|
139
|
-
function isArrayOfPrimitives(value) {
|
|
140
|
-
return value.length === 0 || value.every((item) => isEncodablePrimitive(item));
|
|
141
|
-
}
|
|
142
|
-
function isArrayOfArrays(value) {
|
|
143
|
-
return value.length === 0 || value.every((item) => isJsonArray(item));
|
|
144
|
-
}
|
|
145
|
-
function isArrayOfObjects(value) {
|
|
146
|
-
return value.length === 0 || value.every((item) => isJsonObject(item));
|
|
147
|
-
}
|
|
148
|
-
const NUMERIC_LIKE_PATTERN = /^[+-]?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i;
|
|
149
|
-
/** Narrows an arbitrary delimiter option, shared by the library and the CLI so both report it alike. */
|
|
150
|
-
function assertValidDelimiter(delimiter) {
|
|
151
|
-
if (!Object.values(DELIMITERS).includes(delimiter)) throw new TypeError(`Invalid delimiter ${JSON.stringify(delimiter)}. Valid delimiters are: comma (,), tab (\\t), pipe (|)`);
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Checks if a key can be used without quotes.
|
|
155
|
-
*
|
|
156
|
-
* @remarks
|
|
157
|
-
* Valid unquoted keys must start with a letter or underscore,
|
|
158
|
-
* followed by letters, digits, underscores, or dots.
|
|
159
|
-
*/
|
|
160
|
-
function isValidUnquotedKey(key) {
|
|
161
|
-
return /^[A-Z_][\w.]*$/i.test(key);
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Determines if a string value can be safely encoded without quotes.
|
|
165
|
-
*
|
|
166
|
-
* @remarks
|
|
167
|
-
* A string needs quoting if it:
|
|
168
|
-
* - Is empty
|
|
169
|
-
* - Has leading or trailing whitespace
|
|
170
|
-
* - Could be confused with a literal (boolean, null, number)
|
|
171
|
-
* - Contains structural characters (colons, brackets, braces)
|
|
172
|
-
* - Contains quotes or backslashes (need escaping)
|
|
173
|
-
* - Contains control characters (newlines, tabs, etc.)
|
|
174
|
-
* - Contains the active delimiter
|
|
175
|
-
* - Starts with a list marker (hyphen)
|
|
176
|
-
* - Starts with a comment marker (#)
|
|
177
|
-
*/
|
|
178
|
-
function isSafeUnquoted(value, delimiter = DEFAULT_DELIMITER) {
|
|
179
|
-
if (!value) return false;
|
|
180
|
-
if (/^[ \t]|[ \t]$/.test(value)) return false;
|
|
181
|
-
if (isBooleanOrNullLiteral(value) || isNumericLike(value)) return false;
|
|
182
|
-
if (value.includes(":")) return false;
|
|
183
|
-
if (value.includes("\"") || value.includes("\\")) return false;
|
|
184
|
-
if (/[[\]{}]/.test(value)) return false;
|
|
185
|
-
if (/[\u0000-\u001F]/.test(value)) return false;
|
|
186
|
-
if (value.includes(delimiter)) return false;
|
|
187
|
-
if (value.startsWith("-")) return false;
|
|
188
|
-
if (value.startsWith("#")) return false;
|
|
189
|
-
return true;
|
|
190
|
-
}
|
|
191
|
-
function isNumericLike(value) {
|
|
192
|
-
return NUMERIC_LIKE_PATTERN.test(value);
|
|
193
|
-
}
|
|
194
|
-
function encodePrimitive(value, delimiter) {
|
|
195
|
-
if (isRawString(value)) return value.value;
|
|
196
|
-
if (value === null) return NULL_LITERAL;
|
|
197
|
-
if (typeof value === "boolean") return String(value);
|
|
198
|
-
if (typeof value === "number") return String(value);
|
|
199
|
-
return encodeStringLiteral(value, delimiter);
|
|
200
|
-
}
|
|
201
|
-
function encodeStringLiteral(value, delimiter = DEFAULT_DELIMITER) {
|
|
202
|
-
if (isSafeUnquoted(value, delimiter)) return value;
|
|
203
|
-
return `"${escapeString(value)}"`;
|
|
204
|
-
}
|
|
205
|
-
function encodeKey(key) {
|
|
206
|
-
if (isValidUnquotedKey(key)) return key;
|
|
207
|
-
return `"${escapeString(key)}"`;
|
|
208
|
-
}
|
|
209
|
-
function encodeAndJoinPrimitives(values, delimiter = DEFAULT_DELIMITER) {
|
|
210
|
-
return values.map((v) => encodePrimitive(v, delimiter)).join(delimiter);
|
|
211
|
-
}
|
|
212
|
-
function formatHeader(length, options) {
|
|
213
|
-
const key = options?.key;
|
|
214
|
-
const fields = options?.fields;
|
|
215
|
-
const delimiter = options?.delimiter ?? ",";
|
|
216
|
-
let header = "";
|
|
217
|
-
if (key != null) header += encodeKey(key);
|
|
218
|
-
header += `[${length}${options?.keyed ? ":" : ""}${delimiter !== DEFAULT_DELIMITER ? delimiter : ""}]`;
|
|
219
|
-
if (fields) header += `{${formatFieldSegment(fields, delimiter)}}`;
|
|
220
|
-
header += ":";
|
|
221
|
-
return header;
|
|
222
|
-
}
|
|
223
|
-
function formatFieldSegment(fields, delimiter) {
|
|
224
|
-
return fields.map((field) => encodeKey(field.name) + (field.children ? `{${formatFieldSegment(field.children, delimiter)}}` : "")).join(delimiter);
|
|
225
|
-
}
|
|
226
|
-
/** Classifies rows into a tabular field list, or undefined when they are not uniformly tabular. */
|
|
227
|
-
function extractTabularFields(rows) {
|
|
228
|
-
if (rows.length === 0) return;
|
|
229
|
-
const firstKeys = Object.keys(rows[0]);
|
|
230
|
-
if (firstKeys.length === 0) return;
|
|
231
|
-
for (const row of rows) {
|
|
232
|
-
if (Object.keys(row).length !== firstKeys.length) return;
|
|
233
|
-
for (const key of firstKeys) if (!Object.hasOwn(row, key)) return;
|
|
234
|
-
}
|
|
235
|
-
const fieldNodes = [];
|
|
236
|
-
for (const key of firstKeys) {
|
|
237
|
-
const fieldNode = classifyColumn(key, rows.map((row) => row[key]));
|
|
238
|
-
if (!fieldNode) return;
|
|
239
|
-
fieldNodes.push(fieldNode);
|
|
240
|
-
}
|
|
241
|
-
return fieldNodes;
|
|
242
|
-
}
|
|
243
|
-
/** Classifies an object's values as a keyed tabular field list (>=2 uniform non-empty object entries), or undefined. */
|
|
244
|
-
function extractKeyedTabularFields(value) {
|
|
245
|
-
const entryValues = Object.values(value);
|
|
246
|
-
if (entryValues.length < 2) return;
|
|
247
|
-
if (!entryValues.every((entryValue) => isJsonObject(entryValue) && !isEmptyObject(entryValue))) return;
|
|
248
|
-
return extractTabularFields(entryValues);
|
|
249
|
-
}
|
|
250
|
-
/** Reads one row's leaf cells in the field order `extractTabularFields` produced. */
|
|
251
|
-
function collectRowLeaves(row, fields) {
|
|
252
|
-
const leaves = [];
|
|
253
|
-
collectLeafValues(row, fields, leaves);
|
|
254
|
-
return leaves;
|
|
255
|
-
}
|
|
256
|
-
function classifyColumn(name, values) {
|
|
257
|
-
if (values.every((value) => isEncodablePrimitive(value))) return { name };
|
|
258
|
-
if (!values.every((value) => isJsonObject(value) && !isEmptyObject(value))) return;
|
|
259
|
-
const children = extractTabularFields(values);
|
|
260
|
-
if (!children) return;
|
|
261
|
-
return {
|
|
262
|
-
name,
|
|
263
|
-
children
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
function collectLeafValues(row, fields, leaves) {
|
|
267
|
-
for (const field of fields) {
|
|
268
|
-
const value = row[field.name];
|
|
269
|
-
if (field.children) collectLeafValues(value, field.children, leaves);
|
|
270
|
-
else leaves.push(value);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
function* encodeJsonValue(value, options, depth) {
|
|
274
|
-
if (isEncodablePrimitive(value)) {
|
|
275
|
-
const encodedPrimitive = encodePrimitive(value, options.delimiter);
|
|
276
|
-
if (encodedPrimitive !== "") yield encodedPrimitive;
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
if (isJsonArray(value)) yield* encodeArrayLines(void 0, value, depth, options);
|
|
280
|
-
else if (isJsonObject(value)) {
|
|
281
|
-
const keyedFields = extractKeyedTabularFields(value);
|
|
282
|
-
if (keyedFields) {
|
|
283
|
-
yield* encodeKeyedObjectLines(void 0, value, keyedFields, depth, options);
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
yield* encodeObjectLines(value, depth, options);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
function* encodeObjectLines(value, depth, options) {
|
|
290
|
-
for (const [key, val] of Object.entries(value)) yield* encodeKeyValuePairLines(key, val, depth, options);
|
|
291
|
-
}
|
|
292
|
-
function* encodeKeyValuePairLines(key, value, depth, options) {
|
|
293
|
-
const encodedKey = encodeKey(key);
|
|
294
|
-
if (isEncodablePrimitive(value)) yield indentedLine(depth, `${encodedKey}: ${encodePrimitive(value, options.delimiter)}`, options.indentSize);
|
|
295
|
-
else if (isJsonArray(value)) yield* encodeArrayLines(key, value, depth, options);
|
|
296
|
-
else if (isJsonObject(value)) {
|
|
297
|
-
const keyedFields = extractKeyedTabularFields(value);
|
|
298
|
-
if (keyedFields) {
|
|
299
|
-
yield* encodeKeyedObjectLines(key, value, keyedFields, depth, options);
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
yield indentedLine(depth, `${encodedKey}:`, options.indentSize);
|
|
303
|
-
if (!isEmptyObject(value)) yield* encodeObjectLines(value, depth + 1, options);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
function* encodeKeyedObjectLines(key, value, fields, depth, options) {
|
|
307
|
-
const entries = Object.entries(value);
|
|
308
|
-
yield indentedLine(depth, formatHeader(entries.length, {
|
|
309
|
-
key,
|
|
310
|
-
fields,
|
|
311
|
-
delimiter: options.delimiter,
|
|
312
|
-
keyed: true
|
|
313
|
-
}), options.indentSize);
|
|
314
|
-
yield* encodeKeyedEntryRowsLines(entries, fields, depth + 1, options);
|
|
315
|
-
}
|
|
316
|
-
function* encodeKeyedEntryRowsLines(entries, fields, depth, options) {
|
|
317
|
-
for (const [entryKey, entryValue] of entries) {
|
|
318
|
-
const leaves = collectRowLeaves(entryValue, fields);
|
|
319
|
-
yield indentedLine(depth, `${encodeKey(entryKey)}: ${encodeAndJoinPrimitives(leaves, options.delimiter)}`, options.indentSize);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
function* encodeArrayLines(key, value, depth, options) {
|
|
323
|
-
if (value.length === 0) {
|
|
324
|
-
yield indentedLine(depth, key != null ? `${encodeKey(key)}: []` : "[]", options.indentSize);
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
if (isArrayOfPrimitives(value)) {
|
|
328
|
-
yield indentedLine(depth, encodeInlineArrayLine(value, options.delimiter, key), options.indentSize);
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
if (isArrayOfArrays(value)) {
|
|
332
|
-
if (value.every((arr) => isArrayOfPrimitives(arr))) {
|
|
333
|
-
yield* encodeArrayOfArraysAsListItemsLines(key, value, depth, options);
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
if (isArrayOfObjects(value)) {
|
|
338
|
-
const fields = extractTabularFields(value);
|
|
339
|
-
if (fields) yield* encodeArrayOfObjectsAsTabularLines(key, value, fields, depth, options);
|
|
340
|
-
else yield* encodeMixedArrayAsListItemsLines(key, value, depth, options);
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
yield* encodeMixedArrayAsListItemsLines(key, value, depth, options);
|
|
344
|
-
}
|
|
345
|
-
function* encodeArrayOfArraysAsListItemsLines(prefix, values, depth, options) {
|
|
346
|
-
yield indentedLine(depth, formatHeader(values.length, {
|
|
347
|
-
key: prefix,
|
|
348
|
-
delimiter: options.delimiter
|
|
349
|
-
}), options.indentSize);
|
|
350
|
-
for (const arr of values) if (isArrayOfPrimitives(arr)) {
|
|
351
|
-
const arrayLine = encodeInlineArrayLine(arr, options.delimiter);
|
|
352
|
-
yield indentedListItem(depth + 1, arrayLine, options.indentSize);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
function encodeInlineArrayLine(values, delimiter, prefix) {
|
|
356
|
-
const header = formatHeader(values.length, {
|
|
357
|
-
key: prefix,
|
|
358
|
-
delimiter
|
|
359
|
-
});
|
|
360
|
-
const joinedValue = encodeAndJoinPrimitives(values, delimiter);
|
|
361
|
-
if (values.length === 0) return header;
|
|
362
|
-
return `${header} ${joinedValue}`;
|
|
363
|
-
}
|
|
364
|
-
function* encodeArrayOfObjectsAsTabularLines(prefix, rows, fields, depth, options) {
|
|
365
|
-
yield indentedLine(depth, formatHeader(rows.length, {
|
|
366
|
-
key: prefix,
|
|
367
|
-
fields,
|
|
368
|
-
delimiter: options.delimiter
|
|
369
|
-
}), options.indentSize);
|
|
370
|
-
yield* writeTabularRowsLines(rows, fields, depth + 1, options);
|
|
371
|
-
}
|
|
372
|
-
function* writeTabularRowsLines(rows, fields, depth, options) {
|
|
373
|
-
for (const row of rows) yield indentedLine(depth, encodeAndJoinPrimitives(collectRowLeaves(row, fields), options.delimiter), options.indentSize);
|
|
374
|
-
}
|
|
375
|
-
function* encodeMixedArrayAsListItemsLines(prefix, items, depth, options) {
|
|
376
|
-
yield indentedLine(depth, formatHeader(items.length, {
|
|
377
|
-
key: prefix,
|
|
378
|
-
delimiter: options.delimiter
|
|
379
|
-
}), options.indentSize);
|
|
380
|
-
for (const item of items) yield* encodeListItemValueLines(item, depth + 1, options);
|
|
381
|
-
}
|
|
382
|
-
function* encodeObjectAsListItemLines(obj, depth, options) {
|
|
383
|
-
if (isEmptyObject(obj)) {
|
|
384
|
-
yield indentedLine(depth, "-", options.indentSize);
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
const entries = Object.entries(obj);
|
|
388
|
-
const [firstKey, firstValue] = entries[0];
|
|
389
|
-
const restEntries = entries.slice(1);
|
|
390
|
-
if (isJsonArray(firstValue) && isArrayOfObjects(firstValue)) {
|
|
391
|
-
const fields = extractTabularFields(firstValue);
|
|
392
|
-
if (fields) {
|
|
393
|
-
yield indentedListItem(depth, formatHeader(firstValue.length, {
|
|
394
|
-
key: firstKey,
|
|
395
|
-
fields,
|
|
396
|
-
delimiter: options.delimiter
|
|
397
|
-
}), options.indentSize);
|
|
398
|
-
yield* writeTabularRowsLines(firstValue, fields, depth + 2, options);
|
|
399
|
-
if (restEntries.length > 0) yield* encodeObjectLines(Object.fromEntries(restEntries), depth + 1, options);
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
if (isJsonObject(firstValue)) {
|
|
404
|
-
const keyedFields = extractKeyedTabularFields(firstValue);
|
|
405
|
-
if (keyedFields) {
|
|
406
|
-
const keyedEntries = Object.entries(firstValue);
|
|
407
|
-
yield indentedListItem(depth, formatHeader(keyedEntries.length, {
|
|
408
|
-
key: firstKey,
|
|
409
|
-
fields: keyedFields,
|
|
410
|
-
delimiter: options.delimiter,
|
|
411
|
-
keyed: true
|
|
412
|
-
}), options.indentSize);
|
|
413
|
-
yield* encodeKeyedEntryRowsLines(keyedEntries, keyedFields, depth + 2, options);
|
|
414
|
-
if (restEntries.length > 0) yield* encodeObjectLines(Object.fromEntries(restEntries), depth + 1, options);
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
const encodedKey = encodeKey(firstKey);
|
|
419
|
-
if (isEncodablePrimitive(firstValue)) yield indentedListItem(depth, `${encodedKey}: ${encodePrimitive(firstValue, options.delimiter)}`, options.indentSize);
|
|
420
|
-
else if (isJsonArray(firstValue)) if (firstValue.length === 0) yield indentedListItem(depth, `${encodedKey}: []`, options.indentSize);
|
|
421
|
-
else if (isArrayOfPrimitives(firstValue)) yield indentedListItem(depth, `${encodedKey}${encodeInlineArrayLine(firstValue, options.delimiter)}`, options.indentSize);
|
|
422
|
-
else {
|
|
423
|
-
yield indentedListItem(depth, `${encodedKey}${formatHeader(firstValue.length, { delimiter: options.delimiter })}`, options.indentSize);
|
|
424
|
-
for (const item of firstValue) yield* encodeListItemValueLines(item, depth + 2, options);
|
|
425
|
-
}
|
|
426
|
-
else if (isJsonObject(firstValue)) {
|
|
427
|
-
yield indentedListItem(depth, `${encodedKey}:`, options.indentSize);
|
|
428
|
-
if (!isEmptyObject(firstValue)) yield* encodeObjectLines(firstValue, depth + 2, options);
|
|
429
|
-
}
|
|
430
|
-
if (restEntries.length > 0) yield* encodeObjectLines(Object.fromEntries(restEntries), depth + 1, options);
|
|
431
|
-
}
|
|
432
|
-
function* encodeListItemValueLines(value, depth, options) {
|
|
433
|
-
if (isEncodablePrimitive(value)) yield indentedListItem(depth, encodePrimitive(value, options.delimiter), options.indentSize);
|
|
434
|
-
else if (isJsonArray(value)) if (isArrayOfPrimitives(value)) yield indentedListItem(depth, encodeInlineArrayLine(value, options.delimiter), options.indentSize);
|
|
435
|
-
else {
|
|
436
|
-
yield indentedListItem(depth, formatHeader(value.length, { delimiter: options.delimiter }), options.indentSize);
|
|
437
|
-
for (const item of value) yield* encodeListItemValueLines(item, depth + 1, options);
|
|
438
|
-
}
|
|
439
|
-
else if (isJsonObject(value)) yield* encodeObjectAsListItemLines(value, depth, options);
|
|
440
|
-
}
|
|
441
|
-
function indentedLine(depth, content, indentSize) {
|
|
442
|
-
return " ".repeat(indentSize * depth) + content;
|
|
443
|
-
}
|
|
444
|
-
function indentedListItem(depth, content, indentSize) {
|
|
445
|
-
return indentedLine(depth, "- " + content, indentSize);
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Applies a replacer function to a `JsonValue` and all its descendants.
|
|
449
|
-
*
|
|
450
|
-
* The replacer is called for the root (key='', path=[]), every object property
|
|
451
|
-
* (key = property name), and every array element (key = string index).
|
|
452
|
-
*/
|
|
453
|
-
function applyReplacer(root, replacer) {
|
|
454
|
-
const replacedRoot = replacer("", root, []);
|
|
455
|
-
if (replacedRoot === void 0) return transformChildren(root, replacer, []);
|
|
456
|
-
return transformReplaced(root, replacedRoot, replacer, []);
|
|
457
|
-
}
|
|
458
|
-
/**
|
|
459
|
-
* Resolves a replacer's (non-`undefined`) return value at a single position.
|
|
460
|
-
*
|
|
461
|
-
* A `RawString` only stands in for a primitive: returned for an object or
|
|
462
|
-
* array value, it is ignored and the original container is traversed normally.
|
|
463
|
-
*/
|
|
464
|
-
function transformReplaced(original, replaced, replacer, path) {
|
|
465
|
-
if (isRawString(replaced) && !isEncodablePrimitive(original)) return transformChildren(original, replacer, path);
|
|
466
|
-
return transformChildren(normalizeValue(replaced), replacer, path);
|
|
467
|
-
}
|
|
468
|
-
function transformChildren(value, replacer, path) {
|
|
469
|
-
if (isJsonObject(value)) return transformObject(value, replacer, path);
|
|
470
|
-
if (isJsonArray(value)) return transformArray(value, replacer, path);
|
|
471
|
-
return value;
|
|
472
|
-
}
|
|
473
|
-
function transformObject(obj, replacer, path) {
|
|
474
|
-
const result = {};
|
|
475
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
476
|
-
const childPath = [...path, key];
|
|
477
|
-
const replacedValue = replacer(key, value, childPath);
|
|
478
|
-
if (replacedValue === void 0) continue;
|
|
479
|
-
setOwnProperty(result, key, transformReplaced(value, replacedValue, replacer, childPath));
|
|
480
|
-
}
|
|
481
|
-
return result;
|
|
482
|
-
}
|
|
483
|
-
function transformArray(arr, replacer, path) {
|
|
484
|
-
const result = [];
|
|
485
|
-
for (let i = 0; i < arr.length; i++) {
|
|
486
|
-
const value = arr[i];
|
|
487
|
-
const childPath = [...path, i];
|
|
488
|
-
const replacedValue = replacer(String(i), value, childPath);
|
|
489
|
-
if (replacedValue === void 0) continue;
|
|
490
|
-
result.push(transformReplaced(value, replacedValue, replacer, childPath));
|
|
491
|
-
}
|
|
492
|
-
return result;
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* Encodes a JavaScript value into TOON format string.
|
|
496
|
-
*
|
|
497
|
-
* @param input Any JavaScript value (objects, arrays, primitives)
|
|
498
|
-
* @param options Optional encoding configuration
|
|
499
|
-
* @returns TOON formatted string
|
|
500
|
-
*
|
|
501
|
-
* @example
|
|
502
|
-
* ```ts
|
|
503
|
-
* encode({ name: 'Ada', age: 30 })
|
|
504
|
-
* // name: Ada
|
|
505
|
-
* // age: 30
|
|
506
|
-
*
|
|
507
|
-
* encode({ users: [{ id: 1 }, { id: 2 }] })
|
|
508
|
-
* // users[2]{id}:
|
|
509
|
-
* // 1
|
|
510
|
-
* // 2
|
|
511
|
-
*
|
|
512
|
-
* encode({ tags: [] })
|
|
513
|
-
* // tags: []
|
|
514
|
-
*
|
|
515
|
-
* encode(data, { indentSize: 4 })
|
|
516
|
-
* ```
|
|
517
|
-
*/
|
|
518
|
-
function encode(input, options) {
|
|
519
|
-
return Array.from(encodeLines(input, options)).join("\n");
|
|
520
|
-
}
|
|
13
|
+
//#region src/command-output/command-output.ts
|
|
521
14
|
/**
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
* This function yields TOON lines one at a time without building the full string,
|
|
525
|
-
* making it suitable for streaming large outputs to files, HTTP responses, or process stdout.
|
|
526
|
-
*
|
|
527
|
-
* @param input Any JavaScript value (objects, arrays, primitives)
|
|
528
|
-
* @param options Optional encoding configuration
|
|
529
|
-
* @returns Iterable of TOON lines (without trailing newlines)
|
|
530
|
-
*
|
|
531
|
-
* @example
|
|
532
|
-
* ```ts
|
|
533
|
-
* // Stream to stdout
|
|
534
|
-
* for (const line of encodeLines({ name: 'Ada', age: 30 })) {
|
|
535
|
-
* console.log(line)
|
|
536
|
-
* }
|
|
537
|
-
*
|
|
538
|
-
* // Collect to array
|
|
539
|
-
* const lines = Array.from(encodeLines(data))
|
|
540
|
-
*
|
|
541
|
-
* // Equivalent to encode()
|
|
542
|
-
* const toonString = Array.from(encodeLines(data, options)).join('\n')
|
|
543
|
-
* ```
|
|
15
|
+
* Reports a failure on stderr, not stdout — stdout carries what an agent parses — and fails the cli
|
|
16
|
+
* with the exit code. A `CliError` the cli already reports is rethrown untouched.
|
|
544
17
|
*/
|
|
545
|
-
function
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}
|
|
550
|
-
function resolveOptions(options) {
|
|
551
|
-
const delimiter = options?.delimiter ?? DEFAULT_DELIMITER;
|
|
552
|
-
assertValidDelimiter(delimiter);
|
|
553
|
-
return {
|
|
554
|
-
indentSize: options?.indentSize ?? options?.indent ?? 2,
|
|
555
|
-
delimiter,
|
|
556
|
-
replacer: options?.replacer
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
//#endregion
|
|
560
|
-
//#region src/is-record/is-record.ts
|
|
561
|
-
function isRecord(value) {
|
|
562
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
563
|
-
}
|
|
564
|
-
//#endregion
|
|
565
|
-
//#region src/command-output/command-output.ts
|
|
566
|
-
const formats = [
|
|
567
|
-
"toon",
|
|
568
|
-
"json",
|
|
569
|
-
"text"
|
|
570
|
-
];
|
|
571
|
-
/** Rejects anything but the supported formats so an unknown value never falls back silently. */
|
|
572
|
-
function parseFormat(value) {
|
|
573
|
-
if (!formats.includes(value)) throw new Error("--format must be toon, json, or text.");
|
|
574
|
-
return value;
|
|
575
|
-
}
|
|
576
|
-
/** The single stdout boundary: internal logic stays on plain objects, encoding happens here. */
|
|
577
|
-
function writeResult(value, format) {
|
|
578
|
-
process.stdout.write(`${encodeResult(value, format)}\n`);
|
|
18
|
+
function fail(error, fallback) {
|
|
19
|
+
if (isCliError(error)) throw error;
|
|
20
|
+
process.stderr.write(`error: ${error instanceof Error ? error.message : fallback}\n`);
|
|
21
|
+
throw CliError.reported(exitCodes.error);
|
|
579
22
|
}
|
|
580
23
|
/**
|
|
581
|
-
*
|
|
582
|
-
*
|
|
24
|
+
* An answer that comes with a failing exit code. The cli prints only an answer a command returns, and
|
|
25
|
+
* a thrown error replaces the return, so the answer is printed first.
|
|
583
26
|
*/
|
|
584
|
-
function
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
if (format === "json") return JSON.stringify(value);
|
|
589
|
-
return format === "text" ? renderText(value) : encode(value);
|
|
590
|
-
}
|
|
591
|
-
function cell(value) {
|
|
592
|
-
return value === void 0 ? "" : String(value);
|
|
593
|
-
}
|
|
594
|
-
function table(rows) {
|
|
595
|
-
const columns = [...new Set(rows.flatMap((row) => Object.keys(row)))];
|
|
596
|
-
const widths = columns.map((column) => Math.max(column.length, ...rows.map((row) => cell(row[column]).length)));
|
|
597
|
-
const line = (values) => ` ${values.map((value, index) => value.padEnd(widths[index])).join(" ")}`.trimEnd();
|
|
598
|
-
return [line(columns), ...rows.map((row) => line(columns.map((column) => cell(row[column]))))];
|
|
599
|
-
}
|
|
600
|
-
/** Renders for a person, not an agent — TOON stays the default since that's what an agent parses. */
|
|
601
|
-
function renderText(value) {
|
|
602
|
-
const blocks = Object.entries(value).map(([key, item]) => {
|
|
603
|
-
if (!Array.isArray(item)) return [`${key}: ${isRecord(item) ? JSON.stringify(item) : String(item)}`];
|
|
604
|
-
if (!item.length) return [`${key}: (none)`];
|
|
605
|
-
const body = item.every(isRecord) ? table(item) : item.map((entry) => ` - ${String(entry)}`);
|
|
606
|
-
return [`${key}:`, ...body];
|
|
607
|
-
});
|
|
608
|
-
return blocks.flatMap((block, index) => {
|
|
609
|
-
const previous = blocks[index - 1];
|
|
610
|
-
return previous && (previous.length > 1 || block.length > 1) ? ["", ...block] : block;
|
|
611
|
-
}).join("\n");
|
|
27
|
+
function answerThenFail(answer, format) {
|
|
28
|
+
const text = printAnswer(answer, format);
|
|
29
|
+
if (text !== void 0) process.stdout.write(text);
|
|
30
|
+
throw CliError.reported(exitCodes.error);
|
|
612
31
|
}
|
|
613
32
|
/**
|
|
614
33
|
* AXI §10: collapses the home directory to `~` so a path in a report is portable to another
|
|
@@ -990,21 +409,15 @@ const depPluginsCommand = command({
|
|
|
990
409
|
options: {
|
|
991
410
|
root: {
|
|
992
411
|
description: "Repository directory. Defaults to the current directory.",
|
|
993
|
-
type:
|
|
412
|
+
type: t.string().optional()
|
|
994
413
|
},
|
|
995
414
|
check: {
|
|
996
415
|
description: "Report whether the catalog is current without writing it. For CI and postinstall.",
|
|
997
|
-
type:
|
|
998
|
-
},
|
|
999
|
-
format: {
|
|
1000
|
-
description: "Output format: toon (default), json, or text for a human-readable report.",
|
|
1001
|
-
type: z.optional(z.string()),
|
|
1002
|
-
default: "toon"
|
|
416
|
+
type: t.boolean().optional()
|
|
1003
417
|
}
|
|
1004
418
|
},
|
|
1005
419
|
run(args) {
|
|
1006
420
|
try {
|
|
1007
|
-
const format = parseFormat(args.format);
|
|
1008
421
|
const root = args.root ?? process.cwd();
|
|
1009
422
|
const derived = deriveCatalog(root);
|
|
1010
423
|
const outcome = args.check ? catalogStatus(root, derived.catalog) === "unchanged" ? "current" : "would-change" : writeCatalog(root, derived.catalog);
|
|
@@ -1036,7 +449,7 @@ const depPluginsCommand = command({
|
|
|
1036
449
|
});
|
|
1037
450
|
}
|
|
1038
451
|
if (derived.plugins.length) notes.push(COPILOT_LIMITATION);
|
|
1039
|
-
|
|
452
|
+
const report = {
|
|
1040
453
|
catalog: CATALOG_PATH,
|
|
1041
454
|
marketplace: derived.catalog.name,
|
|
1042
455
|
outcome,
|
|
@@ -1048,11 +461,11 @@ const depPluginsCommand = command({
|
|
|
1048
461
|
harness: harness.length ? harness : "no supported harness detected on this machine",
|
|
1049
462
|
actions: actions.length ? actions : "0 actions — every detected harness matches the catalog",
|
|
1050
463
|
...notes.length ? { notes } : {}
|
|
1051
|
-
}
|
|
1052
|
-
|
|
464
|
+
};
|
|
465
|
+
const answer = result(report);
|
|
466
|
+
return outcome === "would-change" ? answerThenFail(answer, this.format) : answer;
|
|
1053
467
|
} catch (error) {
|
|
1054
|
-
|
|
1055
|
-
return exitCodes.error;
|
|
468
|
+
return fail(error, "Catalog generation failed.");
|
|
1056
469
|
}
|
|
1057
470
|
}
|
|
1058
471
|
});
|
|
@@ -1078,7 +491,7 @@ function filesUnder(directory) {
|
|
|
1078
491
|
function skillsProjection(harness, scope, directories = skillsDirectories(harness)?.[scope]) {
|
|
1079
492
|
const listed = directories ?? [];
|
|
1080
493
|
const [first] = listed;
|
|
1081
|
-
if (first === void 0) throw new Error(
|
|
494
|
+
if (first === void 0) throw new Error(`cyber-agentkit records no ${scope} skills directories for ${harness}.`);
|
|
1082
495
|
return listed.includes(".agents/skills") ? {} : { skillsDirectory: first };
|
|
1083
496
|
}
|
|
1084
497
|
/**
|
|
@@ -1088,7 +501,7 @@ function skillsProjection(harness, scope, directories = skillsDirectories(harnes
|
|
|
1088
501
|
*/
|
|
1089
502
|
const globalInstructions = ".agents/AGENTS.md";
|
|
1090
503
|
/**
|
|
1091
|
-
* Skills directories come from
|
|
504
|
+
* Skills directories come from `cyber-agentkit`, except for the harnesses it does not know
|
|
1092
505
|
* (`devin-desktop`, `windsurf`); see `.research/agentic-configuration-standards/` for the rest.
|
|
1093
506
|
*/
|
|
1094
507
|
const harnessRegistry = [
|
|
@@ -1346,7 +759,7 @@ function repairsOf(table) {
|
|
|
1346
759
|
}));
|
|
1347
760
|
}
|
|
1348
761
|
const commandInvocation = "buddy-agent-harness";
|
|
1349
|
-
const skillInvocation = (version) => `npx -y ${commandInvocation}
|
|
762
|
+
const skillInvocation = (version) => `npx -y ${commandInvocation}@${version}`;
|
|
1350
763
|
/** Named for the subcommand it runs, so a stack trace or process list shows which one it was. */
|
|
1351
764
|
const launcherFor = (subcommand) => `scripts/${subcommand}.mjs`;
|
|
1352
765
|
/**
|
|
@@ -1356,6 +769,14 @@ const launcherFor = (subcommand) => `scripts/${subcommand}.mjs`;
|
|
|
1356
769
|
const launcherInvocation = (subcommand) => `node ${launcherFor(subcommand)}`;
|
|
1357
770
|
const repairSkillInvocation = "/buddy-agent-harness:repair";
|
|
1358
771
|
const initSkillInvocation = `/buddy-agent-harness:init-buddy-agent-harness`;
|
|
772
|
+
/**
|
|
773
|
+
* A skill reference as the CLI prints it and the generated skill carries it: pnpm links a skill as
|
|
774
|
+
* `pnpm-<package>-<skill>`, where the plugin form resolves to nothing. This is the form
|
|
775
|
+
* `universal-plugin plugin build` writes, so a build over generated text changes nothing.
|
|
776
|
+
*/
|
|
777
|
+
const pnpmSkillRef = (invocation) => `\`${invocation}\` (pnpm install: \`pnpm-${commandInvocation}-${invocation.slice(invocation.indexOf(":") + 1)}\`)`;
|
|
778
|
+
const initSkillRef = pnpmSkillRef(initSkillInvocation);
|
|
779
|
+
const repairSkillRef = pnpmSkillRef(repairSkillInvocation);
|
|
1359
780
|
/** Where the user authors the golden MCP server set, named in every MCP repair that points at it. */
|
|
1360
781
|
const goldenSet = ".agents/buddy-agent-harness/mcp.toml";
|
|
1361
782
|
const bridgeTable = {
|
|
@@ -1365,7 +786,7 @@ const bridgeTable = {
|
|
|
1365
786
|
command: `${cli} init`,
|
|
1366
787
|
instruction: `run \`${cli} init\` to create .agents/skills and the bridges into it`
|
|
1367
788
|
}),
|
|
1368
|
-
skillRepair: () => `run
|
|
789
|
+
skillRepair: () => `run ${initSkillRef}, which creates \`.agents/skills\` and the bridges`
|
|
1369
790
|
},
|
|
1370
791
|
missing: {
|
|
1371
792
|
detail: "no bridge at this path — the harness sees zero project skills",
|
|
@@ -1373,7 +794,7 @@ const bridgeTable = {
|
|
|
1373
794
|
command: `${cli} init`,
|
|
1374
795
|
instruction: `run \`${cli} init\` to create the bridge at ${file}`
|
|
1375
796
|
}),
|
|
1376
|
-
skillRepair: () => `run
|
|
797
|
+
skillRepair: () => `run ${initSkillRef}`
|
|
1377
798
|
},
|
|
1378
799
|
degraded: {
|
|
1379
800
|
detail: "expected a directory but found a regular file — checkout without core.symlinks",
|
|
@@ -1442,49 +863,49 @@ const instructionTable = {
|
|
|
1442
863
|
detail: "no AGENTS.md at the repository root, so the instructions this repository has reach one harness at most",
|
|
1443
864
|
repair: () => ({
|
|
1444
865
|
command: "",
|
|
1445
|
-
instruction: `hand this to
|
|
866
|
+
instruction: `hand this to ${initSkillRef}, which consolidates what the repository has into AGENTS.md, or derives it`
|
|
1446
867
|
}),
|
|
1447
|
-
skillRepair: () => `run
|
|
868
|
+
skillRepair: () => `run ${initSkillRef}, which consolidates or derives AGENTS.md`
|
|
1448
869
|
},
|
|
1449
870
|
"instructions-missing": {
|
|
1450
871
|
detail: "no instruction bridge at this path — the harness reads none of AGENTS.md",
|
|
1451
872
|
repair: ({ file }) => ({
|
|
1452
873
|
command: "",
|
|
1453
|
-
instruction: `hand ${file} to
|
|
874
|
+
instruction: `hand ${file} to ${initSkillRef}, which writes the bridge into it`
|
|
1454
875
|
}),
|
|
1455
|
-
skillRepair: () => `run
|
|
876
|
+
skillRepair: () => `run ${initSkillRef}`
|
|
1456
877
|
},
|
|
1457
878
|
"instructions-unbridged": {
|
|
1458
879
|
detail: "the file is present but names AGENTS.md nowhere — the harness reads none of it",
|
|
1459
880
|
repair: ({ file }) => ({
|
|
1460
881
|
command: "",
|
|
1461
|
-
instruction: `hand ${file} to
|
|
882
|
+
instruction: `hand ${file} to ${initSkillRef}, which adds the bridge without discarding what the file already says`
|
|
1462
883
|
}),
|
|
1463
|
-
skillRepair: () => `run
|
|
884
|
+
skillRepair: () => `run ${initSkillRef}, which adds the bridge without discarding what the file already says`
|
|
1464
885
|
},
|
|
1465
886
|
"instructions-shadowing": {
|
|
1466
887
|
detail: "this file suppresses the AGENTS.md beside it — the harness reads this instead, and none of AGENTS.md",
|
|
1467
888
|
repair: ({ file }) => ({
|
|
1468
889
|
command: "",
|
|
1469
|
-
instruction: `hand ${file} to
|
|
890
|
+
instruction: `hand ${file} to ${initSkillRef}, which consolidates what it says into AGENTS.md, or adds an @AGENTS.md import above it where the file has to stay`
|
|
1470
891
|
}),
|
|
1471
|
-
skillRepair: () => `run
|
|
892
|
+
skillRepair: () => `run ${initSkillRef}, which consolidates the file into AGENTS.md or imports AGENTS.md from it`
|
|
1472
893
|
},
|
|
1473
894
|
"instructions-superseded": {
|
|
1474
895
|
detail: "a bridge from before the harness read AGENTS.md itself — it still works, and nothing needs it",
|
|
1475
896
|
repair: ({ file }) => ({
|
|
1476
897
|
command: "",
|
|
1477
|
-
instruction: `remove ${file}, or keep it for sessions that cannot read AGENTS.md directly —
|
|
898
|
+
instruction: `remove ${file}, or keep it for sessions that cannot read AGENTS.md directly — ${initSkillRef} offers the choice`
|
|
1478
899
|
}),
|
|
1479
|
-
skillRepair: () => `run
|
|
900
|
+
skillRepair: () => `run ${initSkillRef}, which offers to remove it`
|
|
1480
901
|
},
|
|
1481
902
|
"instructions-unreadable": {
|
|
1482
903
|
detail: "the settings file does not parse, so the harness reads none of it",
|
|
1483
904
|
repair: ({ file }) => ({
|
|
1484
905
|
command: "",
|
|
1485
|
-
instruction: `fix the JSON in ${file} by hand, then hand it to
|
|
906
|
+
instruction: `fix the JSON in ${file} by hand, then hand it to ${initSkillRef}`
|
|
1486
907
|
}),
|
|
1487
|
-
skillRepair: () => `fix the JSON by hand, then run
|
|
908
|
+
skillRepair: () => `fix the JSON by hand, then run ${initSkillRef}`
|
|
1488
909
|
}
|
|
1489
910
|
};
|
|
1490
911
|
const instructionRepairs = repairsOf(instructionTable);
|
|
@@ -1556,33 +977,33 @@ const configurationTable = {
|
|
|
1556
977
|
detail: "a projection under a harness name that has been superseded — the replacement reads .agents/skills natively and needs no projection at all",
|
|
1557
978
|
repair: ({ file }) => ({
|
|
1558
979
|
command: "",
|
|
1559
|
-
instruction: `remove ${file} and enable the harness that replaced it —
|
|
980
|
+
instruction: `remove ${file} and enable the harness that replaced it — ${repairSkillRef} offers the correction`
|
|
1560
981
|
}),
|
|
1561
|
-
skillRepair: () => `run
|
|
982
|
+
skillRepair: () => `run ${repairSkillRef}`
|
|
1562
983
|
},
|
|
1563
984
|
"ignored-bridge": {
|
|
1564
985
|
detail: "a .gitignore rule matches this bridge — an untracked bridge swallows a real edit silently",
|
|
1565
986
|
repair: ({ file }) => ({
|
|
1566
987
|
command: "",
|
|
1567
|
-
instruction: `narrow or remove the .gitignore rule matching ${file} —
|
|
988
|
+
instruction: `narrow or remove the .gitignore rule matching ${file} — ${repairSkillRef} offers the correction`
|
|
1568
989
|
}),
|
|
1569
|
-
skillRepair: () => `run
|
|
990
|
+
skillRepair: () => `run ${repairSkillRef}`
|
|
1570
991
|
},
|
|
1571
992
|
"unread-local-override": {
|
|
1572
993
|
detail: "no harness reads this filename, so everything in it is invisible to every agent",
|
|
1573
994
|
repair: ({ file }) => ({
|
|
1574
995
|
command: "",
|
|
1575
|
-
instruction: `move ${file} to CLAUDE.local.md, or consolidate it into AGENTS.md —
|
|
996
|
+
instruction: `move ${file} to CLAUDE.local.md, or consolidate it into AGENTS.md — ${repairSkillRef} offers the correction`
|
|
1576
997
|
}),
|
|
1577
|
-
skillRepair: () => `run
|
|
998
|
+
skillRepair: () => `run ${repairSkillRef}`
|
|
1578
999
|
},
|
|
1579
1000
|
"unloadable-skill": {
|
|
1580
1001
|
detail: "frontmatter that does not parse, or no description — either one makes a harness skip the skill outright",
|
|
1581
1002
|
repair: ({ file }) => ({
|
|
1582
1003
|
command: "",
|
|
1583
|
-
instruction: `quote the description in ${file}, or add one —
|
|
1004
|
+
instruction: `quote the description in ${file}, or add one — ${repairSkillRef} offers the correction`
|
|
1584
1005
|
}),
|
|
1585
|
-
skillRepair: () => `run
|
|
1006
|
+
skillRepair: () => `run ${repairSkillRef}`
|
|
1586
1007
|
}
|
|
1587
1008
|
};
|
|
1588
1009
|
repairsOf(configurationTable);
|
|
@@ -1611,33 +1032,33 @@ const mcpTable = {
|
|
|
1611
1032
|
detail: "the golden set declares this server and the harness config does not carry it",
|
|
1612
1033
|
repair: ({ file, server }) => ({
|
|
1613
1034
|
command: "",
|
|
1614
|
-
instruction: `add the server ${server} to ${file}, or drop it from the golden set —
|
|
1035
|
+
instruction: `add the server ${server} to ${file}, or drop it from the golden set — ${repairSkillRef} offers the correction`
|
|
1615
1036
|
}),
|
|
1616
|
-
skillRepair: (at) => `hand ${locatorText(at)} to
|
|
1037
|
+
skillRepair: (at) => `hand ${locatorText(at)} to ${repairSkillRef}, which projects it from its golden entry with \`mcp project\` — or drop it from the golden set`
|
|
1617
1038
|
},
|
|
1618
1039
|
"mcp-undeclared": {
|
|
1619
1040
|
detail: "the harness config carries this server and the golden set does not declare it",
|
|
1620
1041
|
repair: ({ file, server }) => ({
|
|
1621
1042
|
command: "",
|
|
1622
|
-
instruction: `add the server ${server} to ${goldenSet}, or drop it from ${file} —
|
|
1043
|
+
instruction: `add the server ${server} to ${goldenSet}, or drop it from ${file} — ${repairSkillRef} offers the import`
|
|
1623
1044
|
}),
|
|
1624
|
-
skillRepair: (at) => `hand ${locatorText(at)} to
|
|
1045
|
+
skillRepair: (at) => `hand ${locatorText(at)} to ${repairSkillRef}, which imports it field by field with \`mcp reconcile\`, refusing any literal credential — or drop it from ${at.file}`
|
|
1625
1046
|
},
|
|
1626
1047
|
"mcp-diverged-target": {
|
|
1627
1048
|
detail: "only the harness config changed since the two last agreed — the edit was made through the copy",
|
|
1628
1049
|
repair: (at) => ({
|
|
1629
1050
|
command: "",
|
|
1630
|
-
instruction: `reconcile the value at ${locatorText(at)} back into ${goldenSet} —
|
|
1051
|
+
instruction: `reconcile the value at ${locatorText(at)} back into ${goldenSet} — ${repairSkillRef} offers the import`
|
|
1631
1052
|
}),
|
|
1632
|
-
skillRepair: (at) => `hand ${locatorText(at)} to
|
|
1053
|
+
skillRepair: (at) => `hand ${locatorText(at)} to ${repairSkillRef}, which imports the field into ${goldenSet} with \`mcp reconcile\` once approved`
|
|
1633
1054
|
},
|
|
1634
1055
|
"mcp-diverged-golden": {
|
|
1635
1056
|
detail: "only the golden set changed since the two last agreed — the harness copy is stale",
|
|
1636
1057
|
repair: (at) => ({
|
|
1637
1058
|
command: "",
|
|
1638
|
-
instruction: `update ${locatorText(at)} from the golden entry —
|
|
1059
|
+
instruction: `update ${locatorText(at)} from the golden entry — ${repairSkillRef} offers the correction`
|
|
1639
1060
|
}),
|
|
1640
|
-
skillRepair: (at) => `hand ${locatorText(at)} to
|
|
1061
|
+
skillRepair: (at) => `hand ${locatorText(at)} to ${repairSkillRef}, which updates it from the golden entry with \`mcp project\``
|
|
1641
1062
|
},
|
|
1642
1063
|
"mcp-diverged-both": {
|
|
1643
1064
|
detail: "both sides changed since they last agreed — merging either way would discard the other",
|
|
@@ -1682,33 +1103,33 @@ const nonstandardTable = {
|
|
|
1682
1103
|
detail: "instruction content only one harness reads — AGENTS.md carries the same prose to all of them",
|
|
1683
1104
|
repair: ({ file }) => ({
|
|
1684
1105
|
command: "",
|
|
1685
|
-
instruction: `consolidate ${file} into AGENTS.md —
|
|
1106
|
+
instruction: `consolidate ${file} into AGENTS.md — ${initSkillRef} offers the consolidation, and keeps the content reachable where this harness needs it`
|
|
1686
1107
|
}),
|
|
1687
|
-
skillRepair: ({ file }) => `hand ${file} to
|
|
1108
|
+
skillRepair: ({ file }) => `hand ${file} to ${initSkillRef}, which consolidates it into AGENTS.md`
|
|
1688
1109
|
},
|
|
1689
1110
|
"nonstandard-rule": {
|
|
1690
1111
|
detail: "a path-scoped rule only one harness reads — a skill reaches every harness where the scoping is incidental",
|
|
1691
1112
|
repair: ({ file }) => ({
|
|
1692
1113
|
command: "",
|
|
1693
|
-
instruction: `convert ${file} into a skill under .agents/skills where its guidance is not tied to the paths it names —
|
|
1114
|
+
instruction: `convert ${file} into a skill under .agents/skills where its guidance is not tied to the paths it names — ${initSkillRef} offers the conversion; keep it where the path scoping is the point`
|
|
1694
1115
|
}),
|
|
1695
|
-
skillRepair: ({ file }) => `hand ${file} to
|
|
1116
|
+
skillRepair: ({ file }) => `hand ${file} to ${initSkillRef} to convert into a skill, unless the path scoping is load-bearing`
|
|
1696
1117
|
},
|
|
1697
1118
|
"nonstandard-command": {
|
|
1698
1119
|
detail: "a harness command file — a skill is the portable form, and this harness reads skills too",
|
|
1699
1120
|
repair: ({ file }) => ({
|
|
1700
1121
|
command: "",
|
|
1701
|
-
instruction: `move ${file} to .agents/skills/<name>/SKILL.md —
|
|
1122
|
+
instruction: `move ${file} to .agents/skills/<name>/SKILL.md — ${initSkillRef} offers the move`
|
|
1702
1123
|
}),
|
|
1703
|
-
skillRepair: ({ file }) => `hand ${file} to
|
|
1124
|
+
skillRepair: ({ file }) => `hand ${file} to ${initSkillRef}, which moves it to .agents/skills`
|
|
1704
1125
|
},
|
|
1705
1126
|
"nonstandard-skill": {
|
|
1706
1127
|
detail: "a skill under a harness directory rather than the canonical one, where only that harness finds it",
|
|
1707
1128
|
repair: ({ file }) => ({
|
|
1708
1129
|
command: "",
|
|
1709
|
-
instruction: `move ${file} to .agents/skills and project it back where the harness needs one —
|
|
1130
|
+
instruction: `move ${file} to .agents/skills and project it back where the harness needs one — ${initSkillRef} offers the move`
|
|
1710
1131
|
}),
|
|
1711
|
-
skillRepair: ({ file }) => `hand ${file} to
|
|
1132
|
+
skillRepair: ({ file }) => `hand ${file} to ${initSkillRef}, which moves it to .agents/skills`
|
|
1712
1133
|
},
|
|
1713
1134
|
"nonstandard-subagent": {
|
|
1714
1135
|
detail: "a subagent definition with no cross-harness form — nothing outside this harness can read it, and no canonical form exists yet",
|
|
@@ -1789,9 +1210,9 @@ The command is read-only. It never repairs anything, so it is safe to run at any
|
|
|
1789
1210
|
|
|
1790
1211
|
\`command\` is empty whenever no single invocation does the job, and that emptiness is the signal: act on \`instruction\` and do not assemble a command out of it. A runnable invocation quoted *inside* an \`instruction\` is not the repair either — \`diverged-both\` names \`git diff --no-index\` because the diff shows you what differs, not because running it reconciles anything. Apply the repair, then re-run \`doctor\`.
|
|
1791
1212
|
|
|
1792
|
-
Nothing in \`help\` is wrapped.
|
|
1213
|
+
Nothing in \`help\` is wrapped.
|
|
1793
1214
|
|
|
1794
|
-
Do not run an \`init\` command yourself. Rebuilding a skills bridge can move skills a user wrote, and rewriting an instruction file touches prose a person authored — both are the \`init-buddy-agent-harness\` skill's judgment, so hand the repair to
|
|
1215
|
+
Do not run an \`init\` command yourself. Rebuilding a skills bridge can move skills a user wrote, and rewriting an instruction file touches prose a person authored — both are the \`init-buddy-agent-harness\` skill's judgment, so hand the repair to ${initSkillRef} instead. Every such repair carries an empty \`command\`: a skill invocation has no shell equivalent at all.
|
|
1795
1216
|
|
|
1796
1217
|
When every bridge resolves, \`findings\` says so outright rather than being empty.
|
|
1797
1218
|
|
|
@@ -1867,6 +1288,11 @@ function diagnoseConfiguration({ root, git, cli }) {
|
|
|
1867
1288
|
return findings;
|
|
1868
1289
|
}
|
|
1869
1290
|
//#endregion
|
|
1291
|
+
//#region src/is-record/is-record.ts
|
|
1292
|
+
function isRecord(value) {
|
|
1293
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1294
|
+
}
|
|
1295
|
+
//#endregion
|
|
1870
1296
|
//#region src/diagnose-mcp/mcp-model.ts
|
|
1871
1297
|
/** Every field of the model, in the order a finding reports them. */
|
|
1872
1298
|
const mcpFields = [
|
|
@@ -2628,18 +2054,31 @@ function diagnoseNonstandard({ root, cli }) {
|
|
|
2628
2054
|
}
|
|
2629
2055
|
//#endregion
|
|
2630
2056
|
//#region src/governance-overrides/governance-overrides.ts
|
|
2631
|
-
|
|
2057
|
+
function machineWideDir(platform, programData, names) {
|
|
2058
|
+
if (platform === "darwin") return join("/Library/Application Support", names.macos, "governances");
|
|
2059
|
+
if (platform === "win32") return join(programData || "C:\\ProgramData", names.windows, "governances");
|
|
2060
|
+
return join("/etc", names.posix, "governances");
|
|
2061
|
+
}
|
|
2062
|
+
/** @deprecated Use the `reference` command's layers. Removed in the next major version. */
|
|
2632
2063
|
function managedGovernancesDir(platform, programData) {
|
|
2633
|
-
return
|
|
2064
|
+
return machineWideDir(platform, programData, {
|
|
2065
|
+
posix: "buddy-agent-harness",
|
|
2066
|
+
macos: "BuddyAgentHarness",
|
|
2067
|
+
windows: "BuddyAgentHarness"
|
|
2068
|
+
});
|
|
2634
2069
|
}
|
|
2635
2070
|
/**
|
|
2636
2071
|
* Still read below the layer above, so a machine already using it keeps working; reported as
|
|
2637
2072
|
* deprecated so an admin knows to move it.
|
|
2638
2073
|
*
|
|
2639
|
-
* @deprecated Use `
|
|
2074
|
+
* @deprecated Use the `reference` command's layers. Removed in the next major version.
|
|
2640
2075
|
*/
|
|
2641
2076
|
function deprecatedManagedGovernancesDir(platform, programData) {
|
|
2642
|
-
return
|
|
2077
|
+
return machineWideDir(platform, programData, {
|
|
2078
|
+
posix: "universal-plugin",
|
|
2079
|
+
macos: "UniPlugin",
|
|
2080
|
+
windows: "UniPlugin"
|
|
2081
|
+
});
|
|
2643
2082
|
}
|
|
2644
2083
|
function projectGovernancesDir(root) {
|
|
2645
2084
|
return join(root, ".agents", "governances");
|
|
@@ -2755,7 +2194,7 @@ function listGovernances(layers) {
|
|
|
2755
2194
|
//#endregion
|
|
2756
2195
|
//#region src/references/references.ts
|
|
2757
2196
|
const PACKAGE_PLUGIN = "buddy-agent-harness";
|
|
2758
|
-
/**
|
|
2197
|
+
/** `cyber-agentkit`'s reference layers, with this package as the plugin whose own references come first. */
|
|
2759
2198
|
function referenceLayers({ packageRoot = dirname(packageGovernancesDir()), ...options }) {
|
|
2760
2199
|
return referenceLayers$1({
|
|
2761
2200
|
...options,
|
|
@@ -3489,25 +2928,19 @@ const doctorCommand = command({
|
|
|
3489
2928
|
options: {
|
|
3490
2929
|
root: {
|
|
3491
2930
|
description: "Repository or package directory. Defaults to the current directory.",
|
|
3492
|
-
type:
|
|
2931
|
+
type: t.string().optional()
|
|
3493
2932
|
},
|
|
3494
2933
|
harness: {
|
|
3495
2934
|
description: "Comma-separated harnesses to check in addition to Claude Code and Cursor, e.g. codex,gemini-cli.",
|
|
3496
|
-
type:
|
|
3497
|
-
},
|
|
3498
|
-
format: {
|
|
3499
|
-
description: "Output format: toon (default), json, or text for a human-readable report.",
|
|
3500
|
-
type: z.optional(z.string()),
|
|
3501
|
-
default: "toon"
|
|
2935
|
+
type: t.string().optional()
|
|
3502
2936
|
}
|
|
3503
2937
|
},
|
|
3504
2938
|
async run(args) {
|
|
3505
2939
|
try {
|
|
3506
|
-
const format = parseFormat(args.format);
|
|
3507
2940
|
const harnesses = parseHarnesses(args.harness);
|
|
3508
2941
|
const root = args.root ?? process.cwd();
|
|
3509
2942
|
const home = homedir();
|
|
3510
|
-
const
|
|
2943
|
+
const diagnosis = diagnoseBridges({
|
|
3511
2944
|
root,
|
|
3512
2945
|
...harnesses.length ? { harnesses } : {},
|
|
3513
2946
|
cli: commandInvocation,
|
|
@@ -3531,24 +2964,21 @@ const doctorCommand = command({
|
|
|
3531
2964
|
cli: commandInvocation
|
|
3532
2965
|
})
|
|
3533
2966
|
];
|
|
3534
|
-
const { rows } = listReferences$1(await referenceLayers({
|
|
2967
|
+
const { layers: rows } = listReferences$1(await referenceLayers({
|
|
3535
2968
|
root,
|
|
3536
2969
|
home,
|
|
3537
2970
|
platform: process.platform,
|
|
3538
|
-
programData: process.env["ProgramData"],
|
|
3539
2971
|
env: process.env
|
|
3540
2972
|
}));
|
|
3541
|
-
const held = rows.filter(({ tier }) => tier !== "plugin").map(({ name, tier, path,
|
|
2973
|
+
const held = rows.filter(({ tier }) => tier !== "plugin").map(({ name, tier, path, outcome }) => ({
|
|
3542
2974
|
name,
|
|
3543
2975
|
tier,
|
|
3544
2976
|
path: collapseHome(home, path),
|
|
3545
|
-
status
|
|
2977
|
+
status: outcome
|
|
3546
2978
|
}));
|
|
3547
|
-
|
|
3548
|
-
return exitCodes.success;
|
|
2979
|
+
return result(buildDoctorReport(displayBinPath(home, process.argv[1]), diagnosis, configuration, held));
|
|
3549
2980
|
} catch (error) {
|
|
3550
|
-
|
|
3551
|
-
return exitCodes.error;
|
|
2981
|
+
return fail(error, "Harness diagnosis failed.");
|
|
3552
2982
|
}
|
|
3553
2983
|
}
|
|
3554
2984
|
});
|
|
@@ -3567,32 +2997,26 @@ const GOVERNANCE_DEPRECATED = "warning: \"governance\" is deprecated and will be
|
|
|
3567
2997
|
const DEPRECATED_MANAGED = "deprecated — move these documents to the managed layer above";
|
|
3568
2998
|
const rootOption = {
|
|
3569
2999
|
description: "Repository or package directory. Defaults to the current directory.",
|
|
3570
|
-
type:
|
|
3000
|
+
type: t.string().optional()
|
|
3571
3001
|
};
|
|
3572
3002
|
const overridesOnlyOption = {
|
|
3573
3003
|
description: "Resolve only the project, user, and machine-wide layers. Never returns a governance this package ships, and exits non-zero when no layer has one.",
|
|
3574
|
-
type:
|
|
3004
|
+
type: t.boolean().optional()
|
|
3575
3005
|
};
|
|
3576
3006
|
const governanceListCommand = command({
|
|
3577
3007
|
name: "list",
|
|
3578
3008
|
description: "List every governance the override layers hold, each at the layer that would win.",
|
|
3579
3009
|
options: {
|
|
3580
3010
|
root: rootOption,
|
|
3581
|
-
"overrides-only": overridesOnlyOption
|
|
3582
|
-
format: {
|
|
3583
|
-
description: "Output format: toon (default), json, or text for a human-readable report.",
|
|
3584
|
-
type: z.optional(z.string()),
|
|
3585
|
-
default: "toon"
|
|
3586
|
-
}
|
|
3011
|
+
"overrides-only": overridesOnlyOption
|
|
3587
3012
|
},
|
|
3588
3013
|
run(args) {
|
|
3589
3014
|
process.stderr.write(GOVERNANCE_DEPRECATED);
|
|
3590
3015
|
try {
|
|
3591
|
-
const format = parseFormat(args.format);
|
|
3592
3016
|
const home = homedir();
|
|
3593
3017
|
const layers = layersFor(args);
|
|
3594
3018
|
const entries = listGovernances(layers);
|
|
3595
|
-
|
|
3019
|
+
const report = {
|
|
3596
3020
|
layers: layers.map(({ scope, dir }) => ({
|
|
3597
3021
|
scope,
|
|
3598
3022
|
path: collapseHome(home, dir),
|
|
@@ -3603,11 +3027,10 @@ const governanceListCommand = command({
|
|
|
3603
3027
|
scope,
|
|
3604
3028
|
path: collapseHome(home, path)
|
|
3605
3029
|
})) : "0 governances — no layer holds one"
|
|
3606
|
-
}
|
|
3607
|
-
return
|
|
3030
|
+
};
|
|
3031
|
+
return result(report);
|
|
3608
3032
|
} catch (error) {
|
|
3609
|
-
|
|
3610
|
-
return exitCodes.error;
|
|
3033
|
+
return fail(error, "Governance listing failed.");
|
|
3611
3034
|
}
|
|
3612
3035
|
}
|
|
3613
3036
|
});
|
|
@@ -3617,38 +3040,31 @@ const governanceShowCommand = command({
|
|
|
3617
3040
|
arguments: [{
|
|
3618
3041
|
name: "name",
|
|
3619
3042
|
description: "The governance name, without the `.md` extension.",
|
|
3620
|
-
type:
|
|
3043
|
+
type: t.string()
|
|
3621
3044
|
}],
|
|
3622
3045
|
options: {
|
|
3623
3046
|
root: rootOption,
|
|
3624
|
-
"overrides-only": overridesOnlyOption
|
|
3625
|
-
format: {
|
|
3626
|
-
description: "Output format: text (default) writes the document itself; toon and json wrap it with the layer it came from.",
|
|
3627
|
-
type: z.optional(z.string()),
|
|
3628
|
-
default: "text"
|
|
3629
|
-
}
|
|
3047
|
+
"overrides-only": overridesOnlyOption
|
|
3630
3048
|
},
|
|
3631
3049
|
run(args) {
|
|
3050
|
+
const format = this.format;
|
|
3632
3051
|
process.stderr.write(GOVERNANCE_DEPRECATED);
|
|
3633
3052
|
try {
|
|
3634
|
-
const format = parseFormat(args.format);
|
|
3635
3053
|
const name = parseGovernanceName(args.name);
|
|
3636
3054
|
const found = resolveGovernance(name, layersFor(args));
|
|
3637
3055
|
if (!found) {
|
|
3638
3056
|
process.stderr.write(args["overrides-only"] ? `error: no override for governance "${name}" in the project, user, or machine-wide layer.\n` : `error: no governance named "${name}" in any layer.\n`);
|
|
3639
|
-
|
|
3057
|
+
throw CliError.reported(exitCodes.error);
|
|
3640
3058
|
}
|
|
3641
|
-
|
|
3642
|
-
else writeResult({
|
|
3059
|
+
const report = {
|
|
3643
3060
|
name: found.name,
|
|
3644
3061
|
scope: found.scope,
|
|
3645
3062
|
path: collapseHome(homedir(), found.path),
|
|
3646
3063
|
content: found.content
|
|
3647
|
-
}
|
|
3648
|
-
return
|
|
3064
|
+
};
|
|
3065
|
+
return format === "text" ? document(report.content) : result(report);
|
|
3649
3066
|
} catch (error) {
|
|
3650
|
-
|
|
3651
|
-
return exitCodes.error;
|
|
3067
|
+
return fail(error, "Governance lookup failed.");
|
|
3652
3068
|
}
|
|
3653
3069
|
}
|
|
3654
3070
|
});
|
|
@@ -3664,7 +3080,10 @@ function initializeHarnesses({ root, harnesses: preferred = [], copy = false, fo
|
|
|
3664
3080
|
const canonicalSkills = join(root, ".agents", "skills");
|
|
3665
3081
|
const skills = countSkills(canonicalSkills);
|
|
3666
3082
|
mkdirSync(projectReferencesDir(root), { recursive: true });
|
|
3667
|
-
const references = referenceNames(
|
|
3083
|
+
const references = referenceNames([{
|
|
3084
|
+
tier: "project",
|
|
3085
|
+
dir: projectReferencesDir(root)
|
|
3086
|
+
}]).length;
|
|
3668
3087
|
const harnesses = selectHarnesses(root, preferred);
|
|
3669
3088
|
const { linked, skipped } = projectSkills({
|
|
3670
3089
|
root,
|
|
@@ -3696,40 +3115,32 @@ const initCommand = command({
|
|
|
3696
3115
|
options: {
|
|
3697
3116
|
root: {
|
|
3698
3117
|
description: "Repository or package directory. Defaults to the current directory.",
|
|
3699
|
-
type:
|
|
3118
|
+
type: t.string().optional()
|
|
3700
3119
|
},
|
|
3701
3120
|
harness: {
|
|
3702
3121
|
description: "Comma-separated harnesses to enable in addition to Claude Code and Cursor, e.g. codex,gemini-cli.",
|
|
3703
|
-
type:
|
|
3122
|
+
type: t.string().optional()
|
|
3704
3123
|
},
|
|
3705
3124
|
copy: {
|
|
3706
3125
|
description: "Copy skills instead of linking them.",
|
|
3707
|
-
type:
|
|
3126
|
+
type: t.boolean().optional()
|
|
3708
3127
|
},
|
|
3709
3128
|
force: {
|
|
3710
3129
|
description: "Replace conflicting target skill directories. Names the targets to replace, comma-separated (e.g. .claude/skills); given alone it replaces every conflicting target.",
|
|
3711
|
-
type:
|
|
3712
|
-
},
|
|
3713
|
-
format: {
|
|
3714
|
-
description: "Output format: toon (default), json, or text for a human-readable report.",
|
|
3715
|
-
type: z.optional(z.string()),
|
|
3716
|
-
default: "toon"
|
|
3130
|
+
type: t.string().optional()
|
|
3717
3131
|
}
|
|
3718
3132
|
},
|
|
3719
3133
|
run(args) {
|
|
3720
3134
|
try {
|
|
3721
|
-
const format = parseFormat(args.format);
|
|
3722
3135
|
const harnesses = parseHarnesses(args.harness);
|
|
3723
|
-
|
|
3136
|
+
return result(initializeHarnesses({
|
|
3724
3137
|
root: args.root ?? process.cwd(),
|
|
3725
3138
|
...harnesses.length ? { harnesses } : {},
|
|
3726
3139
|
...args.copy === void 0 ? {} : { copy: args.copy },
|
|
3727
3140
|
...args.force === void 0 ? {} : { force: parseForce(args.force) }
|
|
3728
|
-
})
|
|
3729
|
-
return exitCodes.success;
|
|
3141
|
+
}));
|
|
3730
3142
|
} catch (error) {
|
|
3731
|
-
|
|
3732
|
-
return exitCodes.error;
|
|
3143
|
+
return fail(error, "Harness initialization failed.");
|
|
3733
3144
|
}
|
|
3734
3145
|
}
|
|
3735
3146
|
});
|
|
@@ -4170,21 +3581,15 @@ const mcpReconcileCommand = command({
|
|
|
4170
3581
|
options: {
|
|
4171
3582
|
root: {
|
|
4172
3583
|
description: "Repository or package directory. Defaults to the current directory.",
|
|
4173
|
-
type:
|
|
3584
|
+
type: t.string().optional()
|
|
4174
3585
|
},
|
|
4175
3586
|
accept: {
|
|
4176
3587
|
description: "Approve one field by the path a dry run lists for it; repeat for each field. There is no approve-all.",
|
|
4177
|
-
type:
|
|
4178
|
-
},
|
|
4179
|
-
format: {
|
|
4180
|
-
description: "Output format: toon (default), json, or text for a human-readable report.",
|
|
4181
|
-
type: z.optional(z.string()),
|
|
4182
|
-
default: "toon"
|
|
3588
|
+
type: t.array(t.string()).optional()
|
|
4183
3589
|
}
|
|
4184
3590
|
},
|
|
4185
3591
|
run(args) {
|
|
4186
3592
|
try {
|
|
4187
|
-
const format = parseFormat(args.format);
|
|
4188
3593
|
const root = args.root ?? process.cwd();
|
|
4189
3594
|
const accept = args.accept ?? [];
|
|
4190
3595
|
const plan = reconcileMcp({
|
|
@@ -4195,31 +3600,27 @@ const mcpReconcileCommand = command({
|
|
|
4195
3600
|
if (plan.kind === "unreadable") {
|
|
4196
3601
|
const at = plan.position ? ` at line ${plan.position.line}, column ${plan.position.column}` : "";
|
|
4197
3602
|
process.stderr.write(`error: ${goldenSetPath} does not parse${at}.\n`);
|
|
4198
|
-
|
|
3603
|
+
throw CliError.reported(exitCodes.error);
|
|
4199
3604
|
}
|
|
4200
3605
|
if (plan.kind === "rejected") {
|
|
4201
3606
|
process.stderr.write(`error: nothing written.\n${plan.reasons.map((reason) => `- ${reason}\n`).join("")}`);
|
|
4202
|
-
|
|
4203
|
-
}
|
|
4204
|
-
if (plan.kind === "absent") {
|
|
4205
|
-
writeResult({
|
|
4206
|
-
golden: `0 servers — no golden set at ${goldenSetPath}`,
|
|
4207
|
-
mode: "nothing to reconcile into",
|
|
4208
|
-
fields: "0 fields",
|
|
4209
|
-
record: "not written"
|
|
4210
|
-
}, format);
|
|
4211
|
-
return exitCodes.success;
|
|
3607
|
+
throw CliError.reported(exitCodes.error);
|
|
4212
3608
|
}
|
|
4213
|
-
|
|
3609
|
+
if (plan.kind === "absent") return result({
|
|
3610
|
+
golden: `0 servers — no golden set at ${goldenSetPath}`,
|
|
3611
|
+
mode: "nothing to reconcile into",
|
|
3612
|
+
fields: "0 fields",
|
|
3613
|
+
record: "not written"
|
|
3614
|
+
});
|
|
3615
|
+
const report = {
|
|
4214
3616
|
golden: goldenSetPath,
|
|
4215
3617
|
mode: plan.written ? "written — only the fields named by --accept" : "dry run — nothing written; re-run with --accept <path> for each approved field",
|
|
4216
3618
|
fields: plan.rows.length ? plan.rows : "0 fields — no harness holds a change the golden set lacks",
|
|
4217
3619
|
record: plan.written ? projectionRecordPath : "not written — dry run"
|
|
4218
|
-
}
|
|
4219
|
-
return
|
|
3620
|
+
};
|
|
3621
|
+
return result(report);
|
|
4220
3622
|
} catch (error) {
|
|
4221
|
-
|
|
4222
|
-
return exitCodes.error;
|
|
3623
|
+
return fail(error, "MCP reconcile failed.");
|
|
4223
3624
|
}
|
|
4224
3625
|
}
|
|
4225
3626
|
});
|
|
@@ -4468,21 +3869,15 @@ const mcpProjectCommand = command({
|
|
|
4468
3869
|
options: {
|
|
4469
3870
|
root: {
|
|
4470
3871
|
description: "Repository or package directory. Defaults to the current directory.",
|
|
4471
|
-
type:
|
|
3872
|
+
type: t.string().optional()
|
|
4472
3873
|
},
|
|
4473
3874
|
write: {
|
|
4474
3875
|
description: "Apply the plan and record what was projected. Without it nothing on disk changes.",
|
|
4475
|
-
type:
|
|
4476
|
-
},
|
|
4477
|
-
format: {
|
|
4478
|
-
description: "Output format: toon (default), json, or text for a human-readable report.",
|
|
4479
|
-
type: z.optional(z.string()),
|
|
4480
|
-
default: "toon"
|
|
3876
|
+
type: t.boolean().optional()
|
|
4481
3877
|
}
|
|
4482
3878
|
},
|
|
4483
3879
|
run(args) {
|
|
4484
3880
|
try {
|
|
4485
|
-
const format = parseFormat(args.format);
|
|
4486
3881
|
const root = args.root ?? process.cwd();
|
|
4487
3882
|
const write = args.write === true;
|
|
4488
3883
|
const plan = projectMcp({
|
|
@@ -4493,28 +3888,24 @@ const mcpProjectCommand = command({
|
|
|
4493
3888
|
if (plan.kind === "unreadable") {
|
|
4494
3889
|
const at = plan.position ? ` at line ${plan.position.line}, column ${plan.position.column}` : "";
|
|
4495
3890
|
process.stderr.write(`error: ${goldenSetPath} does not parse${at}.\n`);
|
|
4496
|
-
|
|
3891
|
+
throw CliError.reported(exitCodes.error);
|
|
4497
3892
|
}
|
|
4498
|
-
if (plan.kind === "absent") {
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
return exitCodes.success;
|
|
4506
|
-
}
|
|
4507
|
-
writeResult({
|
|
3893
|
+
if (plan.kind === "absent") return result({
|
|
3894
|
+
golden: `0 servers — no golden set at ${goldenSetPath}`,
|
|
3895
|
+
mode: "nothing to project",
|
|
3896
|
+
actions: "0 changes",
|
|
3897
|
+
record: "not written"
|
|
3898
|
+
});
|
|
3899
|
+
const report = {
|
|
4508
3900
|
golden: goldenSetPath,
|
|
4509
3901
|
mode: write ? "written" : "dry run — nothing written; re-run with --write to apply",
|
|
4510
3902
|
actions: plan.rows.length ? plan.rows : "0 changes — every enabled harness already carries the golden set",
|
|
4511
3903
|
...plan.entries.length ? { entries: plan.entries } : {},
|
|
4512
3904
|
record: write ? projectionRecordPath : "not written — dry run"
|
|
4513
|
-
}
|
|
4514
|
-
return
|
|
3905
|
+
};
|
|
3906
|
+
return result(report);
|
|
4515
3907
|
} catch (error) {
|
|
4516
|
-
|
|
4517
|
-
return exitCodes.error;
|
|
3908
|
+
return fail(error, "MCP projection failed.");
|
|
4518
3909
|
}
|
|
4519
3910
|
}
|
|
4520
3911
|
});
|
|
@@ -4539,21 +3930,41 @@ const rootCommand = {
|
|
|
4539
3930
|
governanceCommand
|
|
4540
3931
|
]
|
|
4541
3932
|
};
|
|
4542
|
-
function app() {
|
|
3933
|
+
function app(format) {
|
|
4543
3934
|
const [first, ...rest] = rootCommand.commands;
|
|
4544
3935
|
return rest.reduce((builder, command) => builder.command(command), cli({
|
|
4545
3936
|
name: rootCommand.name,
|
|
4546
3937
|
version,
|
|
4547
|
-
description: rootCommand.description
|
|
3938
|
+
description: rootCommand.description,
|
|
3939
|
+
format
|
|
4548
3940
|
}).command(first));
|
|
4549
3941
|
}
|
|
3942
|
+
const textByDefault = {
|
|
3943
|
+
reference: [
|
|
3944
|
+
"show",
|
|
3945
|
+
"create",
|
|
3946
|
+
"delete"
|
|
3947
|
+
],
|
|
3948
|
+
governance: ["show"]
|
|
3949
|
+
};
|
|
3950
|
+
/**
|
|
3951
|
+
* `reference show|create|delete` and `governance show` answer in `text` unless `--format` says
|
|
3952
|
+
* otherwise; every other command answers in `toon`. clibuilder has one global `--format` default,
|
|
3953
|
+
* so it is chosen from the command the argv names.
|
|
3954
|
+
*/
|
|
3955
|
+
function defaultFormat(argv) {
|
|
3956
|
+
const args = argv.slice(2);
|
|
3957
|
+
if (args.some((arg) => arg === "--format" || arg.startsWith("--format="))) return "toon";
|
|
3958
|
+
const [group, subcommand] = args;
|
|
3959
|
+
return group !== void 0 && subcommand !== void 0 && textByDefault[group]?.includes(subcommand) ? "text" : "toon";
|
|
3960
|
+
}
|
|
4550
3961
|
/**
|
|
4551
3962
|
* The application boundary: argv in, exit code out — returned rather than written, so a caller that
|
|
4552
3963
|
* is not the process (a skill launcher, a test) can act on it.
|
|
4553
3964
|
*/
|
|
4554
3965
|
async function run(argv) {
|
|
4555
3966
|
try {
|
|
4556
|
-
const code = await app().parse(argv);
|
|
3967
|
+
const code = await app(defaultFormat(argv)).parse(argv);
|
|
4557
3968
|
return typeof code === "number" ? code : exitCodes.success;
|
|
4558
3969
|
} catch (error) {
|
|
4559
3970
|
process.stderr.write(`error: ${error instanceof Error ? error.message : "Invalid command."}\n`);
|
|
@@ -4837,6 +4248,6 @@ function listMcpServers(options) {
|
|
|
4837
4248
|
return entries;
|
|
4838
4249
|
}
|
|
4839
4250
|
//#endregion
|
|
4840
|
-
export { activate, bridgeRepairs, buildDoctorReport, deprecatedManagedGovernancesDir, diagnoseBridges, diagnoseGlobalInstructions, diagnoseInstructions, diagnoseMcp, doctorCommand, doctorRepairs, doctorSkill, globalInstructionRepairs, goldenSetPath, governanceCommand, governanceLayers, harnessCommand, harnessRegistry, initCommand, initializeHarnesses, instructionRepairs, listGovernances, listMcpServers, listReferences, managedGovernancesDir,
|
|
4251
|
+
export { activate, bridgeRepairs, buildDoctorReport, deprecatedManagedGovernancesDir, diagnoseBridges, diagnoseGlobalInstructions, diagnoseInstructions, diagnoseMcp, doctorCommand, doctorRepairs, doctorSkill, globalInstructionRepairs, goldenSetPath, governanceCommand, governanceLayers, harnessCommand, harnessRegistry, initCommand, initializeHarnesses, instructionRepairs, listGovernances, listMcpServers, listReferences, managedGovernancesDir, overrideLayers, parseGovernanceName, parseReferenceName, referenceCommand, referenceLayers, renderDoctorSkill, resolveGovernance, resolveReference, run, searchReferences };
|
|
4841
4252
|
|
|
4842
4253
|
//# sourceMappingURL=index.mjs.map
|