@rness/cli 0.6.2 → 0.7.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.
@@ -735,6 +735,9 @@ async function isClean(dir, ignore = []) {
735
735
  [
736
736
  "status",
737
737
  "--porcelain",
738
+ // Every untracked file on its own line: a directory folded into one
739
+ // `?? dir/` entry would slip past a pathspec naming a file inside it.
740
+ "--untracked-files=all",
738
741
  ...pathspec.length > 0 ? ["--", ...pathspec] : []
739
742
  ],
740
743
  dir
@@ -857,7 +860,7 @@ function isWorkspaceClone(name) {
857
860
  }
858
861
  var NAME_RULE = "lowercase letters, digits, '.', '_' and '-', not starting with '-'";
859
862
  var ORG_NAME = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/;
860
- var KEYS = ["contract", "org", "repos", "scopes"];
863
+ var KEYS = ["contract", "org", "agents", "repos", "scopes"];
861
864
  function fail(message) {
862
865
  throw new Error(`rness.json: ${message}`);
863
866
  }
@@ -886,6 +889,15 @@ function readOrg(value) {
886
889
  function workspaceName(manifest, root) {
887
890
  return manifest.org ?? basename2(root);
888
891
  }
892
+ var AGENT_NAME = /^[a-z][a-z0-9-]*$/;
893
+ function readAgents(value) {
894
+ if (value === void 0) return null;
895
+ if (!Array.isArray(value) || !value.every((v) => typeof v === "string" && AGENT_NAME.test(v)) || new Set(value).size !== value.length)
896
+ fail(
897
+ `"agents" must be a list of unique agent names (got ${JSON.stringify(value)})`
898
+ );
899
+ return [...value];
900
+ }
889
901
  function readRepos(value) {
890
902
  const raw = value ?? {};
891
903
  if (!isRecord(raw)) fail('"repos" must be an object');
@@ -944,6 +956,7 @@ async function loadManifest(rnessDir) {
944
956
  return {
945
957
  contract: 1,
946
958
  org: readOrg(data.org),
959
+ agents: readAgents(data.agents),
947
960
  repos: readRepos(data.repos),
948
961
  scopes: readScopes(data.scopes)
949
962
  };
@@ -959,6 +972,10 @@ async function writeManifest(rnessDir, manifest) {
959
972
  const lines = ["{", ' "contract": 1,'];
960
973
  if (manifest.org !== null)
961
974
  lines.push(` "org": ${JSON.stringify(manifest.org)},`);
975
+ if (manifest.agents !== null)
976
+ lines.push(
977
+ ` "agents": [${manifest.agents.map((a) => JSON.stringify(a)).join(", ")}],`
978
+ );
962
979
  lines.push(
963
980
  ' "repos": {',
964
981
  repos.join(",\n"),
@@ -1045,13 +1062,13 @@ async function addRepository(input) {
1045
1062
  await clone(url, dir, input.credentialsFor?.(url) ?? null);
1046
1063
  action = "cloned";
1047
1064
  }
1048
- const declared = [name];
1065
+ const declared2 = [name];
1049
1066
  for (const { path, base } of subs) {
1050
1067
  if (!await exists(join4(input.root, ...path.split("/")))) {
1051
1068
  if (action === "cloned") await rm3(dir, { recursive: true, force: true });
1052
1069
  throw new Error(`${path} does not exist`);
1053
1070
  }
1054
- declared.push(base);
1071
+ declared2.push(base);
1055
1072
  }
1056
1073
  const manifest = {
1057
1074
  ...input.manifest,
@@ -1059,7 +1076,7 @@ async function addRepository(input) {
1059
1076
  scopes
1060
1077
  };
1061
1078
  await writeManifest(input.rnessDir, manifest);
1062
- return { manifest, name, url, action, declared };
1079
+ return { manifest, name, url, action, declared: declared2 };
1063
1080
  }
1064
1081
  async function workspaceDirs(repoDir) {
1065
1082
  let patterns = [];
@@ -1372,6 +1389,9 @@ function reportError(e) {
1372
1389
  return 1;
1373
1390
  }
1374
1391
 
1392
+ // src/version.ts
1393
+ var VERSION = "0.7.0";
1394
+
1375
1395
  // src/core/block.ts
1376
1396
  import { createHash } from "crypto";
1377
1397
  var BEGIN = "<!-- BEGIN rness -->";
@@ -1664,7 +1684,214 @@ async function ensureClaudeMd(dir) {
1664
1684
  }
1665
1685
 
1666
1686
  // src/commands/sync.ts
1667
- import { join as join9 } from "path";
1687
+ import { join as join10 } from "path";
1688
+
1689
+ // src/core/agent-targets.ts
1690
+ import { mkdir as mkdir2 } from "fs/promises";
1691
+ import { dirname as dirname4, join as join9 } from "path";
1692
+
1693
+ // src/core/agents.ts
1694
+ var TARGETS = {
1695
+ claude: {
1696
+ name: "claude",
1697
+ label: "Claude Code",
1698
+ file: ".claude/settings.json",
1699
+ // Read the context repository from a repository without a prompt. The
1700
+ // path is resolved against the project directory, and only once Claude
1701
+ // Code has trusted it (verified on 2.1.284, spec 0011 §7).
1702
+ guarantees: [
1703
+ {
1704
+ path: ["permissions", "additionalDirectories"],
1705
+ contains: "../../.rness"
1706
+ }
1707
+ ]
1708
+ }
1709
+ };
1710
+ var SUPPORTED_AGENTS = Object.keys(TARGETS);
1711
+ function unsupportedAgents(names) {
1712
+ return names.filter((n) => !Object.hasOwn(TARGETS, n));
1713
+ }
1714
+ function unsupportedMessage(name) {
1715
+ return `rness.json: agent "${name}" is not supported by @rness/cli ${VERSION} (supported: ${SUPPORTED_AGENTS.join(", ")})`;
1716
+ }
1717
+
1718
+ // src/core/json-guarantees.ts
1719
+ function isObject(value) {
1720
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1721
+ }
1722
+ function parse3(text) {
1723
+ let data;
1724
+ try {
1725
+ data = JSON.parse(text);
1726
+ } catch {
1727
+ return { ok: false, reason: "not valid JSON" };
1728
+ }
1729
+ return isObject(data) ? { ok: true, data } : { ok: false, reason: "not a JSON object" };
1730
+ }
1731
+ function listAt(root, g, create) {
1732
+ let node = root;
1733
+ for (const [i, key] of g.path.slice(0, -1).entries()) {
1734
+ const next = node[key];
1735
+ if (next === void 0) {
1736
+ if (!create) return { kind: "absent" };
1737
+ node[key] = {};
1738
+ } else if (!isObject(next))
1739
+ return {
1740
+ kind: "invalid",
1741
+ reason: `${g.path.slice(0, i + 1).join(".")} is not an object`
1742
+ };
1743
+ node = node[key];
1744
+ }
1745
+ const last = g.path[g.path.length - 1] ?? "";
1746
+ const value = node[last];
1747
+ if (value === void 0) {
1748
+ if (!create) return { kind: "absent" };
1749
+ const list = [];
1750
+ node[last] = list;
1751
+ return { kind: "list", list };
1752
+ }
1753
+ if (!Array.isArray(value))
1754
+ return { kind: "invalid", reason: `${g.path.join(".")} is not a list` };
1755
+ return { kind: "list", list: value };
1756
+ }
1757
+ function styleOf(text) {
1758
+ const indent = text.split(/\r?\n/).slice(1).map((l) => /^[ \t]+/.exec(l)?.[0]).find((w) => w !== void 0);
1759
+ return {
1760
+ indent: indent ?? " ",
1761
+ eol: text.includes("\r\n") ? "\r\n" : "\n",
1762
+ final: text.endsWith("\n")
1763
+ };
1764
+ }
1765
+ function ensureGuarantees(existing, guarantees) {
1766
+ const parsed = existing === null ? { ok: true, data: {} } : parse3(existing);
1767
+ if (!parsed.ok) return { kind: "invalid", reason: parsed.reason };
1768
+ let changed = false;
1769
+ for (const g of guarantees) {
1770
+ const found = listAt(parsed.data, g, true);
1771
+ if (found.kind === "invalid")
1772
+ return { kind: "invalid", reason: found.reason };
1773
+ if (found.kind === "list" && !found.list.includes(g.contains)) {
1774
+ found.list.push(g.contains);
1775
+ changed = true;
1776
+ }
1777
+ }
1778
+ if (existing === null)
1779
+ return {
1780
+ kind: "created",
1781
+ text: `${JSON.stringify(parsed.data, null, 2)}
1782
+ `
1783
+ };
1784
+ if (!changed) return { kind: "unchanged", text: existing };
1785
+ const { indent, eol, final } = styleOf(existing);
1786
+ const body = JSON.stringify(parsed.data, null, indent).split("\n").join(eol);
1787
+ return { kind: "updated", text: final ? `${body}${eol}` : body };
1788
+ }
1789
+ function missingGuarantees(existing, guarantees) {
1790
+ if (existing === null) return [...guarantees];
1791
+ const parsed = parse3(existing);
1792
+ if (!parsed.ok) return { invalid: parsed.reason };
1793
+ const missing = [];
1794
+ for (const g of guarantees) {
1795
+ const found = listAt(parsed.data, g, false);
1796
+ if (found.kind === "invalid") return { invalid: found.reason };
1797
+ if (found.kind === "absent" || !found.list.includes(g.contains))
1798
+ missing.push(g);
1799
+ }
1800
+ return missing;
1801
+ }
1802
+ function describeGuarantee(g) {
1803
+ return `${g.path.join(".")} lacks ${g.contains}`;
1804
+ }
1805
+
1806
+ // src/core/agent-targets.ts
1807
+ async function filesOf(root, manifest, targets) {
1808
+ const files = [];
1809
+ for (const repo of Object.keys(manifest.repos)) {
1810
+ if (!await exists(join9(root, "org", repo))) continue;
1811
+ for (const target of targets)
1812
+ files.push({
1813
+ target,
1814
+ file: join9(root, "org", repo, ...target.file.split("/")),
1815
+ label: `org/${repo}/${target.file}`
1816
+ });
1817
+ }
1818
+ return files;
1819
+ }
1820
+ function declared(manifest) {
1821
+ return (manifest.agents ?? []).flatMap(
1822
+ (name) => Object.hasOwn(TARGETS, name) ? [TARGETS[name]] : []
1823
+ );
1824
+ }
1825
+ async function agentTargets(root, manifest, opts) {
1826
+ const outcomes = [];
1827
+ for (const { target, file, label } of await filesOf(
1828
+ root,
1829
+ manifest,
1830
+ declared(manifest)
1831
+ )) {
1832
+ const existing = await readOrNull(file);
1833
+ if (opts.check) {
1834
+ const missing = missingGuarantees(existing, target.guarantees);
1835
+ if ("invalid" in missing)
1836
+ outcomes.push({ label, status: "invalid", detail: missing.invalid });
1837
+ else if (missing.length > 0)
1838
+ outcomes.push({
1839
+ label,
1840
+ status: "stale",
1841
+ detail: missing.map(describeGuarantee).join("; ")
1842
+ });
1843
+ else outcomes.push({ label, status: "unchanged", detail: null });
1844
+ continue;
1845
+ }
1846
+ const ensured = ensureGuarantees(existing, target.guarantees);
1847
+ if (ensured.kind === "invalid") {
1848
+ outcomes.push({ label, status: "invalid", detail: ensured.reason });
1849
+ continue;
1850
+ }
1851
+ if (ensured.kind === "unchanged") {
1852
+ outcomes.push({ label, status: "unchanged", detail: null });
1853
+ continue;
1854
+ }
1855
+ await mkdir2(dirname4(file), { recursive: true });
1856
+ await writeFileAtomic(file, ensured.text);
1857
+ outcomes.push({ label, status: "updated", detail: null });
1858
+ }
1859
+ return outcomes;
1860
+ }
1861
+ async function leftoverTargets(root, manifest) {
1862
+ if (manifest.agents === null) return [];
1863
+ const agents = manifest.agents;
1864
+ const undeclared = Object.values(TARGETS).filter(
1865
+ (t) => !agents.includes(t.name)
1866
+ );
1867
+ const leftovers = [];
1868
+ for (const { target, file, label } of await filesOf(
1869
+ root,
1870
+ manifest,
1871
+ undeclared
1872
+ )) {
1873
+ const existing = await readOrNull(file);
1874
+ if (existing === null) continue;
1875
+ const missing = missingGuarantees(existing, target.guarantees);
1876
+ if (!("invalid" in missing) && missing.length === 0) leftovers.push(label);
1877
+ }
1878
+ return leftovers;
1879
+ }
1880
+
1881
+ // src/core/ask-agents.ts
1882
+ var AGENTS_QUESTION = "Which agents does your team use?";
1883
+ async function askAgents(p) {
1884
+ const answer = await p.multiselect({
1885
+ message: AGENTS_QUESTION,
1886
+ options: Object.values(TARGETS).map((t) => ({
1887
+ value: t.name,
1888
+ label: t.label
1889
+ })),
1890
+ required: false
1891
+ });
1892
+ if (p.isCancel(answer)) return null;
1893
+ return Array.isArray(answer) ? answer : [];
1894
+ }
1668
1895
 
1669
1896
  // src/core/progress.ts
1670
1897
  var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -1853,7 +2080,7 @@ function targetsOf(manifest, root, only) {
1853
2080
  const segments = entry.path.split("/");
1854
2081
  targets.push({
1855
2082
  scope: name,
1856
- dir: join9(root, ...segments),
2083
+ dir: join10(root, ...segments),
1857
2084
  depth: segments.length,
1858
2085
  label: `${entry.path}/AGENTS.md`
1859
2086
  });
@@ -1917,16 +2144,60 @@ async function syncCommand(opts, deps = {}) {
1917
2144
  const check = opts.check === true;
1918
2145
  const ownUi = deps.ui === void 0;
1919
2146
  const ui = deps.ui ?? (opts.yes !== true && !check ? await makeUi(terminal) : { ...plainUi });
2147
+ const agentFlags = opts.agent ?? [];
2148
+ if (agentFlags.length > 0 && check) {
2149
+ process.stderr.write(
2150
+ "--agent cannot be combined with --check: it declares agents in rness.json\n"
2151
+ );
2152
+ return 2;
2153
+ }
2154
+ const unknown = unsupportedAgents(agentFlags)[0];
2155
+ if (unknown !== void 0) {
2156
+ process.stderr.write(
2157
+ `unknown agent "${unknown}" (supported: ${SUPPORTED_AGENTS.join(", ")})
2158
+ `
2159
+ );
2160
+ return 2;
2161
+ }
1920
2162
  try {
1921
2163
  const ws = await findWorkspace(cwd);
1922
2164
  const manifest = await loadManifest(ws.rnessDir);
1923
2165
  const org = workspaceName(manifest, ws.root);
2166
+ const unsupported = unsupportedAgents(manifest.agents ?? [])[0];
2167
+ if (unsupported !== void 0) {
2168
+ process.stderr.write(`${unsupportedMessage(unsupported)}
2169
+ `);
2170
+ return 1;
2171
+ }
1924
2172
  if (ownUi) ui.intro(`Sync workspace ${org}`);
1925
2173
  if (opts.scope !== void 0 && !Object.hasOwn(manifest.scopes, opts.scope)) {
1926
2174
  process.stderr.write(`unknown scope: ${opts.scope}
1927
2175
  `);
1928
2176
  return 1;
1929
2177
  }
2178
+ let declare = null;
2179
+ if (agentFlags.length > 0) {
2180
+ const current = manifest.agents ?? [];
2181
+ const added = agentFlags.filter((a) => !current.includes(a));
2182
+ if (added.length > 0) declare = [...current, ...new Set(added)];
2183
+ } else if (manifest.agents === null && !check && opts.yes !== true && opts.scope === void 0 && terminal.isTty()) {
2184
+ const answer = await askAgents(await terminal.prompts());
2185
+ if (answer === null) {
2186
+ ui.cancelled();
2187
+ return 1;
2188
+ }
2189
+ declare = answer;
2190
+ }
2191
+ if (declare !== null) {
2192
+ const before = manifest.agents ?? [];
2193
+ manifest.agents = declare;
2194
+ await writeManifest(ws.rnessDir, manifest);
2195
+ for (const name of declare.filter((a) => !before.includes(a)))
2196
+ ui.line("declared", `agent ${name} in .rness/rness.json`);
2197
+ if (declare.length === 0)
2198
+ ui.line("declared", "no agent in .rness/rness.json");
2199
+ ui.hint("rness.json changed: commit it in .rness, for the whole team");
2200
+ }
1930
2201
  const clones = await clonesIn(ws.root);
1931
2202
  const missing = Object.keys(manifest.repos).filter((n) => !clones.has(n));
1932
2203
  let toClone = new Set(opts.all === true && !check ? missing : []);
@@ -1968,7 +2239,7 @@ async function syncCommand(opts, deps = {}) {
1968
2239
  try {
1969
2240
  const notCloned = /* @__PURE__ */ new Set();
1970
2241
  for (const [name, repo] of Object.entries(manifest.repos)) {
1971
- const dir = join9(ws.root, "org", name);
2242
+ const dir = join10(ws.root, "org", name);
1972
2243
  const label = `org/${name}`;
1973
2244
  if (!await exists(dir)) {
1974
2245
  if (!toClone.has(name)) {
@@ -1989,7 +2260,13 @@ async function syncCommand(opts, deps = {}) {
1989
2260
  }
1990
2261
  } else if (opts.pull === true && !check) {
1991
2262
  try {
1992
- if (!await isClean(dir, MANAGED_FILES)) {
2263
+ const written = [
2264
+ ...MANAGED_FILES,
2265
+ ...(manifest.agents ?? []).flatMap(
2266
+ (a) => Object.hasOwn(TARGETS, a) ? [TARGETS[a].file] : []
2267
+ )
2268
+ ];
2269
+ if (!await isClean(dir, written)) {
1993
2270
  lines.push(["skipped", `${label} (working tree not clean)`]);
1994
2271
  continue;
1995
2272
  }
@@ -2020,7 +2297,7 @@ async function syncCommand(opts, deps = {}) {
2020
2297
  lines.push(
2021
2298
  `not in rness.json: ${undeclared.join(", ")} (${rnessCommand()} add <name> declares it)`
2022
2299
  );
2023
- if (!await exists(join9(ws.root, "org"))) {
2300
+ if (!await exists(join10(ws.root, "org"))) {
2024
2301
  lines.push(["skipped", "blocks (no org/ directory here)"]);
2025
2302
  } else {
2026
2303
  let differences = 0;
@@ -2032,7 +2309,7 @@ async function syncCommand(opts, deps = {}) {
2032
2309
  continue;
2033
2310
  }
2034
2311
  const [agentsLinked, claudeLinked] = await Promise.all(
2035
- MANAGED_FILES.map((f) => isSymlink(join9(t.dir, f)))
2312
+ MANAGED_FILES.map((f) => isSymlink(join10(t.dir, f)))
2036
2313
  );
2037
2314
  const atRoot = t.scope === null;
2038
2315
  if (!atRoot && (agentsLinked === true || claudeLinked === true)) {
@@ -2055,7 +2332,7 @@ async function syncCommand(opts, deps = {}) {
2055
2332
  `${t.label}: block is ${Math.round(block.bytes / 1024)} KB (over 32 KB)`
2056
2333
  );
2057
2334
  }
2058
- const agents = agentsLinked === true ? "skipped" : await syncBlockFile(join9(t.dir, "AGENTS.md"), block, check);
2335
+ const agents = agentsLinked === true ? "skipped" : await syncBlockFile(join10(t.dir, "AGENTS.md"), block, check);
2059
2336
  if (typeof agents === "object") {
2060
2337
  problems.push(`${t.label}: ${agents.error}`);
2061
2338
  continue;
@@ -2070,7 +2347,7 @@ async function syncCommand(opts, deps = {}) {
2070
2347
  }
2071
2348
  if (claudeLinked === true) continue;
2072
2349
  const claude = await syncBlockFile(
2073
- join9(t.dir, "CLAUDE.md"),
2350
+ join10(t.dir, "CLAUDE.md"),
2074
2351
  block,
2075
2352
  check,
2076
2353
  true
@@ -2086,6 +2363,28 @@ async function syncCommand(opts, deps = {}) {
2086
2363
  problems.push(
2087
2364
  `${differences} block(s) out of date \u2014 run ${rnessCommand()} sync`
2088
2365
  );
2366
+ if (opts.scope === void 0) {
2367
+ let stale = 0;
2368
+ for (const o of await agentTargets(ws.root, manifest, { check })) {
2369
+ if (o.status === "invalid")
2370
+ problems.push(
2371
+ `${o.label}: ${o.detail ?? "unreadable"}; fix it, then ${rnessCommand()} sync`
2372
+ );
2373
+ else if (o.status === "stale") {
2374
+ stale += 1;
2375
+ lines.push(["stale", `${o.label} (${o.detail ?? ""})`]);
2376
+ } else lines.push([o.status, o.label]);
2377
+ }
2378
+ if (stale > 0)
2379
+ problems.push(
2380
+ `${stale} agent file(s) out of date \u2014 run ${rnessCommand()} sync`
2381
+ );
2382
+ if (!check)
2383
+ for (const label of await leftoverTargets(ws.root, manifest))
2384
+ lines.push(
2385
+ `${label} still carries the values of an agent no longer in rness.json (left as is)`
2386
+ );
2387
+ }
2089
2388
  }
2090
2389
  return problems.length > 0 ? 1 : 0;
2091
2390
  } finally {
@@ -2105,7 +2404,7 @@ async function syncCommand(opts, deps = {}) {
2105
2404
  }
2106
2405
 
2107
2406
  // src/commands/add.ts
2108
- import { basename as basename3, join as join10 } from "path";
2407
+ import { basename as basename3, join as join11 } from "path";
2109
2408
  function printDeclared(ui, result, already) {
2110
2409
  for (const name of result.declared) {
2111
2410
  if (already.has(name)) continue;
@@ -2198,7 +2497,7 @@ async function addCommand(spec, opts, deps = {}) {
2198
2497
  }
2199
2498
  parsed = parseRepoSpec(spec, org, host);
2200
2499
  const scopes = (opts.scopes ?? "").split(",").map((s) => s.trim()).filter((s) => s !== "");
2201
- const dir = join10(ws.root, "org", parsed.name);
2500
+ const dir = join11(ws.root, "org", parsed.name);
2202
2501
  const present = await exists(dir);
2203
2502
  if (interactive) {
2204
2503
  const { confirm, isCancel } = await terminal.prompts();
@@ -2324,26 +2623,23 @@ async function contextCommand(opts) {
2324
2623
  }
2325
2624
  }
2326
2625
 
2327
- // src/version.ts
2328
- var VERSION = "0.6.2";
2329
-
2330
2626
  // src/core/delegate.ts
2331
2627
  import { readFile as readFile6 } from "fs/promises";
2332
- import { join as join11 } from "path";
2628
+ import { join as join12 } from "path";
2333
2629
  var DEFAULT_BIN = "dist/bin/rness.js";
2334
2630
  async function readPinnedCli(rnessDir) {
2335
- const dir = join11(rnessDir, "node_modules", "@rness", "cli");
2631
+ const dir = join12(rnessDir, "node_modules", "@rness", "cli");
2336
2632
  let pkg;
2337
2633
  try {
2338
- pkg = JSON.parse(await readFile6(join11(dir, "package.json"), "utf8"));
2634
+ pkg = JSON.parse(await readFile6(join12(dir, "package.json"), "utf8"));
2339
2635
  } catch {
2340
2636
  return null;
2341
2637
  }
2342
- const declared = pkg.bin;
2638
+ const declared2 = pkg.bin;
2343
2639
  let bin = DEFAULT_BIN;
2344
- if (typeof declared === "string") bin = declared;
2345
- else if (declared !== null && typeof declared === "object") {
2346
- const named = declared["rness"];
2640
+ if (typeof declared2 === "string") bin = declared2;
2641
+ else if (declared2 !== null && typeof declared2 === "object") {
2642
+ const named = declared2["rness"];
2347
2643
  if (typeof named === "string") bin = named;
2348
2644
  }
2349
2645
  return {
@@ -2355,7 +2651,7 @@ async function readPinnedCli(rnessDir) {
2355
2651
  async function pinnedBinPath(rnessDir) {
2356
2652
  const pinned = await readPinnedCli(rnessDir);
2357
2653
  if (pinned === null) return null;
2358
- return join11(pinned.dir, ...pinned.bin.split("/"));
2654
+ return join12(pinned.dir, ...pinned.bin.split("/"));
2359
2655
  }
2360
2656
  async function findDelegate(cwd, ownVersion) {
2361
2657
  let root;
@@ -2371,12 +2667,12 @@ async function findDelegate(cwd, ownVersion) {
2371
2667
  if (pinned === null) return null;
2372
2668
  const { version } = pinned;
2373
2669
  if (version === null || version === ownVersion) return null;
2374
- return { entry: join11(pinned.dir, "dist", "index.js"), version, root };
2670
+ return { entry: join12(pinned.dir, "dist", "index.js"), version, root };
2375
2671
  }
2376
2672
 
2377
2673
  // src/core/scaffold.ts
2378
2674
  import { statSync } from "fs";
2379
- import { dirname as dirname4, join as join12 } from "path";
2675
+ import { dirname as dirname5, join as join13 } from "path";
2380
2676
  import { fileURLToPath } from "url";
2381
2677
  var SCAFFOLD_FILES = [
2382
2678
  "AGENTS.md",
@@ -2398,30 +2694,30 @@ var SCAFFOLD_FILES = [
2398
2694
  "docs/.gitkeep"
2399
2695
  ];
2400
2696
  function scaffoldDir() {
2401
- let dir = dirname4(fileURLToPath(import.meta.url));
2697
+ let dir = dirname5(fileURLToPath(import.meta.url));
2402
2698
  for (let i = 0; i < 4; i += 1) {
2403
- const candidate = join12(dir, "scaffold");
2699
+ const candidate = join13(dir, "scaffold");
2404
2700
  try {
2405
- if (statSync(join12(candidate, "rness.json")).isFile()) return candidate;
2701
+ if (statSync(join13(candidate, "rness.json")).isFile()) return candidate;
2406
2702
  } catch {
2407
2703
  }
2408
- dir = dirname4(dir);
2704
+ dir = dirname5(dir);
2409
2705
  }
2410
2706
  throw new Error("scaffold directory not found next to the @rness/cli package");
2411
2707
  }
2412
2708
 
2413
2709
  // src/core/scaffold-copy.ts
2414
- import { chmod as chmod2, mkdir as mkdir2, readFile as readFile7, stat as stat2, writeFile as writeFile2 } from "fs/promises";
2415
- import { dirname as dirname5, join as join13 } from "path";
2710
+ import { chmod as chmod2, mkdir as mkdir3, readFile as readFile7, stat as stat2, writeFile as writeFile2 } from "fs/promises";
2711
+ import { dirname as dirname6, join as join14 } from "path";
2416
2712
  var TOKENISED = /* @__PURE__ */ new Set(["package.json"]);
2417
2713
  async function copyScaffold(dest, tokens) {
2418
2714
  const source = scaffoldDir();
2419
2715
  for (const rel of SCAFFOLD_FILES) {
2420
2716
  if (rel === "rness.json") continue;
2421
- const from = join13(source, ...rel.split("/"));
2717
+ const from = join14(source, ...rel.split("/"));
2422
2718
  const targetRel = rel === "_gitignore" ? ".gitignore" : rel;
2423
- const to = join13(dest, ...targetRel.split("/"));
2424
- await mkdir2(dirname5(to), { recursive: true });
2719
+ const to = join14(dest, ...targetRel.split("/"));
2720
+ await mkdir3(dirname6(to), { recursive: true });
2425
2721
  let content = await readFile7(from, "utf8");
2426
2722
  if (TOKENISED.has(rel)) {
2427
2723
  content = content.replaceAll("__RNESS_VERSION__", tokens.version).replaceAll("__RNESS_PM__", tokens.packageManager);
@@ -2582,18 +2878,18 @@ function revokeUrl() {
2582
2878
  }
2583
2879
 
2584
2880
  // src/commands/create.ts
2585
- import { mkdir as mkdir5, mkdtemp, rename as rename2, rm as rm5 } from "fs/promises";
2881
+ import { mkdir as mkdir6, mkdtemp, rename as rename2, rm as rm5 } from "fs/promises";
2586
2882
  import { tmpdir } from "os";
2587
- import { join as join17, relative as relative4, resolve as resolve4 } from "path";
2883
+ import { join as join18, relative as relative4, resolve as resolve4 } from "path";
2588
2884
 
2589
2885
  // src/core/new-workspace.ts
2590
- import { mkdir as mkdir3, readdir as readdir2, rm as rm4, stat as stat3 } from "fs/promises";
2591
- import { dirname as dirname6, join as join15, relative as relative2 } from "path";
2886
+ import { mkdir as mkdir4, readdir as readdir2, rm as rm4, stat as stat3 } from "fs/promises";
2887
+ import { dirname as dirname7, join as join16, relative as relative2 } from "path";
2592
2888
 
2593
2889
  // src/core/pinned.ts
2594
2890
  import { execFile as execFile4 } from "child_process";
2595
2891
  import { readFile as readFile8 } from "fs/promises";
2596
- import { basename as basename4, join as join14 } from "path";
2892
+ import { basename as basename4, join as join15 } from "path";
2597
2893
  import { promisify as promisify4 } from "util";
2598
2894
  var execFileP4 = promisify4(execFile4);
2599
2895
  var PACKAGE = "@rness/cli";
@@ -2618,7 +2914,7 @@ function compareVersions(a, b) {
2618
2914
  async function readPackageJson(rnessDir) {
2619
2915
  try {
2620
2916
  const parsed = JSON.parse(
2621
- await readFile8(join14(rnessDir, "package.json"), "utf8")
2917
+ await readFile8(join15(rnessDir, "package.json"), "utf8")
2622
2918
  );
2623
2919
  return parsed !== null && typeof parsed === "object" ? parsed : null;
2624
2920
  } catch {
@@ -2637,7 +2933,7 @@ async function readPin(rnessDir) {
2637
2933
  return null;
2638
2934
  }
2639
2935
  async function writePin(rnessDir, from, to) {
2640
- const file = join14(rnessDir, "package.json");
2936
+ const file = join15(rnessDir, "package.json");
2641
2937
  const text = await readFile8(file, "utf8");
2642
2938
  const entry = new RegExp(
2643
2939
  `("${PACKAGE}"\\s*:\\s*")${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(")`
@@ -2654,18 +2950,18 @@ var LOCKFILES = [
2654
2950
  ["package-lock.json", "npm"]
2655
2951
  ];
2656
2952
  async function workspacePackageManager(rnessDir) {
2657
- const declared = (await readPackageJson(rnessDir))?.["packageManager"];
2658
- if (typeof declared === "string") {
2659
- const name = declared.split("@")[0] ?? "";
2953
+ const declared2 = (await readPackageJson(rnessDir))?.["packageManager"];
2954
+ if (typeof declared2 === "string") {
2955
+ const name = declared2.split("@")[0] ?? "";
2660
2956
  if (isPackageManager(name)) return name;
2661
2957
  }
2662
2958
  for (const [lockfile, pm] of LOCKFILES)
2663
- if (await exists(join14(rnessDir, lockfile))) return pm;
2959
+ if (await exists(join15(rnessDir, lockfile))) return pm;
2664
2960
  return "npm";
2665
2961
  }
2666
2962
  async function hasLockfile(rnessDir, pm) {
2667
2963
  for (const [lockfile, owner] of LOCKFILES)
2668
- if (owner === pm && await exists(join14(rnessDir, lockfile))) return true;
2964
+ if (owner === pm && await exists(join15(rnessDir, lockfile))) return true;
2669
2965
  return false;
2670
2966
  }
2671
2967
  async function pinDrift(rnessDir) {
@@ -2721,9 +3017,9 @@ async function ensureInstalled(rnessDir, opts = {}) {
2721
3017
  }
2722
3018
  }
2723
3019
  async function runPinnedSync(root) {
2724
- const installed = await readPinnedCli(join14(root, ".rness"));
3020
+ const installed = await readPinnedCli(join15(root, ".rness"));
2725
3021
  if (installed === null) return null;
2726
- const pinned = join14(installed.dir, ...installed.bin.split("/"));
3022
+ const pinned = join15(installed.dir, ...installed.bin.split("/"));
2727
3023
  if (!await exists(pinned))
2728
3024
  return {
2729
3025
  code: 1,
@@ -2785,7 +3081,7 @@ function syncOutcome(result, opts = {}) {
2785
3081
  };
2786
3082
  }
2787
3083
  async function syncPinned(root, shown, fallback) {
2788
- const rnessDir = join14(root, ".rness");
3084
+ const rnessDir = join15(root, ".rness");
2789
3085
  const installed = await readPinnedCli(rnessDir);
2790
3086
  if (installed === null) {
2791
3087
  if (fallback !== void 0) return fallback();
@@ -2795,7 +3091,7 @@ async function syncPinned(root, shown, fallback) {
2795
3091
  );
2796
3092
  return 1;
2797
3093
  }
2798
- const pinned = join14(installed.dir, ...installed.bin.split("/"));
3094
+ const pinned = join15(installed.dir, ...installed.bin.split("/"));
2799
3095
  if (!await exists(pinned)) {
2800
3096
  process.stderr.write(
2801
3097
  `@rness/cli in ${shown}/.rness is not installed correctly (missing ${installed.bin}); reinstall in .rness/
@@ -2842,11 +3138,11 @@ async function insideWorkspace(dir) {
2842
3138
  }
2843
3139
  async function targetProblem(cwd, root, pm) {
2844
3140
  const shown = relative2(cwd, root) || ".";
2845
- if (await insideWorkspace(cwd) || await insideWorkspace(dirname6(root)))
3141
+ if (await insideWorkspace(cwd) || await insideWorkspace(dirname7(root)))
2846
3142
  return "already inside an rness workspace; create runs from outside one";
2847
3143
  if (!await exists(root)) return null;
2848
- if (await exists(join15(root, ".rness", "rness.json"))) {
2849
- if (!await exists(join15(root, ".rness", "node_modules")))
3144
+ if (await exists(join16(root, ".rness", "rness.json"))) {
3145
+ if (!await exists(join16(root, ".rness", "node_modules")))
2850
3146
  return `${shown} holds an rness workspace whose dependencies are not installed; cd ${shown}/.rness && ${pm} install, then ${rnessCommand()} sync`;
2851
3147
  return `${shown} already holds an rness workspace (.rness/); run ${rnessCommand()} add inside it to add repositories`;
2852
3148
  }
@@ -2869,9 +3165,9 @@ async function installContext(input) {
2869
3165
  }
2870
3166
  async function buildContext(input) {
2871
3167
  const { ui, root, shown, pm } = input;
2872
- const rnessDir = join15(root, ".rness");
3168
+ const rnessDir = join16(root, ".rness");
2873
3169
  const pmVersion = await packageManagerVersion(pm);
2874
- await mkdir3(root, { recursive: true });
3170
+ await mkdir4(root, { recursive: true });
2875
3171
  try {
2876
3172
  await copyScaffold(rnessDir, {
2877
3173
  version: VERSION,
@@ -2937,8 +3233,8 @@ async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
2937
3233
  }
2938
3234
 
2939
3235
  // src/commands/create-blank.ts
2940
- import { mkdir as mkdir4 } from "fs/promises";
2941
- import { join as join16, relative as relative3, resolve as resolve3 } from "path";
3236
+ import { mkdir as mkdir5 } from "fs/promises";
3237
+ import { join as join17, relative as relative3, resolve as resolve3 } from "path";
2942
3238
  var WORKSPACE_NAME = /^(?!\.{1,2}$)[A-Za-z0-9._][A-Za-z0-9._-]*$/;
2943
3239
  function workspaceNameError(name) {
2944
3240
  return `workspace name "${name}" may only contain letters, digits, '.', '_' and '-', not starting with '-'`;
@@ -2984,6 +3280,14 @@ async function createBlank(input) {
2984
3280
  );
2985
3281
  return 2;
2986
3282
  }
3283
+ let agents = input.agents.length > 0 ? [...input.agents] : null;
3284
+ if (agents === null && input.interactive) {
3285
+ agents = await askAgents(await input.prompts());
3286
+ if (agents === null) {
3287
+ ui.cancelled();
3288
+ return 0;
3289
+ }
3290
+ }
2987
3291
  if (input.interactive && !prompted) {
2988
3292
  const p = await input.prompts();
2989
3293
  const ok = await p.confirm({
@@ -2998,14 +3302,14 @@ async function createBlank(input) {
2998
3302
  ui,
2999
3303
  root,
3000
3304
  shown,
3001
- manifest: { contract: 1, org: null, repos: {}, scopes: {} },
3305
+ manifest: { contract: 1, org: null, agents, repos: {}, scopes: {} },
3002
3306
  pm,
3003
3307
  skipInstall: input.skipInstall,
3004
3308
  kind: "blank workspace"
3005
3309
  });
3006
3310
  if (!built) return 1;
3007
- await mkdir4(join16(root, "org"), { recursive: true });
3008
- await commitContext({ ui, rnessDir: join16(root, ".rness"), shown });
3311
+ await mkdir5(join17(root, "org"), { recursive: true });
3312
+ await commitContext({ ui, rnessDir: join17(root, ".rness"), shown });
3009
3313
  const code = await syncBlocks(
3010
3314
  ui,
3011
3315
  root,
@@ -3165,7 +3469,7 @@ async function cloneSelection(input) {
3165
3469
  { doing: `cloning org/${repo}`, git: true },
3166
3470
  () => clone(
3167
3471
  entry.url,
3168
- join17(input.root, "org", repo),
3472
+ join18(input.root, "org", repo),
3169
3473
  input.provider.credentialsFor(entry.url)
3170
3474
  ),
3171
3475
  () => ["cloned", `org/${repo}`]
@@ -3296,6 +3600,15 @@ async function createCommand(opts, deps = {}) {
3296
3600
  `);
3297
3601
  return 2;
3298
3602
  }
3603
+ const agentFlags = [...new Set(opts.agent ?? [])];
3604
+ const unknownAgent = unsupportedAgents(agentFlags)[0];
3605
+ if (unknownAgent !== void 0) {
3606
+ process.stderr.write(
3607
+ `unknown agent "${unknownAgent}" (supported: ${SUPPORTED_AGENTS.join(", ")})
3608
+ `
3609
+ );
3610
+ return 2;
3611
+ }
3299
3612
  if (opts.ssh === true && opts.https === true) {
3300
3613
  process.stderr.write("--ssh and --https cannot be combined\n");
3301
3614
  return 2;
@@ -3369,6 +3682,7 @@ async function createCommand(opts, deps = {}) {
3369
3682
  if (blank)
3370
3683
  return await createBlank({
3371
3684
  name: opts.name,
3685
+ agents: agentFlags,
3372
3686
  cwd,
3373
3687
  pm,
3374
3688
  interactive,
@@ -3452,6 +3766,13 @@ async function createCommand(opts, deps = {}) {
3452
3766
  return 1;
3453
3767
  }
3454
3768
  const joining = probe.kind === "found";
3769
+ if (joining && agentFlags.length > 0) {
3770
+ process.stderr.write(
3771
+ `${org}/.rness exists: --agent only applies to a new workspace; after joining, run ${rnessCommand()} sync${agentFlags.map((a) => ` --agent ${a}`).join("")}
3772
+ `
3773
+ );
3774
+ return 2;
3775
+ }
3455
3776
  if (joining)
3456
3777
  ui.line(
3457
3778
  "found",
@@ -3464,10 +3785,16 @@ async function createCommand(opts, deps = {}) {
3464
3785
  `${org}/.rness (or not visible to you) \u2014 starting a new workspace`,
3465
3786
  `${org} has no workspace yet (or none you can see) \u2014 starting one`
3466
3787
  );
3467
- let catalogue = { contract: 1, org, repos: {}, scopes: {} };
3788
+ let catalogue = {
3789
+ contract: 1,
3790
+ org,
3791
+ agents: null,
3792
+ repos: {},
3793
+ scopes: {}
3794
+ };
3468
3795
  const staged = async () => {
3469
- staging ??= await mkdtemp(join17(tmpdir(), "rness-join-"));
3470
- return join17(staging, ".rness");
3796
+ staging ??= await mkdtemp(join18(tmpdir(), "rness-join-"));
3797
+ return join18(staging, ".rness");
3471
3798
  };
3472
3799
  if (joining) {
3473
3800
  const stagedDir = await staged();
@@ -3510,6 +3837,14 @@ async function createCommand(opts, deps = {}) {
3510
3837
  } else {
3511
3838
  specs = Object.keys(catalogue.repos);
3512
3839
  }
3840
+ if (!joining) {
3841
+ if (agentFlags.length > 0) catalogue.agents = agentFlags;
3842
+ else if (interactive) {
3843
+ const answer = await askAgents(await prompts());
3844
+ if (answer === null) return cancelled(ui);
3845
+ catalogue.agents = answer;
3846
+ }
3847
+ }
3513
3848
  if (interactive && !prompted) {
3514
3849
  const p = await prompts();
3515
3850
  const ok = await p.confirm({
@@ -3517,10 +3852,10 @@ async function createCommand(opts, deps = {}) {
3517
3852
  });
3518
3853
  if (p.isCancel(ok) || ok !== true) return cancelled(ui);
3519
3854
  }
3520
- const rnessDir = join17(root, ".rness");
3855
+ const rnessDir = join18(root, ".rness");
3521
3856
  const install = () => installContext({ ui, pm, rnessDir, skip: opts.skipInstall === true });
3522
3857
  if (joining) {
3523
- await mkdir5(root, { recursive: true });
3858
+ await mkdir6(root, { recursive: true });
3524
3859
  await placeContext(
3525
3860
  await staged(),
3526
3861
  rnessDir,
@@ -3530,7 +3865,7 @@ async function createCommand(opts, deps = {}) {
3530
3865
  ui.line("cloned", `${shown}/.rness (joined ${org})`);
3531
3866
  if (opts.pm === void 0) pm = await workspacePackageManager(rnessDir);
3532
3867
  await install();
3533
- await mkdir5(join17(root, "org"), { recursive: true });
3868
+ await mkdir6(join18(root, "org"), { recursive: true });
3534
3869
  const failures2 = await cloneSelection({
3535
3870
  root,
3536
3871
  rnessDir,
@@ -3583,7 +3918,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
3583
3918
  kind: "new workspace"
3584
3919
  });
3585
3920
  if (!built) return 1;
3586
- await mkdir5(join17(root, "org"), { recursive: true });
3921
+ await mkdir6(join18(root, "org"), { recursive: true });
3587
3922
  const failures = await cloneSelection({
3588
3923
  root,
3589
3924
  rnessDir,
@@ -3684,8 +4019,8 @@ async function logoutCommand() {
3684
4019
 
3685
4020
  // src/commands/upgrade.ts
3686
4021
  import { execFile as execFile5 } from "child_process";
3687
- import { mkdir as mkdir6, readFile as readFile9, readdir as readdir3 } from "fs/promises";
3688
- import { basename as basename5, dirname as dirname7, join as join18, relative as relative5 } from "path";
4022
+ import { mkdir as mkdir7, readFile as readFile9, readdir as readdir3 } from "fs/promises";
4023
+ import { basename as basename5, dirname as dirname8, join as join19, relative as relative5 } from "path";
3689
4024
  import { promisify as promisify5 } from "util";
3690
4025
  var execFileP5 = promisify5(execFile5);
3691
4026
  var NOTES = "https://github.com/rness-dev/rness/tree/main/packages/cli#readme";
@@ -3783,15 +4118,15 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
3783
4118
  return 1;
3784
4119
  }
3785
4120
  }
3786
- const packageFile = join18(ws.rnessDir, "package.json");
4121
+ const packageFile = join19(ws.rnessDir, "package.json");
3787
4122
  const restore = /* @__PURE__ */ new Map([
3788
4123
  [packageFile, await readFile9(packageFile, "utf8")]
3789
4124
  ]);
3790
4125
  if (pin.spec !== target) await writePin(ws.rnessDir, pin.spec, target);
3791
- const workflows = join18(ws.rnessDir, ".github", "workflows");
4126
+ const workflows = join19(ws.rnessDir, ".github", "workflows");
3792
4127
  for (const name of await readdir3(workflows).catch(() => [])) {
3793
4128
  if (!/\.ya?ml$/.test(name)) continue;
3794
- const file = join18(workflows, name);
4129
+ const file = join19(workflows, name);
3795
4130
  const text = await readOrNull(file);
3796
4131
  if (text === null) continue;
3797
4132
  const shown = `.github/workflows/${name}`;
@@ -3809,14 +4144,14 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
3809
4144
  ui.warn(`${shown} names @rness/cli@${pin.spec}; update it by hand`);
3810
4145
  }
3811
4146
  }
3812
- const dependabot = join18(ws.rnessDir, ".github", "dependabot.yml");
4147
+ const dependabot = join19(ws.rnessDir, ".github", "dependabot.yml");
3813
4148
  if (await exists(dependabot)) {
3814
4149
  ui.hint(".github/dependabot.yml is already there \u2014 left as it is");
3815
4150
  } else {
3816
- await mkdir6(dirname7(dependabot), { recursive: true });
4151
+ await mkdir7(dirname8(dependabot), { recursive: true });
3817
4152
  await writeFileAtomic(
3818
4153
  dependabot,
3819
- await readFile9(join18(scaffoldDir(), ".github", "dependabot.yml"), "utf8")
4154
+ await readFile9(join19(scaffoldDir(), ".github", "dependabot.yml"), "utf8")
3820
4155
  );
3821
4156
  ui.line(
3822
4157
  "wrote",
@@ -3878,7 +4213,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
3878
4213
  }
3879
4214
 
3880
4215
  // src/core/blocks.ts
3881
- import { join as join19 } from "path";
4216
+ import { join as join20 } from "path";
3882
4217
  var PROBLEM = /* @__PURE__ */ new Set(["stale", "malformed"]);
3883
4218
  async function checkBlocks({
3884
4219
  root,
@@ -3895,7 +4230,7 @@ async function checkBlocks({
3895
4230
  warnings: checks.filter((c) => !PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
3896
4231
  checks
3897
4232
  });
3898
- if (!await exists(join19(root, "org"))) return derive();
4233
+ if (!await exists(join20(root, "org"))) return derive();
3899
4234
  const targets = [
3900
4235
  { scope: null, dir: root, depth: 0, file: "AGENTS.md", label: "AGENTS.md" },
3901
4236
  { scope: null, dir: root, depth: 0, file: "CLAUDE.md", label: "CLAUDE.md" }
@@ -3904,7 +4239,7 @@ async function checkBlocks({
3904
4239
  const segments = entry.path.split("/");
3905
4240
  targets.push({
3906
4241
  scope: name,
3907
- dir: join19(root, ...segments),
4242
+ dir: join20(root, ...segments),
3908
4243
  depth: segments.length,
3909
4244
  file: "AGENTS.md",
3910
4245
  label: `${entry.path}/AGENTS.md`
@@ -3919,7 +4254,7 @@ async function checkBlocks({
3919
4254
  );
3920
4255
  continue;
3921
4256
  }
3922
- const text = await readOrNull(join19(t.dir, t.file));
4257
+ const text = await readOrNull(join20(t.dir, t.file));
3923
4258
  const lines = text === null ? [] : text.split(/\r?\n/);
3924
4259
  const found = findBlock(lines);
3925
4260
  if (found.kind === "none") {
@@ -3959,7 +4294,7 @@ async function checkBlocks({
3959
4294
  }
3960
4295
 
3961
4296
  // src/core/contract.ts
3962
- import { join as join20 } from "path";
4297
+ import { join as join21 } from "path";
3963
4298
  var STATUSES = {
3964
4299
  adr: ["Proposed", "Accepted", "Rejected", "Superseded"],
3965
4300
  specs: [
@@ -3978,7 +4313,7 @@ async function checkContract(rnessDir) {
3978
4313
  const problems = [];
3979
4314
  for (const name of COLLECTIONS) {
3980
4315
  const allowed = STATUSES[name];
3981
- const items = await collectMarkdown(join20(rnessDir, name));
4316
+ const items = await collectMarkdown(join21(rnessDir, name));
3982
4317
  for (const item of items) {
3983
4318
  const where = `${name}/${item.rel}`;
3984
4319
  if (SKIP.has(where)) continue;
@@ -4030,6 +4365,16 @@ async function validateCommand(opts, deps = {}) {
4030
4365
  org
4031
4366
  });
4032
4367
  problems.push(...blocks.problems);
4368
+ for (const name of unsupportedAgents(manifest.agents ?? []))
4369
+ problems.push(unsupportedMessage(name));
4370
+ const targets = await agentTargets(ws.root, manifest, { check: true });
4371
+ for (const o of targets)
4372
+ if (o.status === "stale")
4373
+ problems.push(
4374
+ `${o.label}: ${o.detail ?? ""} (run ${rnessCommand()} sync)`
4375
+ );
4376
+ else if (o.status === "invalid")
4377
+ problems.push(`${o.label}: ${o.detail ?? "unreadable"}`);
4033
4378
  const ui = await makeUi(deps.terminal ?? defaultTerminal);
4034
4379
  if (!ui.session) {
4035
4380
  for (const p of problems) process.stderr.write(`${p}
@@ -4052,6 +4397,8 @@ async function validateCommand(opts, deps = {}) {
4052
4397
  else if (c.status === "stale" || c.status === "malformed")
4053
4398
  ui.error(c.message);
4054
4399
  else ui.warn(c.message);
4400
+ for (const o of targets)
4401
+ if (o.status === "unchanged") ui.line("current", o.label);
4055
4402
  for (const p of problems) if (!blocks.problems.includes(p)) ui.error(p);
4056
4403
  ui.outro(
4057
4404
  problems.length > 0 ? "Context mismatch" : "Context OK",
@@ -4126,7 +4473,11 @@ function buildProgram(state) {
4126
4473
  ).option("--scope <name>", "only this scope (the root block is skipped)").option(
4127
4474
  "--check",
4128
4475
  "render and compare only; exit 1 when a block is out of date"
4129
- ).option("--pull", "git pull --ff-only in every clean clone").option("--all", "clone every rness.json repository missing from org/").option("-y, --yes", "do not ask for confirmation").addOption(new Option("--cwd <dir>").hideHelp()).action(async (opts) => {
4476
+ ).option("--pull", "git pull --ff-only in every clean clone").option("--all", "clone every rness.json repository missing from org/").option(
4477
+ "--agent <name>",
4478
+ "declare an agent in rness.json, then write its files (repeatable; supported: claude)",
4479
+ (value, previous = []) => [...previous, value]
4480
+ ).option("-y, --yes", "do not ask for confirmation").addOption(new Option("--cwd <dir>").hideHelp()).action(async (opts) => {
4130
4481
  state.code = await syncCommand(opts);
4131
4482
  });
4132
4483
  program.command("add").description(
@@ -4174,6 +4525,10 @@ function buildProgram(state) {
4174
4525
  ).option("--https", "force https://github.com/").option(
4175
4526
  "--blank",
4176
4527
  "a local workspace with no GitHub organization: .rness/, an empty org/, the root files"
4528
+ ).option(
4529
+ "--agent <name>",
4530
+ "a new workspace's agent, declared in rness.json (repeatable; supported: claude)",
4531
+ (value, previous = []) => [...previous, value]
4177
4532
  ).option("--skip-install", "do not install .rness/ dependencies").option("-y, --yes", "do not ask for confirmation").option("--template <name>", "reserved").addOption(new Option("--host <base>").hideHelp()).addOption(new Option("--github-api <base>").hideHelp()).addOption(new Option("--cwd <dir>").hideHelp()).action(async (orgArg, opts) => {
4178
4533
  if (opts.blank === true) {
4179
4534
  state.code = await createCommand(
@@ -4289,6 +4644,7 @@ export {
4289
4644
  defaultTransport,
4290
4645
  findWorkspace,
4291
4646
  reportError,
4647
+ VERSION,
4292
4648
  BEGIN,
4293
4649
  END,
4294
4650
  BLOCK_SIZE_WARNING,
@@ -4310,7 +4666,6 @@ export {
4310
4666
  addCommand,
4311
4667
  renderMarkdown,
4312
4668
  contextCommand,
4313
- VERSION,
4314
4669
  findDelegate,
4315
4670
  SCAFFOLD_FILES,
4316
4671
  scaffoldDir,
@@ -4327,4 +4682,4 @@ export {
4327
4682
  OWN_COMMANDS,
4328
4683
  catchUp
4329
4684
  };
4330
- //# sourceMappingURL=chunk-7TOHFPW6.js.map
4685
+ //# sourceMappingURL=chunk-VYP4L7BQ.js.map