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.
Files changed (61) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/.cursor-plugin/plugin.json +1 -1
  4. package/README.md +1 -1
  5. package/dist/cli.mjs +14193 -24818
  6. package/dist/index.d.mts +11 -9
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.mjs +183 -772
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/skill-scripts/doctor.mjs +122 -180
  11. package/dist/skill-scripts/init.mjs +122 -180
  12. package/dist/skill-scripts/mcp.mjs +122 -180
  13. package/llms.txt +11 -8
  14. package/package.json +4 -4
  15. package/plugin.json +2 -2
  16. package/skills/doctor-buddy-agent-harness/README.md +1 -1
  17. package/skills/doctor-buddy-agent-harness/SKILL.md +3 -3
  18. package/skills/doctor-buddy-agent-harness/references/bridges.md +2 -2
  19. package/skills/doctor-buddy-agent-harness/references/configuration.md +4 -4
  20. package/skills/doctor-buddy-agent-harness/references/instructions.md +6 -6
  21. package/skills/doctor-buddy-agent-harness/references/judgment/claude-code.md +64 -0
  22. package/skills/doctor-buddy-agent-harness/references/judgment/codex.md +19 -0
  23. package/skills/doctor-buddy-agent-harness/references/judgment/copilot-cli.md +16 -0
  24. package/skills/doctor-buddy-agent-harness/references/judgment/cursor.md +36 -0
  25. package/skills/doctor-buddy-agent-harness/references/judgment/devin-desktop.md +19 -0
  26. package/skills/doctor-buddy-agent-harness/references/judgment/gemini-cli.md +30 -0
  27. package/skills/doctor-buddy-agent-harness/references/mcp.md +4 -4
  28. package/skills/doctor-buddy-agent-harness/references/nonstandard.md +4 -4
  29. package/skills/doctor-buddy-agent-harness/scripts/doctor.mjs +122 -180
  30. package/skills/enhance/SKILL.md +5 -5
  31. package/skills/enhance/references/agents-md.md +150 -0
  32. package/skills/enhance/references/detection.md +36 -0
  33. package/skills/enhance/references/global-instructions.md +33 -0
  34. package/skills/enhance/scripts/doctor.mjs +122 -180
  35. package/skills/init-buddy-agent-harness/SKILL.md +1 -1
  36. package/skills/init-buddy-agent-harness/scripts/doctor.mjs +122 -180
  37. package/skills/init-buddy-agent-harness/scripts/init.mjs +122 -180
  38. package/skills/repair/SKILL.md +3 -3
  39. package/skills/repair/references/agents-md.md +150 -0
  40. package/skills/repair/references/classes.md +2 -2
  41. package/skills/repair/scripts/doctor.mjs +122 -180
  42. package/skills/repair/scripts/mcp.mjs +122 -180
  43. package/src/cli.ts +22 -4
  44. package/src/command-output/command-output.ts +15 -55
  45. package/src/dep-plugins/dep-plugins.command.ts +8 -14
  46. package/src/dep-plugins/dep-plugins.ts +1 -1
  47. package/src/dep-plugins/resolve.ts +1 -1
  48. package/src/diagnose-bridges/doctor-guidance.ts +64 -54
  49. package/src/diagnose-bridges/doctor.command.ts +12 -22
  50. package/src/governance-overrides/governance-overrides.ts +18 -8
  51. package/src/governance-overrides/governance.command.ts +19 -37
  52. package/src/harness-registry/harness-registry.ts +3 -4
  53. package/src/index.ts +5 -8
  54. package/src/initialize-harnesses/init.command.ts +9 -19
  55. package/src/initialize-harnesses/initialize-harnesses.ts +2 -2
  56. package/src/project-mcp/mcp.command.ts +14 -25
  57. package/src/reconcile-mcp/reconcile.command.ts +15 -26
  58. package/src/references/references.ts +6 -6
  59. package/dist/dist-ktNZ1sJo.mjs +0 -2966
  60. package/dist/esm-BMqwcD7W.mjs +0 -87
  61. package/dist/esm-CVacKtt6.mjs +0 -64
package/dist/index.mjs CHANGED
@@ -1,614 +1,33 @@
1
- import { declaredDependencies, deprecatedManagedGovernancesDir as deprecatedManagedGovernancesDir$1, listReferences, listReferences as listReferences$1, managedGovernancesDir as managedGovernancesDir$1, managedReferencesDir, packageDir, parseReferenceName, projectReferenceLayers, projectReferencesDir, referenceLayers as referenceLayers$1, referenceNames, resolveReference, searchReferences, skillsDirectories } from "@cyberuni/agent-harness";
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, exitCodes, z } from "clibuilder";
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 "@cyberuni/agent-harness/commands";
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 ../../node_modules/.pnpm/@toon-format+toon@4.1.1/node_modules/@toon-format/toon/dist/index.mjs
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
- * Encodes a JavaScript value into TOON format as a sequence of lines.
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 encodeLines(input, options) {
546
- const normalizedValue = normalizeValue(input);
547
- const resolvedOptions = resolveOptions(options);
548
- return encodeJsonValue(resolvedOptions.replacer ? applyReplacer(normalizedValue, resolvedOptions.replacer) : normalizedValue, resolvedOptions, 0);
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
- * Writes a document exactly as read, bypassing the encoders — a Markdown body run through TOON or
582
- * the text renderer comes back as one escaped line. A command writes one or the other, never both.
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 writeDocument(content) {
585
- process.stdout.write(content.endsWith("\n") ? content : `${content}\n`);
586
- }
587
- function encodeResult(value, format) {
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: z.optional(z.string())
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: z.optional(z.boolean())
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
- writeResult({
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
- }, format);
1052
- return outcome === "would-change" ? exitCodes.error : exitCodes.success;
464
+ };
465
+ const answer = result(report);
466
+ return outcome === "would-change" ? answerThenFail(answer, this.format) : answer;
1053
467
  } catch (error) {
1054
- process.stderr.write(`error: ${error instanceof Error ? error.message : "Catalog generation failed."}\n`);
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(`@cyberuni/agent-harness records no ${scope} skills directories for ${harness}.`);
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 `@cyberuni/agent-harness`, except for the harnesses it does not know
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}@^${version}`;
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 \`${initSkillInvocation}\`, which creates \`.agents/skills\` and the bridges`
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 \`${initSkillInvocation}\``
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 \`${initSkillInvocation}\`, which consolidates what the repository has into AGENTS.md, or derives it`
866
+ instruction: `hand this to ${initSkillRef}, which consolidates what the repository has into AGENTS.md, or derives it`
1446
867
  }),
1447
- skillRepair: () => `run \`${initSkillInvocation}\`, which consolidates or derives AGENTS.md`
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 \`${initSkillInvocation}\`, which writes the bridge into it`
874
+ instruction: `hand ${file} to ${initSkillRef}, which writes the bridge into it`
1454
875
  }),
1455
- skillRepair: () => `run \`${initSkillInvocation}\``
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 \`${initSkillInvocation}\`, which adds the bridge without discarding what the file already says`
882
+ instruction: `hand ${file} to ${initSkillRef}, which adds the bridge without discarding what the file already says`
1462
883
  }),
1463
- skillRepair: () => `run \`${initSkillInvocation}\`, which adds the bridge without discarding what the file already says`
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 \`${initSkillInvocation}\`, which consolidates what it says into AGENTS.md, or adds an @AGENTS.md import above it where the file has to stay`
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 \`${initSkillInvocation}\`, which consolidates the file into AGENTS.md or imports AGENTS.md from it`
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 — \`${initSkillInvocation}\` offers the choice`
898
+ instruction: `remove ${file}, or keep it for sessions that cannot read AGENTS.md directly — ${initSkillRef} offers the choice`
1478
899
  }),
1479
- skillRepair: () => `run \`${initSkillInvocation}\`, which offers to remove it`
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 \`${initSkillInvocation}\``
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 \`${initSkillInvocation}\``
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 — \`${repairSkillInvocation}\` offers the correction`
980
+ instruction: `remove ${file} and enable the harness that replaced it — ${repairSkillRef} offers the correction`
1560
981
  }),
1561
- skillRepair: () => `run \`${repairSkillInvocation}\``
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} — \`${repairSkillInvocation}\` offers the correction`
988
+ instruction: `narrow or remove the .gitignore rule matching ${file} — ${repairSkillRef} offers the correction`
1568
989
  }),
1569
- skillRepair: () => `run \`${repairSkillInvocation}\``
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 — \`${repairSkillInvocation}\` offers the correction`
996
+ instruction: `move ${file} to CLAUDE.local.md, or consolidate it into AGENTS.md — ${repairSkillRef} offers the correction`
1576
997
  }),
1577
- skillRepair: () => `run \`${repairSkillInvocation}\``
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 — \`${repairSkillInvocation}\` offers the correction`
1004
+ instruction: `quote the description in ${file}, or add one — ${repairSkillRef} offers the correction`
1584
1005
  }),
1585
- skillRepair: () => `run \`${repairSkillInvocation}\``
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 — \`${repairSkillInvocation}\` offers the correction`
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 \`${repairSkillInvocation}\`, which projects it from its golden entry with \`mcp project\` — or drop it from the golden set`
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} — \`${repairSkillInvocation}\` offers the import`
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 \`${repairSkillInvocation}\`, which imports it field by field with \`mcp reconcile\`, refusing any literal credential — or drop it from ${at.file}`
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} — \`${repairSkillInvocation}\` offers the import`
1051
+ instruction: `reconcile the value at ${locatorText(at)} back into ${goldenSet} — ${repairSkillRef} offers the import`
1631
1052
  }),
1632
- skillRepair: (at) => `hand ${locatorText(at)} to \`${repairSkillInvocation}\`, which imports the field into ${goldenSet} with \`mcp reconcile\` once approved`
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 — \`${repairSkillInvocation}\` offers the correction`
1059
+ instruction: `update ${locatorText(at)} from the golden entry — ${repairSkillRef} offers the correction`
1639
1060
  }),
1640
- skillRepair: (at) => `hand ${locatorText(at)} to \`${repairSkillInvocation}\`, which updates it from the golden entry with \`mcp project\``
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 — \`${initSkillInvocation}\` offers the consolidation, and keeps the content reachable where this harness needs it`
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 \`${initSkillInvocation}\`, which consolidates it into AGENTS.md`
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 — \`${initSkillInvocation}\` offers the conversion; keep it where the path scoping is the point`
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 \`${initSkillInvocation}\` to convert into a skill, unless the path scoping is load-bearing`
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 — \`${initSkillInvocation}\` offers the move`
1122
+ instruction: `move ${file} to .agents/skills/<name>/SKILL.md — ${initSkillRef} offers the move`
1702
1123
  }),
1703
- skillRepair: ({ file }) => `hand ${file} to \`${initSkillInvocation}\`, which moves it to .agents/skills`
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 — \`${initSkillInvocation}\` offers the move`
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 \`${initSkillInvocation}\`, which moves it to .agents/skills`
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. An earlier version prefixed every repair with \`Run\`, which read as an instruction to paste prose into a shell.
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 \`${initSkillInvocation}\` instead. Every such repair carries an empty \`command\`: a skill invocation has no shell equivalent at all.
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
- /** @deprecated Use `managedReferencesDir`. Removed in the next major version. */
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 managedGovernancesDir$1(platform, programData);
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 `managedReferencesDir`. Removed in the next major version.
2074
+ * @deprecated Use the `reference` command's layers. Removed in the next major version.
2640
2075
  */
2641
2076
  function deprecatedManagedGovernancesDir(platform, programData) {
2642
- return deprecatedManagedGovernancesDir$1(platform, programData);
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
- /** `@cyberuni/agent-harness`'s reference layers, with this package as the plugin whose own references come first. */
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: z.optional(z.string())
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: z.optional(z.string())
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 result = diagnoseBridges({
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, status }) => ({
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
- writeResult(buildDoctorReport(displayBinPath(home, process.argv[1]), result, configuration, held), format);
3548
- return exitCodes.success;
2979
+ return result(buildDoctorReport(displayBinPath(home, process.argv[1]), diagnosis, configuration, held));
3549
2980
  } catch (error) {
3550
- process.stderr.write(`error: ${error instanceof Error ? error.message : "Harness diagnosis failed."}\n`);
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: z.optional(z.string())
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: z.optional(z.boolean())
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
- writeResult({
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
- }, format);
3607
- return exitCodes.success;
3030
+ };
3031
+ return result(report);
3608
3032
  } catch (error) {
3609
- process.stderr.write(`error: ${error instanceof Error ? error.message : "Governance listing failed."}\n`);
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: z.string()
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
- return exitCodes.error;
3057
+ throw CliError.reported(exitCodes.error);
3640
3058
  }
3641
- if (format === "text") writeDocument(found.content);
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
- }, format);
3648
- return exitCodes.success;
3064
+ };
3065
+ return format === "text" ? document(report.content) : result(report);
3649
3066
  } catch (error) {
3650
- process.stderr.write(`error: ${error instanceof Error ? error.message : "Governance lookup failed."}\n`);
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(projectReferenceLayers(root)).length;
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: z.optional(z.string())
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: z.optional(z.string())
3122
+ type: t.string().optional()
3704
3123
  },
3705
3124
  copy: {
3706
3125
  description: "Copy skills instead of linking them.",
3707
- type: z.optional(z.boolean())
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: z.optional(z.string())
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
- writeResult(initializeHarnesses({
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
- }), format);
3729
- return exitCodes.success;
3141
+ }));
3730
3142
  } catch (error) {
3731
- process.stderr.write(`error: ${error instanceof Error ? error.message : "Harness initialization failed."}\n`);
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: z.optional(z.string())
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: z.optional(z.array(z.string()))
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
- return exitCodes.error;
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
- return exitCodes.error;
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
- writeResult({
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
- }, format);
4219
- return exitCodes.success;
3620
+ };
3621
+ return result(report);
4220
3622
  } catch (error) {
4221
- process.stderr.write(`error: ${error instanceof Error ? error.message : "MCP reconcile failed."}\n`);
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: z.optional(z.string())
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: z.optional(z.boolean())
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
- return exitCodes.error;
3891
+ throw CliError.reported(exitCodes.error);
4497
3892
  }
4498
- if (plan.kind === "absent") {
4499
- writeResult({
4500
- golden: `0 servers — no golden set at ${goldenSetPath}`,
4501
- mode: "nothing to project",
4502
- actions: "0 changes",
4503
- record: "not written"
4504
- }, format);
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
- }, format);
4514
- return exitCodes.success;
3905
+ };
3906
+ return result(report);
4515
3907
  } catch (error) {
4516
- process.stderr.write(`error: ${error instanceof Error ? error.message : "MCP projection failed."}\n`);
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, managedReferencesDir, overrideLayers, parseGovernanceName, parseReferenceName, referenceCommand, referenceLayers, renderDoctorSkill, resolveGovernance, resolveReference, run, searchReferences };
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