buddy-agent-harness 1.0.1 → 1.2.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 (47) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/.cursor-plugin/plugin.json +1 -1
  4. package/CHANGELOG.md +44 -0
  5. package/dist/cli.mjs +2166 -702
  6. package/dist/index.d.mts +10 -3
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.mjs +597 -431
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/skill-scripts/doctor.mjs +74 -67
  11. package/dist/skill-scripts/init.mjs +74 -67
  12. package/dist/skill-scripts/mcp.mjs +74 -67
  13. package/llms.txt +8 -6
  14. package/package.json +5 -4
  15. package/plugin.json +1 -1
  16. package/skills/doctor-buddy-agent-harness/README.md +1 -1
  17. package/skills/doctor-buddy-agent-harness/SKILL.md +9 -2
  18. package/skills/doctor-buddy-agent-harness/references/worktrees.md +17 -0
  19. package/skills/doctor-buddy-agent-harness/scripts/doctor.mjs +74 -67
  20. package/skills/enhance/SKILL.md +18 -10
  21. package/skills/enhance/references/agent-worktrees.md +59 -0
  22. package/skills/enhance/references/list-identifiers.history.md +9 -1
  23. package/skills/enhance/references/list-identifiers.md +6 -9
  24. package/skills/enhance/references/references-as-links.history.md +9 -0
  25. package/skills/enhance/references/references-as-links.md +76 -0
  26. package/skills/enhance/scripts/doctor.mjs +74 -67
  27. package/skills/init-buddy-agent-harness/SKILL.md +8 -5
  28. package/skills/init-buddy-agent-harness/references/agent-worktrees.md +59 -0
  29. package/skills/init-buddy-agent-harness/scripts/doctor.mjs +74 -67
  30. package/skills/init-buddy-agent-harness/scripts/init.mjs +74 -67
  31. package/skills/repair/README.md +2 -2
  32. package/skills/repair/SKILL.md +3 -3
  33. package/skills/repair/scripts/doctor.mjs +74 -67
  34. package/skills/repair/scripts/mcp.mjs +74 -67
  35. package/src/cli.ts +13 -3
  36. package/src/command-output/command-output.ts +28 -5
  37. package/src/dep-plugins/dep-plugins.command.ts +4 -0
  38. package/src/diagnose-bridges/doctor-guidance.ts +66 -1
  39. package/src/diagnose-bridges/doctor.command.ts +69 -49
  40. package/src/diagnose-bridges/git-bridge-state.ts +5 -0
  41. package/src/diagnose-worktrees/diagnose-worktrees.ts +69 -0
  42. package/src/governance-overrides/governance.command.ts +13 -8
  43. package/src/index.ts +1 -0
  44. package/src/initialize-harnesses/init.command.ts +9 -3
  45. package/src/project-mcp/mcp.command.ts +15 -9
  46. package/src/reconcile-mcp/reconcile.command.ts +17 -11
  47. package/src/references/references.ts +1 -0
package/dist/cli.mjs CHANGED
@@ -1,18 +1,46 @@
1
1
  import { createRequire } from "node:module";
2
- import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, rmdirSync, statSync, symlinkSync, writeFileSync } from "node:fs";
2
+ import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, symlinkSync, writeFileSync } from "node:fs";
3
3
  import { inspect, styleText } from "node:util";
4
4
  import { homedir, platform } from "node:os";
5
5
  import { basename, dirname, extname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
6
6
  import { readFile, readdir } from "node:fs/promises";
7
7
  import { execFileSync } from "node:child_process";
8
8
  import { fileURLToPath } from "node:url";
9
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/render/ref.js
9
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/render/ref.js
10
10
  /**
11
11
  * Next-step references: `ref()` names another command, and the runner renders it as a command line.
12
12
  * Imports nothing from the framework (axi-native RFC section 4.1; next-step-refs RFC).
13
13
  */
14
14
  const refKey = Symbol.for("clibuilder.ref");
15
15
  const placeholderKey = Symbol.for("clibuilder.placeholder");
16
+ /**
17
+ * A reference to another command, for a `help` list.
18
+ *
19
+ * The runner renders it as a complete command line for the channel it runs in:
20
+ * `<cli name> task get --gid 123` under `cli()`, or the sibling script, `pnpm exec` or pinned `npx` line under `runScript()`.
21
+ *
22
+ * Options render as `--name value` in the order given. `true` renders as `--name`, `false` and `undefined` are left out,
23
+ * an array repeats the flag, and a value is single-quoted when the shell would read it otherwise.
24
+ *
25
+ * @param path the command path as a user types it, without the cli name.
26
+ * @param options the options to pass, keyed by option name. Use {@link placeholder} for a value not known yet.
27
+ * @param why what running it does, as a verb phrase: `Run \`…\` to <why>`.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * return result(task, { help: ref('task get', { gid: task.parent.gid }, 'show the parent task') })
32
+ * ```
33
+ */
34
+ function ref(path, options = {}, why) {
35
+ const words = path.trim().split(/\s+/).filter(Boolean);
36
+ if (words.length === 0) throw new TypeError("ref() needs a command path, such as \"task get\"");
37
+ return {
38
+ [refKey]: true,
39
+ path: words.join(" "),
40
+ options: { ...options },
41
+ why
42
+ };
43
+ }
16
44
  function isRef(value) {
17
45
  return typeof value === "object" && value !== null && value[refKey] === true;
18
46
  }
@@ -66,7 +94,7 @@ function shellQuote(value) {
66
94
  return /^[\w./:@%+=,-]+$/.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`;
67
95
  }
68
96
  //#endregion
69
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/app/errors.js
97
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/app/errors.js
70
98
  var _a;
71
99
  /**
72
100
  * The exit codes a clibuilder cli uses, following the AXI convention so an agent
@@ -142,7 +170,7 @@ var CliError = class CliError extends Error {
142
170
  super(report.message);
143
171
  this.name = "CliError";
144
172
  this[_a] = true;
145
- this.exitCode = validateExitCode(report.exitCode ?? exitCodes.error);
173
+ this.exitCode = validateExitCode$1(report.exitCode ?? exitCodes.error);
146
174
  this.code = validateCode(report.code ?? "error");
147
175
  this.details = validateDetails(report.details ?? {});
148
176
  const lines = report.help === void 0 ? [] : Array.isArray(report.help) ? [...report.help] : [report.help];
@@ -163,7 +191,7 @@ function helpLinesOf(err) {
163
191
  function isReportedCliError(err) {
164
192
  return err[reportedBrand] === true;
165
193
  }
166
- function validateExitCode(exitCode) {
194
+ function validateExitCode$1(exitCode) {
167
195
  if (Number.isInteger(exitCode) && exitCode >= 1 && exitCode <= 255) return exitCode;
168
196
  throw new RangeError(`CliError exitCode must be an integer from 1 to 255, received ${exitCode}`);
169
197
  }
@@ -176,7 +204,7 @@ function validateDetails(details) {
176
204
  return { ...details };
177
205
  }
178
206
  //#endregion
179
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/command/define.js
207
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/command/define.js
180
208
  /**
181
209
  * Defines a command with its argument and option types inferred.
182
210
  *
@@ -198,7 +226,7 @@ function command(cmd) {
198
226
  return cmd;
199
227
  }
200
228
  //#endregion
201
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/descriptor.js
229
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/descriptor.js
202
230
  /** Whether a descriptor option takes a value, as `args-minus` decides: a boolean or boolean literal is a flag. */
203
231
  function takesValue$1(data) {
204
232
  const d = data.kind === "array" ? data.element : data;
@@ -228,7 +256,22 @@ function describeRange(min, max) {
228
256
  return "";
229
257
  }
230
258
  //#endregion
231
- //#region ../../node_modules/.pnpm/args-minus@0.2.2/node_modules/args-minus/esm/t.js
259
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/props.js
260
+ /** The property `key` of `object` itself, or `undefined` when only its prototype has one. */
261
+ function getProp$1(object, key) {
262
+ return Object.prototype.hasOwnProperty.call(object, key) ? object[key] : void 0;
263
+ }
264
+ /** Sets `key` on `object` itself, even when the key is `__proto__`. */
265
+ function setProp(object, key, value) {
266
+ Object.defineProperty(object, key, {
267
+ value,
268
+ enumerable: true,
269
+ writable: true,
270
+ configurable: true
271
+ });
272
+ }
273
+ //#endregion
274
+ //#region ../../node_modules/.pnpm/args-minus@0.2.3/node_modules/args-minus/esm/t.js
232
275
  const descriptorBrand = Symbol.for("args-minus.descriptor");
233
276
  const descriptorVersion = 1;
234
277
  const methods = {
@@ -400,7 +443,7 @@ Array.prototype.reduce;
400
443
  const ArraySlice = Array.prototype.slice;
401
444
  const ArrayPush = Array.prototype.push;
402
445
  //#endregion
403
- //#region ../../node_modules/.pnpm/args-minus@0.2.2/node_modules/args-minus/esm/tokenize_args.js
446
+ //#region ../../node_modules/.pnpm/args-minus@0.2.3/node_modules/args-minus/esm/tokenize_args.js
404
447
  const DEFAULT_GRAMMAR = {
405
448
  modifiers: "-",
406
449
  terminator: "--"
@@ -472,7 +515,7 @@ function tokenizeArgs(args, grammar) {
472
515
  return tokens;
473
516
  }
474
517
  //#endregion
475
- //#region ../../node_modules/.pnpm/args-minus@0.2.2/node_modules/args-minus/esm/parse_args.js
518
+ //#region ../../node_modules/.pnpm/args-minus@0.2.3/node_modules/args-minus/esm/parse_args.js
476
519
  /**
477
520
  * Thrown when the input does not fit the declarations: a usage error the caller can fix.
478
521
  */
@@ -602,7 +645,7 @@ function* parse$1(config) {
602
645
  const raw = names[i];
603
646
  const isLast = i === names.length - 1;
604
647
  const name = canonical.get(raw) ?? raw;
605
- const declaration = declarations?.[name];
648
+ const declaration = declarations && getProp(declarations, name);
606
649
  if (config.strict && !declaration) throw new ParseArgsError(`Unknown option '${raw}' (from '${token.raw}')`, raw, token.raw, { kind: "unknown-option" });
607
650
  if (!isLast && declaration && takesValue(declaration)) throw new ParseArgsError(`Invalid option group '${token.raw}': '${raw}' takes a value, so it can only be the last option in a group`, name, token.raw, {
608
651
  kind: "invalid-group",
@@ -666,6 +709,10 @@ function* parse$1(config) {
666
709
  if (config.tokens) result.tokens = tokens;
667
710
  return result;
668
711
  }
712
+ /** The property `key` of `object` itself, or `undefined` when only its prototype has one. */
713
+ function getProp(object, key) {
714
+ return Object.prototype.hasOwnProperty.call(object, key) ? object[key] : void 0;
715
+ }
669
716
  function checkDeclarations(declarations) {
670
717
  for (const [name, declaration] of Object.entries(declarations ?? {})) {
671
718
  const type = declaration.type;
@@ -901,10 +948,10 @@ function* checkDefault(declaration, name) {
901
948
  checkedDefaults.set(declaration, true);
902
949
  }
903
950
  //#endregion
904
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/invocation/argv_occurrences.js
951
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/invocation/argv_occurrences.js
905
952
  const optionOccurrences = /* @__PURE__ */ new WeakMap();
906
953
  //#endregion
907
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/invocation/lookup.js
954
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/invocation/lookup.js
908
955
  /**
909
956
  * @param fallback a command whose options apply to every command (the global options).
910
957
  * It decides how many following tokens such an option takes,
@@ -986,13 +1033,13 @@ function assignOptionValues(rawArgs, command, fallback) {
986
1033
  });
987
1034
  positionals.push(...o.following.slice(count));
988
1035
  const own = o.inline.length + taken.length > 0 ? [...o.inline, ...taken] : ["true"];
989
- values[o.key] = [...values[o.key] ?? [], ...own];
1036
+ setProp(values, o.key, [...getProp$1(values, o.key) ?? [], ...own]);
990
1037
  }
991
1038
  const result = {
992
1039
  ...rawArgs,
993
1040
  _: [...rawArgs._, ...positionals]
994
1041
  };
995
- for (const key of Object.keys(values)) if (rawArgs[key]) result[key] = values[key];
1042
+ for (const key of Object.keys(values)) if (getProp$1(rawArgs, key)) setProp(result, key, values[key]);
996
1043
  return [result, descriptorOccurrences];
997
1044
  }
998
1045
  function countFollowing(entry, o) {
@@ -1116,7 +1163,7 @@ function collectDescriptorOptions(state, occurrences) {
1116
1163
  function lookupOptions(command, key) {
1117
1164
  const opts = command.options;
1118
1165
  if (!opts) return [];
1119
- const options = opts[key];
1166
+ const options = getProp$1(opts, key);
1120
1167
  if (options) return [key, options];
1121
1168
  const optKey = Object.keys(opts).find((k) => {
1122
1169
  const opt = opts[k];
@@ -1180,6 +1227,25 @@ var RawString = class {
1180
1227
  this.value = value;
1181
1228
  }
1182
1229
  };
1230
+ /**
1231
+ * Wraps a pre-formatted string for verbatim emission, typically returned from
1232
+ * an encode `replacer`. Compose with `escapeString` to control quoting yourself.
1233
+ *
1234
+ * @param value The exact text to emit at the value position
1235
+ * @returns A `RawString` marker honored at primitive value positions
1236
+ *
1237
+ * @example
1238
+ * ```ts
1239
+ * encode({ name: 'Ada', age: 30 }, {
1240
+ * replacer: (key, value) => rawString(`"${escapeString(String(value))}"`)
1241
+ * })
1242
+ * // name: "Ada"
1243
+ * // age: "30"
1244
+ * ```
1245
+ */
1246
+ function rawString(value) {
1247
+ return new RawString(value);
1248
+ }
1183
1249
  function isRawString(value) {
1184
1250
  return value instanceof RawString;
1185
1251
  }
@@ -1674,7 +1740,7 @@ function resolveOptions(options) {
1674
1740
  };
1675
1741
  }
1676
1742
  //#endregion
1677
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/render/text.js
1743
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/render/text.js
1678
1744
  /**
1679
1745
  * The `text` encoder, the same rendering as `@clibuilder/axi`'s `renderText`.
1680
1746
  * Imports nothing from the framework (axi-native RFC section 4.1).
@@ -1682,14 +1748,14 @@ function resolveOptions(options) {
1682
1748
  function isRecord$3(value) {
1683
1749
  return typeof value === "object" && value !== null && !Array.isArray(value);
1684
1750
  }
1685
- function cell(value) {
1751
+ function cell$1(value) {
1686
1752
  return value === void 0 ? "" : String(value);
1687
1753
  }
1688
1754
  function table(rows) {
1689
1755
  const columns = [...new Set(rows.flatMap((row) => Object.keys(row)))];
1690
- const widths = columns.map((column) => Math.max(column.length, ...rows.map((row) => cell(row[column]).length)));
1756
+ const widths = columns.map((column) => Math.max(column.length, ...rows.map((row) => cell$1(row[column]).length)));
1691
1757
  const line = (values) => ` ${values.map((value, index) => value.padEnd(widths[index])).join(" ")}`.trimEnd();
1692
- return [line(columns), ...rows.map((row) => line(columns.map((column) => cell(row[column]))))];
1758
+ return [line(columns), ...rows.map((row) => line(columns.map((column) => cell$1(row[column]))))];
1693
1759
  }
1694
1760
  /**
1695
1761
  * Renders a value for a person: a key per line, a list of records as an aligned table, a list of values as bullets,
@@ -1708,7 +1774,7 @@ function renderText(value) {
1708
1774
  }).join("\n");
1709
1775
  }
1710
1776
  //#endregion
1711
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/render/encode.js
1777
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/render/encode.js
1712
1778
  /**
1713
1779
  * The single place an answer is turned into text.
1714
1780
  * Imports nothing from the framework (axi-native RFC section 4.1).
@@ -1718,11 +1784,40 @@ const formats = [
1718
1784
  "json",
1719
1785
  "text"
1720
1786
  ];
1721
- /** Encodes `value` in `format`, without a trailing newline. `toon` uses the official `@toon-format/toon` encoder. */
1787
+ /**
1788
+ * Encodes `value` in `format`, without a trailing newline.
1789
+ *
1790
+ * `toon` uses TOON only for collections (change 1 of the AXI conventions): an array, at any depth, is encoded
1791
+ * by the official `@toon-format/toon` encoder, and a string field outside any array is a plain `key: value` line.
1792
+ * The output is still TOON: a TOON decoder reads it back to the same value.
1793
+ */
1722
1794
  function encode(value, format) {
1723
1795
  if (format === "json") return JSON.stringify(value);
1724
1796
  if (format === "text") return renderText(value);
1725
- return encode$1(value);
1797
+ return encode$1(value, { replacer: plainLine });
1798
+ }
1799
+ function plainLine(_key, value, path) {
1800
+ if (typeof value !== "string" || path.length === 0 || path.some((p) => typeof p === "number")) return value;
1801
+ return readsBackAsIs(value) ? rawString(value) : value;
1802
+ }
1803
+ /**
1804
+ * Whether a TOON decoder reads `value`, written unquoted after `key: `, back as the same string.
1805
+ * Quoted are: the empty string, edge whitespace, control characters such as a newline, a leading `"`,
1806
+ * a leading `#` (the encoder keeps it for comments), and what reads as another type:
1807
+ * `true`, `false`, `null`, a number and `[]`.
1808
+ */
1809
+ function readsBackAsIs(value) {
1810
+ if (value === "" || value === "[]" || value.startsWith("\"") || value.startsWith("#")) return false;
1811
+ if (/^\s|\s$/.test(value) || hasControlCharacter(value)) return false;
1812
+ if (value === "true" || value === "false" || value === "null") return false;
1813
+ return !/^[+-]?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value);
1814
+ }
1815
+ function hasControlCharacter(value) {
1816
+ for (let i = 0; i < value.length; i++) {
1817
+ const code = value.charCodeAt(i);
1818
+ if (code < 32 || code === 127) return true;
1819
+ }
1820
+ return false;
1726
1821
  }
1727
1822
  /** The error as a flat value: `error`, `code`, the `details` fields, then `help` (left out when empty). Keys match `@clibuilder/axi`'s `errorOutput` and axi-sdk-js. */
1728
1823
  function errorOutput({ message, code, details, help }) {
@@ -1735,7 +1830,7 @@ function errorOutput({ message, code, details, help }) {
1735
1830
  return output;
1736
1831
  }
1737
1832
  //#endregion
1738
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/builtin/base_command.js
1833
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/builtin/base_command.js
1739
1834
  const globalFormat = Symbol.for("clibuilder.global-format");
1740
1835
  /**
1741
1836
  * Marks a command's own `format` option that gives way to the global `--format`:
@@ -1807,7 +1902,7 @@ function yieldToGlobalFormat(command) {
1807
1902
  };
1808
1903
  }
1809
1904
  //#endregion
1810
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/plugins/registry.js
1905
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/plugins/registry.js
1811
1906
  /** Creates a key for a single value. `id` must be unique across extensions. */
1812
1907
  function defineKey(id) {
1813
1908
  return {
@@ -1857,7 +1952,7 @@ function createRegistry() {
1857
1952
  };
1858
1953
  }
1859
1954
  //#endregion
1860
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/config/key.js
1955
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/config/key.js
1861
1956
  /**
1862
1957
  * The shared-state key the `clibuilder/config` extension stores the resolved config under.
1863
1958
  *
@@ -1866,7 +1961,7 @@ function createRegistry() {
1866
1961
  */
1867
1962
  const configKey = defineKey("clibuilder.config");
1868
1963
  //#endregion
1869
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/invocation/argv.js
1964
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/invocation/argv.js
1870
1965
  function parseArgv(argv) {
1871
1966
  const occurrences = [];
1872
1967
  const result = argv.slice(2).reduce((result, v) => {
@@ -1941,9 +2036,10 @@ function startMultiCharacterOptions(result, value) {
1941
2036
  function endOption(state) {
1942
2037
  const { key, values } = state;
1943
2038
  const args = { ...state.args };
1944
- if (!args[key]) args[key] = [];
1945
- if (values.length === 0) args[key].push("true");
1946
- else args[key].push(...values);
2039
+ const list = getProp$1(args, key) ?? [];
2040
+ if (values.length === 0) list.push("true");
2041
+ else list.push(...values);
2042
+ setProp(args, key, list);
1947
2043
  return {
1948
2044
  args,
1949
2045
  key: "",
@@ -1951,7 +2047,7 @@ function endOption(state) {
1951
2047
  };
1952
2048
  }
1953
2049
  //#endregion
1954
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/invocation/descriptors.js
2050
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/invocation/descriptors.js
1955
2051
  const declarations = /* @__PURE__ */ new WeakMap();
1956
2052
  /**
1957
2053
  * Converts and checks every declaration of the matched command through `args-minus`,
@@ -2003,7 +2099,7 @@ function configOf(input) {
2003
2099
  };
2004
2100
  }
2005
2101
  function assign(result, input, { options }) {
2006
- if (options[input.name] !== void 0) result.args[input.name] = options[input.name];
2102
+ if (options[input.name] !== void 0) setProp(result.args, input.name, options[input.name]);
2007
2103
  }
2008
2104
  function report$1(result, input, e) {
2009
2105
  if (!(e instanceof ParseArgsError)) throw e;
@@ -2050,7 +2146,7 @@ function rawValues(input) {
2050
2146
  return input.occurrences.map((o) => o.tokens.join(" "));
2051
2147
  }
2052
2148
  //#endregion
2053
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/render/answer.js
2149
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/render/answer.js
2054
2150
  /**
2055
2151
  * The result channel: what `run()` returns, and how it is printed under `output: 'axi'`.
2056
2152
  * Imports nothing from the framework (axi-native RFC section 4.1).
@@ -2063,19 +2159,31 @@ const answerKey = Symbol.for("clibuilder.answer");
2063
2159
  * inside it. A renderer for the format replaces the encoder: its string is written verbatim, and the
2064
2160
  * `help` follows it, encoded. `parse()` resolves to `value`.
2065
2161
  *
2162
+ * The cli exits with `exitCode`, 0 by default, in either output mode.
2163
+ * A non-zero one returns an answer that is also a failure, printed as a result rather than as an error.
2164
+ *
2066
2165
  * @example
2067
2166
  * ```ts
2068
2167
  * run() {
2069
2168
  * return result({ items }, { help: 'Run `my-cli show <id>` for one', render: { text: (v) => table(v.items) } })
2070
2169
  * }
2071
2170
  * ```
2171
+ *
2172
+ * @example
2173
+ * ```ts
2174
+ * run({ name }) {
2175
+ * const exists = panes.has(name)
2176
+ * return result({ exists }, { exitCode: exists ? exitCodes.success : exitCodes.error })
2177
+ * }
2178
+ * ```
2072
2179
  */
2073
2180
  function result(value, options = {}) {
2074
2181
  return {
2075
2182
  [answerKey]: "result",
2076
2183
  value,
2077
2184
  help: helpLines(options.help),
2078
- render: options.render
2185
+ render: options.render,
2186
+ exitCode: validateExitCode(options.exitCode)
2079
2187
  };
2080
2188
  }
2081
2189
  /**
@@ -2083,11 +2191,13 @@ function result(value, options = {}) {
2083
2191
  * whatever the format, ending on a newline. `parse()` resolves to `content`.
2084
2192
  *
2085
2193
  * For output that is already the answer, such as Markdown, a diff or a file.
2194
+ * The cli exits with `exitCode`, 0 by default, in either output mode.
2086
2195
  */
2087
- function document(content) {
2196
+ function document(content, options = {}) {
2088
2197
  return {
2089
2198
  [answerKey]: "document",
2090
- value: String(content)
2199
+ value: String(content),
2200
+ exitCode: validateExitCode(options.exitCode)
2091
2201
  };
2092
2202
  }
2093
2203
  /**
@@ -2115,6 +2225,15 @@ function answerValue(returned) {
2115
2225
  return isAnswer(returned) ? returned.value : returned;
2116
2226
  }
2117
2227
  /**
2228
+ * The code the cli exits with for what `run()` returned: the `exitCode` of a `result()` or `document()`, otherwise 0.
2229
+ * Read field by field: an answer from an older copy of clibuilder has no `exitCode`.
2230
+ */
2231
+ function answerExitCode(returned) {
2232
+ if (!isAnswer(returned)) return 0;
2233
+ const code = returned.exitCode;
2234
+ return typeof code === "number" ? code : 0;
2235
+ }
2236
+ /**
2118
2237
  * The text written on stdout for what `run()` returned, ending on one newline, or `undefined` when nothing is printed.
2119
2238
  * A string is written verbatim, an object is encoded, and an array or scalar is encoded under the `result` key
2120
2239
  * so `help` has a place beside it.
@@ -2122,18 +2241,22 @@ function answerValue(returned) {
2122
2241
  function printAnswer(returned, format, options = {}) {
2123
2242
  if (returned === void 0) return void 0;
2124
2243
  const tail = (lines) => renderHelp(lines, options.resolve, options.refs);
2244
+ const withVerbs = (value, lines) => options.verbs && !hasOwnCommands(value) ? [...lines, options.verbs] : lines;
2125
2245
  if (!isAnswer(returned)) return print(returned, tail([]), format);
2126
2246
  switch (returned[answerKey]) {
2127
2247
  case "written": return;
2128
2248
  case "document": return line(returned.value);
2129
- case "empty": return line(encode(withHelp({ empty: returned.value }, tail(returned.help)), format));
2249
+ case "empty": return line(encode(withHelp({ empty: returned.value }, tail(withVerbs(returned.value, returned.help))), format));
2130
2250
  case "result": {
2131
2251
  const render = returned.render?.[format];
2132
- if (render) return withRenderedHelp(render(returned.value), tail(returned.help), format);
2133
- return print(returned.value, tail(returned.help), format);
2252
+ if (render) return withRenderedHelp(render(returned.value), tail(withVerbs(returned.value, returned.help)), format);
2253
+ return print(returned.value, tail(withVerbs(returned.value, returned.help)), format);
2134
2254
  }
2135
2255
  }
2136
2256
  }
2257
+ function hasOwnCommands(value) {
2258
+ return typeof value === "object" && value !== null && "commands" in value;
2259
+ }
2137
2260
  function print(value, tail, format) {
2138
2261
  if (typeof value === "string") return withRenderedHelp(value, tail, format);
2139
2262
  return line(encode(withHelp(toRecord(value), tail), format));
@@ -2164,11 +2287,16 @@ function helpLines(help) {
2164
2287
  if (help === void 0) return [];
2165
2288
  return Array.isArray(help) ? [...help] : [help];
2166
2289
  }
2290
+ function validateExitCode(exitCode) {
2291
+ if (exitCode === void 0) return 0;
2292
+ if (Number.isInteger(exitCode) && exitCode >= 0 && exitCode <= 255) return exitCode;
2293
+ throw new RangeError(`an answer's exitCode must be an integer from 0 to 255, received ${exitCode}`);
2294
+ }
2167
2295
  function line(text) {
2168
2296
  return text.endsWith("\n") ? text : `${text}\n`;
2169
2297
  }
2170
2298
  //#endregion
2171
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/render/error.js
2299
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/render/error.js
2172
2300
  /** Describes one `lookupCommand` error in the terms the caller used. `command` tells an argument name from an option name. */
2173
2301
  function formatLookupError(error, command) {
2174
2302
  switch (error.type) {
@@ -2196,7 +2324,7 @@ function toArray$1(value) {
2196
2324
  return Array.isArray(value) ? value : [value];
2197
2325
  }
2198
2326
  //#endregion
2199
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/render/wrap.js
2327
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/render/wrap.js
2200
2328
  /**
2201
2329
  * Wraps `text` at `width` columns, breaking only between words; a longer word stays whole.
2202
2330
  * A tab counts as four spaces.
@@ -2219,15 +2347,19 @@ function wrap(text, width) {
2219
2347
  return lines.join("\n");
2220
2348
  }
2221
2349
  //#endregion
2222
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/render/help.js
2350
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/render/help.js
2223
2351
  const RIGHT_PADDING = 2;
2224
2352
  const MIN_LHS_WIDTH = 25;
2225
- function generateHelpMessage(cliName, command) {
2353
+ /**
2354
+ * `signatures` lists a group's sub-commands with their full signatures (AXI change 2).
2355
+ * Left off, the list is names only, as `output: 'console'` has always printed it.
2356
+ */
2357
+ function generateHelpMessage(cliName, command, options) {
2226
2358
  return `
2227
2359
  ${[
2228
2360
  generateUsageSection(cliName, command),
2229
2361
  generateDescriptionSection(command),
2230
- generateCommandsSection(command),
2362
+ generateCommandsSection(command, options?.signatures ?? false),
2231
2363
  generateArgumentsSection(command),
2232
2364
  generateOptionsSection(command),
2233
2365
  generateAliasSection(command),
@@ -2241,12 +2373,23 @@ function generateUsageSection(cliName, command) {
2241
2373
  const hasCommand = command.commands && command.commands.length > 0;
2242
2374
  let message = `Usage: ${nameChain.join(" ")}${hasCommand ? " <command>" : ""}`;
2243
2375
  if (command.arguments) message += command.arguments.some((a) => isRequired(a, true)) ? " <arguments>" : " [arguments]";
2244
- if (command.options) {
2376
+ if (hasOptions(command)) {
2245
2377
  const options = command.options;
2246
2378
  message += Object.keys(options).some((k) => isRequired(options[k], false)) ? " <options>" : " [options]";
2247
2379
  }
2248
2380
  return message;
2249
2381
  }
2382
+ function hasOptions(command) {
2383
+ return !!command.options && Object.keys(command.options).length > 0;
2384
+ }
2385
+ /**
2386
+ * A sub-command as a bare group lists it: its name and its required arguments, such as `get <id>`
2387
+ * (AXI change 2: names and required arguments only).
2388
+ */
2389
+ function commandSignature(command) {
2390
+ const required = (command.arguments ?? []).filter((a) => isRequired(a, true)).map((a) => `<${a.name}>`);
2391
+ return [command.name, ...required].join(" ");
2392
+ }
2250
2393
  function isRequired({ type, default: d }, defaultValue) {
2251
2394
  if (!type) return defaultValue;
2252
2395
  return !type.isOptional && d === void 0;
@@ -2262,22 +2405,37 @@ function getCommandNameChain(cliName, command) {
2262
2405
  }
2263
2406
  return [cliName, ...commands.map((c) => c.name).filter((x) => x)];
2264
2407
  }
2265
- function generateCommandsSection(command) {
2266
- const commandNames = getCommandsNamesAndAlias(command.commands);
2267
- if (commandNames.length === 0) return "";
2408
+ /**
2409
+ * A sub-command as its group's `--help` lists it: name, alias, and the full signature of its
2410
+ * arguments and options, such as `add <name: string> [--force]` (AXI change 2: a node's --help is its schema).
2411
+ */
2412
+ function commandFullSignature(command) {
2413
+ const parts = [command.alias?.length ? `${command.name} (${command.alias.join("|")})` : command.name];
2414
+ if (command.commands && command.commands.length > 0) parts.push("<command>");
2415
+ for (const a of command.arguments ?? []) parts.push(a.type ? formatArgumentDescriptor(a.name, a.type) : `<${a.name}>`);
2416
+ if (hasOptions(command)) {
2417
+ const options = command.options;
2418
+ for (const key in options) parts.push(formatKeyValue(key, options[key]));
2419
+ }
2420
+ return parts.join(" ");
2421
+ }
2422
+ function generateCommandsSection(command, signatures) {
2423
+ const commands = command.commands ?? [];
2424
+ if (commands.length === 0) return "";
2425
+ if (!signatures) return `Commands:
2426
+ ${wrap(commands.map((c) => c.alias ? `${c.name} (${c.alias.join("|")})` : c.name).join(", "), 80)}
2427
+
2428
+ ${(command.name ? `${command.name} <command> -h` : " <command> -h").padEnd(MIN_LHS_WIDTH)}Get help for <command>`;
2429
+ const sigs = commands.map(commandFullSignature);
2430
+ const width = Math.max(23, ...sigs.map((x) => x.length + RIGHT_PADDING));
2268
2431
  return `Commands:
2269
- ${wrap(commandNames.join(", "), 80)}
2432
+ ${commands.map((c, i) => {
2433
+ const line = c.description?.split("\n")[0] ?? "";
2434
+ return ` ${sigs[i].padEnd(width)}${line}`.trimEnd();
2435
+ }).join("\n")}
2270
2436
 
2271
2437
  ${(command.name ? `${command.name} <command> -h` : " <command> -h").padEnd(MIN_LHS_WIDTH)}Get help for <command>`;
2272
2438
  }
2273
- function getCommandsNamesAndAlias(commands) {
2274
- const result = [];
2275
- if (commands) commands.forEach((c) => {
2276
- if (c.alias) result.push(`${c.name} (${c.alias.join("|")})`);
2277
- else result.push(c.name);
2278
- });
2279
- return result;
2280
- }
2281
2439
  function generateArgumentsSection(command) {
2282
2440
  if (!command.arguments) return "";
2283
2441
  let message = "Arguments:\n";
@@ -2293,14 +2451,15 @@ function generateArgumentsSection(command) {
2293
2451
  return message;
2294
2452
  }
2295
2453
  function generateOptionsSection(command) {
2296
- if (!command.options) return "";
2454
+ if (!hasOptions(command)) return "";
2297
2455
  let message = "Options:\n";
2298
2456
  const entries = [];
2299
2457
  let maxOptionStrWidth = 0;
2300
- for (const key in command.options) {
2301
- const value = command.options[key];
2458
+ const options = command.options;
2459
+ for (const key in options) {
2460
+ const value = options[key];
2302
2461
  const optionStr = formatKeyValue(key, value);
2303
- const description = formatDescription(value, conflictsOf(key, command.options));
2462
+ const description = formatDescription(value, conflictsOf(key, options));
2304
2463
  entries.push([optionStr, description]);
2305
2464
  maxOptionStrWidth = Math.max(maxOptionStrWidth, optionStr.length);
2306
2465
  }
@@ -2396,7 +2555,13 @@ function generateHelpModel(cliName, command, globalOptions) {
2396
2555
  if (command.description) model.description = command.description;
2397
2556
  if (command.alias && command.alias.length > 0) model.aliases = [...command.alias];
2398
2557
  if (command.commands && command.commands.length > 0) model.commands = command.commands.map((c) => {
2399
- const summary = { name: c.name };
2558
+ const summary = {
2559
+ name: c.name,
2560
+ usage: commandFullSignature({
2561
+ ...c,
2562
+ alias: void 0
2563
+ })
2564
+ };
2400
2565
  if (c.alias && c.alias.length > 0) summary.aliases = [...c.alias];
2401
2566
  const line = c.description?.split("\n")[0];
2402
2567
  if (line) summary.description = line;
@@ -2420,7 +2585,12 @@ function generateHelpModel(cliName, command, globalOptions) {
2420
2585
  command,
2421
2586
  description
2422
2587
  }));
2423
- model.globalOptions = optionModels(globalOptions, []);
2588
+ if (isRoot) model.globalOptions = optionModels(globalOptions, []);
2589
+ else {
2590
+ const others = Object.keys(globalOptions).filter((k) => k !== "help" && k !== "format");
2591
+ model.globalOptions = optionModels(globalOptions, others);
2592
+ if (others.length > 0) model.otherGlobalOptions = others;
2593
+ }
2424
2594
  return model;
2425
2595
  }
2426
2596
  function optionModels(options, exclude) {
@@ -2470,8 +2640,42 @@ function describeData(data) {
2470
2640
  default: return { type: data.kind };
2471
2641
  }
2472
2642
  }
2643
+ /**
2644
+ * The structured help shaped for `toon`: each collection of rows becomes a uniform table.
2645
+ *
2646
+ * TOON writes an array as a table only when every row has the same keys and every cell is a scalar.
2647
+ * A row of the help model leaves out a key with nothing to say, and some keys hold an array,
2648
+ * so the collections fell back to list form. Here every row of a collection carries the union of its keys,
2649
+ * a missing one as `null`, and an array of scalars as one comma-joined string.
2650
+ * The `json` shape is unchanged: this is applied for `toon` only.
2651
+ */
2652
+ function tabularHelpModel(model) {
2653
+ const shaped = { ...model };
2654
+ for (const key of [
2655
+ "commands",
2656
+ "arguments",
2657
+ "options",
2658
+ "examples",
2659
+ "globalOptions"
2660
+ ]) {
2661
+ const rows = model[key];
2662
+ if (rows) shaped[key] = uniformRows(rows);
2663
+ }
2664
+ return shaped;
2665
+ }
2666
+ function uniformRows(rows) {
2667
+ const keys = [];
2668
+ for (const row of rows) for (const key of Object.keys(row)) if (!keys.includes(key)) keys.push(key);
2669
+ return rows.map((row) => Object.fromEntries(keys.map((key) => [key, cell(row[key])])));
2670
+ }
2671
+ function cell(value) {
2672
+ if (value === void 0) return null;
2673
+ if (Array.isArray(value)) return value.map(String).join(",");
2674
+ if (typeof value === "object" && value !== null) return JSON.stringify(value);
2675
+ return value;
2676
+ }
2473
2677
  //#endregion
2474
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/render/usage_error.js
2678
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/render/usage_error.js
2475
2679
  const rank = {
2476
2680
  "invalid-key": 0,
2477
2681
  "conflicting-options": 1,
@@ -2564,7 +2768,7 @@ function describeUsageError(error, command, cliName, hasHelp = true) {
2564
2768
  /** A command group invoked without its sub-command (#609): `missing-command`, with the valid sub-commands in `help`. */
2565
2769
  function describeMissingCommand(command, cliName) {
2566
2770
  const name = commandName(command, cliName);
2567
- const commands = (command.commands ?? []).map((c) => c.name);
2771
+ const commands = (command.commands ?? []).map(commandSignature);
2568
2772
  return {
2569
2773
  message: `missing command for \`${name}\``,
2570
2774
  code: "missing-command",
@@ -2594,7 +2798,7 @@ function isOptional(type) {
2594
2798
  }
2595
2799
  function argumentsHelp(command, cliName) {
2596
2800
  const name = command.name || cliName;
2597
- const commands = (command.commands ?? []).map((c) => c.name);
2801
+ const commands = (command.commands ?? []).map(commandSignature);
2598
2802
  if (!command.arguments?.length && commands.length > 0) return `valid commands for \`${name}\`: ${commands.join(", ")}`;
2599
2803
  const args = (command.arguments ?? []).map((a) => isOptional(a.type) ? `[${a.name}]` : `<${a.name}>`);
2600
2804
  return args.length > 0 ? `\`${name}\` takes: ${args.join(" ")}` : `\`${name}\` takes no arguments`;
@@ -2622,7 +2826,7 @@ function toArray(value) {
2622
2826
  return Array.isArray(value) ? value : [value];
2623
2827
  }
2624
2828
  //#endregion
2625
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/app/state.js
2829
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/app/state.js
2626
2830
  function state(options) {
2627
2831
  const { name, description = "", version } = options;
2628
2832
  return {
@@ -2634,7 +2838,7 @@ function state(options) {
2634
2838
  };
2635
2839
  }
2636
2840
  //#endregion
2637
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/app/usage_errors.js
2841
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/app/usage_errors.js
2638
2842
  /**
2639
2843
  * Looks the invocation up on the base command, the single source of truth for the global options.
2640
2844
  * A bad `--format` value falls back to `format`, so its usage error is reported in a valid format and cannot loop.
@@ -2693,7 +2897,7 @@ function withAllowedFormats(baseCommand) {
2693
2897
  } : e;
2694
2898
  }
2695
2899
  //#endregion
2696
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/app/builder.js
2900
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/app/builder.js
2697
2901
  function builder(context, options) {
2698
2902
  const s = state(options);
2699
2903
  const format = options.format ?? "toon";
@@ -2881,7 +3085,7 @@ function builder(context, options) {
2881
3085
  for (const hook of beforeRunHooks) await hook(run);
2882
3086
  } catch (e) {
2883
3087
  if (!isCliError(e)) throw e;
2884
- reportCliError(e, globals.format, ui);
3088
+ reportCliError(e, globals.format, ui, command);
2885
3089
  return exit(e.exitCode);
2886
3090
  }
2887
3091
  s.config = run.config;
@@ -2897,16 +3101,16 @@ function builder(context, options) {
2897
3101
  commandInstance.ui.showVersion = () => writeVersion();
2898
3102
  }
2899
3103
  try {
2900
- return answer(await commandInstance.run(args), globals.format);
3104
+ return answer(await commandInstance.run(args), globals.format, command);
2901
3105
  } catch (e) {
2902
3106
  if (!isCliError(e)) throw e;
2903
- reportCliError(e, globals.format, commandInstance.ui);
3107
+ reportCliError(e, globals.format, commandInstance.ui, command);
2904
3108
  return exit(e.exitCode);
2905
3109
  }
2906
3110
  }
2907
- function reportCliError(e, format, ui) {
3111
+ function reportCliError(e, format, ui, command) {
2908
3112
  if (isReportedCliError(e)) return;
2909
- const { help } = renderHelp(helpLinesOf(e), resolveRef);
3113
+ const { help } = renderHelp(helpLinesOf(e), (r) => resolveRef(r, command));
2910
3114
  if (output === "axi") {
2911
3115
  const report = errorOutput({
2912
3116
  message: e.message,
@@ -2924,36 +3128,72 @@ function builder(context, options) {
2924
3128
  function writeReport(report, format) {
2925
3129
  context.writeStdout(`${encode(errorOutput(report), format)}\n`);
2926
3130
  }
2927
- function answer(returned, format) {
3131
+ function answer(returned, format, command) {
2928
3132
  if (output === "axi") {
3133
+ const verbs = command?.commands?.length ? `valid commands for \`${commandName(command, s.name)}\`: ${command.commands.map(commandSignature).join(", ")}` : void 0;
2929
3134
  const text = printAnswer(returned, format, {
2930
- resolve: resolveRef,
2931
- refs
3135
+ resolve: (r) => resolveRef(r, command),
3136
+ refs,
3137
+ verbs
2932
3138
  });
2933
3139
  if (text !== void 0) context.writeStdout(text);
2934
3140
  }
3141
+ const code = answerExitCode(returned);
3142
+ if (code !== exitCodes.success) context.exit(code);
2935
3143
  return answerValue(returned);
2936
3144
  }
2937
- function writeHelp(command, baseCommand, format) {
2938
- context.writeStdout(format === "text" ? `${generateHelpMessage(s.name, command)}\n` : `${encode(generateHelpModel(s.name, command, baseCommand.options), format)}\n`);
3145
+ function helpModel(command, baseCommand, format) {
3146
+ const model = generateHelpModel(s.name, command, baseCommand.options);
3147
+ return format === "toon" ? tabularHelpModel(model) : model;
2939
3148
  }
2940
- function resolveRef(ref) {
2941
- let command = s.command;
2942
- for (const word of ref.path.split(" ")) {
2943
- const next = command.commands?.find((c) => c.name === word || c.alias?.includes(word));
2944
- if (!next) throw new TypeError(`ref('${ref.path}'): ${s.name} has no command "${ref.path}"`);
2945
- command = next;
3149
+ function writeHelp(command, baseCommand, format) {
3150
+ context.writeStdout(format === "text" ? `${generateHelpMessage(s.name, command, { signatures: true })}\n` : `${encode(helpModel(command, baseCommand, format), format)}\n`);
3151
+ }
3152
+ function resolveRef(ref, from) {
3153
+ const words = ref.path.split(" ");
3154
+ let found;
3155
+ for (const scope of scopesOf(from)) {
3156
+ const command = walk(scope, words);
3157
+ if (command) {
3158
+ found = {
3159
+ command,
3160
+ scope
3161
+ };
3162
+ break;
3163
+ }
2946
3164
  }
3165
+ if (!found) throw new TypeError(`ref('${ref.path}'): ${s.name} has no command "${ref.path}"`);
3166
+ const { command, scope } = found;
2947
3167
  for (const name of Object.keys(ref.options)) {
2948
3168
  if (command.options?.[name] || s.command.options?.[name]) continue;
2949
3169
  throw new TypeError(`ref('${ref.path}'): the command has no option "${name}"`);
2950
3170
  }
2951
3171
  return [
2952
3172
  s.name,
3173
+ ...pathOf(scope),
2953
3174
  ref.path,
2954
3175
  ...refArgs(ref)
2955
3176
  ].join(" ");
2956
3177
  }
3178
+ function scopesOf(from) {
3179
+ const scopes = [];
3180
+ for (let c = from?.parent; c?.parent; c = c.parent) scopes.push(c);
3181
+ scopes.push(s.command);
3182
+ return scopes;
3183
+ }
3184
+ function walk(scope, words) {
3185
+ let command = scope;
3186
+ for (const word of words) {
3187
+ command = command.commands?.find((c) => c.name === word || c.alias?.includes(word));
3188
+ if (!command) return void 0;
3189
+ }
3190
+ return command;
3191
+ }
3192
+ function pathOf(command) {
3193
+ const words = [];
3194
+ for (let c = command; c?.parent; c = c.parent) words.unshift(c.name);
3195
+ return words;
3196
+ }
2957
3197
  function writeVersion() {
2958
3198
  context.writeStdout(`${s.version || "not versioned"}\n`);
2959
3199
  }
@@ -3018,7 +3258,7 @@ function adjustCommand(base, command) {
3018
3258
  return command;
3019
3259
  }
3020
3260
  //#endregion
3021
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/drivers/logger.js
3261
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/drivers/logger.js
3022
3262
  function createBuilderUI(ui) {
3023
3263
  let pending = true;
3024
3264
  const entries = [];
@@ -3042,7 +3282,7 @@ function createBuilderUI(ui) {
3042
3282
  };
3043
3283
  }
3044
3284
  //#endregion
3045
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/drivers/stream_ui.js
3285
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/drivers/stream_ui.js
3046
3286
  /**
3047
3287
  * Which stream each `ui` method writes to, per output mode. Internal: a cli picks a preset
3048
3288
  * with `cli({ output })`, never a table (axi-native RFC section 3.10).
@@ -3119,7 +3359,7 @@ function formatLine(args, colors) {
3119
3359
  return args.map((v) => typeof v === "object" && v !== null ? inspect(v, { colors }) : v).join(" ");
3120
3360
  }
3121
3361
  //#endregion
3122
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/drivers/streams.js
3362
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/drivers/streams.js
3123
3363
  /**
3124
3364
  * The stdout and stderr driver: where every byte the cli writes goes through.
3125
3365
  *
@@ -3173,7 +3413,7 @@ function handleEpipe(stream) {
3173
3413
  });
3174
3414
  }
3175
3415
  //#endregion
3176
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/drivers/context.js
3416
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/drivers/context.js
3177
3417
  /** Creates the app context: the built-in ui writing to `streams` as `output` routes it. */
3178
3418
  function context(options = {}) {
3179
3419
  const cwd = process.cwd();
@@ -3197,7 +3437,7 @@ function context(options = {}) {
3197
3437
  };
3198
3438
  }
3199
3439
  //#endregion
3200
- //#region ../../node_modules/.pnpm/clibuilder@12.1.1_js-yaml@4.3.2/node_modules/clibuilder/esm/cli.js
3440
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/cli.js
3201
3441
  function cli(options) {
3202
3442
  return builder(context({
3203
3443
  streams: options.streams,
@@ -3205,37 +3445,76 @@ function cli(options) {
3205
3445
  }), options);
3206
3446
  }
3207
3447
  //#endregion
3208
- //#region src/command-output/command-output.ts
3448
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/axi/home.js
3449
+ /** Shortens a path under the home directory to `~/...`: fewer tokens, and no user name in the output. */
3450
+ function collapseHome$2(path, home = homedir()) {
3451
+ const rest = path.slice(home.length);
3452
+ if (!home || !path.startsWith(home) || rest !== "" && rest[0] !== "/" && rest[0] !== "\\") return path;
3453
+ return `~${rest}`;
3454
+ }
3209
3455
  /**
3210
- * Reports a failure on stderr, not stdout — stdout carries what an agent parses — and fails the cli
3211
- * with the exit code. A `CliError` the cli already reports is rethrown untouched.
3456
+ * The first lines of a CLI's home view — what it prints when run with no arguments — so an agent
3457
+ * knows which tool answered: `bin: ~/.local/bin/my-cli` and `description: ...`.
3458
+ *
3459
+ * Spread it in front of the live content the home view shows:
3460
+ *
3461
+ * @example
3462
+ * ```ts
3463
+ * return result({ ...homeHeader({ description: 'Manage plugins' }), plugins })
3464
+ * ```
3212
3465
  */
3213
- function fail(error, fallback) {
3214
- if (isCliError(error)) throw error;
3215
- process.stderr.write(`error: ${error instanceof Error ? error.message : fallback}\n`);
3216
- throw CliError.reported(exitCodes.error);
3466
+ function homeHeader(options) {
3467
+ return {
3468
+ bin: collapseHome$2(options.bin ?? process.argv[1] ?? "", options.home ?? homedir()),
3469
+ description: options.description
3470
+ };
3217
3471
  }
3472
+ //#endregion
3473
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/axi/truncate.js
3218
3474
  /**
3219
- * An answer that comes with a failing exit code. The cli prints only an answer a command returns, and
3220
- * a thrown error replaces the return, so the answer is printed first.
3475
+ * Keeps the first `limit` items and counts the rest, so the reader knows whether it saw everything
3476
+ * without another call.
3477
+ *
3478
+ * `count` reads `30 of 847 total`. The kept list's own length (`items[30]` in TOON) stays the
3479
+ * number of rows shown, so a header never claims rows that are not there.
3221
3480
  */
3222
- function answerThenFail(answer, format) {
3223
- const text = printAnswer(answer, format);
3224
- if (text !== void 0) process.stdout.write(text);
3225
- throw CliError.reported(exitCodes.error);
3481
+ function truncateList(items, options) {
3482
+ const total = items.length;
3483
+ const kept = options.full ? [...items] : items.slice(0, options.limit);
3484
+ return {
3485
+ items: kept,
3486
+ count: `${kept.length} of ${total} total`,
3487
+ total,
3488
+ truncated: kept.length < total
3489
+ };
3226
3490
  }
3491
+ //#endregion
3492
+ //#region ../../node_modules/.pnpm/clibuilder@12.3.0_js-yaml@4.3.2/node_modules/clibuilder/esm/axi.js
3227
3493
  /**
3228
- * AXI §10: collapses the home directory to `~` so a path in a report is portable to another
3229
- * machine.
3494
+ * `clibuilder/axi`: helpers that shape the value a command returns for an agent.
3495
+ *
3496
+ * Core already renders errors, usage errors and results the AXI way.
3497
+ * This subpath adds truncation, counts, the empty state and the home header,
3498
+ * plus the result channel and exit codes so one import covers a command.
3499
+ *
3500
+ * It also works without `cli()`: `encode`, `errorOutput` and `printAnswer` are the output contract,
3501
+ * and `runScript` runs a command headless, for a script that is not a cli.
3230
3502
  */
3231
- function collapseHome$1(home, path) {
3232
- return home && path.startsWith(home + sep) ? `~${path.slice(home.length)}` : path;
3233
- }
3234
- function displayBinPath(home, executable) {
3235
- return executable ? collapseHome$1(home, executable) : "buddy-agent-harness";
3236
- }
3503
+ /**
3504
+ * The `--full` option: the escape hatch from truncation. Add it to a command's options, and pass
3505
+ * `args.full` to {@link truncateText} and {@link truncateList}.
3506
+ *
3507
+ * @example
3508
+ * ```ts
3509
+ * options: { full: fullOption }
3510
+ * ```
3511
+ */
3512
+ const fullOption = {
3513
+ type: t.boolean().optional(),
3514
+ description: "Show the complete output instead of truncating it"
3515
+ };
3237
3516
  //#endregion
3238
- //#region ../../node_modules/.pnpm/cyber-agentkit@2.0.0_clibuilder@12.1.1_js-yaml@4.3.2_/node_modules/cyber-agentkit/dist/reference-where-CVmGFOwR.js
3517
+ //#region ../../node_modules/.pnpm/cyber-agentkit@2.2.0_clibuilder@12.3.0_js-yaml@4.3.2_/node_modules/cyber-agentkit/dist/reference-where-C93SAYP9.js
3239
3518
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
3240
3519
  var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
3241
3520
  /** Read a file, or `undefined` when it does not exist. Other read errors are thrown. */
@@ -11611,7 +11890,15 @@ const referenceCodes = {
11611
11890
  },
11612
11891
  "reference-renamed": {
11613
11892
  kind: "warning",
11614
- when: "a name or file under an older name, or an interface's older id (R96, R97)"
11893
+ when: "a name or file under an older name, an interface's older id, or a name a plugin's rename table renames (R96, R97, R120)"
11894
+ },
11895
+ "rename-invalid": {
11896
+ kind: "warning",
11897
+ when: "a plugin's rename table, or an entry of it, that is not a pair of bare reference names (R120)"
11898
+ },
11899
+ "reference-legacy-format": {
11900
+ kind: "warning",
11901
+ when: "a contribution in a form version 1 wrote and 2.0 retired, which `reference migrate` rewrites (R119)"
11615
11902
  },
11616
11903
  "alias-not-used": {
11617
11904
  kind: "warning",
@@ -11659,7 +11946,7 @@ const referenceCodes = {
11659
11946
  },
11660
11947
  "interface-unknown": {
11661
11948
  kind: "refused",
11662
- when: "`implementations` of an unknown interface (R35)"
11949
+ when: "`implementations` of an unknown interface (R35), or of a family matching none; `customize` of a family matching none (R135)"
11663
11950
  },
11664
11951
  "template-title": {
11665
11952
  kind: "refused",
@@ -11719,7 +12006,7 @@ const referenceCodes = {
11719
12006
  },
11720
12007
  "name-invalid": {
11721
12008
  kind: "usage",
11722
- when: "a write given a string that is not a name, such as `../x` (J1, R1)"
12009
+ when: "a write given a string that is not a name, such as `../x` (J1, R1); `*` anywhere but the last segment of a family (R136)"
11723
12010
  },
11724
12011
  "reference-qualified": {
11725
12012
  kind: "usage",
@@ -12042,6 +12329,19 @@ const namePattern$1 = /^[a-z0-9]+(?:[-.][a-z0-9]+)*$/i;
12042
12329
  const defaultSuffix = ".default";
12043
12330
  /** The suffix a metadata file's name carries before `.md` (K31); a name ending in it is reserved for that file. */
12044
12331
  const metadataSuffix = ".meta";
12332
+ /** Whether a value is written as a family: it ends in `.*`. Its prefix may still be invalid (R136). */
12333
+ function isFamilyName(value) {
12334
+ return value.endsWith(".*");
12335
+ }
12336
+ /**
12337
+ * The prefix of a family (K39), or why the value is not one. `*` anywhere but the last segment, a
12338
+ * qualified or file-like prefix, or an empty prefix makes the name invalid (R136).
12339
+ */
12340
+ function parseFamily(value) {
12341
+ const prefix = value.slice(0, -2);
12342
+ if (!isFamilyName(value) || !prefix || prefix.includes("*") || !namePattern$1.test(prefix)) return { invalid: `"${value}" is not a family. A family is \`<prefix>.*\`, with \`*\` only as the last segment and a prefix of letters, digits, hyphens, and dots.` };
12343
+ return { prefix };
12344
+ }
12045
12345
  /** No name at all is a usage error (`name-missing`), unlike a string that is not a name, which is `invalid`. */
12046
12346
  function requireName(value) {
12047
12347
  if (!value.trim()) throw new ReferenceFailure("name-missing", "name a reference.", { help: "Run `reference list` for every name." });
@@ -12060,6 +12360,7 @@ function parseReferenceName(value) {
12060
12360
  if (value.endsWith(".md")) return invalid(`"${value}" names a file. Ask for the reference by name, without the .md extension.`);
12061
12361
  const extension = dataExtensions.find((item) => value.endsWith(item.extension));
12062
12362
  if (extension) return invalid(`"${value}" names a file. Ask for the reference by name, without the ${extension.extension} extension.`);
12363
+ if (value.includes("*")) return invalid(`"${value}" is not a reference name: \`*\` makes a family, \`<prefix>.*\`, which only customize and implementations take.`);
12063
12364
  const parts = value.split("/");
12064
12365
  if (parts.length > 2 || value.includes("\\")) return invalid(`"${value}" is a path. A name is letters, digits, hyphens, and dots, optionally after "<plugin>/".`);
12065
12366
  const [first, second] = parts;
@@ -12112,11 +12413,12 @@ function parseDocument(raw, warn) {
12112
12413
  body
12113
12414
  };
12114
12415
  }
12115
- /** The contribution's own `merge`; `undefined` when it writes none. An unknown value reads as `patch`. */
12416
+ /** The contribution's own `merge`; `undefined` when it writes none. An unknown or version 1 value reads as `patch`. */
12116
12417
  function readMergeOperation(metadata, warn) {
12117
12418
  const value = metadata["merge"];
12118
12419
  if (value === void 0) return void 0;
12119
12420
  if (isMergeOperation(value)) return value;
12421
+ if (value === "merge-sections" || value === "first-wins") return "patch";
12120
12422
  warn("merge-unknown", `unknown merge "${String(value)}"; read as patch`);
12121
12423
  return "patch";
12122
12424
  }
@@ -12473,6 +12775,184 @@ function pickSections(body, picks) {
12473
12775
  missing
12474
12776
  };
12475
12777
  }
12778
+ const legacyMerges = {
12779
+ "merge-sections": void 0,
12780
+ "first-wins": "replace"
12781
+ };
12782
+ function isLegacyMerge(value) {
12783
+ return value === "merge-sections" || value === "first-wins";
12784
+ }
12785
+ /** The plugins `overrides` names, each qualified entry reduced to its plugin; `undefined` when it is not a list of them. */
12786
+ function overriddenPlugins(value) {
12787
+ const list = typeof value === "string" ? [value] : Array.isArray(value) ? value : void 0;
12788
+ if (!list?.length || !list.every((item) => typeof item === "string" && item.trim())) return void 0;
12789
+ return [...new Set(list.map((item) => item.split("/")[0].trim()))];
12790
+ }
12791
+ /** The plugin or `default` each `extends` step names, as written; `undefined` when it is not a list of steps. */
12792
+ function stepTargets(value) {
12793
+ const list = typeof value === "string" ? [value] : Array.isArray(value) ? value : void 0;
12794
+ if (!list) return void 0;
12795
+ const targets = [];
12796
+ for (const item of list) if (typeof item === "string") targets.push(item);
12797
+ else if (isRecord$1(item) && Object.keys(item).length === 1) targets.push(Object.keys(item)[0]);
12798
+ else return void 0;
12799
+ return targets;
12800
+ }
12801
+ const flow = (items) => `[${items.join(", ")}]`;
12802
+ /**
12803
+ * The legacy forms a Markdown contribution's frontmatter holds (K37), and the frontmatter that says
12804
+ * the same in 2.0 (R122). The body and every other key are left as written.
12805
+ */
12806
+ function legacyRewrite(metadata, body, place, name) {
12807
+ const findings = [];
12808
+ const keys = {};
12809
+ const merge = metadata["merge"];
12810
+ if (isLegacyMerge(merge)) {
12811
+ const into = legacyMerges[merge];
12812
+ keys["merge"] = place === "project" ? void 0 : into;
12813
+ findings.push({
12814
+ form: merge,
12815
+ from: `merge: ${merge}`,
12816
+ to: place === "project" ? "no merge: a project section carries none" : into === void 0 ? "no merge: patch is the default" : `merge: ${into}`,
12817
+ reads: "it reads as patch"
12818
+ });
12819
+ }
12820
+ if ("overrides" in metadata) findings.push(overridesFinding(metadata, place, keys));
12821
+ if (place === "project" && metadata["extends"] !== void 0 && !hasBody(body)) {
12822
+ const steps = stepTargets(metadata["extends"]);
12823
+ findings.push({
12824
+ form: "project-selection",
12825
+ from: `extends: ${steps ? flow(steps) : JSON.stringify(metadata["extends"])}`,
12826
+ manual: `a pick now applies to the whole repository: run \`reference customize ${name} --extends ${steps?.join(" ") ?? "<step>..."}\` at the root, then delete this file`,
12827
+ reads: "its extends is ignored"
12828
+ });
12829
+ }
12830
+ return {
12831
+ findings,
12832
+ keys
12833
+ };
12834
+ }
12835
+ function overridesFinding(metadata, place, keys) {
12836
+ const value = metadata["overrides"];
12837
+ const plugins = overriddenPlugins(value);
12838
+ const from = `overrides: ${plugins ? flow(Array.isArray(value) ? value : [String(value)]) : JSON.stringify(value)}`;
12839
+ const reads = "it is ignored";
12840
+ if (place === "project") {
12841
+ keys["overrides"] = void 0;
12842
+ return {
12843
+ form: "overrides",
12844
+ from,
12845
+ to: "removed: a project section carries no extends",
12846
+ reads
12847
+ };
12848
+ }
12849
+ if (!plugins) return {
12850
+ form: "overrides",
12851
+ from,
12852
+ manual: "`overrides` is not a list of plugin names",
12853
+ reads
12854
+ };
12855
+ const written = metadata["extends"];
12856
+ if (written === void 0) {
12857
+ keys["overrides"] = void 0;
12858
+ keys["extends"] = plugins;
12859
+ return {
12860
+ form: "overrides",
12861
+ from,
12862
+ to: `extends: ${flow(plugins)}`,
12863
+ reads
12864
+ };
12865
+ }
12866
+ const targets = stepTargets(written);
12867
+ const missing = plugins.filter((plugin) => !targets?.includes(plugin));
12868
+ if (targets && !missing.length) {
12869
+ keys["overrides"] = void 0;
12870
+ return {
12871
+ form: "overrides",
12872
+ from,
12873
+ to: "removed: extends already names each plugin",
12874
+ reads
12875
+ };
12876
+ }
12877
+ return {
12878
+ form: "overrides",
12879
+ from,
12880
+ manual: `\`extends\` is also written and does not name ${missing.join(", ")}; add ${missing.length > 1 ? "them" : "it"} to \`extends\` where it belongs, then remove \`overrides\``,
12881
+ reads
12882
+ };
12883
+ }
12884
+ /** The 2.0 form of a skill's default in the old layout (K37). */
12885
+ function defaultLayoutFinding(name) {
12886
+ return {
12887
+ form: "default-layout",
12888
+ from: `references/${name}.md`,
12889
+ to: `references/${name}.default.md`,
12890
+ reads: "it is not read as the default"
12891
+ };
12892
+ }
12893
+ /** The warning a legacy form raises at load (R119): the form, how it reads, its 2.0 form, and the fix. */
12894
+ function warnLegacy(finding, fix, warn) {
12895
+ const note = finding.note === void 0 ? "" : `, and ${finding.note}`;
12896
+ const into = finding.to === void 0 ? "" : `; its 2.0 form is ${finding.to}${note}`;
12897
+ const how = finding.manual === void 0 ? fix : `To fix it, ${finding.manual}${finding.note === void 0 ? "" : `; ${finding.note}`}.`;
12898
+ warn("reference-legacy-format", `\`${finding.from}\` is a version 1 form, and ${finding.reads}${into}. ${how}`);
12899
+ }
12900
+ function directoryEntries(dir) {
12901
+ try {
12902
+ return readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
12903
+ } catch {
12904
+ return [];
12905
+ }
12906
+ }
12907
+ /**
12908
+ * The qualified-name overrides a tier or project folder holds, of `name` when given. A subfolder
12909
+ * holding `README.md` is a reference in folder form, not a plugin's, so its files are left alone.
12910
+ */
12911
+ function qualifiedOverrides(dir, name) {
12912
+ const found = [];
12913
+ for (const folder of directoryEntries(dir)) {
12914
+ const plugin = folder.name;
12915
+ if (!folder.isDirectory() || !namePattern$1.test(plugin)) continue;
12916
+ const files = directoryEntries(join(dir, plugin));
12917
+ if (files.some((file) => file.name === "README.md")) continue;
12918
+ for (const file of files) {
12919
+ if (!file.isFile() || !file.name.endsWith(".md")) continue;
12920
+ const held = file.name.slice(0, -3);
12921
+ if (!namePattern$1.test(held) || held.endsWith(".default") || held.endsWith(".meta")) continue;
12922
+ if (name !== void 0 && held !== name) continue;
12923
+ found.push({
12924
+ plugin,
12925
+ name: held,
12926
+ path: join(dir, plugin, file.name),
12927
+ file: `${plugin}/${file.name}`
12928
+ });
12929
+ }
12930
+ }
12931
+ return found;
12932
+ }
12933
+ const neverApplied = "no released version read it, so the override never applied";
12934
+ const turnsOn = "applying the plan turns it on";
12935
+ /**
12936
+ * The 2.0 form of a qualified-name override (K37): `<name>.md` beside the subfolder, with
12937
+ * `extends: [<plugin>]`. A project section carries no `extends` (K24), so there it is manual.
12938
+ */
12939
+ function qualifiedOverrideFinding(found, place) {
12940
+ const { plugin, name, file } = found;
12941
+ const said = {
12942
+ form: "qualified-override",
12943
+ from: file,
12944
+ reads: neverApplied,
12945
+ note: turnsOn
12946
+ };
12947
+ if (place === "project") return {
12948
+ ...said,
12949
+ manual: `pick plugin ${plugin} for the whole repository with \`reference customize ${name} --extends ${plugin}\` at the root, since a project section carries no extends; then move this file's body into ${name}.md in this folder and delete this file`
12950
+ };
12951
+ return {
12952
+ ...said,
12953
+ to: `${name}.md with extends: [${plugin}]`
12954
+ };
12955
+ }
12476
12956
  /** Keys a metadata file cannot carry: a non-Markdown contribution always replaces below (R68), and an interface's sections are Markdown. */
12477
12957
  const ignoredKeys$1 = ["merge", "implements"];
12478
12958
  /** The metadata file beside a contribution of `name` in a layer: `<name>.meta.md`, or a default's `<name>.default.meta.md`. */
@@ -12822,6 +13302,11 @@ function readContribution(layer, name, entry, { display, without, warnAt, older
12822
13302
  const stray = join(layer.dir, `${name}${defaultSuffix}.md`);
12823
13303
  if (stray !== without && tryRead$2(stray) !== void 0) warnAt(display(stray))("default-in-tier", "it is ignored: a default goes in a skill's references/ folder");
12824
13304
  }
13305
+ if (layer.tier === "repo" || layer.tier === "user") warnQualifiedOverrides$1(layer, name, {
13306
+ display,
13307
+ without,
13308
+ warnAt
13309
+ });
12825
13310
  const owner = layer.tier === "plugin" || layer.tier === "default";
12826
13311
  const [hit, ...ignored] = [name, ...owner ? [] : older].flatMap((as) => candidateFiles(layer, as).flatMap((file) => {
12827
13312
  const path = join(layer.dir, file);
@@ -12849,6 +13334,11 @@ function readContribution(layer, name, entry, { display, without, warnAt, older
12849
13334
  break;
12850
13335
  }
12851
13336
  if (hit && hit.as !== name) renamed(entry, hit.as, name, hit.path, display, warnAt);
13337
+ if (!hit && !sidecar && layer.tier === "default") warnDefaultLayout(layer, name, {
13338
+ display,
13339
+ without,
13340
+ warnAt
13341
+ });
12852
13342
  if (!hit) return sidecar ? sidecarSelection(layer, entry, sidecar, display, warnAt) : void 0;
12853
13343
  const label = contributionLabel(layer, hit.path, display);
12854
13344
  const warn = warnAt(label);
@@ -12878,6 +13368,8 @@ function readContribution(layer, name, entry, { display, without, warnAt, older
12878
13368
  };
12879
13369
  }
12880
13370
  const { metadata, body } = parseDocument(hit.raw, warn);
13371
+ const fix = legacyFix(layer, name, display);
13372
+ for (const finding of legacyRewrite(metadata, body, "contribution", name).findings) warnLegacy(finding, fix, warn);
12881
13373
  const merge = readMergeOperation(metadata, warn);
12882
13374
  const steps = readExtends(metadata["extends"], warn);
12883
13375
  Object.assign(entry, {
@@ -12904,6 +13396,26 @@ function readContribution(layer, name, entry, { display, without, warnAt, older
12904
13396
  steps
12905
13397
  };
12906
13398
  }
13399
+ /** Who rewrites a legacy form, and how (R119): only a file `migrate` may change gets the command itself. */
13400
+ function legacyFix(layer, name, display) {
13401
+ if (layer.tier === "plugin") return `Plugin ${layer.plugin}'s author rewrites it with \`reference migrate --plugin <its source folder>\`; until then, customize the name.`;
13402
+ if (layer.tier === "default") return `Its skill's author rewrites it with \`reference migrate ${name} --default ${display(dirname(layer.dir))}\`.`;
13403
+ if (layer.tier === "managed") return "An admin rewrites it; `reference migrate` never changes a managed file.";
13404
+ return "Run `reference migrate` to rewrite it.";
13405
+ }
13406
+ /** A qualified-name override of the name in a tier folder is not read; each warns (R119). */
13407
+ function warnQualifiedOverrides$1(layer, name, { display, without, warnAt }) {
13408
+ for (const found of qualifiedOverrides(layer.dir, name)) {
13409
+ if (found.path === without) continue;
13410
+ warnLegacy(qualifiedOverrideFinding(found, "contribution"), legacyFix(layer, name, display), warnAt(contributionLabel(layer, found.path, display)));
13411
+ }
13412
+ }
13413
+ /** A skill's default in the old layout, `references/<name>.md`, is not read; it warns when the skill's folder has no default of the name (R119). */
13414
+ function warnDefaultLayout(layer, name, { display, without, warnAt }) {
13415
+ const path = join(layer.dir, `${name}.md`);
13416
+ if (path === without || tryRead$2(path) === void 0) return;
13417
+ warnLegacy(defaultLayoutFinding(name), legacyFix(layer, name, display), warnAt(display(path)));
13418
+ }
12907
13419
  /** A file filed under an older name applies as the new one, with a warning naming the file to rename (R97). */
12908
13420
  function renamed(entry, as, name, path, display, warnAt) {
12909
13421
  entry.alias = as;
@@ -13147,7 +13659,7 @@ function checkImplementation(iface, { name, version, content, accept = iface.ver
13147
13659
  unsupported
13148
13660
  };
13149
13661
  }
13150
- const cache = /* @__PURE__ */ new WeakMap();
13662
+ const cache$1 = /* @__PURE__ */ new WeakMap();
13151
13663
  const quiet = () => () => {};
13152
13664
  function ownerLabel(owner) {
13153
13665
  return owner.tier === "plugin" ? `plugin ${owner.plugin}` : `default of ${owner.skill}`;
@@ -13169,7 +13681,7 @@ function listed(value) {
13169
13681
  */
13170
13682
  function aliasIndex(source) {
13171
13683
  const keyed = Array.isArray(source) ? void 0 : source;
13172
- const hit = keyed && cache.get(keyed);
13684
+ const hit = keyed && cache$1.get(keyed);
13173
13685
  if (hit) return hit;
13174
13686
  const layers = Array.isArray(source) ? source : source.layers;
13175
13687
  const byAlias = /* @__PURE__ */ new Map();
@@ -13209,16 +13721,15 @@ function aliasIndex(source) {
13209
13721
  });
13210
13722
  continue;
13211
13723
  }
13212
- if (id !== void 0) {
13213
- push(interfaces, id, {
13214
- alias,
13215
- owner
13216
- });
13217
- continue;
13724
+ if (id !== void 0) push(interfaces, id, {
13725
+ alias,
13726
+ owner
13727
+ });
13728
+ for (const older of id === void 0 ? [alias] : documentNames$1(alias, read.metadata)) {
13729
+ push(byAlias, older, owner);
13730
+ const known = byName.get(name) ?? [];
13731
+ if (!known.includes(older)) byName.set(name, [...known, older]);
13218
13732
  }
13219
- push(byAlias, alias, owner);
13220
- const older = byName.get(name) ?? [];
13221
- if (!older.includes(alias)) byName.set(name, [...older, alias]);
13222
13733
  }
13223
13734
  }
13224
13735
  }
@@ -13229,9 +13740,18 @@ function aliasIndex(source) {
13229
13740
  warnings,
13230
13741
  interfaces
13231
13742
  };
13232
- if (keyed) cache.set(keyed, index);
13743
+ if (keyed) cache$1.set(keyed, index);
13233
13744
  return index;
13234
13745
  }
13746
+ /**
13747
+ * The older names of an interface document that lists `alias` (R128): `<old id>.interface` always,
13748
+ * and `<old id>` itself when the interface is `many`, as no implementation of one is named `<old id>`.
13749
+ * For a `one` interface, `<old id>` is the implementation's older name (R96).
13750
+ */
13751
+ function documentNames$1(alias, metadata) {
13752
+ const block = metadata["interface"];
13753
+ return isRecord$1(block) && block["cardinality"] === "many" ? [alias, `${alias}${interfaceSuffix}`] : [`${alias}${interfaceSuffix}`];
13754
+ }
13235
13755
  function aliasFile(read) {
13236
13756
  return read.entry.frontmatter && read.whole ? read.path.replace(/\.[^./]+$/, ".meta.md") : read.path;
13237
13757
  }
@@ -13476,7 +13996,7 @@ function ignoredFolderOf(root, entry) {
13476
13996
  const folder = segments.join("/");
13477
13997
  return folder && isDirectory$3(join(root, ...segments, ...referencesPath)) ? folder : void 0;
13478
13998
  }
13479
- function entries(dir) {
13999
+ function entries$1(dir) {
13480
14000
  try {
13481
14001
  return readdirSync(dir, { withFileTypes: true });
13482
14002
  } catch {
@@ -13493,7 +14013,7 @@ function walk(root) {
13493
14013
  const found = [];
13494
14014
  const packages = [];
13495
14015
  const visit = (relative) => {
13496
- const listing = entries(join(root, relative));
14016
+ const listing = entries$1(join(root, relative));
13497
14017
  if (relative && !relative.split("/").includes("node_modules")) {
13498
14018
  if (listing.some((entry) => entry.isFile() && entry.name === "package.json")) packages.push(relative);
13499
14019
  }
@@ -13504,8 +14024,8 @@ function walk(root) {
13504
14024
  visit(child);
13505
14025
  continue;
13506
14026
  }
13507
- if (relative && entries(join(root, child, referencesPath[1])).length) found.push(relative);
13508
- for (const inner of entries(join(root, child))) if (inner.isDirectory() && inner.name !== referencesPath[1]) visit(`${child}/${inner.name}`);
14027
+ if (relative && entries$1(join(root, child, referencesPath[1])).length) found.push(relative);
14028
+ for (const inner of entries$1(join(root, child))) if (inner.isDirectory() && inner.name !== referencesPath[1]) visit(`${child}/${inner.name}`);
13509
14029
  }
13510
14030
  };
13511
14031
  visit("");
@@ -13590,7 +14110,7 @@ function gitIgnores(root, path) {
13590
14110
  }
13591
14111
  /** One answer per discovery, so a run resolving many names reads each `package.json` once. */
13592
14112
  const found = /* @__PURE__ */ new WeakMap();
13593
- function readJson$2(path) {
14113
+ function readJson$3(path) {
13594
14114
  try {
13595
14115
  return JSON.parse(readFileSync(path, "utf8"));
13596
14116
  } catch {
@@ -13601,7 +14121,7 @@ function declared(top, folders) {
13601
14121
  const plugins = [];
13602
14122
  for (const folder of folders) {
13603
14123
  const manifestPath = join(top, ...folder.split("/"), "package.json");
13604
- const manifest = readJson$2(manifestPath);
14124
+ const manifest = readJson$3(manifestPath);
13605
14125
  for (const pkg of declaredDependencies(manifest)) {
13606
14126
  const dir = packageDir(pkg, manifestPath);
13607
14127
  const plugin = dir && pluginManifestName(dir);
@@ -13978,10 +14498,26 @@ function readPluginSection(name, found, read, result) {
13978
14498
  plugin: plugin.plugin
13979
14499
  });
13980
14500
  }
14501
+ /** A qualified-name override of the name in a project folder is not read; each warns (R119). */
14502
+ function warnQualifiedOverrides(name, folder, read) {
14503
+ const { display, without, warnAt, listed } = read;
14504
+ for (const found of qualifiedOverrides(folder.dir, name)) {
14505
+ const hit = {
14506
+ folder,
14507
+ file: found.file,
14508
+ as: name,
14509
+ path: found.path,
14510
+ relative: `${folder.folder}/.agents/references/${found.file}`
14511
+ };
14512
+ if (found.path === without || !listed(hit)) continue;
14513
+ warnLegacy(qualifiedOverrideFinding(found, "project"), "", warnAt(`project ${display(found.path)}`));
14514
+ }
14515
+ }
13981
14516
  /** The folder's own `.agents/references/<name>.md` or `<name>/README.md`, when git does not ignore it. */
13982
14517
  function readOwnSection(name, folder, ignored, read, result) {
13983
14518
  const { trace, sections } = result;
13984
14519
  const { format, display, without, warnAt, catalog, older, listed, dropping, managed } = read;
14520
+ if (!ignored) warnQualifiedOverrides(name, folder, read);
13985
14521
  const filed = [name, ...older].map((as) => filesFor(folder, as, without));
13986
14522
  const markdown = filed.flatMap((files) => files.markdown);
13987
14523
  const data = filed.flatMap((files) => files.data);
@@ -14017,9 +14553,14 @@ function readOwnSection(name, folder, ignored, read, result) {
14017
14553
  const { metadata, body } = parseDocument(tryRead$2(hit.path) ?? "", warnAt(label));
14018
14554
  row.frontmatter = metadata;
14019
14555
  const kept = { ...metadata };
14556
+ const legacy = legacyRewrite(metadata, body, "project", name).findings;
14557
+ for (const finding of legacy) warnLegacy(finding, "Run `reference migrate` to rewrite it.", warnAt(label));
14558
+ const warnedAsLegacy = new Set(legacy.flatMap(({ form }) => form === "project-selection" ? ["extends"] : form === "overrides" ? ["overrides"] : ["merge"]));
14559
+ delete kept["overrides"];
14020
14560
  for (const key of ignoredKeys) {
14021
14561
  if (!(key in kept)) continue;
14022
14562
  delete kept[key];
14563
+ if (warnedAsLegacy.has(key)) continue;
14023
14564
  warnAt(label)("project-section-key-ignored", `\`${key}\` is ignored in the project section for ${folder.folder}: a project section states only what differs in its folder`);
14024
14565
  }
14025
14566
  if (dropping) {
@@ -14102,6 +14643,88 @@ function projectSectionNames(root, cache, plugins = /* @__PURE__ */ new Set()) {
14102
14643
  }
14103
14644
  return [...held];
14104
14645
  }
14646
+ /** Every name a qualified-name override (K37) in a project folder below the root names, in a file git does not ignore. */
14647
+ function projectQualifiedNames(root, cache) {
14648
+ const found = projectFolders(root, cache);
14649
+ return found.folders.flatMap((folder) => qualifiedOverrides(folder.dir).filter(({ file }) => !found.listed || found.listed.has(`${folder.folder}/.agents/references/${file}`)).map(({ name }) => name));
14650
+ }
14651
+ /**
14652
+ * A plugin's rename table, in its `references/` folder (K38). A leading `_` is not a reference name,
14653
+ * so the file never answers one.
14654
+ */
14655
+ const renameTableFile = "_renames.yaml";
14656
+ const cache = /* @__PURE__ */ new WeakMap();
14657
+ function bare(value) {
14658
+ if (typeof value !== "string") return void 0;
14659
+ const parsed = parseReferenceName(value.trim());
14660
+ return parsed.invalid || parsed.plugin !== void 0 ? void 0 : parsed.name;
14661
+ }
14662
+ /**
14663
+ * Every rename table the plugin tier's plugins ship (R120, R124), read once per set of layers. Only
14664
+ * a plugin renames its names, so no other tier is read.
14665
+ */
14666
+ function renameTable(source) {
14667
+ const keyed = Array.isArray(source) ? void 0 : source;
14668
+ const hit = keyed && cache.get(keyed);
14669
+ if (hit) return hit;
14670
+ const layers = Array.isArray(source) ? source : source.layers;
14671
+ const byOld = /* @__PURE__ */ new Map();
14672
+ const warnings = [];
14673
+ const seen = /* @__PURE__ */ new Set();
14674
+ for (const layer of layers) {
14675
+ if (layer.tier !== "plugin" || layer.skipped || layer.plugin === void 0) continue;
14676
+ const path = join(layer.dir, renameTableFile);
14677
+ if (seen.has(path)) continue;
14678
+ seen.add(path);
14679
+ const raw = tryRead$2(path);
14680
+ if (raw === void 0) continue;
14681
+ const plugin = layer.plugin;
14682
+ let value;
14683
+ try {
14684
+ value = (0, import_dist.parse)(raw);
14685
+ } catch {
14686
+ value = void 0;
14687
+ }
14688
+ if (value === null || value === void 0) {
14689
+ if (raw.trim()) warnings.push({
14690
+ code: "rename-invalid",
14691
+ message: `plugin ${plugin}: ${path} is not YAML; it is ignored`
14692
+ });
14693
+ continue;
14694
+ }
14695
+ if (!isRecord$1(value)) {
14696
+ warnings.push({
14697
+ code: "rename-invalid",
14698
+ message: `plugin ${plugin}: ${path} is not a mapping of older names to new names; it is ignored`
14699
+ });
14700
+ continue;
14701
+ }
14702
+ for (const [key, target] of Object.entries(value)) {
14703
+ const from = bare(key);
14704
+ const to = bare(target);
14705
+ if (from === void 0 || to === void 0 || from === to) {
14706
+ warnings.push({
14707
+ code: "rename-invalid",
14708
+ message: `plugin ${plugin}: the rename ${JSON.stringify(key)}: ${JSON.stringify(target)} in ${renameTableFile} is not a pair of bare reference names; it is ignored`
14709
+ });
14710
+ continue;
14711
+ }
14712
+ const list = byOld.get(from) ?? [];
14713
+ if (!list.some((entry) => entry.to === to && entry.plugin === plugin)) list.push({
14714
+ from,
14715
+ to,
14716
+ plugin
14717
+ });
14718
+ byOld.set(from, list);
14719
+ }
14720
+ }
14721
+ const table = {
14722
+ byOld,
14723
+ warnings
14724
+ };
14725
+ if (keyed) cache.set(keyed, table);
14726
+ return table;
14727
+ }
14105
14728
  function layersOf(source) {
14106
14729
  return Array.isArray(source) ? {
14107
14730
  layers: [...source],
@@ -14188,6 +14811,7 @@ function resolveAdjusted(input, source, { display = (path) => path, without, bel
14188
14811
  resolvedAs: ref.name
14189
14812
  };
14190
14813
  const older = olderNames(ref.name, aliases, warn);
14814
+ if (followed.kind === "own") warnTableRename(ref, source, aliases, warn);
14191
14815
  const cut = below === void 0 ? -1 : all.findIndex((layer) => layer.dir === below);
14192
14816
  const layers = cut === -1 ? all : all.slice(cut + 1);
14193
14817
  const contributions = [];
@@ -14475,7 +15099,19 @@ function resolveInterface(ref, contributions, holders, defaultContribution, resu
14475
15099
  function referenceNames(source) {
14476
15100
  return [...new Set(layersOf(source).layers.flatMap(layerNames))].sort((a, b) => a.localeCompare(b));
14477
15101
  }
14478
- /** Every name each installed skill holds a default of, by name then skill. */
15102
+ /**
15103
+ * A load of a name a plugin's rename table lists as older (R120) answers as itself, with a warning
15104
+ * naming the new name. A name a plugin or a default still holds as its own is not renamed.
15105
+ */
15106
+ function warnTableRename(ref, source, aliases, warn) {
15107
+ if (aliases.owned.has(ref.name)) return;
15108
+ const entries = (renameTable(source).byOld.get(ref.name) ?? []).filter((entry) => ref.plugin === void 0 || entry.plugin === ref.plugin);
15109
+ if (!entries.length) return;
15110
+ const by = [...new Set(entries.map(({ plugin }) => `plugin ${plugin}`))].join(" and ");
15111
+ const targets = [...new Set(entries.map(({ to }) => `"${to}"`))].join(" or ");
15112
+ warn("reference-renamed", `${by} renamed "${ref.name}" to ${targets}: load ${targets} instead, and run \`reference migrate\` to rename the files filed under "${ref.name}"`);
15113
+ }
15114
+ /** Every name each installed skill holds a default of, by name then skill. */
14479
15115
  function skillHoldings(source) {
14480
15116
  return (Array.isArray(source) ? [] : source.skills?.skills ?? []).flatMap((skill) => layerNames(defaultLayer(skill.folder)).map((name) => ({
14481
15117
  name,
@@ -14790,6 +15426,8 @@ const checkWarningClass = {
14790
15426
  "reference-stale": "environment",
14791
15427
  "reference-renamed": "environment",
14792
15428
  "alias-not-used": "environment",
15429
+ "rename-invalid": "environment",
15430
+ "reference-legacy-format": "defect",
14793
15431
  "alias-invalid": "defect",
14794
15432
  "project-section-not-markdown": "project-section",
14795
15433
  "reference-hidden": "environment",
@@ -14874,6 +15512,14 @@ function interfaceLookup(source, options = {}) {
14874
15512
  }
14875
15513
  function lookUp(id, source, options) {
14876
15514
  const resolved = resolveReference(`${id}${interfaceSuffix}`, source, options);
15515
+ const owner = interfaceIdOf(resolved.trace.alias?.resolvedAs ?? "");
15516
+ if (owner !== void 0) {
15517
+ const found = readFound(owner, resolved);
15518
+ return found.value ? {
15519
+ ...found,
15520
+ renamed: owner
15521
+ } : found;
15522
+ }
14877
15523
  if (resolved.status === "missing") {
14878
15524
  const owners = [...aliasIndex(source).interfaces].filter(([, aliases]) => aliases.some(({ alias }) => alias === id)).map(([owner]) => owner);
14879
15525
  if (owners.length > 1) return lookupFailure(id, `${id} is an older id of ${owners.join(" and ")}; implement one of them by its own id`);
@@ -14903,13 +15549,15 @@ function readFound(id, resolved) {
14903
15549
  const { value, problems } = readInterface(id, resolved.metadata ?? {});
14904
15550
  return value ? { value } : lookupFailure(id, `${name} is malformed: ${problems.join("; ")}`);
14905
15551
  }
14906
- function bareName$1(name) {
15552
+ /** The bare name a document answers as: the name an older one asked for resolves as (R96, R128), or the name itself. */
15553
+ function bareName$1(resolved) {
15554
+ const name = resolved.trace.alias?.resolvedAs ?? resolved.name;
14907
15555
  return name.slice(name.lastIndexOf("/") + 1);
14908
15556
  }
14909
15557
  /** What a composed document declares: an interface, an implementation, or neither. */
14910
15558
  function kindOf(resolved) {
14911
15559
  const metadata = resolved.metadata ?? {};
14912
- if (interfaceIdOf(bareName$1(resolved.name)) !== void 0 || metadata["interface"] !== void 0) return "interface";
15560
+ if (interfaceIdOf(bareName$1(resolved)) !== void 0 || metadata["interface"] !== void 0) return "interface";
14913
15561
  return metadata["implements"] === void 0 ? "none" : "implementation";
14914
15562
  }
14915
15563
  /** The `compose` failure for the defects composing a document raised (UC6.8), if any. */
@@ -14935,7 +15583,7 @@ function checkResolved(resolved, lookup, accept) {
14935
15583
  };
14936
15584
  if (resolved.status !== "found") return result;
14937
15585
  const { failures, warnings } = result;
14938
- const name = bareName$1(resolved.name);
15586
+ const name = bareName$1(resolved);
14939
15587
  const metadata = resolved.metadata ?? {};
14940
15588
  if (kindOf(resolved) === "interface") {
14941
15589
  const id = interfaceIdOf(name);
@@ -15052,7 +15700,7 @@ function checkReferences(source, options = {}) {
15052
15700
  };
15053
15701
  const lookup = interfaceLookup(source, rest);
15054
15702
  const layers = Array.isArray(source) ? void 0 : source;
15055
- const sectionNames = new Set(layers?.root === void 0 ? [] : projectSectionNames(layers.root, layers.projects, heldPlugins(layers)));
15703
+ const sectionNames = new Set(layers?.root === void 0 ? [] : [...projectSectionNames(layers.root, layers.projects, heldPlugins(layers)), ...projectQualifiedNames(layers.root, layers.projects)]);
15056
15704
  const resolve = (name) => {
15057
15705
  const ref = parseReferenceName(name);
15058
15706
  const main = resolveReference(ref, source, resolveOptions);
@@ -15260,6 +15908,198 @@ function generatorBase(resolved, iface) {
15260
15908
  if (stub !== void 0) base.stub = `${stub}\n`;
15261
15909
  return base;
15262
15910
  }
15911
+ function firstLine(value) {
15912
+ return typeof value === "string" ? value.split("\n")[0]?.trim() || "" : "";
15913
+ }
15914
+ /** Whether a name is in the interface's namespace but declares another interface that its name fits. */
15915
+ function belongsElsewhere(name, declared, id) {
15916
+ return declared !== void 0 && declared !== id && (name === declared || name.startsWith(`${declared}.`));
15917
+ }
15918
+ /** The highest `repo` or `user` file with a body the document holds: what makes the bare name its own. */
15919
+ function ownTier(resolved) {
15920
+ return resolved.trace.layers.find((layer) => (layer.tier === "repo" || layer.tier === "user") && layer.file && (layer.outcome === "used" || layer.outcome === "stale") && !layer.selection && !layer.empty)?.tier;
15921
+ }
15922
+ /** The plugins holding a file for the name, as read. */
15923
+ function holders(resolved) {
15924
+ return [...new Set(resolved.trace.layers.flatMap(({ tier, plugin, file }) => tier === "plugin" && plugin !== void 0 && file ? [plugin] : []))];
15925
+ }
15926
+ function referenceImplementations(id, source, options = {}) {
15927
+ return isFamilyName(id) || id.includes("*") ? familyImplementations(id, source, options) : interfaceImplementations(id, source, options);
15928
+ }
15929
+ /**
15930
+ * The interfaces a family names (K39): each interface document whose id starts with the prefix and a
15931
+ * dot, at any depth, never a plain document or an implementation (R130). Sorted by id. Throws
15932
+ * `name-invalid` for a malformed family (R136), and `interface-unknown` when none match (R135).
15933
+ */
15934
+ function familyInterfaces(family, source, options = {}) {
15935
+ const parsed = parseFamily(family);
15936
+ if (parsed.invalid !== void 0) throw new ReferenceFailure("name-invalid", parsed.invalid);
15937
+ const lookup = interfaceLookup(source, options);
15938
+ const aliases = aliasIndex(source);
15939
+ const ids = /* @__PURE__ */ new Set();
15940
+ for (const name of referenceNames(source)) {
15941
+ const id = interfaceIdOf(shownName(name, aliases));
15942
+ if (id === void 0 || !id.startsWith(`${parsed.prefix}.`)) continue;
15943
+ const value = lookup(id).value;
15944
+ if (value) ids.add(value.id);
15945
+ }
15946
+ if (!ids.size) throw new ReferenceFailure("interface-unknown", `no interface document matches ${family}.`, { help: "Run `reference list` for the names found." });
15947
+ return [...ids].sort((a, b) => a.localeCompare(b));
15948
+ }
15949
+ /** `implementations <prefix>.*`: each matched interface, in name order, as `implementations <id>` lists it (R134). */
15950
+ function familyImplementations(family, source, options = {}) {
15951
+ return {
15952
+ family,
15953
+ interfaces: familyInterfaces(family, source, options).map((id) => interfaceImplementations(id, source, options))
15954
+ };
15955
+ }
15956
+ function interfaceImplementations(id, source, options) {
15957
+ const { accept: acceptOption, ...rest } = options;
15958
+ const resolveOptions = {
15959
+ ...rest,
15960
+ projectSections: false
15961
+ };
15962
+ const ifaceName = parseReferenceName(`${id}${interfaceSuffix}`);
15963
+ if (ifaceName.invalid || ifaceName.plugin !== void 0) throw new ReferenceFailure("interface-unknown", `"${id}" is not an interface id.`);
15964
+ const lookup = interfaceLookup(source, rest);
15965
+ const iface = lookup(id);
15966
+ const value = iface.value;
15967
+ if (!value) throw new ReferenceFailure("interface-unknown", iface.failure?.message ?? `no interface ${id}`);
15968
+ const aliases = aliasIndex(source);
15969
+ const ids = [value.id, ...(aliases.interfaces.get(value.id) ?? []).map(({ alias }) => alias)];
15970
+ const accept = [...acceptOption?.length ? acceptOption : value.versions];
15971
+ const report = {
15972
+ interface: value.id,
15973
+ cardinality: value.cardinality,
15974
+ accept,
15975
+ implementations: [],
15976
+ excluded: [],
15977
+ warnings: []
15978
+ };
15979
+ const seen = /* @__PURE__ */ new Set();
15980
+ const warn = (warnings) => {
15981
+ for (const warning of warnings) {
15982
+ const key = `${warning.code} ${warning.message}`;
15983
+ if (!seen.has(key)) report.warnings.push(warning);
15984
+ seen.add(key);
15985
+ }
15986
+ };
15987
+ if (iface.renamed !== void 0) warn([{
15988
+ code: "reference-renamed",
15989
+ message: `${id} is an older id of ${value.id}; ask for ${value.id} instead`
15990
+ }]);
15991
+ const inNamespace = (name) => ids.some((each) => name === each || name.startsWith(`${each}.`));
15992
+ /** Whether a resolved document is a candidate at all: it declares the interface, or its name is in the namespace. */
15993
+ const relevant = (name, resolved) => {
15994
+ const declared = readImplements(resolved.metadata ?? {}).entry?.id;
15995
+ if (declared !== void 0 && ids.includes(declared)) return true;
15996
+ return inNamespace(name) && !ids.some((each) => belongsElsewhere(name, declared, each));
15997
+ };
15998
+ const consider = ({ pin, name, resolved, tier }) => {
15999
+ warn(resolved.warnings);
16000
+ const declared = readImplements(resolved.metadata ?? {}).entry;
16001
+ if (!declared || !ids.includes(declared.id)) {
16002
+ const shape = fitsNamespace(value.cardinality, ids, name) ? "" : ` and is not named ${value.cardinality === "one" ? value.id : `${value.id}.<key>`}`;
16003
+ report.excluded.push({
16004
+ name: pin,
16005
+ reasons: [`declares no implements ${value.id}@<version>${shape}`]
16006
+ });
16007
+ return;
16008
+ }
16009
+ const checked = checkResolved(resolved, lookup, {
16010
+ id: value.id,
16011
+ versions: accept
16012
+ });
16013
+ warn(checked.warnings);
16014
+ if (checked.failures.length) {
16015
+ report.excluded.push({
16016
+ name: pin,
16017
+ reasons: checked.failures.map(({ check, message }) => `${check}: ${message}`)
16018
+ });
16019
+ return;
16020
+ }
16021
+ report.implementations.push({
16022
+ pin,
16023
+ description: firstLine(resolved.metadata?.["description"]),
16024
+ unsupported: checked.unsupported,
16025
+ name,
16026
+ tier,
16027
+ version: declared.version
16028
+ });
16029
+ };
16030
+ const qualified = (plugin, name) => resolveReference(parseReferenceName(`${plugin}/${name}`), source, resolveOptions);
16031
+ const names = [...new Set(referenceNames(source).map((name) => shownName(name, aliases)))];
16032
+ for (const name of names) {
16033
+ if (interfaceIdOf(name) !== void 0) continue;
16034
+ const resolved = resolveReference(name, source, resolveOptions);
16035
+ if (resolved.status === "ambiguous") {
16036
+ for (const option of resolved.options ?? []) {
16037
+ if (option === "default") continue;
16038
+ const copy = resolveReference(parseReferenceName(option), source, resolveOptions);
16039
+ if (copy.status !== "found" || !relevant(name, copy)) continue;
16040
+ warn(copy.warnings);
16041
+ report.excluded.push({
16042
+ name: option,
16043
+ reasons: [`ambiguous: ${resolved.message}; each is left out until a customizer picks one`]
16044
+ });
16045
+ }
16046
+ continue;
16047
+ }
16048
+ if (resolved.status !== "found" || !relevant(name, resolved)) continue;
16049
+ const plugins = holders(resolved);
16050
+ const tier = value.cardinality === "many" ? ownTier(resolved) : void 0;
16051
+ if (tier !== void 0) {
16052
+ consider({
16053
+ pin: name,
16054
+ name,
16055
+ resolved,
16056
+ tier
16057
+ });
16058
+ for (const plugin of plugins) report.excluded.push({
16059
+ name: `${plugin}/${name}`,
16060
+ reasons: [`the ${tier} implementation answers ${name}; a consumer loads it by that name`]
16061
+ });
16062
+ continue;
16063
+ }
16064
+ const pick = resolved.trace.stack.filter((target) => target !== "default").at(-1);
16065
+ if (pick === void 0) continue;
16066
+ consider({
16067
+ pin: `${pick}/${name}`,
16068
+ name,
16069
+ resolved: qualified(pick, name),
16070
+ tier: "plugin"
16071
+ });
16072
+ for (const plugin of plugins) {
16073
+ if (plugin === pick) continue;
16074
+ report.excluded.push({
16075
+ name: `${plugin}/${name}`,
16076
+ reasons: [`${name} loads the stack of ${pick}, the pick; load ${pick}/${name}`]
16077
+ });
16078
+ }
16079
+ }
16080
+ if (value.cardinality === "one") {
16081
+ const inUse = implementationInUse(value.id, source, resolveOptions, report.implementations);
16082
+ if (inUse !== void 0) report.inUse = inUse;
16083
+ }
16084
+ return report;
16085
+ }
16086
+ /**
16087
+ * The implementation a load of a `one` interface's name builds on (R129), read from the stack the
16088
+ * load composes: the pin of the listed candidate for the plugin it builds on, or `default` when only
16089
+ * the skill's default is in the stack. None when the name is not `found`, or the plugin it builds on
16090
+ * is not listed.
16091
+ */
16092
+ function implementationInUse(id, source, options, listed) {
16093
+ const resolved = resolveReference(parseReferenceName(id), source, options);
16094
+ if (resolved.status !== "found") return void 0;
16095
+ const pick = resolved.trace.stack.filter((target) => target !== "default").at(-1);
16096
+ if (pick === void 0) return resolved.trace.stack.includes("default") ? "default" : void 0;
16097
+ return listed.find(({ pin }) => pin === `${pick}/${id}`)?.pin;
16098
+ }
16099
+ /** Whether a name fits the interface's cardinality under its id or one of its older ids. */
16100
+ function fitsNamespace(cardinality, ids, name) {
16101
+ return ids.some((id) => cardinality === "one" ? name === id : name.startsWith(`${id}.`) && name.length > id.length + 1);
16102
+ }
15263
16103
  /** A bare, valid name, or a refusal naming the bare name to use. */
15264
16104
  function bareName(value) {
15265
16105
  requireName(value);
@@ -16071,13 +16911,91 @@ function after(result, plan, layers, options) {
16071
16911
  }, options)
16072
16912
  };
16073
16913
  }
16914
+ /** Whether a step, at any depth, gives `sections`: they differ per interface, so a family takes none (UC24.4). */
16915
+ function givesSections(value) {
16916
+ if (Array.isArray(value)) return value.some(givesSections);
16917
+ if (typeof value !== "object" || value === null) return false;
16918
+ return Object.values(value).some((keys) => {
16919
+ if (typeof keys !== "object" || keys === null || Array.isArray(keys)) return false;
16920
+ const record = keys;
16921
+ return record["sections"] !== void 0 || givesSections(record["extends"]);
16922
+ });
16923
+ }
16074
16924
  /**
16075
- * Customizes a name for the `repo` or `user` tier or a folder's project section, running no model:
16076
- * writes the picks (a selection), or a body the generator wrote with its record, or, with `base`,
16077
- * returns what the generator reads and writes nothing. Every check runs first, in the order
16078
- * `validateWrite` gives; a file a contribution above hides is written all the same, with a warning.
16925
+ * Customizes every `one` interface of a family (K39) with the same steps (R131): for each, the file
16926
+ * `customize <id>` with those steps writes, in the tier asked. An interface no step can answer keeps
16927
+ * what it had (R132); a `many` interface is skipped (R133). Every interface is planned first, so a
16928
+ * refusal for any of them, a family matching none (R135), or a step giving `sections` leaves every
16929
+ * file as it was.
16079
16930
  */
16931
+ function customizeFamily(layers, args, options = {}) {
16932
+ const parsed = parseFamily(args.name);
16933
+ if (parsed.invalid !== void 0) throw new ReferenceFailure("name-invalid", parsed.invalid);
16934
+ const usage = validateWrite({
16935
+ command: "customize",
16936
+ ...args,
16937
+ name: parsed.prefix
16938
+ });
16939
+ if ([
16940
+ args.base,
16941
+ args.request,
16942
+ args.body,
16943
+ args.implements,
16944
+ args.merge
16945
+ ].some((value) => value !== void 0)) throw new ReferenceFailure("options-conflict", "a family takes only picks: --extends or --extends-file, with --scope and --dry-run; a body, a merge, or a base is per name.");
16946
+ const picks = args.extendsFile === void 0 ? usage.picks : readExtendsFile(args.extendsFile);
16947
+ if (picks !== void 0 && picks !== "none" && givesSections(picks.values)) throw new ReferenceFailure("step-invalid", `a step of ${args.name} gives sections, which differ per interface; pick for one interface at a time to change sections.`);
16948
+ const lookup = interfaceLookup(layers, options);
16949
+ const interfaces = familyInterfaces(args.name, layers, options).map((id) => {
16950
+ if (lookup(id).value?.cardinality === "many") return {
16951
+ interface: id,
16952
+ outcome: "skipped",
16953
+ reason: `${id} is a many interface, which has no single pick.`
16954
+ };
16955
+ const each = {
16956
+ ...args,
16957
+ name: id
16958
+ };
16959
+ try {
16960
+ const plan = validateWrite({
16961
+ command: "customize",
16962
+ ...each
16963
+ }, layers, options);
16964
+ if (picks !== "none" && plan.base.status !== "found") return {
16965
+ interface: id,
16966
+ outcome: "kept",
16967
+ reason: `no step holds ${id}; it keeps what it had.`
16968
+ };
16969
+ } catch (error) {
16970
+ if (error instanceof ReferenceFailure && error.code === "step-invalid") return {
16971
+ interface: id,
16972
+ outcome: "kept",
16973
+ reason: `${error.message} It keeps what it had.`
16974
+ };
16975
+ throw error;
16976
+ }
16977
+ return {
16978
+ interface: id,
16979
+ outcome: "written",
16980
+ answer: each
16981
+ };
16982
+ }).map(({ answer, ...entry }) => answer === void 0 ? entry : {
16983
+ ...entry,
16984
+ result: customizeSingle(layers, answer, options)
16985
+ });
16986
+ return {
16987
+ family: args.name,
16988
+ scope: args.scope ?? "repo",
16989
+ dryRun: Boolean(args.dryRun),
16990
+ interfaces
16991
+ };
16992
+ }
16080
16993
  function customizeContribution(layers, args, options = {}) {
16994
+ if (isFamilyName(args.name) || args.name.includes("*")) return customizeFamily(layers, args, options);
16995
+ return customizeSingle(layers, args, options);
16996
+ }
16997
+ /** Customizes one name that is not a family. */
16998
+ function customizeSingle(layers, args, options) {
16081
16999
  const plan = validateWrite({
16082
17000
  command: "customize",
16083
17001
  ...args
@@ -16099,160 +17017,324 @@ function customizeContribution(layers, args, options = {}) {
16099
17017
  if (plan.request !== void 0) return writeGenerated(plan, layers, result, options);
16100
17018
  return writePicks(plan, layers, result, options);
16101
17019
  }
16102
- function firstLine(value) {
16103
- return typeof value === "string" ? value.split("\n")[0]?.trim() || "" : "";
16104
- }
16105
- /** Whether a name is in the interface's namespace but declares another interface that its name fits. */
16106
- function belongsElsewhere(name, declared, id) {
16107
- return declared !== void 0 && declared !== id && (name === declared || name.startsWith(`${declared}.`));
17020
+ const dataSuffixes = dataExtensions.map(({ extension }) => extension);
17021
+ function entries(dir) {
17022
+ try {
17023
+ return readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
17024
+ } catch {
17025
+ return [];
17026
+ }
16108
17027
  }
16109
- /** The highest `repo` or `user` file with a body the document holds: what makes the bare name its own. */
16110
- function ownTier(resolved) {
16111
- return resolved.trace.layers.find((layer) => (layer.tier === "repo" || layer.tier === "user") && layer.file && (layer.outcome === "used" || layer.outcome === "stale") && !layer.selection && !layer.empty)?.tier;
17028
+ function valid(name) {
17029
+ return !parseReferenceName(name).invalid;
17030
+ }
17031
+ /** The files a place holds for each name, in the forms its tier answers. */
17032
+ function heldFiles(place) {
17033
+ const held = [];
17034
+ const isDefault = place.tier === "default";
17035
+ const own = isDefault ? defaultSuffix : "";
17036
+ for (const entry of entries(place.dir)) {
17037
+ const file = entry.name;
17038
+ const path = join(place.dir, file);
17039
+ if (entry.isDirectory()) {
17040
+ if (!isDefault && valid(file) && tryRead$2(join(path, "README.md")) !== void 0) held.push({
17041
+ name: file,
17042
+ path: join(path, "README.md"),
17043
+ suffix: "/README.md",
17044
+ kind: "markdown",
17045
+ folderForm: true
17046
+ });
17047
+ continue;
17048
+ }
17049
+ if (file === "_renames.yaml") continue;
17050
+ const metadata = `${own}${metadataSuffix}.md`;
17051
+ if (file.endsWith(metadata)) {
17052
+ const name = file.slice(0, -metadata.length);
17053
+ if (valid(name)) held.push({
17054
+ name,
17055
+ path,
17056
+ suffix: metadata,
17057
+ kind: "metadata",
17058
+ folderForm: false
17059
+ });
17060
+ continue;
17061
+ }
17062
+ const markdown = `${own}.md`;
17063
+ if (file.endsWith(markdown)) {
17064
+ const name = file.slice(0, -markdown.length);
17065
+ if (valid(name)) held.push({
17066
+ name,
17067
+ path,
17068
+ suffix: markdown,
17069
+ kind: "markdown",
17070
+ folderForm: false
17071
+ });
17072
+ continue;
17073
+ }
17074
+ const data = dataSuffixes.find((extension) => file.endsWith(`${own}${extension}`));
17075
+ if (data) {
17076
+ const name = file.slice(0, -`${own}${data}`.length);
17077
+ if (valid(name)) held.push({
17078
+ name,
17079
+ path,
17080
+ suffix: `${own}${data}`,
17081
+ kind: "data",
17082
+ folderForm: false
17083
+ });
17084
+ }
17085
+ }
17086
+ return held;
16112
17087
  }
16113
- /** The plugins holding a file for the name, as read. */
16114
- function holders(resolved) {
16115
- return [...new Set(resolved.trace.layers.flatMap(({ tier, plugin, file }) => tier === "plugin" && plugin !== void 0 && file ? [plugin] : []))];
17088
+ /** The places a run reads (R123): a source folder alone, or the `repo` and `user` tiers and every project section. */
17089
+ function placesOf(layers, root, options) {
17090
+ if (options.plugin !== void 0 && options.default !== void 0) throw new ReferenceFailure("options-conflict", "pass one of --plugin and --default.");
17091
+ if (options.plugin !== void 0) return [{
17092
+ tier: "plugin",
17093
+ dir: join(sourceFolder(options.plugin, root, "plugin"), "references")
17094
+ }];
17095
+ if (options.default !== void 0) return [{
17096
+ tier: "default",
17097
+ dir: join(sourceFolder(options.default, root, "skill"), "references")
17098
+ }];
17099
+ const places = [
17100
+ {
17101
+ tier: "repo",
17102
+ dir: repoReferencesDir(root)
17103
+ },
17104
+ {
17105
+ tier: "user",
17106
+ dir: userReferencesDir(options.home)
17107
+ },
17108
+ ...projectFolders(root, layers.projects).folders.map(({ folder, dir }) => ({
17109
+ tier: "project",
17110
+ dir,
17111
+ folder
17112
+ }))
17113
+ ];
17114
+ const seen = /* @__PURE__ */ new Set();
17115
+ return places.filter(({ dir }) => {
17116
+ const key = resolve(dir);
17117
+ if (seen.has(key)) return false;
17118
+ seen.add(key);
17119
+ return true;
17120
+ });
16116
17121
  }
16117
- /**
16118
- * The implementations of interface `id` a consumer may load, each checked as `check` would on its
16119
- * composed document, and every candidate left out with why (R35). A candidate is a name in the
16120
- * interface's namespace (`<id>` or `<id>.<key>`) or a reference that declares `implements: <id>@<n>`:
16121
- * a plugin's, pinned by its qualified name, or, for a `many` interface, a `repo` or `user` one,
16122
- * pinned by its bare name. When a `repo` or `user` implementation answers the bare name, a plugin's
16123
- * of that name is left out (C1, J3), since loading the bare name returns the other. Plugins holding
16124
- * a name no `extends` orders are all left out until a customizer picks one (R87). A skill's default
16125
- * is never listed: it has no qualified name to pin. An unknown interface throws.
16126
- */
16127
- function referenceImplementations(id, source, options = {}) {
16128
- const { accept: acceptOption, ...rest } = options;
16129
- const resolveOptions = {
16130
- ...rest,
16131
- projectSections: false
17122
+ /** The new names an older name takes: from each plugin's rename table, and from each owner's `aliases` (R124). */
17123
+ function renameSources(layers) {
17124
+ const table = renameTable(layers);
17125
+ const aliases = aliasIndex(layers);
17126
+ return (name) => {
17127
+ if (aliases.owned.has(name)) return [];
17128
+ const found = /* @__PURE__ */ new Map();
17129
+ const add = (to, by) => found.set(to, (found.get(to) ?? /* @__PURE__ */ new Set()).add(by));
17130
+ for (const entry of table.byOld.get(name) ?? []) add(entry.to, `plugin ${entry.plugin}'s ${renameTableFile}`);
17131
+ for (const owner of aliases.byAlias.get(name) ?? []) add(owner.name, `the aliases of ${owner.tier === "plugin" ? `plugin ${owner.plugin}` : `the default of ${owner.skill}`}`);
17132
+ return [...found].map(([to, by]) => ({
17133
+ to,
17134
+ by: [...by].join(" and ")
17135
+ }));
16132
17136
  };
16133
- const ifaceName = parseReferenceName(`${id}${interfaceSuffix}`);
16134
- if (ifaceName.invalid || ifaceName.plugin !== void 0) throw new ReferenceFailure("interface-unknown", `"${id}" is not an interface id.`);
16135
- const lookup = interfaceLookup(source, rest);
16136
- const iface = lookup(id);
16137
- const value = iface.value;
16138
- if (!value) throw new ReferenceFailure("interface-unknown", iface.failure?.message ?? `no interface ${id}`);
16139
- const aliases = aliasIndex(source);
16140
- const ids = [value.id, ...(aliases.interfaces.get(value.id) ?? []).map(({ alias }) => alias)];
16141
- const accept = [...acceptOption?.length ? acceptOption : value.versions];
16142
- const report = {
16143
- interface: value.id,
16144
- cardinality: value.cardinality,
16145
- accept,
16146
- implementations: [],
16147
- excluded: [],
16148
- warnings: []
16149
- };
16150
- const seen = /* @__PURE__ */ new Set();
16151
- const warn = (warnings) => {
16152
- for (const warning of warnings) {
16153
- const key = `${warning.code} ${warning.message}`;
16154
- if (!seen.has(key)) report.warnings.push(warning);
16155
- seen.add(key);
17137
+ }
17138
+ /** Whether a folder holds any file answering `name` in its tier (R60's forms). */
17139
+ function holdsName(place, name) {
17140
+ const own = place.tier === "default" ? defaultSuffix : "";
17141
+ return [
17142
+ `${name}${own}.md`,
17143
+ ...place.tier === "default" ? [] : [join(name, "README.md")],
17144
+ ...dataSuffixes.map((extension) => `${name}${own}${extension}`),
17145
+ `${name}${own}${metadataSuffix}.md`
17146
+ ].some((file) => existsSync(join(place.dir, file)));
17147
+ }
17148
+ /**
17149
+ * Finds every file in a legacy form (K37), or under a name a plugin renamed, and returns the plan
17150
+ * (R121). With `write`, it applies each change that is not manual. It reads the `repo` tier, the
17151
+ * `user` tier, and every project section below the root, or only a plugin's or a skill's source
17152
+ * folder in the repository (R123), and never changes a managed folder or an installed copy.
17153
+ */
17154
+ function migrateReferences(layers, options) {
17155
+ const root = resolve(options.root ?? layers.root ?? process.cwd());
17156
+ const names = options.names?.length ? new Set(options.names.map((name) => bareName(name).name)) : void 0;
17157
+ const places = placesOf(layers, root, options);
17158
+ const targetsOf = renameSources(layers);
17159
+ const installed = new Set(layers.layers.flatMap(({ tier, plugin, skipped }) => tier === "plugin" && plugin && !skipped ? [plugin] : []));
17160
+ const rows = [];
17161
+ const operations = [];
17162
+ for (const place of places) {
17163
+ const where = place.folder === void 0 ? {} : { folder: place.folder };
17164
+ const renames = place.tier === "repo" || place.tier === "user" || place.tier === "project";
17165
+ /** New names this run gives files in the folder, so a second file renamed to one is manual. */
17166
+ const claimed = /* @__PURE__ */ new Set();
17167
+ const held = heldFiles(place);
17168
+ if (place.tier === "default") held.push(...oldDefaults(place, names, held));
17169
+ for (const file of held) {
17170
+ const targets = renames ? targetsOf(file.name) : [];
17171
+ if (names && !names.has(file.name) && !targets.some(({ to }) => names.has(to))) continue;
17172
+ const base = {
17173
+ path: file.path,
17174
+ tier: place.tier,
17175
+ ...where,
17176
+ name: file.name
17177
+ };
17178
+ const operation = { path: file.path };
17179
+ if (file.kind === "markdown") {
17180
+ const raw = tryRead$2(file.path) ?? "";
17181
+ const { metadata, body } = parseDocument(raw, () => {});
17182
+ const { findings, keys } = legacyRewrite(metadata, body, place.tier === "project" ? "project" : "contribution", file.name);
17183
+ for (const finding of findings) rows.push(rowOf(base, finding));
17184
+ if (Object.keys(keys).length) operation.content = editFrontmatter(raw, keys);
17185
+ }
17186
+ if (file.suffix === ".md" && place.tier === "default") {
17187
+ const to = join(place.dir, `${file.name}${defaultSuffix}.md`);
17188
+ const finding = defaultLayoutFinding(file.name);
17189
+ if (existsSync(to)) {
17190
+ const { to: _, ...kept } = finding;
17191
+ rows.push(rowOf(base, {
17192
+ ...kept,
17193
+ manual: `${file.name}${defaultSuffix}.md is there too and answers the name; the old file is not read, so delete it by hand once nothing in it is needed`
17194
+ }));
17195
+ } else {
17196
+ rows.push(rowOf(base, finding));
17197
+ Object.assign(operation, {
17198
+ moveFrom: file.path,
17199
+ moveTo: to
17200
+ });
17201
+ }
17202
+ }
17203
+ if (targets.length) renameRow(place, file, targets, claimed, base, rows, operation);
17204
+ if (operation.content !== void 0 || operation.moveTo !== void 0) operations.push(operation);
16156
17205
  }
17206
+ if (renames) for (const found of qualifiedOverrides(place.dir)) {
17207
+ if (names && !names.has(found.name)) continue;
17208
+ const operation = qualifiedRow(place, found, installed, claimed, {
17209
+ path: found.path,
17210
+ tier: place.tier,
17211
+ ...where,
17212
+ name: found.name
17213
+ }, rows);
17214
+ if (operation) operations.push(operation);
17215
+ }
17216
+ }
17217
+ let written = false;
17218
+ if (options.write) for (const operation of operations) {
17219
+ if (operation.content !== void 0) writeFileSync(operation.path, operation.content);
17220
+ if (operation.moveFrom !== void 0 && operation.moveTo !== void 0) renameSync(operation.moveFrom, operation.moveTo);
17221
+ if (operation.prune !== void 0) pruneEmpty(operation.prune);
17222
+ written = true;
17223
+ }
17224
+ return {
17225
+ rows,
17226
+ written,
17227
+ warnings: [...renameTable(layers).warnings]
16157
17228
  };
16158
- if (iface.renamed !== void 0) warn([{
16159
- code: "reference-renamed",
16160
- message: `${id} is an older id of ${value.id}; ask for ${value.id} instead`
16161
- }]);
16162
- const inNamespace = (name) => ids.some((each) => name === each || name.startsWith(`${each}.`));
16163
- /** Whether a resolved document is a candidate at all: it declares the interface, or its name is in the namespace. */
16164
- const relevant = (name, resolved) => {
16165
- const declared = readImplements(resolved.metadata ?? {}).entry?.id;
16166
- if (declared !== void 0 && ids.includes(declared)) return true;
16167
- return inNamespace(name) && !ids.some((each) => belongsElsewhere(name, declared, each));
17229
+ }
17230
+ function rowOf(base, finding) {
17231
+ const row = {
17232
+ ...base,
17233
+ form: finding.form,
17234
+ from: finding.from
16168
17235
  };
16169
- const consider = ({ pin, name, resolved, tier }) => {
16170
- warn(resolved.warnings);
16171
- const declared = readImplements(resolved.metadata ?? {}).entry;
16172
- if (!declared || !ids.includes(declared.id)) {
16173
- const shape = fitsNamespace(value.cardinality, ids, name) ? "" : ` and is not named ${value.cardinality === "one" ? value.id : `${value.id}.<key>`}`;
16174
- report.excluded.push({
16175
- name: pin,
16176
- reasons: [`declares no implements ${value.id}@<version>${shape}`]
16177
- });
16178
- return;
16179
- }
16180
- const checked = checkResolved(resolved, lookup, {
16181
- id: value.id,
16182
- versions: accept
16183
- });
16184
- warn(checked.warnings);
16185
- if (checked.failures.length) {
16186
- report.excluded.push({
16187
- name: pin,
16188
- reasons: checked.failures.map(({ check, message }) => `${check}: ${message}`)
16189
- });
16190
- return;
16191
- }
16192
- report.implementations.push({
16193
- pin,
16194
- description: firstLine(resolved.metadata?.["description"]),
16195
- unsupported: checked.unsupported,
17236
+ const note = finding.note === void 0 ? "" : `; ${finding.reads}, and ${finding.note}`;
17237
+ if (finding.to !== void 0) row.to = `${finding.to}${note}`;
17238
+ if (finding.manual !== void 0) row.manual = `${finding.manual}${note}`;
17239
+ return row;
17240
+ }
17241
+ /**
17242
+ * A skill's defaults in the old layout, `references/<name>.md`, for the names given (UC22.7): the
17243
+ * folder's other files may be the skill's own documents, so no name given recognizes none.
17244
+ */
17245
+ function oldDefaults(place, names, held) {
17246
+ if (!names) return [];
17247
+ return [...names].flatMap((name) => {
17248
+ const path = join(place.dir, `${name}.md`);
17249
+ if (tryRead$2(path) === void 0 || held.some((file) => file.path === path)) return [];
17250
+ return [{
16196
17251
  name,
16197
- tier,
16198
- version: declared.version
16199
- });
17252
+ path,
17253
+ suffix: ".md",
17254
+ kind: "markdown",
17255
+ folderForm: false
17256
+ }];
17257
+ });
17258
+ }
17259
+ /** The rename row of a file under an older name (R124), and the move that applies it. */
17260
+ function renameRow(place, file, targets, claimed, base, rows, operation) {
17261
+ const from = file.folderForm ? `${file.name}/` : `${file.name}${file.suffix}`;
17262
+ const row = {
17263
+ ...base,
17264
+ form: "renamed",
17265
+ from,
17266
+ by: targets.map(({ by }) => by).join("; ")
16200
17267
  };
16201
- const qualified = (plugin, name) => resolveReference(parseReferenceName(`${plugin}/${name}`), source, resolveOptions);
16202
- const names = [...new Set(referenceNames(source).map((name) => shownName(name, aliases)))];
16203
- for (const name of names) {
16204
- if (interfaceIdOf(name) !== void 0) continue;
16205
- const resolved = resolveReference(name, source, resolveOptions);
16206
- if (resolved.status === "ambiguous") {
16207
- for (const option of resolved.options ?? []) {
16208
- if (option === "default") continue;
16209
- const copy = resolveReference(parseReferenceName(option), source, resolveOptions);
16210
- if (copy.status !== "found" || !relevant(name, copy)) continue;
16211
- warn(copy.warnings);
16212
- report.excluded.push({
16213
- name: option,
16214
- reasons: [`ambiguous: ${resolved.message}; each is left out until a customizer picks one`]
16215
- });
16216
- }
16217
- continue;
16218
- }
16219
- if (resolved.status !== "found" || !relevant(name, resolved)) continue;
16220
- const plugins = holders(resolved);
16221
- const tier = value.cardinality === "many" ? ownTier(resolved) : void 0;
16222
- if (tier !== void 0) {
16223
- consider({
16224
- pin: name,
16225
- name,
16226
- resolved,
16227
- tier
16228
- });
16229
- for (const plugin of plugins) report.excluded.push({
16230
- name: `${plugin}/${name}`,
16231
- reasons: [`the ${tier} implementation answers ${name}; a consumer loads it by that name`]
16232
- });
16233
- continue;
16234
- }
16235
- const pick = resolved.trace.stack.filter((target) => target !== "default").at(-1);
16236
- if (pick === void 0) continue;
16237
- consider({
16238
- pin: `${pick}/${name}`,
16239
- name,
16240
- resolved: qualified(pick, name),
16241
- tier: "plugin"
17268
+ if (targets.length > 1) {
17269
+ rows.push({
17270
+ ...row,
17271
+ manual: `it is renamed differently: ${targets.map(({ to, by }) => `"${to}" by ${by}`).join(", ")}; rename it by hand to the one that applies`
16242
17272
  });
16243
- for (const plugin of plugins) {
16244
- if (plugin === pick) continue;
16245
- report.excluded.push({
16246
- name: `${plugin}/${name}`,
16247
- reasons: [`${name} loads the stack of ${pick}, the pick; load ${pick}/${name}`]
16248
- });
16249
- }
17273
+ return;
16250
17274
  }
16251
- return report;
17275
+ const { to } = targets[0];
17276
+ const companion = file.kind === "metadata" || file.kind === "data";
17277
+ if (holdsName(place, to) && !claimed.has(to) || claimed.has(to) && !companion) {
17278
+ rows.push({
17279
+ ...row,
17280
+ manual: `"${to}" already has a file in that folder; merge the two by hand, then delete this one`
17281
+ });
17282
+ return;
17283
+ }
17284
+ claimed.add(to);
17285
+ const into = file.folderForm ? `${to}/` : `${to}${file.suffix}`;
17286
+ rows.push({
17287
+ ...row,
17288
+ to: into
17289
+ });
17290
+ const moveFrom = file.folderForm ? dirname(file.path) : file.path;
17291
+ Object.assign(operation, {
17292
+ moveFrom,
17293
+ moveTo: join(place.dir, file.folderForm ? to : `${to}${file.suffix}`)
17294
+ });
16252
17295
  }
16253
- /** Whether a name fits the interface's cardinality under its id or one of its older ids. */
16254
- function fitsNamespace(cardinality, ids, name) {
16255
- return ids.some((id) => cardinality === "one" ? name === id : name.startsWith(`${id}.`) && name.length > id.length + 1);
17296
+ function pruneEmpty(dir) {
17297
+ try {
17298
+ if (!readdirSync(dir).length) rmdirSync(dir);
17299
+ } catch {}
17300
+ }
17301
+ /**
17302
+ * The row of a qualified-name override (K37), and the move that applies it: `<plugin>/<name>.md`
17303
+ * becomes `<name>.md` beside the subfolder, extending the plugin. It is manual in a project folder,
17304
+ * for a plugin the plugin tier does not hold, beside a file that already answers the name, and for a
17305
+ * file that writes its own `extends` or `overrides`.
17306
+ */
17307
+ function qualifiedRow(place, found, installed, claimed, base, rows) {
17308
+ const { plugin, name } = found;
17309
+ const finding = qualifiedOverrideFinding(found, place.tier === "project" ? "project" : "contribution");
17310
+ const { to, ...kept } = finding;
17311
+ const manual = (why) => {
17312
+ rows.push(rowOf(base, {
17313
+ ...kept,
17314
+ manual: why
17315
+ }));
17316
+ };
17317
+ if (to === void 0) return manual(finding.manual);
17318
+ if (!installed.has(plugin)) return manual(`plugin ${plugin} is not installed; it would become ${to}, extending a plugin that is not there: install ${plugin} and run migrate again, or delete this file`);
17319
+ if (holdsName(place, name) || claimed.has(name)) return manual(`"${name}" already has a file in that folder; move this file's sections into it and add ${plugin} to its extends, then delete this one`);
17320
+ const raw = tryRead$2(found.path) ?? "";
17321
+ const { metadata, body } = parseDocument(raw, () => {});
17322
+ const own = ["extends", "overrides"].find((key) => key in metadata);
17323
+ if (own !== void 0) return manual(`it also writes \`${own}\`; move it to ${name}.md by hand, with an extends that names ${plugin}`);
17324
+ claimed.add(name);
17325
+ rows.push(rowOf(base, finding));
17326
+ const { findings, keys } = legacyRewrite(metadata, body, "contribution", name);
17327
+ for (const other of findings) rows.push(rowOf(base, other));
17328
+ return {
17329
+ path: found.path,
17330
+ content: editFrontmatter(raw, {
17331
+ ...keys,
17332
+ extends: [plugin]
17333
+ }),
17334
+ moveFrom: found.path,
17335
+ moveTo: join(place.dir, `${name}.md`),
17336
+ prune: dirname(found.path)
17337
+ };
16256
17338
  }
16257
17339
  /**
16258
17340
  * The files a customization of `name` is written to, `repo` and `user` and, with `in`, that folder's
@@ -16306,7 +17388,314 @@ function whereReference(input, source, { root, display = (path) => path, in: fol
16306
17388
  if (hider) file.hiddenBy = hiderLabel(hider, display);
16307
17389
  report.files.push(file);
16308
17390
  }
16309
- return report;
17391
+ return report;
17392
+ }
17393
+ //#endregion
17394
+ //#region src/harness-registry/harness-registry.ts
17395
+ /**
17396
+ * Throws on a harness `skillsDirectories` does not confirm: treating it as native would silently
17397
+ * drop a projection it may need.
17398
+ */
17399
+ function skillsProjection(harness, scope, directories = skillsDirectories(harness)?.[scope]) {
17400
+ const listed = directories ?? [];
17401
+ const [first] = listed;
17402
+ if (first === void 0) throw new Error(`cyber-agentkit records no ${scope} skills directories for ${harness}.`);
17403
+ return listed.includes(".agents/skills") ? {} : { skillsDirectory: first };
17404
+ }
17405
+ /**
17406
+ * The user-scope counterpart of the root `AGENTS.md`, relative to the home directory. No harness
17407
+ * reads it natively; each loads it only through a user-scope `instructionBridge` (E-CC-18, E-CODEX-05,
17408
+ * E-GEM-03, E-COPILOT-04, E-CUR-08).
17409
+ */
17410
+ const globalInstructions = ".agents/AGENTS.md";
17411
+ /**
17412
+ * Skills directories come from `cyber-agentkit`, except for the harnesses it does not know
17413
+ * (`devin-desktop`, `windsurf`); see `.research/agentic-configuration-standards/` for the rest.
17414
+ */
17415
+ const harnessRegistry = [
17416
+ {
17417
+ name: "claude-code",
17418
+ project: {
17419
+ detect: ".claude",
17420
+ ...skillsProjection("claude-code", "project"),
17421
+ shadowedBy: [
17422
+ "CLAUDE.md",
17423
+ ".claude/CLAUDE.md",
17424
+ "CLAUDE.local.md"
17425
+ ],
17426
+ mcpConfig: {
17427
+ path: ".mcp.json",
17428
+ key: "mcpServers",
17429
+ format: "json",
17430
+ dialect: "claude-code"
17431
+ },
17432
+ nonstandard: [
17433
+ {
17434
+ path: ".claude/commands",
17435
+ shape: "directory",
17436
+ kind: "command"
17437
+ },
17438
+ {
17439
+ path: ".claude/rules",
17440
+ shape: "directory",
17441
+ kind: "rule"
17442
+ },
17443
+ {
17444
+ path: ".claude/agents",
17445
+ shape: "directory",
17446
+ kind: "subagent"
17447
+ }
17448
+ ]
17449
+ },
17450
+ user: {
17451
+ detect: ".claude",
17452
+ ...skillsProjection("claude-code", "user"),
17453
+ relocatedBy: "CLAUDE_CONFIG_DIR",
17454
+ emptyRelocatesToStart: true,
17455
+ instructionBridge: {
17456
+ kind: "import",
17457
+ path: ".claude/CLAUDE.md",
17458
+ line: `@~/${globalInstructions}`
17459
+ }
17460
+ }
17461
+ },
17462
+ {
17463
+ name: "cursor",
17464
+ project: {
17465
+ detect: ".cursor",
17466
+ ...skillsProjection("cursor", "project"),
17467
+ mcpConfig: {
17468
+ path: ".cursor/mcp.json",
17469
+ key: "mcpServers",
17470
+ format: "json",
17471
+ dialect: "cursor"
17472
+ },
17473
+ nonstandard: [
17474
+ {
17475
+ path: ".cursorrules",
17476
+ shape: "file",
17477
+ kind: "instructions"
17478
+ },
17479
+ {
17480
+ path: ".cursor/rules",
17481
+ shape: "directory",
17482
+ kind: "rule"
17483
+ },
17484
+ {
17485
+ path: ".cursor/commands",
17486
+ shape: "directory",
17487
+ kind: "command"
17488
+ },
17489
+ {
17490
+ path: ".cursor/skills",
17491
+ shape: "directory",
17492
+ kind: "skill"
17493
+ }
17494
+ ]
17495
+ },
17496
+ user: {
17497
+ detect: ".cursor",
17498
+ ...skillsProjection("cursor", "user")
17499
+ }
17500
+ },
17501
+ {
17502
+ name: "codex",
17503
+ project: {
17504
+ detect: ".codex",
17505
+ ...skillsProjection("codex", "project"),
17506
+ mcpConfig: {
17507
+ path: ".codex/config.toml",
17508
+ key: "mcp_servers",
17509
+ format: "toml",
17510
+ dialect: "codex",
17511
+ shared: true
17512
+ },
17513
+ nonstandard: [{
17514
+ path: ".codex/skills",
17515
+ shape: "directory",
17516
+ kind: "skill"
17517
+ }]
17518
+ },
17519
+ user: {
17520
+ detect: ".codex",
17521
+ ...skillsProjection("codex", "user"),
17522
+ instructionBridge: {
17523
+ kind: "symlink",
17524
+ path: ".codex/AGENTS.md"
17525
+ },
17526
+ shadowedBy: [".codex/AGENTS.override.md"],
17527
+ relocatedBy: "CODEX_HOME"
17528
+ }
17529
+ },
17530
+ {
17531
+ name: "copilot-cli",
17532
+ project: {
17533
+ detect: ".github/skills",
17534
+ ...skillsProjection("copilot-cli", "project"),
17535
+ nonstandard: [
17536
+ {
17537
+ path: ".github/copilot-instructions.md",
17538
+ shape: "file",
17539
+ kind: "instructions"
17540
+ },
17541
+ {
17542
+ path: ".github/instructions",
17543
+ shape: "directory",
17544
+ kind: "instructions"
17545
+ },
17546
+ {
17547
+ path: ".github/skills",
17548
+ shape: "directory",
17549
+ kind: "skill"
17550
+ }
17551
+ ]
17552
+ },
17553
+ user: {
17554
+ detect: ".copilot",
17555
+ ...skillsProjection("copilot-cli", "user"),
17556
+ relocatedBy: "COPILOT_HOME",
17557
+ instructionBridge: {
17558
+ kind: "symlink",
17559
+ path: ".copilot/copilot-instructions.md"
17560
+ }
17561
+ }
17562
+ },
17563
+ {
17564
+ name: "gemini-cli",
17565
+ project: {
17566
+ detect: ".gemini",
17567
+ ...skillsProjection("gemini-cli", "project"),
17568
+ instructionBridge: {
17569
+ kind: "settings-entry",
17570
+ path: ".gemini/settings.json",
17571
+ key: "context.fileName"
17572
+ },
17573
+ mcpConfig: {
17574
+ path: ".gemini/settings.json",
17575
+ key: "mcpServers",
17576
+ format: "json",
17577
+ dialect: "gemini-cli",
17578
+ shared: true
17579
+ },
17580
+ nonstandard: [{
17581
+ path: "GEMINI.md",
17582
+ shape: "file",
17583
+ kind: "instructions"
17584
+ }, {
17585
+ path: ".gemini/skills",
17586
+ shape: "directory",
17587
+ kind: "skill"
17588
+ }]
17589
+ },
17590
+ user: {
17591
+ detect: ".gemini",
17592
+ ...skillsProjection("gemini-cli", "user"),
17593
+ instructionBridge: {
17594
+ kind: "symlink",
17595
+ path: ".gemini/GEMINI.md"
17596
+ }
17597
+ }
17598
+ },
17599
+ {
17600
+ name: "devin-desktop",
17601
+ project: { detect: ".devin" }
17602
+ },
17603
+ {
17604
+ name: "windsurf",
17605
+ project: {
17606
+ detect: ".windsurf",
17607
+ skillsDirectory: ".windsurf/skills",
17608
+ nonstandard: [{
17609
+ path: ".windsurfrules",
17610
+ shape: "file",
17611
+ kind: "instructions"
17612
+ }, {
17613
+ path: ".windsurf/rules",
17614
+ shape: "directory",
17615
+ kind: "rule"
17616
+ }]
17617
+ },
17618
+ deprecated: "devin-desktop"
17619
+ }
17620
+ ];
17621
+ const harnessNames = new Set(harnessRegistry.map((harness) => harness.name));
17622
+ function isHarnessName(value) {
17623
+ return harnessNames.has(value);
17624
+ }
17625
+ /**
17626
+ * An unknown name throws rather than being dropped — silently falling back to the defaults would
17627
+ * misreport what was diagnosed.
17628
+ */
17629
+ function parseHarnesses(value) {
17630
+ const requested = (value ?? "").split(",").map((name) => name.trim()).filter(Boolean);
17631
+ const unsupported = requested.filter((name) => !isHarnessName(name));
17632
+ if (unsupported.length) throw new Error(`Unsupported harness: ${unsupported.join(", ")}. Supported: ${harnessRegistry.map((harness) => harness.name).join(", ")}.`);
17633
+ return requested.filter(isHarnessName);
17634
+ }
17635
+ /** Enabled whether or not the repository already contains their directories. */
17636
+ const defaultHarnesses = ["claude-code", "cursor"];
17637
+ function directoryExists(path) {
17638
+ try {
17639
+ return lstatSync(path).isDirectory();
17640
+ } catch {
17641
+ return false;
17642
+ }
17643
+ }
17644
+ /** The default harnesses, plus the preferred ones, plus every harness whose directory is present. */
17645
+ function selectHarnesses(root, preferred) {
17646
+ const enabled = /* @__PURE__ */ new Set([...defaultHarnesses, ...preferred]);
17647
+ return harnessRegistry.filter((harness) => enabled.has(harness.name) || directoryExists(join(root, harness.project.detect)));
17648
+ }
17649
+ //#endregion
17650
+ //#region src/command-output/command-output.ts
17651
+ /**
17652
+ * AXI §6: a failure is a coded error on stdout, in the caller's format, with a `help` line saying
17653
+ * what to do next. A `CliError` the command already built is rethrown untouched; anything else
17654
+ * becomes `command-failed`, exit code 1.
17655
+ */
17656
+ function fail(error, fallback, help) {
17657
+ if (isCliError(error)) throw error;
17658
+ throw new CliError(error instanceof Error ? error.message : fallback, {
17659
+ code: "command-failed",
17660
+ exitCode: exitCodes.error,
17661
+ ...help ? { help } : {},
17662
+ cause: error
17663
+ });
17664
+ }
17665
+ /**
17666
+ * An answer that comes with a failing exit code. The cli prints only an answer a command returns, and
17667
+ * a thrown error replaces the return, so the answer is printed first.
17668
+ */
17669
+ function answerThenFail(answer, format) {
17670
+ const text = printAnswer(answer, format);
17671
+ if (text !== void 0) process.stdout.write(text);
17672
+ throw CliError.reported(exitCodes.error);
17673
+ }
17674
+ /**
17675
+ * AXI §10: collapses the home directory to `~` so a path in a report is portable to another
17676
+ * machine.
17677
+ */
17678
+ function collapseHome$1(home, path) {
17679
+ return home && path.startsWith(home + sep) ? `~${path.slice(home.length)}` : path;
17680
+ }
17681
+ function displayBinPath(home, executable) {
17682
+ return executable ? collapseHome$1(home, executable) : "buddy-agent-harness";
17683
+ }
17684
+ /**
17685
+ * `parseHarnesses` for a command's `--harness`: an unsupported name is a usage error, exit code 2.
17686
+ * The registry stays free of clibuilder, which the library entry treats as an optional peer.
17687
+ */
17688
+ function parseHarnessOption(value) {
17689
+ try {
17690
+ return parseHarnesses(value);
17691
+ } catch (error) {
17692
+ throw new CliError(error.message, {
17693
+ code: "unsupported-harness",
17694
+ exitCode: exitCodes.usage,
17695
+ help: "Pass --harness as a comma-separated list of the supported names.",
17696
+ cause: error
17697
+ });
17698
+ }
16310
17699
  }
16311
17700
  //#endregion
16312
17701
  //#region src/dep-plugins/dep-plugins.ts
@@ -16363,7 +17752,7 @@ function serializeCatalog(catalog) {
16363
17752
  }
16364
17753
  //#endregion
16365
17754
  //#region src/dep-plugins/resolve.ts
16366
- function readJson$1(path) {
17755
+ function readJson$2(path) {
16367
17756
  try {
16368
17757
  return JSON.parse(readFileSync(path, "utf8"));
16369
17758
  } catch {
@@ -16378,7 +17767,7 @@ function readDepPlugin(pkg, fromManifest) {
16378
17767
  package: pkg,
16379
17768
  reason: "unresolved"
16380
17769
  };
16381
- const manifest = readJson$1(join(dir, "plugin.json"));
17770
+ const manifest = readJson$2(join(dir, "plugin.json"));
16382
17771
  if (!manifest || typeof manifest !== "object") return {
16383
17772
  ok: false,
16384
17773
  package: pkg,
@@ -16391,7 +17780,7 @@ function readDepPlugin(pkg, fromManifest) {
16391
17780
  package: pkg,
16392
17781
  reason: "unnamed"
16393
17782
  };
16394
- const installed = readJson$1(join(dir, "package.json"));
17783
+ const installed = readJson$2(join(dir, "package.json"));
16395
17784
  const version = typeof installed?.version === "string" ? installed.version : void 0;
16396
17785
  if (!version) return {
16397
17786
  ok: false,
@@ -16484,7 +17873,7 @@ function writeCatalog(root, catalog) {
16484
17873
  //#endregion
16485
17874
  //#region src/dep-plugins/harness-plugins.ts
16486
17875
  const COPILOT_LIMITATION = "Copilot CLI rejects an npm marketplace source, so it cannot install from this catalog. It needs a path-sourced catalog, which cannot reach node_modules from a subdirectory.";
16487
- function readJson(path) {
17876
+ function readJson$1(path) {
16488
17877
  try {
16489
17878
  return JSON.parse(readFileSync(path, "utf8"));
16490
17879
  } catch {
@@ -16502,7 +17891,7 @@ function claudeConfigDir(home, env = process.env) {
16502
17891
  return env["CLAUDE_CONFIG_DIR"] || join(home, ".claude");
16503
17892
  }
16504
17893
  function readClaudeState(configDir) {
16505
- const installedFile = readJson(join(configDir, "plugins", "installed_plugins.json"));
17894
+ const installedFile = readJson$1(join(configDir, "plugins", "installed_plugins.json"));
16506
17895
  const installed = [];
16507
17896
  for (const [id, entries] of Object.entries(installedFile?.plugins ?? {})) {
16508
17897
  if (!Array.isArray(entries)) continue;
@@ -16525,7 +17914,7 @@ function readClaudeState(configDir) {
16525
17914
  });
16526
17915
  }
16527
17916
  }
16528
- const knownFile = readJson(join(configDir, "plugins", "known_marketplaces.json"));
17917
+ const knownFile = readJson$1(join(configDir, "plugins", "known_marketplaces.json"));
16529
17918
  const marketplaces = [];
16530
17919
  for (const [name, value] of Object.entries(knownFile ?? {})) marketplaces.push({
16531
17920
  name,
@@ -16685,6 +18074,13 @@ const depPluginsCommand = command({
16685
18074
  type: t.boolean().optional()
16686
18075
  }
16687
18076
  },
18077
+ examples: [{
18078
+ command: "buddy-agent-harness dep-plugins",
18079
+ description: "Write the marketplace catalog for dependency plugins"
18080
+ }, {
18081
+ command: "buddy-agent-harness dep-plugins --check",
18082
+ description: "Fail when the catalog is stale (CI)"
18083
+ }],
16688
18084
  run(args) {
16689
18085
  try {
16690
18086
  const root = args.root ?? process.cwd();
@@ -16727,285 +18123,29 @@ const depPluginsCommand = command({
16727
18123
  package: pkg,
16728
18124
  version
16729
18125
  })) : "0 dependencies ship a plugin — nothing to install",
16730
- harness: harness.length ? harness : "no supported harness detected on this machine",
16731
- actions: actions.length ? actions : "0 actions — every detected harness matches the catalog",
16732
- ...notes.length ? { notes } : {}
16733
- });
16734
- return outcome === "would-change" ? answerThenFail(answer, this.format) : answer;
16735
- } catch (error) {
16736
- return fail(error, "Catalog generation failed.");
16737
- }
16738
- }
16739
- });
16740
- //#endregion
16741
- //#region src/diagnose-bridges/directory-files.ts
16742
- /** A directory that does not exist reads as empty: `doctor` never creates one to look at it. */
16743
- function filesUnder(directory) {
16744
- try {
16745
- return readdirSync(directory, {
16746
- recursive: true,
16747
- withFileTypes: true
16748
- }).filter((entry) => entry.isFile()).map((entry) => relative(directory, join(entry.parentPath, entry.name)).split(sep).join(posix.sep)).sort();
16749
- } catch {
16750
- return [];
16751
- }
16752
- }
16753
- //#endregion
16754
- //#region src/harness-registry/harness-registry.ts
16755
- /**
16756
- * Throws on a harness `skillsDirectories` does not confirm: treating it as native would silently
16757
- * drop a projection it may need.
16758
- */
16759
- function skillsProjection(harness, scope, directories = skillsDirectories(harness)?.[scope]) {
16760
- const listed = directories ?? [];
16761
- const [first] = listed;
16762
- if (first === void 0) throw new Error(`cyber-agentkit records no ${scope} skills directories for ${harness}.`);
16763
- return listed.includes(".agents/skills") ? {} : { skillsDirectory: first };
16764
- }
16765
- /**
16766
- * The user-scope counterpart of the root `AGENTS.md`, relative to the home directory. No harness
16767
- * reads it natively; each loads it only through a user-scope `instructionBridge` (E-CC-18, E-CODEX-05,
16768
- * E-GEM-03, E-COPILOT-04, E-CUR-08).
16769
- */
16770
- const globalInstructions = ".agents/AGENTS.md";
16771
- /**
16772
- * Skills directories come from `cyber-agentkit`, except for the harnesses it does not know
16773
- * (`devin-desktop`, `windsurf`); see `.research/agentic-configuration-standards/` for the rest.
16774
- */
16775
- const harnessRegistry = [
16776
- {
16777
- name: "claude-code",
16778
- project: {
16779
- detect: ".claude",
16780
- ...skillsProjection("claude-code", "project"),
16781
- shadowedBy: [
16782
- "CLAUDE.md",
16783
- ".claude/CLAUDE.md",
16784
- "CLAUDE.local.md"
16785
- ],
16786
- mcpConfig: {
16787
- path: ".mcp.json",
16788
- key: "mcpServers",
16789
- format: "json",
16790
- dialect: "claude-code"
16791
- },
16792
- nonstandard: [
16793
- {
16794
- path: ".claude/commands",
16795
- shape: "directory",
16796
- kind: "command"
16797
- },
16798
- {
16799
- path: ".claude/rules",
16800
- shape: "directory",
16801
- kind: "rule"
16802
- },
16803
- {
16804
- path: ".claude/agents",
16805
- shape: "directory",
16806
- kind: "subagent"
16807
- }
16808
- ]
16809
- },
16810
- user: {
16811
- detect: ".claude",
16812
- ...skillsProjection("claude-code", "user"),
16813
- relocatedBy: "CLAUDE_CONFIG_DIR",
16814
- emptyRelocatesToStart: true,
16815
- instructionBridge: {
16816
- kind: "import",
16817
- path: ".claude/CLAUDE.md",
16818
- line: `@~/${globalInstructions}`
16819
- }
16820
- }
16821
- },
16822
- {
16823
- name: "cursor",
16824
- project: {
16825
- detect: ".cursor",
16826
- ...skillsProjection("cursor", "project"),
16827
- mcpConfig: {
16828
- path: ".cursor/mcp.json",
16829
- key: "mcpServers",
16830
- format: "json",
16831
- dialect: "cursor"
16832
- },
16833
- nonstandard: [
16834
- {
16835
- path: ".cursorrules",
16836
- shape: "file",
16837
- kind: "instructions"
16838
- },
16839
- {
16840
- path: ".cursor/rules",
16841
- shape: "directory",
16842
- kind: "rule"
16843
- },
16844
- {
16845
- path: ".cursor/commands",
16846
- shape: "directory",
16847
- kind: "command"
16848
- },
16849
- {
16850
- path: ".cursor/skills",
16851
- shape: "directory",
16852
- kind: "skill"
16853
- }
16854
- ]
16855
- },
16856
- user: {
16857
- detect: ".cursor",
16858
- ...skillsProjection("cursor", "user")
16859
- }
16860
- },
16861
- {
16862
- name: "codex",
16863
- project: {
16864
- detect: ".codex",
16865
- ...skillsProjection("codex", "project"),
16866
- mcpConfig: {
16867
- path: ".codex/config.toml",
16868
- key: "mcp_servers",
16869
- format: "toml",
16870
- dialect: "codex",
16871
- shared: true
16872
- },
16873
- nonstandard: [{
16874
- path: ".codex/skills",
16875
- shape: "directory",
16876
- kind: "skill"
16877
- }]
16878
- },
16879
- user: {
16880
- detect: ".codex",
16881
- ...skillsProjection("codex", "user"),
16882
- instructionBridge: {
16883
- kind: "symlink",
16884
- path: ".codex/AGENTS.md"
16885
- },
16886
- shadowedBy: [".codex/AGENTS.override.md"],
16887
- relocatedBy: "CODEX_HOME"
16888
- }
16889
- },
16890
- {
16891
- name: "copilot-cli",
16892
- project: {
16893
- detect: ".github/skills",
16894
- ...skillsProjection("copilot-cli", "project"),
16895
- nonstandard: [
16896
- {
16897
- path: ".github/copilot-instructions.md",
16898
- shape: "file",
16899
- kind: "instructions"
16900
- },
16901
- {
16902
- path: ".github/instructions",
16903
- shape: "directory",
16904
- kind: "instructions"
16905
- },
16906
- {
16907
- path: ".github/skills",
16908
- shape: "directory",
16909
- kind: "skill"
16910
- }
16911
- ]
16912
- },
16913
- user: {
16914
- detect: ".copilot",
16915
- ...skillsProjection("copilot-cli", "user"),
16916
- relocatedBy: "COPILOT_HOME",
16917
- instructionBridge: {
16918
- kind: "symlink",
16919
- path: ".copilot/copilot-instructions.md"
16920
- }
16921
- }
16922
- },
16923
- {
16924
- name: "gemini-cli",
16925
- project: {
16926
- detect: ".gemini",
16927
- ...skillsProjection("gemini-cli", "project"),
16928
- instructionBridge: {
16929
- kind: "settings-entry",
16930
- path: ".gemini/settings.json",
16931
- key: "context.fileName"
16932
- },
16933
- mcpConfig: {
16934
- path: ".gemini/settings.json",
16935
- key: "mcpServers",
16936
- format: "json",
16937
- dialect: "gemini-cli",
16938
- shared: true
16939
- },
16940
- nonstandard: [{
16941
- path: "GEMINI.md",
16942
- shape: "file",
16943
- kind: "instructions"
16944
- }, {
16945
- path: ".gemini/skills",
16946
- shape: "directory",
16947
- kind: "skill"
16948
- }]
16949
- },
16950
- user: {
16951
- detect: ".gemini",
16952
- ...skillsProjection("gemini-cli", "user"),
16953
- instructionBridge: {
16954
- kind: "symlink",
16955
- path: ".gemini/GEMINI.md"
16956
- }
18126
+ harness: harness.length ? harness : "no supported harness detected on this machine",
18127
+ actions: actions.length ? actions : "0 actions — every detected harness matches the catalog",
18128
+ ...notes.length ? { notes } : {}
18129
+ });
18130
+ return outcome === "would-change" ? answerThenFail(answer, this.format) : answer;
18131
+ } catch (error) {
18132
+ return fail(error, "Catalog generation failed.");
16957
18133
  }
16958
- },
16959
- {
16960
- name: "devin-desktop",
16961
- project: { detect: ".devin" }
16962
- },
16963
- {
16964
- name: "windsurf",
16965
- project: {
16966
- detect: ".windsurf",
16967
- skillsDirectory: ".windsurf/skills",
16968
- nonstandard: [{
16969
- path: ".windsurfrules",
16970
- shape: "file",
16971
- kind: "instructions"
16972
- }, {
16973
- path: ".windsurf/rules",
16974
- shape: "directory",
16975
- kind: "rule"
16976
- }]
16977
- },
16978
- deprecated: "devin-desktop"
16979
18134
  }
16980
- ];
16981
- const harnessNames = new Set(harnessRegistry.map((harness) => harness.name));
16982
- function isHarnessName(value) {
16983
- return harnessNames.has(value);
16984
- }
16985
- /**
16986
- * An unknown name throws rather than being dropped — silently falling back to the defaults would
16987
- * misreport what was diagnosed.
16988
- */
16989
- function parseHarnesses(value) {
16990
- const requested = (value ?? "").split(",").map((name) => name.trim()).filter(Boolean);
16991
- const unsupported = requested.filter((name) => !isHarnessName(name));
16992
- if (unsupported.length) throw new Error(`Unsupported harness: ${unsupported.join(", ")}. Supported: ${harnessRegistry.map((harness) => harness.name).join(", ")}.`);
16993
- return requested.filter(isHarnessName);
16994
- }
16995
- /** Enabled whether or not the repository already contains their directories. */
16996
- const defaultHarnesses = ["claude-code", "cursor"];
16997
- function directoryExists(path) {
18135
+ });
18136
+ //#endregion
18137
+ //#region src/diagnose-bridges/directory-files.ts
18138
+ /** A directory that does not exist reads as empty: `doctor` never creates one to look at it. */
18139
+ function filesUnder(directory) {
16998
18140
  try {
16999
- return lstatSync(path).isDirectory();
18141
+ return readdirSync(directory, {
18142
+ recursive: true,
18143
+ withFileTypes: true
18144
+ }).filter((entry) => entry.isFile()).map((entry) => relative(directory, join(entry.parentPath, entry.name)).split(sep).join(posix.sep)).sort();
17000
18145
  } catch {
17001
- return false;
18146
+ return [];
17002
18147
  }
17003
18148
  }
17004
- /** The default harnesses, plus the preferred ones, plus every harness whose directory is present. */
17005
- function selectHarnesses(root, preferred) {
17006
- const enabled = /* @__PURE__ */ new Set([...defaultHarnesses, ...preferred]);
17007
- return harnessRegistry.filter((harness) => enabled.has(harness.name) || directoryExists(join(root, harness.project.detect)));
17008
- }
17009
18149
  //#endregion
17010
18150
  //#region src/diagnose-bridges/locator.ts
17011
18151
  /** The locator as one string: the finding's `path`, and what a repair names. */
@@ -17036,6 +18176,7 @@ const initSkillInvocation = `/buddy-agent-harness:init-buddy-agent-harness`;
17036
18176
  */
17037
18177
  const pnpmSkillRef = (invocation) => `\`${invocation}\` (pnpm install: \`pnpm-${commandInvocation}-${invocation.slice(invocation.indexOf(":") + 1)}\`)`;
17038
18178
  const initSkillRef = pnpmSkillRef(initSkillInvocation);
18179
+ const enhanceSkillRef = pnpmSkillRef("/buddy-agent-harness:enhance");
17039
18180
  const repairSkillRef = pnpmSkillRef(repairSkillInvocation);
17040
18181
  /** Where the user authors the golden MCP server set, named in every MCP repair that points at it. */
17041
18182
  const goldenSet = ".agents/buddy-agent-harness/mcp.toml";
@@ -17401,6 +18542,35 @@ const nonstandardTable = {
17401
18542
  }
17402
18543
  };
17403
18544
  repairsOf(nonstandardTable);
18545
+ /** Who offers every worktree repair, named the same way in each row. */
18546
+ const worktreeOffer = `${initSkillRef} offers it, or ${enhanceSkillRef} where the repository already has an AGENTS.md`;
18547
+ const worktreeTable = {
18548
+ "worktrees-untracked": {
18549
+ detail: ".claude/worktrees/ is not git-ignored, so every agent worktree Claude Code creates there shows up as untracked in git status",
18550
+ repair: ({ file }) => ({
18551
+ command: "",
18552
+ instruction: `add \`.claude/worktrees/\` to ${file} — ${worktreeOffer}`
18553
+ }),
18554
+ skillRepair: () => `run ${initSkillRef} or ${enhanceSkillRef}, which offers the .gitignore line`
18555
+ },
18556
+ "worktrees-scanned": {
18557
+ detail: "this config does not exclude .claude/worktrees, so the tool walks into each agent worktree, a whole checkout with its own config, and Biome stops on the nested root configuration",
18558
+ repair: ({ file }) => ({
18559
+ command: "",
18560
+ instruction: `add \`"!**/.claude/worktrees"\` to \`files.includes\` in ${file} — ${worktreeOffer}`
18561
+ }),
18562
+ skillRepair: () => `run ${initSkillRef} or ${enhanceSkillRef}, which offers the exclusion`
18563
+ },
18564
+ "worktree-base-unset": {
18565
+ detail: "worktree.baseRef is unset, so agent worktrees branch from the default branch rather than the current HEAD — work on a feature branch starts without its commits",
18566
+ repair: ({ file }) => ({
18567
+ command: "",
18568
+ instruction: `decide whether agent worktrees should branch from HEAD, and if so set \`"worktree": { "baseRef": "head" }\` in ${file} — ${worktreeOffer}`
18569
+ }),
18570
+ skillRepair: () => `run ${initSkillRef} or ${enhanceSkillRef}, which explains the trade-off and offers the setting`
18571
+ }
18572
+ };
18573
+ repairsOf(worktreeTable);
17404
18574
  /**
17405
18575
  * Annotated over the whole union rather than inferred, so an added problem fails to compile until
17406
18576
  * its section carries it.
@@ -17411,7 +18581,8 @@ const doctorTable = {
17411
18581
  ...globalInstructionTable,
17412
18582
  ...configurationTable,
17413
18583
  ...mcpTable,
17414
- ...nonstandardTable
18584
+ ...nonstandardTable,
18585
+ ...worktreeTable
17415
18586
  };
17416
18587
  repairsOf(doctorTable);
17417
18588
  /** The repair for one problem. Total by construction: the table is keyed by the union. */
@@ -19200,8 +20371,45 @@ function diagnoseNonstandard({ root, cli }) {
19200
20371
  }
19201
20372
  return findings.sort((left, right) => left.path.localeCompare(right.path));
19202
20373
  }
20374
+ const biomeConfigs = ["biome.json", "biome.jsonc"];
20375
+ const claudeSettings = ".claude/settings.json";
20376
+ /** `undefined` when the file is missing or does not parse; either way Claude Code reads no base from it. */
20377
+ function readJson(path) {
20378
+ try {
20379
+ return JSON.parse(readFileSync(path, "utf8"));
20380
+ } catch {
20381
+ return;
20382
+ }
20383
+ }
20384
+ /**
20385
+ * Checked only where `.claude/` exists, the sign the repository is worked in with Claude Code.
20386
+ * Biome is the one tool checked: it is the confirmed failure, and the others are offered by the
20387
+ * skills without a finding.
20388
+ */
20389
+ function diagnoseWorktrees({ root, git, cli }) {
20390
+ if (!existsSync(join(root, ".claude"))) return [];
20391
+ const findings = [];
20392
+ const add = (path, problem) => {
20393
+ const { detail, repair } = repairFor(problem);
20394
+ findings.push({
20395
+ path,
20396
+ problem,
20397
+ detail,
20398
+ repair: repair({ file: path }, cli)
20399
+ });
20400
+ };
20401
+ if (git.isRepository() && !git.isIgnored(".claude/worktrees/")) add(".gitignore", "worktrees-untracked");
20402
+ for (const config of biomeConfigs) {
20403
+ const path = join(root, config);
20404
+ if (existsSync(path) && !readFileSync(path, "utf8").includes(".claude/worktrees")) add(config, "worktrees-scanned");
20405
+ }
20406
+ const settings = readJson(join(root, claudeSettings));
20407
+ const worktree = isRecord(settings) ? settings["worktree"] : void 0;
20408
+ if (!isRecord(worktree) || worktree["baseRef"] === void 0) add(claudeSettings, "worktree-base-unset");
20409
+ return findings;
20410
+ }
19203
20411
  //#endregion
19204
- //#region ../../node_modules/.pnpm/cyber-agentkit@2.0.0_clibuilder@12.1.1_js-yaml@4.3.2_/node_modules/cyber-agentkit/dist/commands.js
20412
+ //#region ../../node_modules/.pnpm/cyber-agentkit@2.2.0_clibuilder@12.3.0_js-yaml@4.3.2_/node_modules/cyber-agentkit/dist/commands.js
19205
20413
  /**
19206
20414
  * AXI §10: collapses the home directory to `~` so a path in a report is portable to another
19207
20415
  * machine.
@@ -19214,6 +20422,8 @@ t.boolean().optional();
19214
20422
  t.string().optional();
19215
20423
  t.string();
19216
20424
  t.string().optional();
20425
+ /** The command line that reaches the reference commands in `cyber-agentkit`'s own CLI. */
20426
+ const defaultReferencePath = "cyber-agentkit reference";
19217
20427
  const rootOption$3 = {
19218
20428
  description: "The repository root: its `.agents/references/` is the repo tier, and its `package.json` names the dependency plugins. Defaults to the nearest folder at or above the current directory holding `.git`, else the current directory.",
19219
20429
  type: t.string().optional()
@@ -19366,7 +20576,7 @@ const skillOption = {
19366
20576
  description: "A skill's source folder in this repository: acts on its `references/<name>.default.md`.",
19367
20577
  type: t.string().optional()
19368
20578
  };
19369
- function checkCommand(layersFor) {
20579
+ function checkCommand(layersFor, path = defaultReferencePath) {
19370
20580
  return command({
19371
20581
  name: "check",
19372
20582
  description: "Check references, each composed as `load` composes it: an interface document against its shape, an implementation against its interface, and every document for the defects composing it raises. With no names, checks every name found. Every warning `load` raises is reported. A failure is an entry in `failed`, not an error; a warning never fails a check.",
@@ -19382,6 +20592,10 @@ function checkCommand(layersFor) {
19382
20592
  description: "Add every entry checked, with its kind and unsupported sections."
19383
20593
  }
19384
20594
  },
20595
+ examples: [{
20596
+ command: `${path} check`,
20597
+ description: "Check every reference against its interface"
20598
+ }],
19385
20599
  async run(args) {
19386
20600
  try {
19387
20601
  const home = homedir();
@@ -19423,7 +20637,7 @@ function checkCommand(layersFor) {
19423
20637
  }
19424
20638
  });
19425
20639
  }
19426
- function createCommand(layersFor) {
20640
+ function createCommand(layersFor, path = defaultReferencePath) {
19427
20641
  return command({
19428
20642
  name: "create",
19429
20643
  description: "Start a contribution in the repo or user tier, a folder's project section, or a plugin's or a skill's source folder, from a template or an interface. Never overwrites a file with a body; writes a file a contribution above hides, with a warning.",
@@ -19459,6 +20673,10 @@ function createCommand(layersFor) {
19459
20673
  description: "After the write, add the trace, in which the new file applies."
19460
20674
  }
19461
20675
  },
20676
+ examples: [{
20677
+ command: `${path} create my-topic`,
20678
+ description: "Start a new reference named my-topic"
20679
+ }],
19462
20680
  async run(args) {
19463
20681
  const format = this.format;
19464
20682
  try {
@@ -19569,13 +20787,32 @@ function text(customized, shown) {
19569
20787
  const meta = shown.metadataFile ? ` Its record is in ${shown.metadataFile}.` : "";
19570
20788
  return `${shown.path}\n\n${shown.hint}${meta}\n`;
19571
20789
  }
19572
- function customizeCommand(layersFor) {
20790
+ function familyReport(customized, args, home) {
20791
+ return compact({
20792
+ family: customized.family,
20793
+ interfaces: customized.interfaces.map(({ interface: id, outcome, reason, result: each }) => compact({
20794
+ interface: id,
20795
+ outcome,
20796
+ reason,
20797
+ ...each ? report(each, args, home) : {}
20798
+ })),
20799
+ hint: customized.dryRun ? "Nothing was written. Run again without --dry-run to write it." : "Run `load <interface>` for each composed document."
20800
+ });
20801
+ }
20802
+ function familyText(customized, shown) {
20803
+ return `${customized.interfaces.map(({ interface: id, outcome, reason, result: each }, at) => {
20804
+ if (!each) return `${id}: ${outcome}. ${reason}\n`;
20805
+ const own = shown.interfaces[at];
20806
+ return `${id}: ${outcome}.\n${text(each, own)}`;
20807
+ }).join("\n")}`;
20808
+ }
20809
+ function customizeCommand(layersFor, path = defaultReferencePath) {
19573
20810
  return command({
19574
20811
  name: "customize",
19575
20812
  description: "Customize a name for the repo or user tier, or a folder's project section, running no model: pick what it builds on (`extends`), write a body generated from a request with its record, or print what the generator reads (--base). Writes a file a contribution above hides, with a warning.",
19576
20813
  arguments: [{
19577
20814
  name: "name",
19578
- description: "Reference name, without the `.md` extension.",
20815
+ description: "Reference name, without the `.md` extension; or a family, `<prefix>.*`, to pick for every interface under the prefix at once (takes --extends or --extends-file, no `sections`).",
19579
20816
  type: t.string()
19580
20817
  }, {
19581
20818
  name: "steps",
@@ -19634,6 +20871,13 @@ function customizeCommand(layersFor) {
19634
20871
  description: "After the write, add the trace, in which the written file applies."
19635
20872
  }
19636
20873
  },
20874
+ examples: [{
20875
+ command: `${path} customize my-topic`,
20876
+ description: "Customize a reference for this project"
20877
+ }, {
20878
+ command: `${path} customize my-skill.aspects.* --extends house-style`,
20879
+ description: "Pick one plugin for every interface under a prefix"
20880
+ }],
19637
20881
  async run(args) {
19638
20882
  const format = this.format;
19639
20883
  try {
@@ -19657,19 +20901,33 @@ function customizeCommand(layersFor) {
19657
20901
  base: args.base,
19658
20902
  dryRun: args["dry-run"]
19659
20903
  };
20904
+ const family = isFamilyName(args.name) || args.name.includes("*") ? parseFamily(args.name) : void 0;
19660
20905
  validateWrite({
19661
20906
  command: "customize",
19662
- ...request
20907
+ ...request,
20908
+ name: family === void 0 || family.invalid !== void 0 ? args.name : family.prefix
19663
20909
  });
19664
20910
  if (args.steps?.length && args.extends === void 0) throw new ReferenceFailure("options-missing", `${args.steps.join(" ")} follow no --extends; pass --extends before the steps.`);
19665
20911
  const layers = await layersFor({
19666
20912
  root: args.root,
19667
20913
  default: args.default
19668
20914
  }, home);
20915
+ if (family !== void 0) {
20916
+ const customized = customizeContribution(layers, {
20917
+ ...request,
20918
+ root: layers.root ?? request.root
20919
+ }, { display });
20920
+ if (!("family" in customized)) throw new Error(`${args.name} was customized as a single name, not a family.`);
20921
+ const shown = familyReport(customized, args, home);
20922
+ if (format !== "text") return result(shown);
20923
+ for (const { result: each } of customized.interfaces) writeWarnings(each?.warnings ?? []);
20924
+ return document(familyText(customized, shown));
20925
+ }
19669
20926
  const customized = customizeContribution(layers, {
19670
20927
  ...request,
19671
20928
  root: layers.root ?? request.root
19672
20929
  }, { display });
20930
+ if ("family" in customized) throw new Error(`${args.name} was customized as a family, not a single name.`);
19673
20931
  if (customized.action === "base") {
19674
20932
  if (format !== "text") return result(baseReport(customized));
19675
20933
  return document(baseText(customized));
@@ -19702,7 +20960,7 @@ function deleteReport(deleted, home) {
19702
20960
  options: deleted.after.options
19703
20961
  });
19704
20962
  }
19705
- function deleteCommand(layersFor) {
20963
+ function deleteCommand(layersFor, path = defaultReferencePath) {
19706
20964
  return command({
19707
20965
  name: "delete",
19708
20966
  description: "Delete a repo or user contribution, a folder's project section, or one in a plugin's or a skill's source folder, with a non-Markdown file's metadata file, and report what answers the name afterwards. Never deletes an installed or managed one.",
@@ -19725,6 +20983,10 @@ function deleteCommand(layersFor) {
19725
20983
  type: t.boolean().optional()
19726
20984
  }
19727
20985
  },
20986
+ examples: [{
20987
+ command: `${path} delete my-topic`,
20988
+ description: "Delete your contribution to my-topic"
20989
+ }],
19728
20990
  async run(args) {
19729
20991
  const format = this.format;
19730
20992
  try {
@@ -19772,13 +21034,40 @@ function parseAccept(value) {
19772
21034
  } });
19773
21035
  return versions.map(Number);
19774
21036
  }
19775
- function implementationsCommand(layersFor) {
21037
+ function rowsOf(found, trace) {
21038
+ return found.implementations.map(({ pin, description, unsupported, name, tier, version }) => compact(trace ? {
21039
+ pin,
21040
+ description,
21041
+ unsupported,
21042
+ name,
21043
+ tier,
21044
+ version
21045
+ } : {
21046
+ pin,
21047
+ ...tier === "plugin" ? {} : { tier },
21048
+ description,
21049
+ unsupported
21050
+ }));
21051
+ }
21052
+ function reportOf(found, trace) {
21053
+ const report = { implementations: rowsOf(found, trace) };
21054
+ if (found.inUse !== void 0) report.inUse = found.inUse;
21055
+ if (trace) Object.assign(report, compact({
21056
+ interface: found.interface,
21057
+ cardinality: found.cardinality,
21058
+ accept: found.accept,
21059
+ excluded: found.excluded,
21060
+ warnings: found.warnings
21061
+ }));
21062
+ return report;
21063
+ }
21064
+ function implementationsCommand(layersFor, path = defaultReferencePath) {
19776
21065
  return command({
19777
21066
  name: "implementations",
19778
21067
  description: "List the implementations of an interface a consumer can load, each with its pin, description, and unsupported sections.",
19779
21068
  arguments: [{
19780
21069
  name: "id",
19781
- description: "Interface id, the name of its `<id>.interface` document without the suffix.",
21070
+ description: "Interface id, the name of its `<id>.interface` document without the suffix; or a family, `<prefix>.*`, for every interface under the prefix.",
19782
21071
  type: t.string()
19783
21072
  }],
19784
21073
  options: {
@@ -19792,36 +21081,34 @@ function implementationsCommand(layersFor) {
19792
21081
  description: "Add each one's name, tier, and version, the interface's cardinality and accepted versions, and the ones left out with why."
19793
21082
  }
19794
21083
  },
21084
+ examples: [{
21085
+ command: `${path} implementations my-interface`,
21086
+ description: "List what implements an interface"
21087
+ }, {
21088
+ command: `${path} implementations my-skill.aspects.*`,
21089
+ description: "List, for every interface under a prefix, what implements it and which one is in use"
21090
+ }],
19795
21091
  async run(args) {
19796
21092
  try {
19797
21093
  const accept = parseAccept(args.accept);
19798
21094
  const home = homedir();
19799
- const found = referenceImplementations(args.id, await layersFor(args, home), {
21095
+ const options = {
19800
21096
  accept,
19801
21097
  display: (path) => collapseHome(home, path)
19802
- });
19803
- const rows = found.implementations.map(({ pin, description, unsupported, name, tier, version }) => compact(args.trace ? {
19804
- pin,
19805
- description,
19806
- unsupported,
19807
- name,
19808
- tier,
19809
- version
19810
- } : {
19811
- pin,
19812
- ...tier === "plugin" ? {} : { tier },
19813
- description,
19814
- unsupported
19815
- }));
19816
- if (!rows.length && !args.trace) return empty(`0 implementations of ${args.id}`, { help: `Run \`reference implementations ${args.id} --trace\` for the ones left out, with why.` });
19817
- const report = { implementations: rows };
19818
- if (args.trace) Object.assign(report, compact({
19819
- interface: found.interface,
19820
- cardinality: found.cardinality,
19821
- accept: found.accept,
19822
- excluded: found.excluded,
19823
- warnings: found.warnings
19824
- }));
21098
+ };
21099
+ const found = referenceImplementations(args.id, await layersFor(args, home), options);
21100
+ if ("family" in found) {
21101
+ const family = found;
21102
+ return result({
21103
+ family: family.family,
21104
+ interfaces: family.interfaces.map((each) => ({
21105
+ ...reportOf(each, args.trace),
21106
+ interface: each.interface
21107
+ }))
21108
+ });
21109
+ }
21110
+ const report = reportOf(found, args.trace);
21111
+ if (!report.implementations.length && report.inUse === void 0 && !args.trace) return empty(`0 implementations of ${args.id}`, { help: `Run \`reference implementations ${args.id} --trace\` for the ones left out, with why.` });
19825
21112
  return result(report);
19826
21113
  } catch (error) {
19827
21114
  return asCliError(error, "Implementation lookup failed.");
@@ -19829,7 +21116,7 @@ function implementationsCommand(layersFor) {
19829
21116
  }
19830
21117
  });
19831
21118
  }
19832
- function listCommand(layersFor) {
21119
+ function listCommand(layersFor, path = defaultReferencePath) {
19833
21120
  return command({
19834
21121
  name: "list",
19835
21122
  description: "List every reference name, one row each, with its description, and its status when not found. A pattern keeps only the names it matches exactly.",
@@ -19845,6 +21132,13 @@ function listCommand(layersFor) {
19845
21132
  description: "Add one row per name per layer holding it, with its outcome."
19846
21133
  }
19847
21134
  },
21135
+ examples: [{
21136
+ command: `${path} list`,
21137
+ description: "List every reference name"
21138
+ }, {
21139
+ command: `${path} list 'project-tracker.*'`,
21140
+ description: "List the names matching a pattern"
21141
+ }],
19848
21142
  async run(args) {
19849
21143
  try {
19850
21144
  const home = homedir();
@@ -19912,7 +21206,7 @@ function loadText(resolved) {
19912
21206
  }
19913
21207
  return document(resolved.map(tag).join("\n\n"));
19914
21208
  }
19915
- function loadCommand(layersFor) {
21209
+ function loadCommand(layersFor, path = defaultReferencePath) {
19916
21210
  return command({
19917
21211
  name: "load",
19918
21212
  description: "Print one or more references, each composed from its contributions as their extends and merge declare.",
@@ -19930,6 +21224,13 @@ function loadCommand(layersFor) {
19930
21224
  },
19931
21225
  trace: traceOption
19932
21226
  },
21227
+ examples: [{
21228
+ command: `${path} load my-topic`,
21229
+ description: "Print one reference"
21230
+ }, {
21231
+ command: `${path} load my-topic other-topic --format json`,
21232
+ description: "Print several as JSON"
21233
+ }],
19933
21234
  async run(args) {
19934
21235
  const format = this.format;
19935
21236
  try {
@@ -19970,7 +21271,87 @@ function loadCommand(layersFor) {
19970
21271
  }
19971
21272
  });
19972
21273
  }
19973
- function searchCommand(layersFor) {
21274
+ function rowLine(row) {
21275
+ const where = row.folder === void 0 ? row.tier : `${row.tier} ${row.folder}`;
21276
+ const change = row.manual === void 0 ? `${row.from} → ${row.to}` : `${row.from}: manual: ${row.manual}`;
21277
+ const by = row.by === void 0 ? "" : ` (by ${row.by})`;
21278
+ const user = row.tier === "user" ? "; a user file, which no git history restores" : "";
21279
+ return `${row.path} (${where}): ${row.form}: ${change}${by}${user}`;
21280
+ }
21281
+ function summary(rows, written) {
21282
+ const manual = rows.filter((row) => row.manual !== void 0).length;
21283
+ const applied = rows.length - manual;
21284
+ const left = manual ? ` ${manual} ${manual === 1 ? "needs" : "need"} a person, as each says.` : "";
21285
+ if (written) return `migrated ${applied} ${applied === 1 ? "change" : "changes"}.${left}`;
21286
+ if (!applied) return `nothing to apply.${left}`;
21287
+ return `${applied} ${applied === 1 ? "change" : "changes"} to make; run \`reference migrate --write\` with the same arguments to apply ${applied === 1 ? "it" : "them"}.${left}`;
21288
+ }
21289
+ function migrateCommand(layersFor, path = defaultReferencePath) {
21290
+ return command({
21291
+ name: "migrate",
21292
+ description: "Rewrite reference files still in a form version 1 wrote, and rename files under a name a plugin renamed, in the repo and user tiers and every project section, or in one plugin or skill source folder. Prints the plan; `--write` applies it. Never changes a managed or installed file.",
21293
+ arguments: [{
21294
+ name: "names",
21295
+ description: "Reference names to limit the run to, without the `.md` extension; a file under an older name of one is included.",
21296
+ type: t.array(t.string()).optional()
21297
+ }],
21298
+ options: {
21299
+ root: rootOption$3,
21300
+ plugin: {
21301
+ description: "A plugin's source folder in this repository: migrate only its `references/`, in place of the tiers.",
21302
+ type: t.string().optional()
21303
+ },
21304
+ default: {
21305
+ description: "A skill's source folder in this repository: migrate only its `references/`, in place of the tiers. A default at `references/<name>.md` is renamed only for a name given.",
21306
+ type: t.string().optional()
21307
+ },
21308
+ write: {
21309
+ description: "Apply the changes. It edits `user` files too, which no git history restores.",
21310
+ type: t.boolean().optional()
21311
+ }
21312
+ },
21313
+ examples: [{
21314
+ command: `${path} migrate`,
21315
+ description: "Migrate version 1 reference files"
21316
+ }],
21317
+ async run(args) {
21318
+ const format = this.format;
21319
+ try {
21320
+ const home = homedir();
21321
+ if (args.plugin !== void 0 && args.default !== void 0) throw new ReferenceFailure("options-conflict", "pass one of --plugin and --default.");
21322
+ const layers = await layersFor({ root: args.root }, home);
21323
+ const migrated = migrateReferences(layers, {
21324
+ root: layers.root ?? givenRoot(args.root),
21325
+ home,
21326
+ names: args.names,
21327
+ plugin: args.plugin,
21328
+ default: args.default,
21329
+ write: args.write
21330
+ });
21331
+ const changes = migrated.rows.map((row) => ({
21332
+ ...row,
21333
+ path: collapseHome(home, row.path)
21334
+ }));
21335
+ if (!changes.length && !migrated.warnings.length) return empty("0 changes — no reference file is in a version 1 form or under a renamed name", { help: ["Run `reference check` for every warning `load` raises."] });
21336
+ const report = compact({
21337
+ changes,
21338
+ written: migrated.written ? true : void 0,
21339
+ warnings: migrated.warnings
21340
+ });
21341
+ if (format !== "text") return result(report);
21342
+ writeWarnings(migrated.warnings);
21343
+ return document(`${[
21344
+ ...changes.map(rowLine),
21345
+ "",
21346
+ summary(changes, migrated.written)
21347
+ ].join("\n").trimStart()}\n`);
21348
+ } catch (error) {
21349
+ return asCliError(error, "Reference migration failed.");
21350
+ }
21351
+ }
21352
+ });
21353
+ }
21354
+ function searchCommand(layersFor, path = defaultReferencePath) {
19974
21355
  return command({
19975
21356
  name: "search",
19976
21357
  description: "Find references by name, description, tags, headings, or body, best match first.",
@@ -19984,16 +21365,29 @@ function searchCommand(layersFor) {
19984
21365
  trace: {
19985
21366
  ...traceOption,
19986
21367
  description: "Add the tier and plugin or skill of each match."
21368
+ },
21369
+ full: {
21370
+ ...fullOption,
21371
+ description: `Show every match, not only the best 10.`
19987
21372
  }
19988
21373
  },
21374
+ examples: [{
21375
+ command: `${path} search "worktree leases"`,
21376
+ description: "Find references by topic"
21377
+ }],
19989
21378
  async run(args) {
19990
21379
  const query = args.query.trim();
19991
21380
  try {
19992
21381
  const home = homedir();
19993
21382
  const { matches, defaults } = searchReferences(query, await layersFor(args, home), { display: (path) => collapseHome(home, path) });
19994
21383
  if (!matches.length) return empty(`0 references match "${query}"`, { help: [...defaults ? [defaults] : [], "Try other words, or run `reference list` for every name."] });
19995
- return result(compact({
19996
- references: matches.map(({ name, description, match, folder, tier, plugin, skill, loadedBy }) => compact(args.trace ? {
21384
+ const shown = truncateList(matches, {
21385
+ limit: 10,
21386
+ full: args.full
21387
+ });
21388
+ const report = compact({
21389
+ count: shown.truncated ? shown.count : void 0,
21390
+ references: shown.items.map(({ name, description, match, folder, tier, plugin, skill, loadedBy }) => compact(args.trace ? {
19997
21391
  name,
19998
21392
  description,
19999
21393
  match,
@@ -20010,14 +21404,15 @@ function searchCommand(layersFor) {
20010
21404
  loadedBy
20011
21405
  })),
20012
21406
  defaults
20013
- }));
21407
+ });
21408
+ return shown.truncated ? result(report, { help: [`Run \`reference search ${JSON.stringify(query)} --full\` for every match.`] }) : result(report);
20014
21409
  } catch (error) {
20015
21410
  return asCliError(error, "Reference search failed.");
20016
21411
  }
20017
21412
  }
20018
21413
  });
20019
21414
  }
20020
- function whereCommand(layersFor) {
21415
+ function whereCommand(layersFor, path = defaultReferencePath) {
20021
21416
  return command({
20022
21417
  name: "where",
20023
21418
  description: "Give the repo and user files, and with --in a folder's project section, a customization or a selection of a reference is written to, who each reaches and what hides it, the main document's heading paths, and the read-only plugins and default the stack uses.",
@@ -20038,6 +21433,10 @@ function whereCommand(layersFor) {
20038
21433
  description: "Add every layer checked, with its outcome."
20039
21434
  }
20040
21435
  },
21436
+ examples: [{
21437
+ command: `${path} where my-topic`,
21438
+ description: "Show which layers hold my-topic"
21439
+ }],
20041
21440
  async run(args) {
20042
21441
  const format = this.format;
20043
21442
  try {
@@ -20072,25 +21471,26 @@ function whereCommand(layersFor) {
20072
21471
  });
20073
21472
  }
20074
21473
  /** A factory, so each host CLI names itself as the plugin whose own references come first. */
20075
- function createReferenceCommands({ plugin } = {}) {
21474
+ function createReferenceCommands({ plugin, path = defaultReferencePath } = {}) {
20076
21475
  const layersFor = layerSource(plugin);
20077
21476
  return {
20078
- load: loadCommand(layersFor),
20079
- list: listCommand(layersFor),
20080
- search: searchCommand(layersFor),
20081
- where: whereCommand(layersFor),
20082
- create: createCommand(layersFor),
20083
- customize: customizeCommand(layersFor),
20084
- delete: deleteCommand(layersFor),
20085
- check: checkCommand(layersFor),
20086
- implementations: implementationsCommand(layersFor)
21477
+ load: loadCommand(layersFor, path),
21478
+ list: listCommand(layersFor, path),
21479
+ search: searchCommand(layersFor, path),
21480
+ where: whereCommand(layersFor, path),
21481
+ create: createCommand(layersFor, path),
21482
+ customize: customizeCommand(layersFor, path),
21483
+ delete: deleteCommand(layersFor, path),
21484
+ migrate: migrateCommand(layersFor, path),
21485
+ check: checkCommand(layersFor, path),
21486
+ implementations: implementationsCommand(layersFor, path)
20087
21487
  };
20088
21488
  }
20089
21489
  function createReferenceCommand(options = {}) {
20090
- const { load, list, search, where, create, customize, delete: remove, check, implementations } = createReferenceCommands(options);
21490
+ const { load, list, search, where, create, customize, delete: remove, migrate, check, implementations } = createReferenceCommands(options);
20091
21491
  return command({
20092
21492
  name: "reference",
20093
- description: "Read reference documents by name, composed from the managed, repo, user, plugin, and default tiers, and create, customize, or delete a contribution.",
21493
+ description: "Read reference documents by name, composed from the managed, repo, user, plugin, and default tiers, create, customize, or delete a contribution, and migrate version 1 files.",
20094
21494
  commands: [
20095
21495
  load,
20096
21496
  list,
@@ -20099,6 +21499,7 @@ function createReferenceCommand(options = {}) {
20099
21499
  create,
20100
21500
  customize,
20101
21501
  remove,
21502
+ migrate,
20102
21503
  implementations,
20103
21504
  check
20104
21505
  ]
@@ -20256,10 +21657,13 @@ function referenceLayers({ packageRoot = dirname(packageGovernancesDir()), ...op
20256
21657
  }
20257
21658
  });
20258
21659
  }
20259
- const referenceCommand = createReferenceCommand({ plugin: {
20260
- name: PACKAGE_PLUGIN,
20261
- root: dirname(packageGovernancesDir())
20262
- } });
21660
+ const referenceCommand = createReferenceCommand({
21661
+ plugin: {
21662
+ name: PACKAGE_PLUGIN,
21663
+ root: dirname(packageGovernancesDir())
21664
+ },
21665
+ path: "buddy-agent-harness reference"
21666
+ });
20263
21667
  //#endregion
20264
21668
  //#region src/skill-projection/skill-projection.ts
20265
21669
  function skillNames(skillsDirectory) {
@@ -20712,6 +22116,10 @@ var GitBridgeState = class {
20712
22116
  this.root = root;
20713
22117
  this.repoRootPrefix = git(root, ["rev-parse", "--show-prefix"])?.trim();
20714
22118
  }
22119
+ /** Whether `root` is inside a git work tree at all; a check that needs git's answer skips when not. */
22120
+ isRepository() {
22121
+ return this.repoRootPrefix !== void 0;
22122
+ }
20715
22123
  /** The skip-worktree bit is a hint some checkouts clear, so it is verified rather than assumed. */
20716
22124
  trackingOf(path) {
20717
22125
  if (this.repoRootPrefix === void 0) return "untracked";
@@ -20974,64 +22382,82 @@ function buildDoctorReport(bin, result, configuration = [], held = []) {
20974
22382
  help: [...new Map(findings.map((finding) => [`${finding.repair.command}\u0000${finding.repair.instruction}`, finding.repair])).values()]
20975
22383
  };
20976
22384
  }
20977
- const doctorCommand = command({
20978
- name: "doctor",
20979
- description: "Report whether this repository's harness bridges still resolve — skills into .agents/skills, instructions into AGENTS.md — and whether each harness's user-scope file loads ~/.agents/AGENTS.md. Read-only.",
20980
- options: {
20981
- root: {
20982
- description: "Repository or package directory. Defaults to the current directory.",
20983
- type: t.string().optional()
20984
- },
20985
- harness: {
20986
- description: "Comma-separated harnesses to check in addition to Claude Code and Cursor, e.g. codex,gemini-cli.",
20987
- type: t.string().optional()
20988
- }
22385
+ const doctorOptions = {
22386
+ root: {
22387
+ description: "Repository or package directory. Defaults to the current directory.",
22388
+ type: t.string().optional()
20989
22389
  },
20990
- async run(args) {
20991
- try {
20992
- const harnesses = parseHarnesses(args.harness);
20993
- const root = args.root ?? process.cwd();
20994
- const home = homedir();
20995
- const diagnosis = diagnoseBridges({
22390
+ harness: {
22391
+ description: "Comma-separated harnesses to check in addition to Claude Code and Cursor, e.g. codex,gemini-cli.",
22392
+ type: t.string().optional()
22393
+ }
22394
+ };
22395
+ const doctorDescription = "Report whether this repository's harness bridges still resolve — skills into .agents/skills, instructions into AGENTS.md — and whether each harness's user-scope file loads ~/.agents/AGENTS.md. Read-only.";
22396
+ /** The live report `doctor` answers with, shared with the bare invocation of the CLI. */
22397
+ async function diagnoseReport(args) {
22398
+ try {
22399
+ const harnesses = parseHarnessOption(args.harness);
22400
+ const root = args.root ?? process.cwd();
22401
+ const home = homedir();
22402
+ const diagnosis = diagnoseBridges({
22403
+ root,
22404
+ ...harnesses.length ? { harnesses } : {},
22405
+ cli: commandInvocation,
22406
+ home,
22407
+ env: process.env
22408
+ });
22409
+ const git = new GitBridgeState(root);
22410
+ const configuration = [
22411
+ ...diagnoseConfiguration({
20996
22412
  root,
20997
- ...harnesses.length ? { harnesses } : {},
20998
- cli: commandInvocation,
20999
- home,
21000
- env: process.env
21001
- });
21002
- const git = new GitBridgeState(root);
21003
- const configuration = [
21004
- ...diagnoseConfiguration({
21005
- root,
21006
- git,
21007
- cli: commandInvocation
21008
- }),
21009
- ...diagnoseMcp({
21010
- root,
21011
- git,
21012
- cli: commandInvocation
21013
- }),
21014
- ...diagnoseNonstandard({
21015
- root,
21016
- cli: commandInvocation
21017
- })
21018
- ];
21019
- const { layers: rows } = listReferences(await referenceLayers({
22413
+ git,
22414
+ cli: commandInvocation
22415
+ }),
22416
+ ...diagnoseMcp({
21020
22417
  root,
21021
- home,
21022
- platform: process.platform,
21023
- env: process.env
21024
- }));
21025
- const held = rows.filter(({ tier }) => tier !== "plugin").map(({ name, tier, path, outcome }) => ({
21026
- name,
21027
- tier,
21028
- path: collapseHome$1(home, path),
21029
- status: outcome
21030
- }));
21031
- return result(buildDoctorReport(displayBinPath(home, process.argv[1]), diagnosis, configuration, held));
21032
- } catch (error) {
21033
- return fail(error, "Harness diagnosis failed.");
21034
- }
22418
+ git,
22419
+ cli: commandInvocation
22420
+ }),
22421
+ ...diagnoseNonstandard({
22422
+ root,
22423
+ cli: commandInvocation
22424
+ }),
22425
+ ...diagnoseWorktrees({
22426
+ root,
22427
+ git,
22428
+ cli: commandInvocation
22429
+ })
22430
+ ];
22431
+ const { layers: rows } = listReferences(await referenceLayers({
22432
+ root,
22433
+ home,
22434
+ platform: process.platform,
22435
+ env: process.env
22436
+ }));
22437
+ const held = rows.filter(({ tier }) => tier !== "plugin").map(({ name, tier, path, outcome }) => ({
22438
+ name,
22439
+ tier,
22440
+ path: collapseHome$1(home, path),
22441
+ status: outcome
22442
+ }));
22443
+ return buildDoctorReport(displayBinPath(home, process.argv[1]), diagnosis, configuration, held);
22444
+ } catch (error) {
22445
+ return fail(error, "Harness diagnosis failed.");
22446
+ }
22447
+ }
22448
+ const doctorCommand = command({
22449
+ name: "doctor",
22450
+ description: doctorDescription,
22451
+ options: doctorOptions,
22452
+ examples: [{
22453
+ command: "buddy-agent-harness doctor",
22454
+ description: "Check the current repository"
22455
+ }, {
22456
+ command: "buddy-agent-harness doctor --harness codex,gemini-cli",
22457
+ description: "Also check Codex and Gemini CLI"
22458
+ }],
22459
+ async run(args) {
22460
+ return result(await diagnoseReport(args));
21035
22461
  }
21036
22462
  });
21037
22463
  //#endregion
@@ -21066,6 +22492,10 @@ const governanceCommand = command({
21066
22492
  root: rootOption,
21067
22493
  "overrides-only": overridesOnlyOption
21068
22494
  },
22495
+ examples: [{
22496
+ command: "buddy-agent-harness governance list",
22497
+ description: "List the governances the layers hold"
22498
+ }],
21069
22499
  run(args) {
21070
22500
  process.stderr.write(GOVERNANCE_DEPRECATED);
21071
22501
  try {
@@ -21100,16 +22530,21 @@ const governanceCommand = command({
21100
22530
  root: rootOption,
21101
22531
  "overrides-only": overridesOnlyOption
21102
22532
  },
22533
+ examples: [{
22534
+ command: "buddy-agent-harness governance show skill-design",
22535
+ description: "Print one governance document"
22536
+ }],
21103
22537
  run(args) {
21104
22538
  const format = this.format;
21105
22539
  process.stderr.write(GOVERNANCE_DEPRECATED);
21106
22540
  try {
21107
22541
  const name = parseGovernanceName(args.name);
21108
22542
  const found = resolveGovernance(name, layersFor(args));
21109
- if (!found) {
21110
- 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`);
21111
- throw CliError.reported(exitCodes.error);
21112
- }
22543
+ if (!found) throw new CliError(args["overrides-only"] ? `no override for governance "${name}" in the project, user, or machine-wide layer.` : `no governance named "${name}" in any layer.`, {
22544
+ code: "governance-not-found",
22545
+ details: { name },
22546
+ help: ref("reference list", {}, "see what is available")
22547
+ });
21113
22548
  const report = {
21114
22549
  name: found.name,
21115
22550
  scope: found.scope,
@@ -21185,9 +22620,16 @@ const initCommand = command({
21185
22620
  type: t.string().optional()
21186
22621
  }
21187
22622
  },
22623
+ examples: [{
22624
+ command: "buddy-agent-harness init",
22625
+ description: "Set up skills for Claude Code and Cursor"
22626
+ }, {
22627
+ command: "buddy-agent-harness init --harness codex --copy",
22628
+ description: "Also enable Codex, copying skills instead of linking"
22629
+ }],
21188
22630
  run(args) {
21189
22631
  try {
21190
- const harnesses = parseHarnesses(args.harness);
22632
+ const harnesses = parseHarnessOption(args.harness);
21191
22633
  return result(initializeHarnesses({
21192
22634
  root: args.root ?? process.cwd(),
21193
22635
  ...harnesses.length ? { harnesses } : {},
@@ -23763,6 +25205,13 @@ const mcpReconcileCommand = command({
23763
25205
  type: t.array(t.string()).optional()
23764
25206
  }
23765
25207
  },
25208
+ examples: [{
25209
+ command: "buddy-agent-harness mcp reconcile",
25210
+ description: "List harness-side changes the golden set lacks"
25211
+ }, {
25212
+ command: "buddy-agent-harness mcp reconcile --accept <path>",
25213
+ description: "Import one approved field"
25214
+ }],
23766
25215
  run(args) {
23767
25216
  try {
23768
25217
  const root = args.root ?? process.cwd();
@@ -23774,19 +25223,17 @@ const mcpReconcileCommand = command({
23774
25223
  });
23775
25224
  if (plan.kind === "unreadable") {
23776
25225
  const at = plan.position ? ` at line ${plan.position.line}, column ${plan.position.column}` : "";
23777
- process.stderr.write(`error: ${goldenSetPath} does not parse${at}.\n`);
23778
- throw CliError.reported(exitCodes.error);
23779
- }
23780
- if (plan.kind === "rejected") {
23781
- process.stderr.write(`error: nothing written.\n${plan.reasons.map((reason) => `- ${reason}\n`).join("")}`);
23782
- throw CliError.reported(exitCodes.error);
23783
- }
23784
- if (plan.kind === "absent") return result({
23785
- golden: `0 servers — no golden set at ${goldenSetPath}`,
23786
- mode: "nothing to reconcile into",
23787
- fields: "0 fields",
23788
- record: "not written"
25226
+ throw new CliError(`${goldenSetPath} does not parse${at}.`, {
25227
+ code: "golden-set-unreadable",
25228
+ help: `Fix the TOML syntax in ${goldenSetPath}, then re-run.`
25229
+ });
25230
+ }
25231
+ if (plan.kind === "rejected") throw new CliError("nothing written.", {
25232
+ code: "accept-rejected",
25233
+ details: { reasons: plan.reasons },
25234
+ help: ref("mcp reconcile", {}, "list the fields that can be approved")
23789
25235
  });
25236
+ if (plan.kind === "absent") return empty(`0 servers — no golden set at ${goldenSetPath}`, { help: `Create ${goldenSetPath} first; there is nothing to reconcile into.` });
23790
25237
  return result({
23791
25238
  golden: goldenSetPath,
23792
25239
  mode: plan.written ? "written — only the fields named by --accept" : "dry run — nothing written; re-run with --accept <path> for each approved field",
@@ -24836,6 +26283,13 @@ const mcpCommand = command({
24836
26283
  type: t.boolean().optional()
24837
26284
  }
24838
26285
  },
26286
+ examples: [{
26287
+ command: "buddy-agent-harness mcp project",
26288
+ description: "Preview what would be projected"
26289
+ }, {
26290
+ command: "buddy-agent-harness mcp project --write",
26291
+ description: "Project the golden set into each harness"
26292
+ }],
24839
26293
  run(args) {
24840
26294
  try {
24841
26295
  const root = args.root ?? process.cwd();
@@ -24847,15 +26301,12 @@ const mcpCommand = command({
24847
26301
  });
24848
26302
  if (plan.kind === "unreadable") {
24849
26303
  const at = plan.position ? ` at line ${plan.position.line}, column ${plan.position.column}` : "";
24850
- process.stderr.write(`error: ${goldenSetPath} does not parse${at}.\n`);
24851
- throw CliError.reported(exitCodes.error);
26304
+ throw new CliError(`${goldenSetPath} does not parse${at}.`, {
26305
+ code: "golden-set-unreadable",
26306
+ help: `Fix the TOML syntax in ${goldenSetPath}, then re-run.`
26307
+ });
24852
26308
  }
24853
- if (plan.kind === "absent") return result({
24854
- golden: `0 servers — no golden set at ${goldenSetPath}`,
24855
- mode: "nothing to project",
24856
- actions: "0 changes",
24857
- record: "not written"
24858
- });
26309
+ if (plan.kind === "absent") return empty(`0 servers — no golden set at ${goldenSetPath}`, { help: [`Create ${goldenSetPath} with the MCP servers to project.`, ref("mcp project", {}, "preview the projection")] });
24859
26310
  return result({
24860
26311
  golden: goldenSetPath,
24861
26312
  mode: write ? "written" : "dry run — nothing written; re-run with --write to apply",
@@ -24892,6 +26343,19 @@ function app(format) {
24892
26343
  version,
24893
26344
  description: rootCommand.description,
24894
26345
  format
26346
+ }).default({
26347
+ description: "Report whether this repository's harness bridges resolve (the same as `doctor`).",
26348
+ options: doctorCommand.options,
26349
+ async run(args) {
26350
+ const { bin: _bin, ...report } = await diagnoseReport({
26351
+ root: args["root"],
26352
+ harness: args["harness"]
26353
+ });
26354
+ return result({
26355
+ ...homeHeader({ description: rootCommand.description }),
26356
+ ...report
26357
+ });
26358
+ }
24895
26359
  }).command(first));
24896
26360
  }
24897
26361
  const textByDefault = {