@sechroom/cli 2026.8.9-rc.6433a3deb → 2026.9.1

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 (2) hide show
  1. package/dist/index.js +652 -277
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync19 } from "fs";
4
+ import { readFileSync as readFileSync20 } from "fs";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/auth.ts
@@ -702,17 +702,17 @@ function formatFailureMessage(error) {
702
702
  } else if (isRecord(error)) {
703
703
  const problem = error;
704
704
  const title = typeof problem.title === "string" && problem.title.length > 0 ? problem.title : void 0;
705
- const problemDetail = typeof problem.detail === "string" && problem.detail.length > 0 ? problem.detail : void 0;
705
+ const problemDetail2 = typeof problem.detail === "string" && problem.detail.length > 0 ? problem.detail : void 0;
706
706
  const fieldErrors = formatProblemErrors(problem.errors);
707
707
  const structuredErrors = fieldErrors.length === 0 ? formatStructuredViolations(problem.violations) : [];
708
708
  const parts = [
709
709
  ...title ? [title] : [],
710
- ...problemDetail ? [problemDetail] : [],
710
+ ...problemDetail2 ? [problemDetail2] : [],
711
711
  ...fieldErrors,
712
712
  ...structuredErrors
713
713
  ];
714
714
  if (parts.length > 0) {
715
- if (title && !problemDetail && fieldErrors.length === 0 && structuredErrors.length === 0) {
715
+ if (title && !problemDetail2 && fieldErrors.length === 0 && structuredErrors.length === 0) {
716
716
  parts.push("No additional error detail was returned by the API.");
717
717
  }
718
718
  msg = parts.join("\n");
@@ -1001,8 +1001,8 @@ function recordMaterialisedSkills(dir, slug2, skills, meta = {}) {
1001
1001
  }
1002
1002
 
1003
1003
  // src/setup/materialise.ts
1004
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
1005
- import { join as join4 } from "path";
1004
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
1005
+ import { join as join5 } from "path";
1006
1006
 
1007
1007
  // src/setup/config-dirs.ts
1008
1008
  import { homedir as homedir2 } from "os";
@@ -1371,6 +1371,89 @@ function resolveReferenceSet(rows, surface) {
1371
1371
  return resolveReferences(rows.systemRows, rows.personalRows, surface);
1372
1372
  }
1373
1373
 
1374
+ // src/setup/skill-orphans.ts
1375
+ import { readdirSync, readFileSync as readFileSync3, rmSync as rmSync2, statSync } from "fs";
1376
+ import { join as join4 } from "path";
1377
+ var INSTALL_MARKER = "<!-- sechroom-install:";
1378
+ var MAX_MARKER_SCAN_BYTES = 2 * 1024 * 1024;
1379
+ function bundleFromBody(body) {
1380
+ const start = body.indexOf(INSTALL_MARKER);
1381
+ if (start < 0) return null;
1382
+ const from = start + INSTALL_MARKER.length;
1383
+ const end = body.indexOf("-->", from);
1384
+ if (end < 0) return null;
1385
+ try {
1386
+ const payload = JSON.parse(body.slice(from, end).replaceAll("\\", "").trim());
1387
+ return typeof payload.Bundle === "string" && payload.Bundle ? payload.Bundle : null;
1388
+ } catch {
1389
+ return null;
1390
+ }
1391
+ }
1392
+ function readBody(path) {
1393
+ try {
1394
+ if (statSync(path).size > MAX_MARKER_SCAN_BYTES) return null;
1395
+ return readFileSync3(path, "utf8");
1396
+ } catch {
1397
+ return null;
1398
+ }
1399
+ }
1400
+ var SKILL_LAYOUT = {
1401
+ scan: (dir) => readEntries(dir).filter((e) => e.isDirectory()).map((e) => ({ name: e.name, bodyPath: join4(dir, e.name, "SKILL.md") }))
1402
+ };
1403
+ var AGENT_LAYOUT = {
1404
+ scan: (dir) => readEntries(dir).filter((e) => e.isFile() && (e.name.endsWith(".md") || e.name.endsWith(".toml"))).map((e) => ({ name: e.name, bodyPath: join4(dir, e.name) }))
1405
+ };
1406
+ function readEntries(dir) {
1407
+ try {
1408
+ return readdirSync(dir, { withFileTypes: true });
1409
+ } catch {
1410
+ return [];
1411
+ }
1412
+ }
1413
+ function scanMarked(dir, layout) {
1414
+ const marked = [];
1415
+ for (const item of layout.scan(dir)) {
1416
+ const body = readBody(item.bodyPath);
1417
+ if (body === null || !body.includes(INSTALL_MARKER)) continue;
1418
+ marked.push({ name: item.name, bundle: bundleFromBody(body) });
1419
+ }
1420
+ return marked;
1421
+ }
1422
+ function markedItems(dir, layout) {
1423
+ return scanMarked(dir, layout).map((item) => item.name);
1424
+ }
1425
+ function lockedNames(lock, exceptSlug) {
1426
+ const names = /* @__PURE__ */ new Set();
1427
+ for (const [slug2, entry] of Object.entries(lock)) {
1428
+ if (slug2 === exceptSlug) continue;
1429
+ for (const name of entry.skills ?? []) names.add(name);
1430
+ }
1431
+ return names;
1432
+ }
1433
+ function orphansAfterInstall(dir, layout, slug2, keep, ownBundles2 = /* @__PURE__ */ new Set()) {
1434
+ if (keep.length === 0) return [];
1435
+ const lock = readSkillsLock(dir);
1436
+ const protectedNames = /* @__PURE__ */ new Set([...keep, ...lockedNames(lock, slug2)]);
1437
+ const mine = scanMarked(dir, layout).filter((item) => ownBundles2.size === 0 || item.bundle === null || ownBundles2.has(item.bundle)).map((item) => item.name);
1438
+ const managed = /* @__PURE__ */ new Set([...lock[slug2]?.skills ?? [], ...mine]);
1439
+ return [...managed].filter((name) => !protectedNames.has(name)).sort();
1440
+ }
1441
+ function orphansAfterClean(dir, layout) {
1442
+ const protectedNames = lockedNames(readSkillsLock(dir));
1443
+ return markedItems(dir, layout).filter((name) => !protectedNames.has(name)).sort();
1444
+ }
1445
+ function pruneItems(dir, names) {
1446
+ const removed = [];
1447
+ for (const name of names) {
1448
+ try {
1449
+ rmSync2(join4(dir, name), { recursive: true, force: true });
1450
+ removed.push(name);
1451
+ } catch {
1452
+ }
1453
+ }
1454
+ return removed;
1455
+ }
1456
+
1374
1457
  // src/setup/materialise.ts
1375
1458
  var CLIENT_SURFACE = {
1376
1459
  claude: "claude-code",
@@ -1379,8 +1462,8 @@ var CLIENT_SURFACE = {
1379
1462
  function writeSkills(dir, skills, surface) {
1380
1463
  const written = [];
1381
1464
  for (const s of skills) {
1382
- mkdirSync3(join4(dir, s.name), { recursive: true });
1383
- writeFileSync3(join4(dir, s.name, "SKILL.md"), s.body.endsWith("\n") ? s.body : s.body + "\n");
1465
+ mkdirSync3(join5(dir, s.name), { recursive: true });
1466
+ writeFileSync3(join5(dir, s.name, "SKILL.md"), s.body.endsWith("\n") ? s.body : s.body + "\n");
1384
1467
  written.push(s.name);
1385
1468
  }
1386
1469
  if (written.length) recordMaterialisedSkills(dir, DEFAULT_SKILLS_SLUG, written, { surface });
@@ -1430,14 +1513,17 @@ function codexAgentToml(agent) {
1430
1513
  ""
1431
1514
  ].join("\n");
1432
1515
  }
1516
+ function agentFileName(name, surface) {
1517
+ return `${name}.${surface === CLIENT_SURFACE.codex ? "toml" : "md"}`;
1518
+ }
1433
1519
  function writeAgents(dir, agents, surface) {
1434
1520
  if (agents.length) mkdirSync3(dir, { recursive: true });
1435
1521
  const written = [];
1436
1522
  for (const a of agents) {
1437
1523
  const codex = surface === CLIENT_SURFACE.codex;
1438
- const file = `${a.name}.${codex ? "toml" : "md"}`;
1524
+ const file = agentFileName(a.name, surface);
1439
1525
  const body = codex ? codexAgentToml(a) : a.body.endsWith("\n") ? a.body : a.body + "\n";
1440
- writeFileSync3(join4(dir, file), body);
1526
+ writeFileSync3(join5(dir, file), body);
1441
1527
  written.push(file);
1442
1528
  }
1443
1529
  if (written.length) recordMaterialisedSkills(dir, DEFAULT_SKILLS_SLUG, written, { surface });
@@ -1446,10 +1532,10 @@ function writeAgents(dir, agents, surface) {
1446
1532
  function writeReferencesIntoSkillDirs(dir, skills, refs) {
1447
1533
  if (!refs.length || !skills.length) return [];
1448
1534
  for (const s of skills) {
1449
- const refDir = join4(dir, s.name, "references");
1535
+ const refDir = join5(dir, s.name, "references");
1450
1536
  mkdirSync3(refDir, { recursive: true });
1451
1537
  for (const r of refs) {
1452
- writeFileSync3(join4(refDir, `${r.name}.md`), r.body.endsWith("\n") ? r.body : r.body + "\n");
1538
+ writeFileSync3(join5(refDir, `${r.name}.md`), r.body.endsWith("\n") ? r.body : r.body + "\n");
1453
1539
  }
1454
1540
  }
1455
1541
  return refs.map((r) => r.name);
@@ -1459,6 +1545,8 @@ var SKILL_SPEC = {
1459
1545
  dir: skillsDir,
1460
1546
  resolve: resolveSkillSet,
1461
1547
  write: writeSkills,
1548
+ lockNames: (items) => items.map((i) => i.name),
1549
+ layout: SKILL_LAYOUT,
1462
1550
  supportsCodex: true
1463
1551
  };
1464
1552
  var AGENT_SPEC = {
@@ -1466,8 +1554,18 @@ var AGENT_SPEC = {
1466
1554
  dir: agentsDir,
1467
1555
  resolve: resolveAgentSet,
1468
1556
  write: writeAgents,
1557
+ lockNames: (items, surface) => items.map((i) => agentFileName(i.name, surface)),
1558
+ layout: AGENT_LAYOUT,
1469
1559
  supportsCodex: true
1470
1560
  };
1561
+ function ownBundles(items) {
1562
+ const bundles = /* @__PURE__ */ new Set();
1563
+ for (const item of items) {
1564
+ const bundle = bundleFromBody(item.body);
1565
+ if (bundle) bundles.add(bundle);
1566
+ }
1567
+ return bundles;
1568
+ }
1471
1569
  function scopeOf(opts) {
1472
1570
  return opts.local ? "project" : resolveScope(opts.scope);
1473
1571
  }
@@ -1547,8 +1645,16 @@ async function runInstall(spec, cmd, opts) {
1547
1645
  validateCodexWorkerDependencies(items, resolveAgentSet(rows, t.surface));
1548
1646
  }
1549
1647
  const refs = spec.kind === "skill" ? resolveReferenceSet(rows, t.surface) : [];
1550
- const written = dryRun ? items.map((i) => i.name) : spec.write(t.dir, items, t.surface);
1648
+ const orphans = orphansAfterInstall(
1649
+ t.dir,
1650
+ spec.layout,
1651
+ DEFAULT_SKILLS_SLUG,
1652
+ spec.lockNames(items, t.surface),
1653
+ ownBundles(items)
1654
+ );
1655
+ const written = dryRun ? spec.lockNames(items, t.surface) : spec.write(t.dir, items, t.surface);
1551
1656
  const refsWritten = dryRun ? refs.map((r) => r.name) : writeReferencesIntoSkillDirs(t.dir, items, refs);
1657
+ const pruned = dryRun ? orphans : pruneItems(t.dir, orphans);
1552
1658
  return {
1553
1659
  client: t.client,
1554
1660
  surface: t.surface,
@@ -1559,7 +1665,8 @@ async function runInstall(spec, cmd, opts) {
1559
1665
  items: items.map(({ name, source }) => ({ name, source })),
1560
1666
  referenceItems: refs.map(({ name, source }) => ({ name, source })),
1561
1667
  written,
1562
- refsWritten
1668
+ refsWritten,
1669
+ pruned
1563
1670
  };
1564
1671
  });
1565
1672
  if (json) return emit({ kind: spec.kind, client: selection, dryRun, targets: results }, true);
@@ -1575,6 +1682,12 @@ async function runInstall(spec, cmd, opts) {
1575
1682
  console.log(
1576
1683
  `${dryRun ? "" : style.green("\u2713 ")}${dryRun ? "would write" : "wrote"} ${r.refsWritten.length} reference(s) into each skill ${style.dim("\u2192")} ${r.dir}/<skill>/references`
1577
1684
  );
1685
+ if (r.pruned.length) {
1686
+ console.log(
1687
+ style.yellow("! ") + `${dryRun ? "would prune" : "pruned"} ${r.pruned.length} superseded ${spec.kind}(s) no longer in the bundle ${style.dim("\u2192")} ${r.dir}`
1688
+ );
1689
+ for (const name of r.pruned) console.log(` ${style.yellow("-")} ${name}`);
1690
+ }
1578
1691
  if (dryRun) for (const i of r.items) console.log(` ${i.name} ${style.dim(`[${i.source}]`)}`);
1579
1692
  }
1580
1693
  }
@@ -1598,7 +1711,7 @@ function runList(spec, cmd, opts) {
1598
1711
  const out = targets.map((t) => {
1599
1712
  const lock = readSkillsLock(t.dir);
1600
1713
  const entries = Object.entries(lock).flatMap(
1601
- ([slug2, e]) => (e.skills ?? []).map((name) => ({ slug: slug2, name, present: existsSync3(join4(t.dir, name)) }))
1714
+ ([slug2, e]) => (e.skills ?? []).map((name) => ({ slug: slug2, name, present: existsSync3(join5(t.dir, name)) }))
1602
1715
  );
1603
1716
  return { client: t.client, surface: t.surface, dir: t.dir, label: t.label, entries };
1604
1717
  });
@@ -1633,33 +1746,47 @@ function runClean(spec, cmd, opts, slugArg) {
1633
1746
  } catch (err2) {
1634
1747
  return fail(err2.message);
1635
1748
  }
1749
+ const pruneOrphans = Boolean(opts.pruneOrphans);
1636
1750
  const cleaned = [];
1637
1751
  const missing = [];
1638
1752
  for (const t of targets) {
1639
1753
  const lock = readSkillsLock(t.dir);
1640
1754
  const entry = lock[slug2];
1641
- if (!entry) {
1642
- missing.push(join4(t.dir, SKILLS_LOCK));
1755
+ if (!entry && !pruneOrphans) {
1756
+ missing.push(join5(t.dir, SKILLS_LOCK));
1643
1757
  continue;
1644
1758
  }
1645
1759
  const removed = [];
1646
- for (const name of entry.skills) {
1647
- const p = join4(t.dir, name);
1760
+ for (const name of entry?.skills ?? []) {
1761
+ const p = join5(t.dir, name);
1648
1762
  if (existsSync3(p)) {
1649
- rmSync2(p, { recursive: true, force: true });
1763
+ rmSync3(p, { recursive: true, force: true });
1650
1764
  removed.push(name);
1651
1765
  }
1652
1766
  }
1653
- delete lock[slug2];
1654
- writeSkillsLock(t.dir, lock);
1655
- cleaned.push({ client: t.client, surface: t.surface, dir: t.dir, removed });
1767
+ if (entry) {
1768
+ delete lock[slug2];
1769
+ writeSkillsLock(t.dir, lock);
1770
+ }
1771
+ const pruned = pruneOrphans ? pruneItems(t.dir, orphansAfterClean(t.dir, spec.layout)) : [];
1772
+ if (!entry && pruned.length === 0) {
1773
+ missing.push(join5(t.dir, SKILLS_LOCK));
1774
+ continue;
1775
+ }
1776
+ cleaned.push({ client: t.client, surface: t.surface, dir: t.dir, removed, pruned });
1656
1777
  }
1657
1778
  if (cleaned.length === 0) {
1658
1779
  return fail(`No materialised ${spec.kind}s recorded for '${slug2}' in ${missing.join(", ")}.`);
1659
1780
  }
1660
- if (json) return emit({ kind: spec.kind, client: selection, slug: slug2, cleaned, missing }, true);
1781
+ if (json) return emit({ kind: spec.kind, client: selection, slug: slug2, pruneOrphans, cleaned, missing }, true);
1661
1782
  for (const c of cleaned) {
1662
1783
  console.log(style.green(`Removed ${c.removed.length} ${spec.kind}(s) for ${slug2} from ${c.dir}`));
1784
+ if (c.pruned.length) {
1785
+ console.log(
1786
+ style.yellow("! ") + `pruned ${c.pruned.length} orphaned ${spec.kind}(s) with no lock entry from ${c.dir}`
1787
+ );
1788
+ for (const name of c.pruned) console.log(` ${style.yellow("-")} ${name}`);
1789
+ }
1663
1790
  }
1664
1791
  }
1665
1792
 
@@ -1683,12 +1810,12 @@ target:gpt-codex-agent), the dispatchable workers your loop skills call
1683
1810
  );
1684
1811
  agents.command("install").description("Materialise your installed subagents to disk (the already-installed bundle \u2014 no server install)").option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--dry-run", "print what would be written; write nothing").option("--client <client>", "claude, codex, or all").option("--json", "machine output").action((opts, cmd) => runInstall(AGENT_SPEC, cmd, opts));
1685
1812
  agents.command("list").description("List the subagents materialised on disk (per resolved config dir)").option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--json", "machine output").option("--client <client>", "claude, codex, or all").action((opts, cmd) => runList(AGENT_SPEC, cmd, opts));
1686
- agents.command("clean [slug]").description(`Remove subagent files materialised to disk (default ${DEFAULT_SKILLS_SLUG})`).option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--json", "machine output").option("--client <client>", "claude, codex, or all").action((slugArg, opts, cmd) => runClean(AGENT_SPEC, cmd, opts, slugArg));
1813
+ agents.command("clean [slug]").description(`Remove subagent files materialised to disk (default ${DEFAULT_SKILLS_SLUG})`).option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--prune-orphans", "also remove sechroom-installed agent files that no lock entry claims (renamed/removed upstream)").option("--json", "machine output").option("--client <client>", "claude, codex, or all").action((slugArg, opts, cmd) => runClean(AGENT_SPEC, cmd, opts, slugArg));
1687
1814
  }
1688
1815
 
1689
1816
  // src/commands/channel.ts
1690
- import { existsSync as existsSync9, mkdirSync as mkdirSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
1691
- import { dirname as dirname9, join as join12 } from "path";
1817
+ import { existsSync as existsSync9, mkdirSync as mkdirSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
1818
+ import { dirname as dirname9, join as join13 } from "path";
1692
1819
  import {
1693
1820
  HttpTransportType,
1694
1821
  HubConnectionBuilder
@@ -1872,6 +1999,44 @@ async function runDriverLoop(ports, options) {
1872
1999
  }
1873
2000
  throw error;
1874
2001
  }
2002
+ if (task.reviewExecution) {
2003
+ const binding = ports.verifyTaskBinding ? await ports.verifyTaskBinding(task) : {
2004
+ ok: false,
2005
+ reason: "review-execution task has no driver-side target verifier; refusing before turn"
2006
+ };
2007
+ if (!binding.ok) {
2008
+ const evidence = binding.evidence ? `
2009
+
2010
+ Observed binding:
2011
+ ${binding.evidence}` : "";
2012
+ const text2 = `Review target preflight refused before turn. ${binding.reason ?? "repository/head mismatch"}${evidence}`;
2013
+ ports.log(
2014
+ `REVIEW TARGET REFUSED ${claim.memoryId}: ${binding.reason ?? "mismatch"}`
2015
+ );
2016
+ try {
2017
+ const done = await ports.completeLease(
2018
+ claim,
2019
+ "blocked",
2020
+ text2,
2021
+ `${task.title} \u2014 review target preflight refused`
2022
+ );
2023
+ summary.completed++;
2024
+ ports.log(
2025
+ `completed ${claim.memoryId} verdict:blocked \u2192 ${done.completionMemoryId ?? done.outcome}`
2026
+ );
2027
+ } catch (e) {
2028
+ if (e instanceof AuthExpiredError) throw e;
2029
+ summary.abandoned++;
2030
+ ports.log(
2031
+ `COMPLETE REJECTED for ${claim.memoryId} (${String(e)}) \u2014 refused review target will re-offer; investigate the lease gap.`
2032
+ );
2033
+ }
2034
+ if (options.once) break;
2035
+ continue;
2036
+ }
2037
+ if (binding.evidence)
2038
+ task = { ...task, reviewTargetEvidence: binding.evidence };
2039
+ }
1875
2040
  const stopHeartbeat = ports.startLeaseHeartbeat(claim);
1876
2041
  let result;
1877
2042
  try {
@@ -1956,15 +2121,19 @@ Delivery: FAILED unexpectedly (${String(e)}) \u2014 changes remain in the execut
1956
2121
  return summary;
1957
2122
  }
1958
2123
  function closeoutText(task, result) {
2124
+ const targetEvidence = task.reviewTargetEvidence ? `
2125
+
2126
+ Review target preflight evidence:
2127
+ ${task.reviewTargetEvidence}` : "";
1959
2128
  if (!result.packet)
1960
2129
  return `Driven codex run ended without a sechroom_closeout packet (soft-fail). Last agent message:
1961
2130
 
1962
- ${result.lastAgentMessage || "(none)"}`;
2131
+ ${result.lastAgentMessage || "(none)"}${targetEvidence}`;
1963
2132
  const evidence = result.packet.evidence?.length ? `
1964
2133
 
1965
2134
  Evidence:
1966
2135
  ${result.packet.evidence.map((e) => `- ${e}`).join("\n")}` : "";
1967
- return `${result.packet.summary}${evidence}
2136
+ return `${result.packet.summary}${evidence}${targetEvidence}
1968
2137
 
1969
2138
  (terminal_status: ${result.packet.terminal_status}; driven by sechroom executor run.)`;
1970
2139
  }
@@ -2006,7 +2175,8 @@ async function materializeClaimedTask(request, memoryId) {
2006
2175
  }
2007
2176
  return {
2008
2177
  title: card.title ?? memoryId,
2009
- text: assemblePrompt(card, components)
2178
+ text: assemblePrompt(card, components),
2179
+ reviewExecution: card.reviewExecution ?? void 0
2010
2180
  };
2011
2181
  }
2012
2182
  function validatePackage(value, expectedSlug, expectedVersion) {
@@ -2040,6 +2210,11 @@ ${card.task.boundaries}`,
2040
2210
  `## Closeout
2041
2211
  ${card.task.closeout}`
2042
2212
  ];
2213
+ if (card.reviewExecution)
2214
+ sections.push(
2215
+ `## Review target
2216
+ ${renderReviewExecution(card.reviewExecution)}`
2217
+ );
2043
2218
  if (components.length > 0)
2044
2219
  sections.push(
2045
2220
  `## Task context
@@ -2047,6 +2222,17 @@ ${components.map(renderComponent).join("\n\n")}`
2047
2222
  );
2048
2223
  return sections.join("\n\n");
2049
2224
  }
2225
+ function renderReviewExecution(target) {
2226
+ return [
2227
+ `kind: ${target.kind}`,
2228
+ `round: ${target.round}`,
2229
+ `repository: ${target.repository}`,
2230
+ `base commit: ${target.baseCommit}`,
2231
+ `head commit: ${target.headCommit}`,
2232
+ target.preferredInstanceKey ? `preferred instance: ${target.preferredInstanceKey}` : void 0,
2233
+ target.preferredLaneId ? `preferred lane: ${target.preferredLaneId}` : void 0
2234
+ ].filter((line) => line !== void 0).join("\n");
2235
+ }
2050
2236
  function renderComponent(component) {
2051
2237
  return [
2052
2238
  `<!-- sechroom-task-context component=${JSON.stringify(component.slug)} id=${JSON.stringify(component.sourceId)} sourceVersion=${component.sourceVersion} -->`,
@@ -2057,21 +2243,22 @@ function renderComponent(component) {
2057
2243
 
2058
2244
  // src/commands/executor.ts
2059
2245
  import { execFileSync } from "child_process";
2060
- import { existsSync as existsSync8, mkdirSync as mkdirSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "fs";
2061
- import { dirname as dirname8, join as join11 } from "path";
2246
+ import { randomUUID } from "crypto";
2247
+ import { existsSync as existsSync8, mkdirSync as mkdirSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
2248
+ import { dirname as dirname8, join as join12 } from "path";
2062
2249
 
2063
2250
  // src/sem.ts
2064
- import { dirname as dirname2, join as join5 } from "path";
2065
- import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync4 } from "fs";
2066
- var SEM_FILE = join5(".sechroom", "lane.json");
2251
+ import { dirname as dirname2, join as join6 } from "path";
2252
+ import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
2253
+ var SEM_FILE = join6(".sechroom", "lane.json");
2067
2254
  var STATE_DIR_NAME2 = ".sechroom";
2068
2255
  function localSemPath(cwd = process.cwd()) {
2069
- return join5(cwd, SEM_FILE);
2256
+ return join6(cwd, SEM_FILE);
2070
2257
  }
2071
2258
  function resolveSemPathForRead(start = process.cwd()) {
2072
2259
  let dir = start;
2073
2260
  while (true) {
2074
- const candidate = join5(dir, SEM_FILE);
2261
+ const candidate = join6(dir, SEM_FILE);
2075
2262
  if (existsSync4(candidate)) return candidate;
2076
2263
  const parent = dirname2(dir);
2077
2264
  if (parent === dir) return void 0;
@@ -2083,7 +2270,7 @@ function applyWorktreeLaneSuffix(lane, start = process.cwd()) {
2083
2270
  let dir = start;
2084
2271
  let gitPath;
2085
2272
  for (; ; ) {
2086
- const candidate = join5(dir, ".git");
2273
+ const candidate = join6(dir, ".git");
2087
2274
  if (existsSync4(candidate)) {
2088
2275
  gitPath = candidate;
2089
2276
  break;
@@ -2092,14 +2279,14 @@ function applyWorktreeLaneSuffix(lane, start = process.cwd()) {
2092
2279
  if (parent === dir) break;
2093
2280
  dir = parent;
2094
2281
  }
2095
- if (!gitPath || statSync(gitPath).isDirectory()) return lane;
2096
- const gitFile = readFileSync3(gitPath, "utf8");
2282
+ if (!gitPath || statSync2(gitPath).isDirectory()) return lane;
2283
+ const gitFile = readFileSync4(gitPath, "utf8");
2097
2284
  const common = gitFile.trim().match(/^gitdir:\s*(.+)\/worktrees\/[^/\s]+\s*$/);
2098
2285
  if (!common) return lane;
2099
- const worktreesDir = join5(common[1], "worktrees");
2100
- const siblings = readdirSync(worktreesDir).filter((n) => {
2286
+ const worktreesDir = join6(common[1], "worktrees");
2287
+ const siblings = readdirSync2(worktreesDir).filter((n) => {
2101
2288
  try {
2102
- return statSync(join5(worktreesDir, n)).isDirectory();
2289
+ return statSync2(join6(worktreesDir, n)).isDirectory();
2103
2290
  } catch {
2104
2291
  return false;
2105
2292
  }
@@ -2121,10 +2308,10 @@ function serializeSem(values) {
2121
2308
  function readSem(path) {
2122
2309
  const p = path ?? resolveSemPathForRead();
2123
2310
  if (!p || !existsSync4(p)) return void 0;
2124
- return { path: p, values: parseLaneJson(readFileSync3(p, "utf8")) };
2311
+ return { path: p, values: parseLaneJson(readFileSync4(p, "utf8")) };
2125
2312
  }
2126
2313
  function readLocalSemValues(cwd = process.cwd()) {
2127
- const next = join5(cwd, SEM_FILE);
2314
+ const next = join6(cwd, SEM_FILE);
2128
2315
  if (existsSync4(next)) return readSem(next)?.values ?? {};
2129
2316
  return {};
2130
2317
  }
@@ -2170,7 +2357,7 @@ var CONTINUITY_SCAFFOLD = JSON.stringify(
2170
2357
  ) + "\n";
2171
2358
  function ensureContinuityScaffold(semPath) {
2172
2359
  try {
2173
- const target = join5(dirname2(semPath), CONTINUITY_FILE_NAME);
2360
+ const target = join6(dirname2(semPath), CONTINUITY_FILE_NAME);
2174
2361
  if (existsSync4(target)) return;
2175
2362
  writeFileSync4(target, CONTINUITY_SCAFFOLD);
2176
2363
  } catch {
@@ -2185,7 +2372,7 @@ function ignoresSem(content) {
2185
2372
  function inGitRepo(startDir) {
2186
2373
  let dir = startDir;
2187
2374
  for (; ; ) {
2188
- if (existsSync4(join5(dir, ".git"))) return true;
2375
+ if (existsSync4(join6(dir, ".git"))) return true;
2189
2376
  const parent = dirname2(dir);
2190
2377
  if (parent === dir) return false;
2191
2378
  dir = parent;
@@ -2194,11 +2381,11 @@ function inGitRepo(startDir) {
2194
2381
  function resolveGitignoreTarget(startDir) {
2195
2382
  let dir = startDir;
2196
2383
  for (; ; ) {
2197
- const gi = join5(dir, ".gitignore");
2384
+ const gi = join6(dir, ".gitignore");
2198
2385
  if (existsSync4(gi)) return { path: gi, exists: true };
2199
2386
  const parent = dirname2(dir);
2200
- if (existsSync4(join5(dir, ".git")) || parent === dir) {
2201
- return { path: join5(startDir, ".gitignore"), exists: false };
2387
+ if (existsSync4(join6(dir, ".git")) || parent === dir) {
2388
+ return { path: join6(startDir, ".gitignore"), exists: false };
2202
2389
  }
2203
2390
  dir = parent;
2204
2391
  }
@@ -2209,7 +2396,7 @@ function ensureSemIgnored(semPath) {
2209
2396
  if (!inGitRepo(checkoutDir)) return;
2210
2397
  const target = resolveGitignoreTarget(checkoutDir);
2211
2398
  if (target.exists) {
2212
- const content = readFileSync3(target.path, "utf8");
2399
+ const content = readFileSync4(target.path, "utf8");
2213
2400
  if (ignoresSem(content)) return;
2214
2401
  const sep2 = content.length === 0 || content.endsWith("\n") ? "" : "\n";
2215
2402
  appendFileSync(target.path, `${sep2}${STATE_DIR_IGNORE}
@@ -2223,7 +2410,7 @@ function ensureSemIgnored(semPath) {
2223
2410
  }
2224
2411
 
2225
2412
  // src/commands/executor-run.ts
2226
- import { join as join10, resolve as resolve3 } from "path";
2413
+ import { join as join11, resolve as resolve3 } from "path";
2227
2414
 
2228
2415
  // src/executor-run/usage.ts
2229
2416
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5 } from "fs";
@@ -2842,7 +3029,9 @@ var CodexAppServer = class {
2842
3029
  modelId,
2843
3030
  executorInstanceId: this.options.executorInstanceId,
2844
3031
  leaseId: telemetry?.leaseId ?? null,
2845
- turnId: turnId || null
3032
+ turnId: turnId || null,
3033
+ originatingReviewId: telemetry?.originatingReviewId ?? null,
3034
+ reviewRound: telemetry?.reviewRound ?? null
2846
3035
  });
2847
3036
  this.onNotification = (msg) => {
2848
3037
  if (msg.method === "thread/tokenUsage/updated") {
@@ -3366,8 +3555,8 @@ import {
3366
3555
  closeSync,
3367
3556
  mkdirSync as mkdirSync6,
3368
3557
  openSync,
3369
- readFileSync as readFileSync4,
3370
- rmSync as rmSync3,
3558
+ readFileSync as readFileSync5,
3559
+ rmSync as rmSync4,
3371
3560
  writeFileSync as writeFileSync5
3372
3561
  } from "fs";
3373
3562
  import { readFile } from "fs/promises";
@@ -3376,7 +3565,7 @@ import {
3376
3565
  createServer as createServer2
3377
3566
  } from "net";
3378
3567
  import { tmpdir } from "os";
3379
- import { dirname as dirname4, join as join6, resolve } from "path";
3568
+ import { dirname as dirname4, join as join7, resolve } from "path";
3380
3569
  var DEFAULT_RESTART_POLICY = {
3381
3570
  enabled: true,
3382
3571
  maxRetries: 10,
@@ -4233,7 +4422,7 @@ function isProcessAlive(pid) {
4233
4422
  function readPidFile(path) {
4234
4423
  let raw;
4235
4424
  try {
4236
- raw = readFileSync4(path, "utf8");
4425
+ raw = readFileSync5(path, "utf8");
4237
4426
  } catch (error) {
4238
4427
  if (error.code === "ENOENT") return void 0;
4239
4428
  throw error;
@@ -4247,7 +4436,7 @@ function claimPidFile(path, options = {}) {
4247
4436
  });
4248
4437
  let raw;
4249
4438
  try {
4250
- raw = readFileSync4(path, "utf8");
4439
+ raw = readFileSync5(path, "utf8");
4251
4440
  } catch (error) {
4252
4441
  if (error.code === "ENOENT") return;
4253
4442
  throw error;
@@ -4261,7 +4450,7 @@ function claimPidFile(path, options = {}) {
4261
4450
  log(
4262
4451
  `stale pid-file ${path} (${valid ? `pid ${pid}` : "unparseable"} not running) \u2014 reclaiming`
4263
4452
  );
4264
- rmSync3(path, { force: true });
4453
+ rmSync4(path, { force: true });
4265
4454
  }
4266
4455
  function writePidFile(path, pid) {
4267
4456
  mkdirSync6(dirname4(path), { recursive: true });
@@ -4271,7 +4460,7 @@ function writePidFile(path, pid) {
4271
4460
  function removePidFileIfOwned(path, pid, log) {
4272
4461
  if (readPidFile(path) !== pid) return;
4273
4462
  try {
4274
- rmSync3(path, { force: true });
4463
+ rmSync4(path, { force: true });
4275
4464
  } catch (error) {
4276
4465
  log?.(`pid-file ${path} cleanup failed: ${String(error)}`);
4277
4466
  }
@@ -4321,12 +4510,12 @@ function launchDetachedSupervisor(options) {
4321
4510
  }
4322
4511
  function controlSocketPath(pidFile) {
4323
4512
  const hash = createHash2("sha256").update(resolve(pidFile)).digest("hex").slice(0, 16);
4324
- return join6(tmpdir(), `sechroom-fleet-${hash}.sock`);
4513
+ return join7(tmpdir(), `sechroom-fleet-${hash}.sock`);
4325
4514
  }
4326
4515
  function startControlServer(socketPath, handlers, options = {}) {
4327
4516
  const log = options.log ?? (() => {
4328
4517
  });
4329
- rmSync3(socketPath, { force: true });
4518
+ rmSync4(socketPath, { force: true });
4330
4519
  const dispatch = (req) => {
4331
4520
  switch (req.command) {
4332
4521
  case "status":
@@ -4388,7 +4577,7 @@ function startControlServer(socketPath, handlers, options = {}) {
4388
4577
  socketPath,
4389
4578
  close: () => new Promise((res) => {
4390
4579
  server.close(() => {
4391
- rmSync3(socketPath, { force: true });
4580
+ rmSync4(socketPath, { force: true });
4392
4581
  res();
4393
4582
  });
4394
4583
  })
@@ -4467,29 +4656,88 @@ function sendControlCommand(socketPath, request, options = {}) {
4467
4656
  });
4468
4657
  }
4469
4658
 
4659
+ // src/executor-run/review-target.ts
4660
+ async function verifyReviewTarget(git, task) {
4661
+ const target = task.reviewExecution;
4662
+ if (!target) return { ok: true };
4663
+ const [remote, head] = await Promise.all([
4664
+ git("git", ["remote", "get-url", "origin"]),
4665
+ git("git", ["rev-parse", "HEAD"])
4666
+ ]);
4667
+ const observedRepository = remote.ok ? canonicalRepository(remote.stdout) : void 0;
4668
+ const observedHead = head.ok ? head.stdout.trim().toLowerCase() || void 0 : void 0;
4669
+ const failures = [];
4670
+ if (observedRepository !== target.repository)
4671
+ failures.push(
4672
+ `repository required ${target.repository}, observed ${observedRepository ?? "(unavailable)"}`
4673
+ );
4674
+ if (observedHead !== target.headCommit)
4675
+ failures.push(
4676
+ `head required ${target.headCommit}, observed ${observedHead ?? "(unavailable)"}`
4677
+ );
4678
+ const evidence = [
4679
+ `required repository: ${target.repository}`,
4680
+ `observed repository: ${observedRepository ?? "(unavailable)"}`,
4681
+ `required base: ${target.baseCommit}`,
4682
+ `required head: ${target.headCommit}`,
4683
+ `observed head: ${observedHead ?? "(unavailable)"}`
4684
+ ].join("\n");
4685
+ return {
4686
+ ok: failures.length === 0,
4687
+ reason: failures.length > 0 ? `review target mismatch before turn: ${failures.join("; ")}` : void 0,
4688
+ evidence
4689
+ };
4690
+ }
4691
+ function canonicalRepository(remote) {
4692
+ let value = remote.trim().split(/\r?\n/, 1)[0] ?? "";
4693
+ if (!value) return void 0;
4694
+ if (value.startsWith("git@")) {
4695
+ const separator = value.indexOf(":");
4696
+ if (separator < 0) return void 0;
4697
+ value = value.slice(separator + 1);
4698
+ } else {
4699
+ try {
4700
+ const parsed = new URL(value);
4701
+ if (parsed.protocol === "file:") return void 0;
4702
+ value = parsed.pathname;
4703
+ } catch {
4704
+ return void 0;
4705
+ }
4706
+ }
4707
+ value = value.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/i, "");
4708
+ const parts = value.split("/").filter(Boolean);
4709
+ if (parts.length < 2) return void 0;
4710
+ const owner = parts.at(-2);
4711
+ const repository = parts.at(-1);
4712
+ if (!owner || !repository) return void 0;
4713
+ const part = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
4714
+ if (!part.test(owner) || !part.test(repository)) return void 0;
4715
+ return `${owner}/${repository}`.toLowerCase();
4716
+ }
4717
+
4470
4718
  // src/commands/telemetry.ts
4471
4719
  import {
4472
4720
  existsSync as existsSync7,
4473
4721
  mkdirSync as mkdirSync8,
4474
- readFileSync as readFileSync6,
4475
- rmSync as rmSync4,
4722
+ readFileSync as readFileSync7,
4723
+ rmSync as rmSync5,
4476
4724
  writeFileSync as writeFileSync7
4477
4725
  } from "fs";
4478
4726
  import { homedir as homedir4 } from "os";
4479
- import { dirname as dirname7, join as join9, parse, resolve as resolve2, sep } from "path";
4727
+ import { dirname as dirname7, join as join10, parse, resolve as resolve2, sep } from "path";
4480
4728
 
4481
4729
  // src/commands/hook-install.ts
4482
- import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
4483
- import { delimiter, dirname as dirname6, join as join8 } from "path";
4730
+ import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
4731
+ import { delimiter, dirname as dirname6, join as join9 } from "path";
4484
4732
 
4485
4733
  // src/setup/clients.ts
4486
4734
  import { existsSync as existsSync5 } from "fs";
4487
4735
  import { homedir as homedir3 } from "os";
4488
- import { dirname as dirname5, join as join7 } from "path";
4736
+ import { dirname as dirname5, join as join8 } from "path";
4489
4737
  function claudeDesktopConfigPath(home) {
4490
4738
  switch (process.platform) {
4491
4739
  case "darwin":
4492
- return join7(
4740
+ return join8(
4493
4741
  home,
4494
4742
  "Library",
4495
4743
  "Application Support",
@@ -4497,19 +4745,20 @@ function claudeDesktopConfigPath(home) {
4497
4745
  "claude_desktop_config.json"
4498
4746
  );
4499
4747
  case "win32":
4500
- return join7(
4501
- process.env.APPDATA ?? join7(home, "AppData", "Roaming"),
4748
+ return join8(
4749
+ process.env.APPDATA ?? join8(home, "AppData", "Roaming"),
4502
4750
  "Claude",
4503
4751
  "claude_desktop_config.json"
4504
4752
  );
4505
4753
  default:
4506
- return join7(home, ".config", "Claude", "claude_desktop_config.json");
4754
+ return join8(home, ".config", "Claude", "claude_desktop_config.json");
4507
4755
  }
4508
4756
  }
4509
4757
  function clientTargets(cwd, opts = {}) {
4510
4758
  const home = homedir3();
4511
- const claudeDir = opts.claudeDir ?? join7(home, ".claude");
4512
- const codexHome = opts.codexHome === void 0 ? join7(home, ".codex") : opts.codexHome;
4759
+ const claudeDir = opts.claudeDir ?? join8(home, ".claude");
4760
+ const codexHome = opts.codexHome === void 0 ? join8(home, ".codex") : opts.codexHome;
4761
+ const codexConfigPath = opts.codexScope === "project" ? join8(cwd, ".codex", "config.toml") : codexHome ? join8(codexHome, "config.toml") : null;
4513
4762
  return {
4514
4763
  "claude-code": {
4515
4764
  key: "claude-code",
@@ -4517,10 +4766,10 @@ function clientTargets(cwd, opts = {}) {
4517
4766
  mcp: {
4518
4767
  surfaceKey: "claude-code",
4519
4768
  sectionType: SectionType.McpConfig,
4520
- path: join7(cwd, ".mcp.json"),
4769
+ path: join8(cwd, ".mcp.json"),
4521
4770
  format: "json"
4522
4771
  },
4523
- instruction: { surfaceKey: "claude-code", path: join7(cwd, "CLAUDE.md") }
4772
+ instruction: { surfaceKey: "claude-code", path: join8(cwd, "CLAUDE.md") }
4524
4773
  },
4525
4774
  "claude-desktop": {
4526
4775
  key: "claude-desktop",
@@ -4533,19 +4782,19 @@ function clientTargets(cwd, opts = {}) {
4533
4782
  },
4534
4783
  instruction: {
4535
4784
  surfaceKey: "claude-desktop",
4536
- path: join7(claudeDir, "CLAUDE.md")
4785
+ path: join8(claudeDir, "CLAUDE.md")
4537
4786
  }
4538
4787
  },
4539
4788
  codex: {
4540
4789
  key: "codex",
4541
4790
  label: "Codex CLI",
4542
- mcp: codexHome ? {
4791
+ mcp: codexConfigPath ? {
4543
4792
  surfaceKey: "chatgpt",
4544
4793
  sectionType: SectionType.McpConfigToml,
4545
- path: join7(codexHome, "config.toml"),
4794
+ path: codexConfigPath,
4546
4795
  format: "toml"
4547
4796
  } : null,
4548
- instruction: { surfaceKey: "chatgpt", path: join7(cwd, "AGENTS.md") }
4797
+ instruction: { surfaceKey: "chatgpt", path: join8(cwd, "AGENTS.md") }
4549
4798
  },
4550
4799
  cursor: {
4551
4800
  key: "cursor",
@@ -4553,10 +4802,10 @@ function clientTargets(cwd, opts = {}) {
4553
4802
  mcp: {
4554
4803
  surfaceKey: "claude-code",
4555
4804
  sectionType: SectionType.McpConfig,
4556
- path: join7(cwd, ".cursor", "mcp.json"),
4805
+ path: join8(cwd, ".cursor", "mcp.json"),
4557
4806
  format: "json"
4558
4807
  },
4559
- instruction: { surfaceKey: "chatgpt", path: join7(cwd, "AGENTS.md") }
4808
+ instruction: { surfaceKey: "chatgpt", path: join8(cwd, "AGENTS.md") }
4560
4809
  },
4561
4810
  antigravity: {
4562
4811
  key: "antigravity",
@@ -4570,10 +4819,10 @@ function clientTargets(cwd, opts = {}) {
4570
4819
  mcp: {
4571
4820
  surfaceKey: "antigravity",
4572
4821
  sectionType: SectionType.McpConfig,
4573
- path: join7(home, ".gemini", "config", "mcp_config.json"),
4822
+ path: join8(home, ".gemini", "config", "mcp_config.json"),
4574
4823
  format: "json"
4575
4824
  },
4576
- instruction: { surfaceKey: "antigravity", path: join7(cwd, "AGENTS.md") }
4825
+ instruction: { surfaceKey: "antigravity", path: join8(cwd, "AGENTS.md") }
4577
4826
  }
4578
4827
  };
4579
4828
  }
@@ -4593,9 +4842,9 @@ function detectInstalledClients(cwd) {
4593
4842
  if (existsSync5(dirname5(claudeDesktopConfigPath(home))))
4594
4843
  detected.push("claude-desktop");
4595
4844
  if (resolveCodexHomes({}).some((d) => existsSync5(d))) detected.push("codex");
4596
- if (existsSync5(join7(home, ".cursor")) || existsSync5(join7(cwd, ".cursor")))
4845
+ if (existsSync5(join8(home, ".cursor")) || existsSync5(join8(cwd, ".cursor")))
4597
4846
  detected.push("cursor");
4598
- if (existsSync5(join7(home, ".gemini"))) detected.push("antigravity");
4847
+ if (existsSync5(join8(home, ".gemini"))) detected.push("antigravity");
4599
4848
  return detected;
4600
4849
  }
4601
4850
 
@@ -4639,12 +4888,12 @@ function mergeHooks(config2, commands) {
4639
4888
  }
4640
4889
  function readJsonConfig2(path) {
4641
4890
  if (!existsSync6(path)) return {};
4642
- const raw = readFileSync5(path, "utf8");
4891
+ const raw = readFileSync6(path, "utf8");
4643
4892
  if (!raw.trim()) return {};
4644
4893
  return JSON.parse(raw);
4645
4894
  }
4646
4895
  function installHooksJson(path, commands, dryRun) {
4647
- const existed = existsSync6(path) && readFileSync5(path, "utf8").trim().length > 0;
4896
+ const existed = existsSync6(path) && readFileSync6(path, "utf8").trim().length > 0;
4648
4897
  const config2 = readJsonConfig2(path);
4649
4898
  const added = mergeHooks(config2, commands);
4650
4899
  if (added === 0 && existed) return { path, status: "current" };
@@ -4655,12 +4904,12 @@ function installHooksJson(path, commands, dryRun) {
4655
4904
  return { path, status: existed ? "merged" : "created" };
4656
4905
  }
4657
4906
  function installClaudeCommands(claudeDir, commands, dryRun) {
4658
- return installHooksJson(join8(claudeDir, "settings.json"), commands, dryRun);
4907
+ return installHooksJson(join9(claudeDir, "settings.json"), commands, dryRun);
4659
4908
  }
4660
4909
  function installCodexCommands(codexHome, commands, dryRun) {
4661
4910
  return [
4662
- installHooksJson(join8(codexHome, "hooks.json"), commands, dryRun),
4663
- installCodexFeatureFlag(join8(codexHome, "config.toml"), dryRun)
4911
+ installHooksJson(join9(codexHome, "hooks.json"), commands, dryRun),
4912
+ installCodexFeatureFlag(join9(codexHome, "config.toml"), dryRun)
4664
4913
  ];
4665
4914
  }
4666
4915
  function ensureCodexFeaturesHooks(content) {
@@ -4685,7 +4934,7 @@ function ensureCodexFeaturesHooks(content) {
4685
4934
  }
4686
4935
  function installCodexFeatureFlag(path, dryRun) {
4687
4936
  const existed = existsSync6(path);
4688
- const content = existed ? readFileSync5(path, "utf8") : "";
4937
+ const content = existed ? readFileSync6(path, "utf8") : "";
4689
4938
  const { next, changed } = ensureCodexFeaturesHooks(content);
4690
4939
  if (!changed) return { path, status: "current" };
4691
4940
  if (!dryRun) {
@@ -4715,11 +4964,11 @@ function installHookSurfaces(surfaces, opts) {
4715
4964
  const out = [];
4716
4965
  for (const surface of surfaces) {
4717
4966
  if (surface === "claude") {
4718
- const path = join8(opts.claudeDir, "settings.json");
4967
+ const path = join9(opts.claudeDir, "settings.json");
4719
4968
  out.push({ surface, results: [installHooksJson(path, CLAUDE_HOOK_COMMANDS, opts.dryRun)] });
4720
4969
  } else {
4721
- const hooksJson = installHooksJson(join8(opts.codexHome, "hooks.json"), CODEX_HOOK_COMMANDS, opts.dryRun);
4722
- const featureFlag = installCodexFeatureFlag(join8(opts.codexHome, "config.toml"), opts.dryRun);
4970
+ const hooksJson = installHooksJson(join9(opts.codexHome, "hooks.json"), CODEX_HOOK_COMMANDS, opts.dryRun);
4971
+ const featureFlag = installCodexFeatureFlag(join9(opts.codexHome, "config.toml"), opts.dryRun);
4723
4972
  out.push({ surface, results: [hooksJson, featureFlag] });
4724
4973
  }
4725
4974
  }
@@ -4739,7 +4988,7 @@ function isSechroomOnPath() {
4739
4988
  for (const dir of pathEnv.split(delimiter)) {
4740
4989
  if (!dir) continue;
4741
4990
  for (const name of names) {
4742
- if (existsSync6(join8(dir, name))) return true;
4991
+ if (existsSync6(join9(dir, name))) return true;
4743
4992
  }
4744
4993
  }
4745
4994
  return false;
@@ -4784,6 +5033,13 @@ function registerTelemetry(program2) {
4784
5033
  ).option("--text <s>", "Raw/parsed payload text").option("--approval <state>", "Approval gate state (approval events)").option(
4785
5034
  "--verdict <v>",
4786
5035
  "Typed verdict (terminal events): pass | soft-fail | plan-invalid | blocked"
5036
+ ).option(
5037
+ "--originating-review-id <id>",
5038
+ "Existing review entity id that caused this rework; never creates a review"
5039
+ ).option(
5040
+ "--review-round <n>",
5041
+ "Review round for the originating review",
5042
+ parseIntOpt
4787
5043
  ).action(async (opts, cmd) => {
4788
5044
  const json = Boolean(cmd.optsWithGlobals().json);
4789
5045
  const cfg = resolveConfig(cmd.optsWithGlobals());
@@ -4796,7 +5052,9 @@ function registerTelemetry(program2) {
4796
5052
  contextWindow: opts.contextWindow ?? null,
4797
5053
  text: opts.text ?? null,
4798
5054
  approvalState: opts.approval ?? null,
4799
- verdict: opts.verdict ?? null
5055
+ verdict: opts.verdict ?? null,
5056
+ originatingReviewId: opts.originatingReviewId,
5057
+ reviewRound: opts.reviewRound
4800
5058
  };
4801
5059
  let body;
4802
5060
  try {
@@ -4837,14 +5095,23 @@ function registerTelemetry(program2) {
4837
5095
  ).requiredOption(
4838
5096
  "--decomposition <id>",
4839
5097
  "Decomposition id this session executes"
4840
- ).requiredOption("--task <id>", "Task id this session executes").action((opts, cmd) => {
5098
+ ).requiredOption("--task <id>", "Task id this session executes").option(
5099
+ "--originating-review-id <id>",
5100
+ "Existing review entity id driving this rework; never creates a review"
5101
+ ).option(
5102
+ "--review-round <n>",
5103
+ "Review round for the originating review",
5104
+ parseIntOpt
5105
+ ).action((opts, cmd) => {
4841
5106
  const json = Boolean(cmd.optsWithGlobals().json);
4842
- const dir = join9(process.cwd(), ".sechroom");
5107
+ const dir = join10(process.cwd(), ".sechroom");
4843
5108
  mkdirSync8(dir, { recursive: true });
4844
- const path = join9(dir, BINDING_FILE);
5109
+ const path = join10(dir, BINDING_FILE);
4845
5110
  const binding = {
4846
5111
  decompositionId: opts.decomposition,
4847
- taskId: opts.task
5112
+ taskId: opts.task,
5113
+ originatingReviewId: opts.originatingReviewId,
5114
+ reviewRound: opts.reviewRound
4848
5115
  };
4849
5116
  writeFileSync7(path, JSON.stringify(binding, null, 2) + "\n");
4850
5117
  ensureStateDirIgnored(process.cwd());
@@ -4861,9 +5128,9 @@ function registerTelemetry(program2) {
4861
5128
  });
4862
5129
  telemetry.command("unbind").description("Clear this checkout's telemetry binding").action((_opts, cmd) => {
4863
5130
  const json = Boolean(cmd.optsWithGlobals().json);
4864
- const path = join9(process.cwd(), ".sechroom", BINDING_FILE);
5131
+ const path = join10(process.cwd(), ".sechroom", BINDING_FILE);
4865
5132
  const existed = existsSync7(path);
4866
- if (existed) rmSync4(path);
5133
+ if (existed) rmSync5(path);
4867
5134
  if (json) emit({ unbound: existed, path }, true);
4868
5135
  else
4869
5136
  process.stdout.write(
@@ -4889,7 +5156,9 @@ function registerTelemetry(program2) {
4889
5156
  input,
4890
5157
  usage,
4891
5158
  binding.taskId,
4892
- configRoot
5159
+ configRoot,
5160
+ binding.originatingReviewId ?? null,
5161
+ binding.reviewRound ?? null
4893
5162
  );
4894
5163
  if (events.length === 0) return process.exit(0);
4895
5164
  const taskId = await taskIdForHook(cfg, binding);
@@ -4983,11 +5252,11 @@ async function postTelemetry(cfg, decompositionId, events) {
4983
5252
  function findBinding(start) {
4984
5253
  let dir = start;
4985
5254
  for (; ; ) {
4986
- const path = join9(dir, ".sechroom", BINDING_FILE);
5255
+ const path = join10(dir, ".sechroom", BINDING_FILE);
4987
5256
  if (existsSync7(path)) {
4988
5257
  try {
4989
5258
  const b = JSON.parse(
4990
- readFileSync6(path, "utf8")
5259
+ readFileSync7(path, "utf8")
4991
5260
  );
4992
5261
  if (b.decompositionId && b.taskId)
4993
5262
  return {
@@ -4995,6 +5264,8 @@ function findBinding(start) {
4995
5264
  taskId: b.taskId,
4996
5265
  activeTaskCheckedAt: b.activeTaskCheckedAt,
4997
5266
  lifecycleWarning: b.lifecycleWarning,
5267
+ originatingReviewId: b.originatingReviewId,
5268
+ reviewRound: b.reviewRound,
4998
5269
  path
4999
5270
  };
5000
5271
  if (b.decompositionId && b.invalidatedTaskId && b.invalidatedReason === "terminal-task")
@@ -5002,6 +5273,8 @@ function findBinding(start) {
5002
5273
  decompositionId: b.decompositionId,
5003
5274
  invalidatedTaskId: b.invalidatedTaskId,
5004
5275
  invalidatedReason: b.invalidatedReason,
5276
+ originatingReviewId: b.originatingReviewId,
5277
+ reviewRound: b.reviewRound,
5005
5278
  path
5006
5279
  };
5007
5280
  } catch {
@@ -5064,7 +5337,7 @@ async function getTaskLifecycleVerdict(cfg, binding) {
5064
5337
  function markLifecycleWarningIfCurrent(binding) {
5065
5338
  try {
5066
5339
  const current = JSON.parse(
5067
- readFileSync6(binding.path, "utf8")
5340
+ readFileSync7(binding.path, "utf8")
5068
5341
  );
5069
5342
  if (current.decompositionId !== binding.decompositionId || current.taskId !== binding.taskId)
5070
5343
  return;
@@ -5072,7 +5345,9 @@ function markLifecycleWarningIfCurrent(binding) {
5072
5345
  decompositionId: binding.decompositionId,
5073
5346
  taskId: binding.taskId,
5074
5347
  activeTaskCheckedAt: current.activeTaskCheckedAt,
5075
- lifecycleWarning: "insufficient-permission"
5348
+ lifecycleWarning: "insufficient-permission",
5349
+ originatingReviewId: current.originatingReviewId,
5350
+ reviewRound: current.reviewRound
5076
5351
  };
5077
5352
  writeFileSync7(binding.path, JSON.stringify(warned, null, 2) + "\n");
5078
5353
  } catch {
@@ -5081,14 +5356,16 @@ function markLifecycleWarningIfCurrent(binding) {
5081
5356
  function cacheActiveBindingIfCurrent(binding) {
5082
5357
  try {
5083
5358
  const current = JSON.parse(
5084
- readFileSync6(binding.path, "utf8")
5359
+ readFileSync7(binding.path, "utf8")
5085
5360
  );
5086
5361
  if (current.decompositionId !== binding.decompositionId || current.taskId !== binding.taskId)
5087
5362
  return;
5088
5363
  const cached = {
5089
5364
  decompositionId: binding.decompositionId,
5090
5365
  taskId: binding.taskId,
5091
- activeTaskCheckedAt: (/* @__PURE__ */ new Date()).toISOString()
5366
+ activeTaskCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
5367
+ originatingReviewId: current.originatingReviewId,
5368
+ reviewRound: current.reviewRound
5092
5369
  };
5093
5370
  writeFileSync7(binding.path, JSON.stringify(cached, null, 2) + "\n");
5094
5371
  } catch {
@@ -5097,7 +5374,7 @@ function cacheActiveBindingIfCurrent(binding) {
5097
5374
  function invalidateBindingIfCurrent(binding) {
5098
5375
  try {
5099
5376
  const current = JSON.parse(
5100
- readFileSync6(binding.path, "utf8")
5377
+ readFileSync7(binding.path, "utf8")
5101
5378
  );
5102
5379
  if (current.decompositionId !== binding.decompositionId || current.taskId !== binding.taskId)
5103
5380
  return;
@@ -5107,7 +5384,9 @@ function invalidateBindingIfCurrent(binding) {
5107
5384
  {
5108
5385
  decompositionId: binding.decompositionId,
5109
5386
  invalidatedTaskId: binding.taskId,
5110
- invalidatedReason: "terminal-task"
5387
+ invalidatedReason: "terminal-task",
5388
+ originatingReviewId: current.originatingReviewId,
5389
+ reviewRound: current.reviewRound
5111
5390
  },
5112
5391
  null,
5113
5392
  2
@@ -5122,7 +5401,7 @@ function parseTranscript(path) {
5122
5401
  let tokensOut = 0;
5123
5402
  let contextUsed = 0;
5124
5403
  let model = "";
5125
- for (const line of readFileSync6(path, "utf8").split("\n")) {
5404
+ for (const line of readFileSync7(path, "utf8").split("\n")) {
5126
5405
  if (!line.trim()) continue;
5127
5406
  let obj;
5128
5407
  try {
@@ -5139,14 +5418,20 @@ function parseTranscript(path) {
5139
5418
  if (obj.message?.model) model = obj.message.model;
5140
5419
  }
5141
5420
  if (tokensIn === 0 && tokensOut === 0) return null;
5142
- return { tokensIn, tokensOut, contextUsed, contextWindow: windowFor(model, contextUsed), modelId: model || null };
5421
+ return {
5422
+ tokensIn,
5423
+ tokensOut,
5424
+ contextUsed,
5425
+ contextWindow: windowFor(model, contextUsed),
5426
+ modelId: model || null
5427
+ };
5143
5428
  }
5144
5429
  function windowFor(model, contextUsed = 0) {
5145
5430
  const m = model.toLowerCase();
5146
5431
  if (m.includes("[1m]") || m.includes("-1m")) return 1e6;
5147
5432
  return contextUsed > 2e5 ? 1e6 : 2e5;
5148
5433
  }
5149
- function buildHookEvents(input, usage, taskId, configRoot = null) {
5434
+ function buildHookEvents(input, usage, taskId, configRoot = null, originatingReviewId = null, reviewRound = null) {
5150
5435
  const events = [];
5151
5436
  const base = (kind, over) => ({
5152
5437
  taskId,
@@ -5160,6 +5445,8 @@ function buildHookEvents(input, usage, taskId, configRoot = null) {
5160
5445
  verdict: null,
5161
5446
  modelId: null,
5162
5447
  configRoot,
5448
+ originatingReviewId,
5449
+ reviewRound,
5163
5450
  ...over
5164
5451
  });
5165
5452
  if (usage) {
@@ -5188,7 +5475,9 @@ function buildHookEvents(input, usage, taskId, configRoot = null) {
5188
5475
  break;
5189
5476
  case "Stop":
5190
5477
  case "SubagentStop":
5191
- events.push(base("Terminal", { text: input.last_assistant_message ?? null }));
5478
+ events.push(
5479
+ base("Terminal", { text: input.last_assistant_message ?? null })
5480
+ );
5192
5481
  break;
5193
5482
  }
5194
5483
  return events;
@@ -5209,7 +5498,7 @@ function resolveClaudeConfigRoot(configuredRoot, transcriptPath) {
5209
5498
  function normalizeClaudeConfigRoot(candidate) {
5210
5499
  const trimmed = candidate?.trim();
5211
5500
  if (!trimmed) return null;
5212
- const expanded = trimmed === "~" ? homedir4() : trimmed.startsWith(`~${sep}`) ? join9(homedir4(), trimmed.slice(2)) : trimmed;
5501
+ const expanded = trimmed === "~" ? homedir4() : trimmed.startsWith(`~${sep}`) ? join10(homedir4(), trimmed.slice(2)) : trimmed;
5213
5502
  let normalized = resolve2(expanded);
5214
5503
  const rootLength = parse(normalized).root.length;
5215
5504
  while (normalized.length > rootLength && normalized.endsWith(sep))
@@ -5283,10 +5572,10 @@ function registerExecutorRunCommand(executor) {
5283
5572
  fail("--detach and --foreground are mutually exclusive");
5284
5573
  const detach = !foreground;
5285
5574
  const pidFile = resolve3(
5286
- opts.pidFile ? String(opts.pidFile) : join10(process.cwd(), ".sechroom", "fleet.pid")
5575
+ opts.pidFile ? String(opts.pidFile) : join11(process.cwd(), ".sechroom", "fleet.pid")
5287
5576
  );
5288
5577
  const logFile = resolve3(
5289
- opts.logFile ? String(opts.logFile) : join10(process.cwd(), ".sechroom", "fleet.log")
5578
+ opts.logFile ? String(opts.logFile) : join11(process.cwd(), ".sechroom", "fleet.log")
5290
5579
  );
5291
5580
  if (detach && !isDetachedChild) {
5292
5581
  await readFleetConfig(String(opts.config));
@@ -5572,7 +5861,7 @@ function registerExecutorRunCommand(executor) {
5572
5861
  if (excludeTags.length)
5573
5862
  log(`excluding offers tagged: ${excludeTags.join(", ")}`);
5574
5863
  const rootDir = resolve3(String(opts.root));
5575
- const usageLogPath = join10(
5864
+ const usageLogPath = join11(
5576
5865
  rootDir,
5577
5866
  ".sechroom",
5578
5867
  `executor-usage-${located.state.instanceKey.replace(/[^\w.-]/g, "-")}.jsonl`
@@ -5702,6 +5991,7 @@ function registerExecutorRunCommand(executor) {
5702
5991
  },
5703
5992
  claimNext: async () => await fleetInbox.waitForClaim() ?? await claimNext(request, instance.id, log, excludeTags, skipLog),
5704
5993
  loadTask: (memoryId) => materializeClaimedTask(request, memoryId),
5994
+ verifyTaskBinding: (task) => verifyReviewTarget(gitRunner, task),
5705
5995
  startLeaseHeartbeat: (claim) => startLeaseHeartbeat(
5706
5996
  () => request(
5707
5997
  `/me/executor-task-leases/${encodeURIComponent(claim.leaseId)}/heartbeat`,
@@ -5830,7 +6120,7 @@ function registerExecutorRunCommand(executor) {
5830
6120
  }
5831
6121
  function resolveFleetPidFile(flag) {
5832
6122
  return resolve3(
5833
- flag ? String(flag) : join10(process.cwd(), ".sechroom", "fleet.pid")
6123
+ flag ? String(flag) : join11(process.cwd(), ".sechroom", "fleet.pid")
5834
6124
  );
5835
6125
  }
5836
6126
  function requireLiveSupervisor(pidFile) {
@@ -5966,6 +6256,13 @@ var CODEX_EXECUTOR_HOOKS = {
5966
6256
  PreToolUse: EXECUTOR_PULSE_COMMAND,
5967
6257
  Stop: EXECUTOR_PULSE_COMMAND
5968
6258
  };
6259
+ function claimNextHint(claim) {
6260
+ if (claim.outcome === "NoOffer")
6261
+ return "no offer is currently standing for this instance";
6262
+ if (claim.lease?.id && claim.claimToken)
6263
+ return `claimed lease ${claim.lease.id} \u2014 keep it alive with the claimToken above, then close it out with work_executor_complete`;
6264
+ return void 0;
6265
+ }
5969
6266
  function registerExecutor(program2) {
5970
6267
  const executor = program2.command("executor").description(
5971
6268
  "Register and operate a local Claude Code/Codex executor advertisement"
@@ -6078,8 +6375,8 @@ function registerExecutor(program2) {
6078
6375
  fail("refresh-after must be shorter than the TTL");
6079
6376
  const sem = readSem();
6080
6377
  const checkout = sem ? dirname8(dirname8(sem.path)) : process.cwd();
6081
- const statePath = join11(checkout, ".sechroom", EXECUTOR_STATE);
6082
- const previous = existsSync8(statePath) ? JSON.parse(readFileSync7(statePath, "utf8")) : void 0;
6378
+ const statePath = join12(checkout, ".sechroom", EXECUTOR_STATE);
6379
+ const previous = existsSync8(statePath) ? JSON.parse(readFileSync8(statePath, "utf8")) : void 0;
6083
6380
  const canUpdateExisting = previous?.instanceId && previous.instanceKey === instanceKey && previous.laneId === laneId && previous.runtime === (runtime.toLowerCase() === "codex" ? "codex" : "claude-code") && previous.relayId === opts.relay && previous.connectorId === connector;
6084
6381
  const state = {
6085
6382
  schemaVersion: 1,
@@ -6108,8 +6405,8 @@ function registerExecutor(program2) {
6108
6405
  }
6109
6406
  const configuredClaudeDirs = globals.claudeConfigDir || process.env.CLAUDE_CONFIG_DIR ? resolveClaudeTargets({ override: globals.claudeConfigDir }).map(
6110
6407
  (target) => target.dir
6111
- ) : [join11(checkout, ".claude")];
6112
- const configuredCodexHomes = globals.codexHome || process.env.CODEX_HOME ? resolveCodexHomes({ override: globals.codexHome }) : [join11(checkout, ".codex")];
6408
+ ) : [join12(checkout, ".claude")];
6409
+ const configuredCodexHomes = globals.codexHome || process.env.CODEX_HOME ? resolveCodexHomes({ override: globals.codexHome }) : [join12(checkout, ".codex")];
6113
6410
  const hookTargets = surface === "claude" ? configuredClaudeDirs : configuredCodexHomes;
6114
6411
  for (const target of hookTargets) {
6115
6412
  const results = surface === "claude" ? [
@@ -6324,6 +6621,29 @@ function registerExecutor(program2) {
6324
6621
  );
6325
6622
  emit(data, Boolean(cmd.optsWithGlobals().json));
6326
6623
  });
6624
+ executor.command("claim <id>").description(
6625
+ "Claim the next dispatch offer standing for this exact instance (one-shot attended self-claim)"
6626
+ ).option(
6627
+ "--idempotency-key <key>",
6628
+ "Replay the same claim on retry instead of taking another offer (default: a fresh key per call)"
6629
+ ).action(async (id, opts, cmd) => {
6630
+ const cfg = resolveConfig(cmd.optsWithGlobals());
6631
+ const data = await api(
6632
+ cfg,
6633
+ `/me/executor-instances/${encodeURIComponent(id)}/dispatch-offers/claim-next`,
6634
+ {
6635
+ method: "POST",
6636
+ body: JSON.stringify({
6637
+ idempotencyKey: opts.idempotencyKey ?? `cli:${randomUUID().replace(/-/g, "")}`
6638
+ })
6639
+ }
6640
+ );
6641
+ emit(data, Boolean(cmd.optsWithGlobals().json));
6642
+ if (cmd.optsWithGlobals().json) return;
6643
+ const hint = claimNextHint(data);
6644
+ if (hint) process.stderr.write(style.dim(`${hint}
6645
+ `));
6646
+ });
6327
6647
  executor.command("proxy-claim <generationId>").description(
6328
6648
  "Node-held proxy claim: hold a task lease on a child's behalf so an attended/harness session is board-visible (node = holder-of-record, child = worker)"
6329
6649
  ).requiredOption(
@@ -6644,13 +6964,13 @@ function refreshRuntimeVersion(state, binaryOverride) {
6644
6964
  function readExecutorState(start = process.cwd()) {
6645
6965
  const semPath = resolveSemPathForRead(start);
6646
6966
  const sem = semPath ? readSem(semPath) : void 0;
6647
- const path = join11(
6648
- sem ? dirname8(sem.path) : join11(start, ".sechroom"),
6967
+ const path = join12(
6968
+ sem ? dirname8(sem.path) : join12(start, ".sechroom"),
6649
6969
  EXECUTOR_STATE
6650
6970
  );
6651
6971
  if (!existsSync8(path)) return void 0;
6652
6972
  return {
6653
- state: JSON.parse(readFileSync7(path, "utf8")),
6973
+ state: JSON.parse(readFileSync8(path, "utf8")),
6654
6974
  path
6655
6975
  };
6656
6976
  }
@@ -6820,7 +7140,7 @@ function registerChannel(program2) {
6820
7140
  channel.command("install").description(
6821
7141
  "Wire `sechroom channel mcp` into the project .mcp.json as a Claude Code channel MCP server (idempotent)"
6822
7142
  ).option("--dry-run", "Print what would change; write nothing").action((opts) => {
6823
- const path = join12(process.cwd(), ".mcp.json");
7143
+ const path = join13(process.cwd(), ".mcp.json");
6824
7144
  const dryRun = Boolean(opts.dryRun);
6825
7145
  const name = "sechroom-channel";
6826
7146
  const args = ["channel", "mcp"];
@@ -6917,36 +7237,69 @@ function startExecutorHeartbeat(refresh, intervalMilliseconds, dependencies = {}
6917
7237
  }, intervalMilliseconds);
6918
7238
  return () => cancel(timer);
6919
7239
  }
7240
+ var leaseHeartbeatApi = (cfg, path, init, deps = {}) => createAuthedRequest(cfg, deps)(path, init);
7241
+ function classifyLeaseHeartbeatFailure(error) {
7242
+ if (!(error instanceof HttpError)) return "retry";
7243
+ if (error.status === 408 || error.status === 429) return "retry";
7244
+ if (error.status < 400 || error.status >= 500) return "retry";
7245
+ if (error.status === 409 && /\bLease is Released\b/.test(error.body))
7246
+ return "released";
7247
+ return "terminal";
7248
+ }
7249
+ function leaseHeartbeatStoppedLine(leaseId, error) {
7250
+ if (!(error instanceof HttpError))
7251
+ return `lease heartbeat stopped for ${leaseId}: ${String(error)}`;
7252
+ return `lease heartbeat stopped for ${leaseId} (${error.status}): ${problemDetail(error.body)}`;
7253
+ }
7254
+ function problemDetail(body) {
7255
+ try {
7256
+ const parsed = JSON.parse(body);
7257
+ const detail = parsed.detail ?? parsed.title;
7258
+ if (typeof detail === "string" && detail.length > 0) return detail;
7259
+ } catch {
7260
+ }
7261
+ return body;
7262
+ }
6920
7263
  function startChannelTaskLeaseHeartbeat(cfg, claim, intervalMilliseconds = 3e4, dependencies = {}) {
6921
7264
  const leaseId = claim.lease?.id;
6922
7265
  const claimToken = claim.claimToken;
6923
7266
  if (!leaseId || !claimToken) return void 0;
6924
- const request = dependencies.request ?? api;
7267
+ const request = dependencies.request ?? leaseHeartbeatApi;
6925
7268
  const onError = dependencies.onError ?? ((value) => process.stderr.write(
6926
7269
  err(`channel lease heartbeat failed: ${String(value)}
6927
7270
  `)
6928
7271
  ));
6929
- return startLeaseHeartbeat(
6930
- () => request(
6931
- cfg,
6932
- `/me/executor-task-leases/${encodeURIComponent(leaseId)}/heartbeat`,
6933
- {
6934
- method: "POST",
6935
- body: JSON.stringify({
6936
- claimToken,
6937
- tokenVersion: claim.tokenVersion ?? 1
6938
- })
6939
- }
6940
- ),
6941
- onError,
6942
- intervalMilliseconds,
6943
- {
6944
- setInterval: dependencies.setInterval,
6945
- clearInterval: dependencies.clearInterval
7272
+ let stop;
7273
+ const beat = async () => {
7274
+ try {
7275
+ return await request(
7276
+ cfg,
7277
+ `/me/executor-task-leases/${encodeURIComponent(leaseId)}/heartbeat`,
7278
+ {
7279
+ method: "POST",
7280
+ body: JSON.stringify({
7281
+ claimToken,
7282
+ tokenVersion: claim.tokenVersion ?? 1
7283
+ })
7284
+ }
7285
+ );
7286
+ } catch (error) {
7287
+ const disposition = classifyLeaseHeartbeatFailure(error);
7288
+ if (disposition === "retry") throw error;
7289
+ stop?.();
7290
+ dependencies.onLeaseTerminal?.(leaseId, error);
7291
+ if (disposition === "terminal")
7292
+ onError(leaseHeartbeatStoppedLine(leaseId, error));
7293
+ return void 0;
6946
7294
  }
6947
- );
7295
+ };
7296
+ stop = startLeaseHeartbeat(beat, onError, intervalMilliseconds, {
7297
+ setInterval: dependencies.setInterval,
7298
+ clearInterval: dependencies.clearInterval
7299
+ });
7300
+ return stop;
6948
7301
  }
6949
- function createChannelLeaseHeartbeatManager(cfg, taskLeaseTtlSeconds) {
7302
+ function createChannelLeaseHeartbeatManager(cfg, taskLeaseTtlSeconds, dependencies = {}) {
6950
7303
  const stops = /* @__PURE__ */ new Map();
6951
7304
  const intervalMilliseconds = Math.max(
6952
7305
  1e3,
@@ -6959,10 +7312,20 @@ function createChannelLeaseHeartbeatManager(cfg, taskLeaseTtlSeconds) {
6959
7312
  const stop = startChannelTaskLeaseHeartbeat(
6960
7313
  cfg,
6961
7314
  claim,
6962
- intervalMilliseconds
7315
+ intervalMilliseconds,
7316
+ {
7317
+ ...dependencies,
7318
+ // The beat has already cancelled its own timer; drop the dead entry so a
7319
+ // later re-claim of the same lease id can start a fresh beat, and so the
7320
+ // map does not accumulate stopped leases for the life of the channel.
7321
+ onLeaseTerminal: (id) => {
7322
+ stops.delete(id);
7323
+ }
7324
+ }
6963
7325
  );
6964
7326
  if (stop) stops.set(leaseId, stop);
6965
7327
  },
7328
+ activeLeaseIds: () => [...stops.keys()],
6966
7329
  stop: () => {
6967
7330
  for (const stop of stops.values()) stop();
6968
7331
  stops.clear();
@@ -7024,7 +7387,7 @@ async function drainClaims(cfg, executorInstanceId, deliver, dependencies = {})
7024
7387
  }
7025
7388
  function readMcpConfig(path) {
7026
7389
  if (!existsSync9(path)) return {};
7027
- const raw = readFileSync8(path, "utf8");
7390
+ const raw = readFileSync9(path, "utf8");
7028
7391
  if (!raw.trim()) return {};
7029
7392
  try {
7030
7393
  return JSON.parse(raw);
@@ -7198,12 +7561,12 @@ Examples:
7198
7561
 
7199
7562
  // src/commands/checkpoint.ts
7200
7563
  import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync14 } from "fs";
7201
- import { dirname as dirname14, join as join16 } from "path";
7564
+ import { dirname as dirname14, join as join17 } from "path";
7202
7565
 
7203
7566
  // src/commands/hook.ts
7204
7567
  import { createHash as createHash4 } from "crypto";
7205
- import { existsSync as existsSync12, mkdirSync as mkdirSync14, readFileSync as readFileSync11, statSync as statSync3, writeFileSync as writeFileSync13 } from "fs";
7206
- import { dirname as dirname13, join as join15 } from "path";
7568
+ import { existsSync as existsSync12, mkdirSync as mkdirSync14, readFileSync as readFileSync12, statSync as statSync4, writeFileSync as writeFileSync13 } from "fs";
7569
+ import { dirname as dirname13, join as join16 } from "path";
7207
7570
 
7208
7571
  // src/commands/lane-commit-hook.ts
7209
7572
  import { execFileSync as execFileSync2 } from "child_process";
@@ -7211,7 +7574,7 @@ import {
7211
7574
  chmodSync,
7212
7575
  existsSync as existsSync10,
7213
7576
  mkdirSync as mkdirSync11,
7214
- readFileSync as readFileSync9,
7577
+ readFileSync as readFileSync10,
7215
7578
  renameSync,
7216
7579
  unlinkSync,
7217
7580
  writeFileSync as writeFileSync10
@@ -7232,7 +7595,7 @@ function resolveCheckoutLane(start) {
7232
7595
  return pin ? applyWorktreeLaneSuffix(pin, start) : void 0;
7233
7596
  }
7234
7597
  function appendLaneTrailer(messagePath, lane) {
7235
- const original = readFileSync9(messagePath, "utf8").replace(/\r\n/g, "\n");
7598
+ const original = readFileSync10(messagePath, "utf8").replace(/\r\n/g, "\n");
7236
7599
  const lines = original.split("\n");
7237
7600
  const scissorsIndex = lines.findIndex(
7238
7601
  (line) => /^#\s*-+\s*>8\s*-+\s*$/.test(line)
@@ -7304,7 +7667,7 @@ function resolveHookPath(root, hookName) {
7304
7667
  function removeLegacyPrepareCommitMsgLeg(root) {
7305
7668
  const path = resolveHookPath(root, LEGACY_HOOK_NAME);
7306
7669
  if (!existsSync10(path)) return;
7307
- const current = readFileSync9(path, "utf8");
7670
+ const current = readFileSync10(path, "utf8");
7308
7671
  const pattern = managedBlockPattern();
7309
7672
  if (!pattern.test(current)) return;
7310
7673
  const next = current.replace(pattern, "").replace(/\n{3,}/g, "\n\n").trimEnd();
@@ -7324,7 +7687,7 @@ function installLaneCommitHook(start) {
7324
7687
  }).trim();
7325
7688
  removeLegacyPrepareCommitMsgLeg(root);
7326
7689
  const path = resolveHookPath(root, HOOK_NAME);
7327
- const current = existsSync10(path) ? readFileSync9(path, "utf8") : "";
7690
+ const current = existsSync10(path) ? readFileSync10(path, "utf8") : "";
7328
7691
  let next;
7329
7692
  if (current && !isShellHook(current)) {
7330
7693
  const incumbentPath = nextIncumbentPath(path);
@@ -7347,25 +7710,25 @@ function installLaneCommitHook(start) {
7347
7710
  }
7348
7711
 
7349
7712
  // src/commands/session-context.ts
7350
- import { randomUUID as randomUUID2 } from "crypto";
7351
- import { mkdirSync as mkdirSync13, renameSync as renameSync3, rmSync as rmSync6, writeFileSync as writeFileSync12 } from "fs";
7352
- import { dirname as dirname12, join as join14 } from "path";
7713
+ import { randomUUID as randomUUID3 } from "crypto";
7714
+ import { mkdirSync as mkdirSync13, renameSync as renameSync3, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
7715
+ import { dirname as dirname12, join as join15 } from "path";
7353
7716
 
7354
7717
  // src/setup/skill-composition-materialise.ts
7355
- import { createHash as createHash3, randomUUID } from "crypto";
7718
+ import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
7356
7719
  import {
7357
7720
  existsSync as existsSync11,
7358
7721
  mkdirSync as mkdirSync12,
7359
- readdirSync as readdirSync2,
7360
- readFileSync as readFileSync10,
7722
+ readdirSync as readdirSync3,
7723
+ readFileSync as readFileSync11,
7361
7724
  renameSync as renameSync2,
7362
7725
  rmdirSync,
7363
- rmSync as rmSync5,
7364
- statSync as statSync2,
7726
+ rmSync as rmSync6,
7727
+ statSync as statSync3,
7365
7728
  writeFileSync as writeFileSync11
7366
7729
  } from "fs";
7367
- import { dirname as dirname11, isAbsolute as isAbsolute2, join as join13, relative, resolve as resolve5 } from "path";
7368
- var COMPILED_SKILL_LOCK = join13(
7730
+ import { dirname as dirname11, isAbsolute as isAbsolute2, join as join14, relative, resolve as resolve5 } from "path";
7731
+ var COMPILED_SKILL_LOCK = join14(
7369
7732
  ".sechroom",
7370
7733
  "compiled-skill-materialisation.json"
7371
7734
  );
@@ -7430,7 +7793,7 @@ function materialiseCompiledSkillCompositions(cwd, payload, options = {}) {
7430
7793
  desired.delete(key);
7431
7794
  }
7432
7795
  }
7433
- const lockPath = join13(cwd, COMPILED_SKILL_LOCK);
7796
+ const lockPath = join14(cwd, COMPILED_SKILL_LOCK);
7434
7797
  const previous = readLock(lockPath, destinations);
7435
7798
  const next = {
7436
7799
  version: 2,
@@ -7441,7 +7804,7 @@ function materialiseCompiledSkillCompositions(cwd, payload, options = {}) {
7441
7804
  const path = skillPath(value.skillsRoot, value.skill.name);
7442
7805
  const directory = dirname11(path);
7443
7806
  const prior = previous.entries[key];
7444
- const existing = existsSync11(path) ? readFileSync10(path, "utf8") : void 0;
7807
+ const existing = existsSync11(path) ? readFileSync11(path, "utf8") : void 0;
7445
7808
  const existingHash = existing === void 0 ? void 0 : sha256(existing);
7446
7809
  const owned = prior !== void 0 && prior.skillsRoot === value.skillsRoot && existing !== void 0 && (existingHash === prior.contentHash || existingHash === prior.previousContentHash);
7447
7810
  const recoverableMissing = prior !== void 0 && existing === void 0;
@@ -7489,7 +7852,7 @@ function materialiseCompiledSkillCompositions(cwd, payload, options = {}) {
7489
7852
  };
7490
7853
  writeLock(lockPath, next);
7491
7854
  mkdirSync12(directory, { recursive: true });
7492
- if (existingHash !== void 0 && (!existsSync11(path) || sha256(readFileSync10(path, "utf8")) !== existingHash)) {
7855
+ if (existingHash !== void 0 && (!existsSync11(path) || sha256(readFileSync11(path, "utf8")) !== existingHash)) {
7493
7856
  delete next.entries[key];
7494
7857
  writeLock(lockPath, next);
7495
7858
  items.push({
@@ -7572,7 +7935,7 @@ function entryKey(target, name, skillsRoot) {
7572
7935
  return `${target}:${sha256(skillsRoot)}:${name}`;
7573
7936
  }
7574
7937
  function skillPath(skillsRoot, name) {
7575
- return join13(skillsRoot, name, "SKILL.md");
7938
+ return join14(skillsRoot, name, "SKILL.md");
7576
7939
  }
7577
7940
  function withFinalNewline(body) {
7578
7941
  return body.endsWith("\n") ? body : body + "\n";
@@ -7587,7 +7950,7 @@ function readLock(path, destinations) {
7587
7950
  )
7588
7951
  );
7589
7952
  try {
7590
- const parsed = JSON.parse(readFileSync10(path, "utf8"));
7953
+ const parsed = JSON.parse(readFileSync11(path, "utf8"));
7591
7954
  if (parsed.version === 2 && parsed.entries && typeof parsed.entries === "object") {
7592
7955
  const entries = {};
7593
7956
  for (const [key, candidate] of Object.entries(parsed.entries)) {
@@ -7618,22 +7981,22 @@ function writeAtomic(path, body) {
7618
7981
  renameSync2(temporary, path);
7619
7982
  }
7620
7983
  function removeOwnedFile(path, acceptableHashes) {
7621
- const quarantine = `${path}.sechroom-retire-${process.pid}-${randomUUID()}`;
7984
+ const quarantine = `${path}.sechroom-retire-${process.pid}-${randomUUID2()}`;
7622
7985
  try {
7623
7986
  renameSync2(path, quarantine);
7624
7987
  } catch (error) {
7625
7988
  if (error.code === "ENOENT") return "missing";
7626
7989
  throw error;
7627
7990
  }
7628
- const quarantinedHash = sha256(readFileSync10(quarantine, "utf8"));
7991
+ const quarantinedHash = sha256(readFileSync11(quarantine, "utf8"));
7629
7992
  if (acceptableHashes.includes(quarantinedHash)) {
7630
- rmSync5(quarantine);
7993
+ rmSync6(quarantine);
7631
7994
  return "removed";
7632
7995
  }
7633
7996
  if (!existsSync11(path)) {
7634
7997
  renameSync2(quarantine, path);
7635
7998
  } else {
7636
- renameSync2(quarantine, `${path}.sechroom-preserved-${randomUUID()}`);
7999
+ renameSync2(quarantine, `${path}.sechroom-preserved-${randomUUID2()}`);
7637
8000
  }
7638
8001
  return "collision";
7639
8002
  }
@@ -7642,7 +8005,7 @@ function syncGitExcludes(cwd, lock) {
7642
8005
  if (!path) return;
7643
8006
  let current = "";
7644
8007
  try {
7645
- current = readFileSync10(path, "utf8");
8008
+ current = readFileSync11(path, "utf8");
7646
8009
  } catch {
7647
8010
  }
7648
8011
  const withoutOwnedBlock = removeOwnedExcludeBlock(current);
@@ -7657,14 +8020,14 @@ function syncGitExcludes(cwd, lock) {
7657
8020
  writeAtomic(path, updated);
7658
8021
  }
7659
8022
  function gitExcludePath(cwd) {
7660
- const dotGit = join13(cwd, ".git");
8023
+ const dotGit = join14(cwd, ".git");
7661
8024
  try {
7662
- if (statSync2(dotGit).isDirectory()) return join13(dotGit, "info", "exclude");
7663
- const pointer = readFileSync10(dotGit, "utf8").trim();
8025
+ if (statSync3(dotGit).isDirectory()) return join14(dotGit, "info", "exclude");
8026
+ const pointer = readFileSync11(dotGit, "utf8").trim();
7664
8027
  if (!pointer.startsWith("gitdir:")) return void 0;
7665
8028
  const raw = pointer.slice("gitdir:".length).trim();
7666
8029
  const gitDir = isAbsolute2(raw) ? raw : resolve5(cwd, raw);
7667
- return join13(gitDir, "info", "exclude");
8030
+ return join14(gitDir, "info", "exclude");
7668
8031
  } catch {
7669
8032
  return void 0;
7670
8033
  }
@@ -7681,13 +8044,13 @@ function removeOwnedExcludeBlock(body) {
7681
8044
  }
7682
8045
  function removeDirectoryIfEmpty(path) {
7683
8046
  try {
7684
- if (readdirSync2(path).length === 0) rmdirSync(path);
8047
+ if (readdirSync3(path).length === 0) rmdirSync(path);
7685
8048
  } catch {
7686
8049
  }
7687
8050
  }
7688
8051
 
7689
8052
  // src/commands/session-context.ts
7690
- var DYNAMIC_AGENT_CONTEXT_FILE = join14(".sechroom", "CLAUDE.md");
8053
+ var DYNAMIC_AGENT_CONTEXT_FILE = join15(".sechroom", "CLAUDE.md");
7691
8054
  function checkoutRoot(start) {
7692
8055
  const semPath = resolveSemPathForRead(start);
7693
8056
  return semPath ? dirname12(dirname12(semPath)) : start;
@@ -7724,7 +8087,7 @@ function renderSessionContext(result, lane) {
7724
8087
  }
7725
8088
  function writeSessionContext(start, lane, result, options = {}) {
7726
8089
  const root = checkoutRoot(start);
7727
- const path = join14(root, DYNAMIC_AGENT_CONTEXT_FILE);
8090
+ const path = join15(root, DYNAMIC_AGENT_CONTEXT_FILE);
7728
8091
  const skills = materialiseCompiledSkillCompositions(
7729
8092
  root,
7730
8093
  result.status === "hold" ? {
@@ -7740,13 +8103,13 @@ function writeSessionContext(start, lane, result, options = {}) {
7740
8103
  );
7741
8104
  const context = renderSessionContext(result, lane);
7742
8105
  mkdirSync13(dirname12(path), { recursive: true });
7743
- const temporaryPath = `${path}.${process.pid}.${randomUUID2()}.tmp`;
8106
+ const temporaryPath = `${path}.${process.pid}.${randomUUID3()}.tmp`;
7744
8107
  try {
7745
8108
  writeFileSync12(temporaryPath, context.endsWith("\n") ? context : `${context}
7746
8109
  `, "utf8");
7747
8110
  renameSync3(temporaryPath, path);
7748
8111
  } catch (error) {
7749
- rmSync6(temporaryPath, { force: true });
8112
+ rmSync7(temporaryPath, { force: true });
7750
8113
  throw error;
7751
8114
  }
7752
8115
  return { status: result.status, path, context, skills };
@@ -7797,12 +8160,12 @@ function resolveLane(flagLane, cwd) {
7797
8160
  if (!base) return void 0;
7798
8161
  return applyWorktreeLaneSuffix(base, start);
7799
8162
  }
7800
- var INTENT_FILE = join15(".sechroom", "continuity.json");
8163
+ var INTENT_FILE = join16(".sechroom", "continuity.json");
7801
8164
  var LOCAL_DRY_RUN_VALIDATION_WARNING = "LOCAL-ONLY \u2014 NOT SERVER-VALIDATED";
7802
8165
  function resolveIntentPath(start) {
7803
8166
  let dir = start;
7804
8167
  for (; ; ) {
7805
- const candidate = join15(dir, INTENT_FILE);
8168
+ const candidate = join16(dir, INTENT_FILE);
7806
8169
  if (existsSync12(candidate)) return candidate;
7807
8170
  const parent = dirname13(dir);
7808
8171
  if (parent === dir) return void 0;
@@ -7813,7 +8176,7 @@ function readIntent(start) {
7813
8176
  const path = resolveIntentPath(start);
7814
8177
  if (!path) return void 0;
7815
8178
  try {
7816
- return JSON.parse(readFileSync11(path, "utf8"));
8179
+ return JSON.parse(readFileSync12(path, "utf8"));
7817
8180
  } catch {
7818
8181
  return void 0;
7819
8182
  }
@@ -7867,14 +8230,14 @@ async function saveSnapshotFromIntent(cmd, cwd, laneFlag, scopeFlag, defaultScop
7867
8230
  }
7868
8231
  function ledgerPath(start) {
7869
8232
  const intent = resolveIntentPath(start);
7870
- const dir = intent ? dirname13(intent) : join15(start, ".sechroom");
7871
- return join15(dir, ".checkpoint-state.json");
8233
+ const dir = intent ? dirname13(intent) : join16(start, ".sechroom");
8234
+ return join16(dir, ".checkpoint-state.json");
7872
8235
  }
7873
8236
  function readLedger(start) {
7874
8237
  try {
7875
8238
  const p = ledgerPath(start);
7876
8239
  if (!existsSync12(p)) return {};
7877
- return JSON.parse(readFileSync11(p, "utf8"));
8240
+ return JSON.parse(readFileSync12(p, "utf8"));
7878
8241
  } catch {
7879
8242
  return {};
7880
8243
  }
@@ -7905,7 +8268,7 @@ function unchangedSinceLastPush(start, intent) {
7905
8268
  const path = resolveIntentPath(start);
7906
8269
  if (path && ledger.lastMtimeMs != null) {
7907
8270
  try {
7908
- if (statSync3(path).mtimeMs <= ledger.lastMtimeMs) return true;
8271
+ if (statSync4(path).mtimeMs <= ledger.lastMtimeMs) return true;
7909
8272
  } catch {
7910
8273
  }
7911
8274
  }
@@ -7917,7 +8280,7 @@ function recordPush(start, intent) {
7917
8280
  const path = resolveIntentPath(start);
7918
8281
  let mtimeMs;
7919
8282
  try {
7920
- if (path) mtimeMs = statSync3(path).mtimeMs;
8283
+ if (path) mtimeMs = statSync4(path).mtimeMs;
7921
8284
  } catch {
7922
8285
  mtimeMs = void 0;
7923
8286
  }
@@ -8241,7 +8604,7 @@ Examples:
8241
8604
  const client = await makeClient(cfg);
8242
8605
  return client.POST("/continuity/snapshots", { body });
8243
8606
  });
8244
- const path = resolveIntentPath(cwd) ?? join16(cwd, INTENT_FILE);
8607
+ const path = resolveIntentPath(cwd) ?? join17(cwd, INTENT_FILE);
8245
8608
  const fileBody = { ...merged, scope, lastSnapshotId: data.snapshotId };
8246
8609
  mkdirSync15(dirname14(path), { recursive: true });
8247
8610
  writeFileSync14(path, JSON.stringify(fileBody, null, 2) + "\n");
@@ -8258,7 +8621,7 @@ Examples:
8258
8621
  }
8259
8622
 
8260
8623
  // src/commands/close.ts
8261
- import { readFileSync as readFileSync12 } from "fs";
8624
+ import { readFileSync as readFileSync13 } from "fs";
8262
8625
  var VERDICTS = ["pass", "soft-fail", "plan-invalid", "blocked"];
8263
8626
  function registerClose(program2) {
8264
8627
  program2.command("close").description(
@@ -8299,7 +8662,7 @@ Examples:
8299
8662
  );
8300
8663
  let bodyText;
8301
8664
  try {
8302
- bodyText = opts.file ? readFileSync12(opts.file, "utf8") : readFileSync12(0, "utf8");
8665
+ bodyText = opts.file ? readFileSync13(opts.file, "utf8") : readFileSync13(0, "utf8");
8303
8666
  } catch {
8304
8667
  fail(
8305
8668
  opts.file ? `could not read --file ${opts.file}` : "no closeout body \u2014 pass --file <path> or pipe it on stdin"
@@ -8770,7 +9133,7 @@ Examples:
8770
9133
  );
8771
9134
  });
8772
9135
  workPlan.command("append <decompositionId>").description(
8773
- "Append hand-authored task(s) to a running work plan; they land proposed until ratified (POST /decompositions/{id}/append-tasks)"
9136
+ "Append hand-authored task(s) to a running work plan; they land proposed until ratified. Verification tasks may carry reviewExecution { kind: local-code-review, round, repository, baseCommit, headCommit, preferredInstanceKey?, preferredLaneId? } (POST /decompositions/{id}/append-tasks)"
8774
9137
  ).requiredOption(
8775
9138
  "--file <path>",
8776
9139
  "JSON file containing { tasks, gates? } (the AppendTasksInput shape); use - for stdin"
@@ -9190,13 +9553,13 @@ function registerGitHub(program2) {
9190
9553
  }
9191
9554
 
9192
9555
  // src/commands/herdr.ts
9193
- import { readFileSync as readFileSync13 } from "fs";
9556
+ import { readFileSync as readFileSync14 } from "fs";
9194
9557
  import { basename as basename3 } from "path";
9195
9558
 
9196
9559
  // src/herdr/client.ts
9197
9560
  import { createConnection as createConnection2 } from "net";
9198
9561
  import { homedir as homedir5 } from "os";
9199
- import { join as join17 } from "path";
9562
+ import { join as join18 } from "path";
9200
9563
  var DEFAULT_HERDR_SOCKET_RELATIVE = ".config/herdr/herdr.sock";
9201
9564
  var HerdrUnreachableError = class extends Error {
9202
9565
  constructor(socketPath, reason) {
@@ -9221,7 +9584,7 @@ function resolveSocketPath(flag, env = process.env, home = homedir5()) {
9221
9584
  if (fromFlag) return fromFlag;
9222
9585
  const fromEnv = env.HERDR_SOCKET?.trim();
9223
9586
  if (fromEnv) return fromEnv;
9224
- return join17(home, DEFAULT_HERDR_SOCKET_RELATIVE);
9587
+ return join18(home, DEFAULT_HERDR_SOCKET_RELATIVE);
9225
9588
  }
9226
9589
  function expandTarget(target) {
9227
9590
  const trimmed = target.trim();
@@ -9561,7 +9924,7 @@ function parseSource(value, fallback = DEFAULT_READ_SOURCE) {
9561
9924
  }
9562
9925
  return match;
9563
9926
  }
9564
- function resolveSendText(textArgs, useStdin, readStdin5 = () => readFileSync13(0, "utf8")) {
9927
+ function resolveSendText(textArgs, useStdin, readStdin5 = () => readFileSync14(0, "utf8")) {
9565
9928
  if (useStdin) {
9566
9929
  if (textArgs.length > 0) {
9567
9930
  throw new Error(
@@ -10301,11 +10664,11 @@ Examples:
10301
10664
  }
10302
10665
 
10303
10666
  // src/commands/memory.ts
10304
- import { readFileSync as readFileSync15 } from "fs";
10667
+ import { readFileSync as readFileSync16 } from "fs";
10305
10668
 
10306
10669
  // src/commands/memory-import.ts
10307
- import { readdirSync as readdirSync3, readFileSync as readFileSync14, realpathSync, statSync as statSync4 } from "fs";
10308
- import { basename as basename4, join as join18, resolve as resolve6 } from "path";
10670
+ import { readdirSync as readdirSync4, readFileSync as readFileSync15, realpathSync, statSync as statSync5 } from "fs";
10671
+ import { basename as basename4, join as join19, resolve as resolve6 } from "path";
10309
10672
  var MARKDOWN_RE = /\.(md|markdown)$/i;
10310
10673
  function isMarkdownPath(path) {
10311
10674
  return MARKDOWN_RE.test(path);
@@ -10332,18 +10695,18 @@ function collectImportFiles(inputs, opts = {}) {
10332
10695
  files.push(path);
10333
10696
  };
10334
10697
  const walk = (dir) => {
10335
- const entries = readdirSync3(dir, { withFileTypes: true }).sort(
10698
+ const entries = readdirSync4(dir, { withFileTypes: true }).sort(
10336
10699
  (a, b) => a.name.localeCompare(b.name)
10337
10700
  );
10338
10701
  for (const entry of entries) {
10339
10702
  if (entry.name.startsWith(".")) continue;
10340
- const child = join18(dir, entry.name);
10703
+ const child = join19(dir, entry.name);
10341
10704
  let isDirectory = entry.isDirectory();
10342
10705
  let isFile = entry.isFile();
10343
10706
  if (entry.isSymbolicLink()) {
10344
10707
  let target;
10345
10708
  try {
10346
- target = statSync4(child);
10709
+ target = statSync5(child);
10347
10710
  } catch {
10348
10711
  skipped.push({ path: child, reason: "broken symlink" });
10349
10712
  continue;
@@ -10381,7 +10744,7 @@ function collectImportFiles(inputs, opts = {}) {
10381
10744
  for (const input of inputs) {
10382
10745
  let isDirectory;
10383
10746
  try {
10384
- isDirectory = statSync4(input).isDirectory();
10747
+ isDirectory = statSync5(input).isDirectory();
10385
10748
  } catch {
10386
10749
  missing.push(input);
10387
10750
  continue;
@@ -10397,7 +10760,7 @@ function buildImportPlan(collected) {
10397
10760
  for (const path of collected.files) {
10398
10761
  let text2;
10399
10762
  try {
10400
- text2 = readFileSync14(path, "utf8");
10763
+ text2 = readFileSync15(path, "utf8");
10401
10764
  } catch (error) {
10402
10765
  throw new Error(
10403
10766
  `couldn't read ${path}: ${error instanceof Error ? error.message : String(error)}`
@@ -10498,7 +10861,7 @@ function resolveCreateBody(textOpt, fileOpt) {
10498
10861
  }
10499
10862
  if (textOpt != null) return { text: textOpt, defaultTitle: null };
10500
10863
  const fromStdin = fileOpt === "-";
10501
- const text2 = fromStdin ? readFileSync15(0, "utf8") : readFileSync15(String(fileOpt), "utf8");
10864
+ const text2 = fromStdin ? readFileSync16(0, "utf8") : readFileSync16(String(fileOpt), "utf8");
10502
10865
  if (text2.trim().length === 0) {
10503
10866
  fail(fromStdin ? "Stdin was empty." : `File is empty: ${fileOpt}`);
10504
10867
  }
@@ -11150,16 +11513,16 @@ Note: CLI memory get omits relationships by default; MCP get_memory includes the
11150
11513
  }
11151
11514
 
11152
11515
  // src/setup/apply.ts
11153
- import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
11516
+ import { createHash as createHash5, randomUUID as randomUUID4 } from "crypto";
11154
11517
  import {
11155
11518
  chmodSync as chmodSync2,
11156
11519
  copyFileSync,
11157
11520
  existsSync as existsSync13,
11158
11521
  mkdirSync as mkdirSync16,
11159
- readFileSync as readFileSync16,
11522
+ readFileSync as readFileSync17,
11160
11523
  renameSync as renameSync4,
11161
- rmSync as rmSync7,
11162
- statSync as statSync5,
11524
+ rmSync as rmSync8,
11525
+ statSync as statSync6,
11163
11526
  writeFileSync as writeFileSync15
11164
11527
  } from "fs";
11165
11528
  import { dirname as dirname15 } from "path";
@@ -11226,7 +11589,7 @@ function ensureDir2(path) {
11226
11589
  }
11227
11590
  function readOr(path, fallback) {
11228
11591
  try {
11229
- return readFileSync16(path, "utf8");
11592
+ return readFileSync17(path, "utf8");
11230
11593
  } catch {
11231
11594
  return fallback;
11232
11595
  }
@@ -11237,7 +11600,7 @@ function mergeMcpJson(path, snippet, dryRun) {
11237
11600
  let current = {};
11238
11601
  if (existed) {
11239
11602
  try {
11240
- current = JSON.parse(readFileSync16(path, "utf8"));
11603
+ current = JSON.parse(readFileSync17(path, "utf8"));
11241
11604
  } catch {
11242
11605
  return {
11243
11606
  kind: "mcp",
@@ -11607,24 +11970,24 @@ var defaultTomlFileOperations = {
11607
11970
  function writeTomlAtomic(path, content, fileOperations = {}) {
11608
11971
  ensureDir2(path);
11609
11972
  const operations = { ...defaultTomlFileOperations, ...fileOperations };
11610
- const temporary = `${path}.sechroom-${process.pid}-${randomUUID3()}.tmp`;
11973
+ const temporary = `${path}.sechroom-${process.pid}-${randomUUID4()}.tmp`;
11611
11974
  const backup = `${path}.bak`;
11612
- const backupTemporary = `${backup}.sechroom-${process.pid}-${randomUUID3()}.tmp`;
11613
- const mode = existsSync13(path) ? statSync5(path).mode & 4095 : 384;
11975
+ const backupTemporary = `${backup}.sechroom-${process.pid}-${randomUUID4()}.tmp`;
11976
+ const mode = existsSync13(path) ? statSync6(path).mode & 4095 : 384;
11614
11977
  try {
11615
11978
  writeFileSync15(temporary, content, { mode });
11616
11979
  chmodSync2(temporary, mode);
11617
- validateToml(readFileSync16(temporary, "utf8"));
11980
+ validateToml(readFileSync17(temporary, "utf8"));
11618
11981
  if (existsSync13(path) && !existsSync13(backup)) {
11619
11982
  operations.copyFileSync(path, backupTemporary);
11620
11983
  chmodSync2(backupTemporary, mode);
11621
- validateToml(readFileSync16(backupTemporary, "utf8"));
11984
+ validateToml(readFileSync17(backupTemporary, "utf8"));
11622
11985
  operations.renameSync(backupTemporary, backup);
11623
11986
  }
11624
11987
  operations.renameSync(temporary, path);
11625
11988
  } finally {
11626
- rmSync7(temporary, { force: true });
11627
- rmSync7(backupTemporary, { force: true });
11989
+ rmSync8(temporary, { force: true });
11990
+ rmSync8(backupTemporary, { force: true });
11628
11991
  }
11629
11992
  }
11630
11993
  function mergeCodexToml(path, snippet, dryRun, fileOperations = {}) {
@@ -11913,7 +12276,7 @@ auto-resumes where you left off and checkpoints working state before compacting.
11913
12276
 
11914
12277
  // src/setup/skills-offer.ts
11915
12278
  import { mkdirSync as mkdirSync17, writeFileSync as writeFileSync16 } from "fs";
11916
- import { join as join19 } from "path";
12279
+ import { join as join20 } from "path";
11917
12280
 
11918
12281
  // src/setup/lane-pin.ts
11919
12282
  var CODE_LANE_PREFIX_BY_CLIENT = {
@@ -12029,8 +12392,8 @@ Found ${summary} available to you for ${surface}.
12029
12392
  if (skills.length > 0) {
12030
12393
  const written = [];
12031
12394
  for (const s of skills) {
12032
- mkdirSync17(join19(sDir, s.name), { recursive: true });
12033
- writeFileSync16(join19(sDir, s.name, "SKILL.md"), s.body.endsWith("\n") ? s.body : s.body + "\n");
12395
+ mkdirSync17(join20(sDir, s.name), { recursive: true });
12396
+ writeFileSync16(join20(sDir, s.name, "SKILL.md"), s.body.endsWith("\n") ? s.body : s.body + "\n");
12034
12397
  written.push(s.name);
12035
12398
  }
12036
12399
  recordMaterialisedSkills(sDir, DEFAULT_SKILLS_SLUG, written, { surface });
@@ -12042,7 +12405,7 @@ Found ${summary} available to you for ${surface}.
12042
12405
  const written = [];
12043
12406
  for (const a of agents) {
12044
12407
  const file = `${a.name}.md`;
12045
- writeFileSync16(join19(aDir, file), a.body.endsWith("\n") ? a.body : a.body + "\n");
12408
+ writeFileSync16(join20(aDir, file), a.body.endsWith("\n") ? a.body : a.body + "\n");
12046
12409
  written.push(file);
12047
12410
  }
12048
12411
  recordMaterialisedSkills(aDir, DEFAULT_SKILLS_SLUG, written, { surface });
@@ -12345,7 +12708,8 @@ Examples:
12345
12708
  const codexHomes = resolveCodexHomes({ override: g.codexHome, scope });
12346
12709
  const targets = clientTargets(process.cwd(), {
12347
12710
  claudeDir: claudeTargets[0]?.dir,
12348
- codexHome: codexHomes[0] ?? null
12711
+ codexHome: codexHomes[0] ?? null,
12712
+ codexScope: scope
12349
12713
  });
12350
12714
  const keys = resolveClientKeys(opts.client);
12351
12715
  const json = g.json;
@@ -12637,15 +13001,30 @@ Examples:
12637
13001
  "--client <list>",
12638
13002
  `comma-separated clients (${ALL_CLIENT_KEYS.join(", ")}) or 'all'`,
12639
13003
  DEFAULT_CLIENT_KEY
13004
+ ).option(
13005
+ "--scope <scope>",
13006
+ "Codex MCP config scope: 'project' (<cwd>/.codex) or 'global' (CODEX_HOME / ~/.codex) \u2014 default project",
13007
+ "project"
12640
13008
  ).option("--dry-run", "print what would be written without writing", false).action(async (slug2, opts, cmd) => {
12641
13009
  const cfg = resolveConfig(cmd.optsWithGlobals());
12642
13010
  const setup = await withSpinner(
12643
13011
  "Fetching setup descriptors",
12644
13012
  () => fetchSetup(cfg, slug2)
12645
13013
  );
12646
- const targets = clientTargets(process.cwd());
13014
+ let scope;
13015
+ try {
13016
+ scope = resolveScope(opts.scope);
13017
+ } catch (error) {
13018
+ return fail(error.message);
13019
+ }
13020
+ const globals = cmd.optsWithGlobals();
13021
+ const codexHome = scope === "global" ? resolveCodexHomes({ override: globals.codexHome, scope })[0] ?? null : null;
13022
+ const targets = clientTargets(process.cwd(), {
13023
+ codexHome,
13024
+ codexScope: scope
13025
+ });
12647
13026
  const keys = resolveClientKeys(opts.client);
12648
- const json = cmd.optsWithGlobals().json;
13027
+ const json = globals.json;
12649
13028
  const result = [];
12650
13029
  for (const key of keys) {
12651
13030
  const target = targets[key];
@@ -12672,12 +13051,12 @@ Wired to namespace '${slug2}'. Restart your AI client (or reload MCP) to pick it
12672
13051
 
12673
13052
  // src/commands/onboard.ts
12674
13053
  import { existsSync as existsSync15 } from "fs";
12675
- import { basename as basename5, join as join21 } from "path";
13054
+ import { basename as basename5, join as join22 } from "path";
12676
13055
 
12677
13056
  // src/commands/fanout.ts
12678
13057
  import { spawnSync } from "child_process";
12679
- import { existsSync as existsSync14, readFileSync as readFileSync17, readdirSync as readdirSync4, statSync as statSync6 } from "fs";
12680
- import { isAbsolute as isAbsolute3, join as join20, resolve as resolve7 } from "path";
13058
+ import { existsSync as existsSync14, readFileSync as readFileSync18, readdirSync as readdirSync5, statSync as statSync7 } from "fs";
13059
+ import { isAbsolute as isAbsolute3, join as join21, resolve as resolve7 } from "path";
12681
13060
  var ICON = {
12682
13061
  refresh: "\u21BB",
12683
13062
  bind: "+",
@@ -12690,20 +13069,20 @@ function resolveChildDir(path, root) {
12690
13069
  function discoverChildren(root) {
12691
13070
  let names;
12692
13071
  try {
12693
- names = readdirSync4(root);
13072
+ names = readdirSync5(root);
12694
13073
  } catch {
12695
13074
  return [];
12696
13075
  }
12697
13076
  const out = [];
12698
13077
  for (const name of names.sort()) {
12699
13078
  if (name.startsWith(".") || name === "node_modules") continue;
12700
- const dir = join20(root, name);
13079
+ const dir = join21(root, name);
12701
13080
  try {
12702
- if (!statSync6(dir).isDirectory()) continue;
13081
+ if (!statSync7(dir).isDirectory()) continue;
12703
13082
  } catch {
12704
13083
  continue;
12705
13084
  }
12706
- if (existsSync14(join20(dir, ".git")) || committedBindingPath(dir)) out.push(name);
13085
+ if (existsSync14(join21(dir, ".git")) || committedBindingPath(dir)) out.push(name);
12707
13086
  }
12708
13087
  return out;
12709
13088
  }
@@ -12711,7 +13090,7 @@ function readManifest(path) {
12711
13090
  if (!existsSync14(path)) return null;
12712
13091
  let parsed;
12713
13092
  try {
12714
- parsed = JSON.parse(readFileSync17(path, "utf8"));
13093
+ parsed = JSON.parse(readFileSync18(path, "utf8"));
12715
13094
  } catch (err2) {
12716
13095
  throw new Error(`couldn't parse ${path}: ${err2 instanceof Error ? err2.message : String(err2)}`);
12717
13096
  }
@@ -13179,7 +13558,7 @@ async function planRecurseChild(entry, root, client, opts) {
13179
13558
  reason: "directory does not exist"
13180
13559
  };
13181
13560
  }
13182
- if (existsSync15(join21(dir, ".sechroom.json"))) {
13561
+ if (existsSync15(join22(dir, ".sechroom.json"))) {
13183
13562
  return {
13184
13563
  label: entry.path,
13185
13564
  dir,
@@ -13286,7 +13665,7 @@ This fan-out will pin the same lane in every repo:
13286
13665
  async function runRecurse(cfg, g, opts) {
13287
13666
  const { yes, dryRun, json } = opts;
13288
13667
  const root = process.cwd();
13289
- const manifestPath = join21(root, ".sechroom", "repos.json");
13668
+ const manifestPath = join22(root, ".sechroom", "repos.json");
13290
13669
  const fromManifest = readManifest(manifestPath);
13291
13670
  const entries = fromManifest ?? discoverChildren(root).map((path) => ({ path }));
13292
13671
  const sourceLabel = fromManifest ? `manifest ${manifestPath}` : `auto-discovered under ${root}`;
@@ -13535,20 +13914,15 @@ Try: ${style.cyan('sechroom memory search "..."')} or ${style.cyan("sechroom -
13535
13914
  yes,
13536
13915
  process.cwd()
13537
13916
  );
13538
- const keys = scope === "project" ? requestedKeys.filter((key) => key !== "codex") : requestedKeys;
13539
- if (scope === "project" && requestedKeys.includes("codex") && !json) {
13540
- process.stderr.write(
13541
- `${style.dim("Codex has no project scope \u2014 skipped (use --scope global for Codex).")}
13542
- `
13543
- );
13544
- }
13917
+ const keys = requestedKeys;
13545
13918
  const setup = await withSpinner(
13546
13919
  "Fetching setup descriptors",
13547
13920
  () => fetchSetup(cfg)
13548
13921
  );
13549
13922
  const targets = clientTargets(process.cwd(), {
13550
13923
  claudeDir: claudeTargets[0]?.dir,
13551
- codexHome: codexHomes[0] ?? null
13924
+ codexHome: codexHomes[0] ?? null,
13925
+ codexScope: scope
13552
13926
  });
13553
13927
  const personalWorkspaceId = await getPersonalWorkspaceId(cfg);
13554
13928
  if (!dryRun && !check) {
@@ -14078,31 +14452,31 @@ Examples:
14078
14452
 
14079
14453
  // src/commands/reset.ts
14080
14454
  import { homedir as homedir6 } from "os";
14081
- import { join as join22 } from "path";
14082
- import { existsSync as existsSync16, readFileSync as readFileSync18, rmSync as rmSync8 } from "fs";
14455
+ import { join as join23 } from "path";
14456
+ import { existsSync as existsSync16, readFileSync as readFileSync19, rmSync as rmSync9 } from "fs";
14083
14457
  var SKILLS_LOCK2 = ".sechroom-skills.json";
14084
- var localSkillsDir = () => join22(process.cwd(), ".claude", "skills");
14085
- var globalSkillsDir = () => join22(homedir6(), ".claude", "skills");
14086
- var localAgentsDir = () => join22(process.cwd(), ".claude", "agents");
14087
- var globalAgentsDir = () => join22(homedir6(), ".claude", "agents");
14458
+ var localSkillsDir = () => join23(process.cwd(), ".claude", "skills");
14459
+ var globalSkillsDir = () => join23(homedir6(), ".claude", "skills");
14460
+ var localAgentsDir = () => join23(process.cwd(), ".claude", "agents");
14461
+ var globalAgentsDir = () => join23(homedir6(), ".claude", "agents");
14088
14462
  function removeMaterialisedSkills(dir) {
14089
14463
  const removed = [];
14090
- const lockPath = join22(dir, SKILLS_LOCK2);
14464
+ const lockPath = join23(dir, SKILLS_LOCK2);
14091
14465
  if (!existsSync16(lockPath)) return removed;
14092
14466
  try {
14093
- const lock = JSON.parse(readFileSync18(lockPath, "utf8"));
14467
+ const lock = JSON.parse(readFileSync19(lockPath, "utf8"));
14094
14468
  for (const entry of Object.values(lock)) {
14095
14469
  for (const name of entry.skills ?? []) {
14096
- const p = join22(dir, name);
14470
+ const p = join23(dir, name);
14097
14471
  if (existsSync16(p)) {
14098
- rmSync8(p, { recursive: true, force: true });
14472
+ rmSync9(p, { recursive: true, force: true });
14099
14473
  removed.push(p);
14100
14474
  }
14101
14475
  }
14102
14476
  }
14103
14477
  } catch {
14104
14478
  }
14105
- rmSync8(lockPath, { force: true });
14479
+ rmSync9(lockPath, { force: true });
14106
14480
  removed.push(lockPath);
14107
14481
  return removed;
14108
14482
  }
@@ -14139,19 +14513,19 @@ function registerReset(program2) {
14139
14513
  }
14140
14514
  }
14141
14515
  const removed = [];
14142
- const stateDir = join22(process.cwd(), ".sechroom");
14516
+ const stateDir = join23(process.cwd(), ".sechroom");
14143
14517
  if (existsSync16(stateDir)) {
14144
- rmSync8(stateDir, { recursive: true, force: true });
14518
+ rmSync9(stateDir, { recursive: true, force: true });
14145
14519
  removed.push(stateDir);
14146
14520
  }
14147
- const legacyCfg = join22(process.cwd(), ".sechroom.json");
14521
+ const legacyCfg = join23(process.cwd(), ".sechroom.json");
14148
14522
  if (existsSync16(legacyCfg)) {
14149
- rmSync8(legacyCfg, { force: true });
14523
+ rmSync9(legacyCfg, { force: true });
14150
14524
  removed.push(legacyCfg);
14151
14525
  }
14152
- const legacySem = join22(process.cwd(), ".sem");
14526
+ const legacySem = join23(process.cwd(), ".sem");
14153
14527
  if (existsSync16(legacySem)) {
14154
- rmSync8(legacySem, { force: true });
14528
+ rmSync9(legacySem, { force: true });
14155
14529
  removed.push(legacySem);
14156
14530
  }
14157
14531
  removed.push(...removeMaterialisedSkills(localSkillsDir()));
@@ -14176,8 +14550,8 @@ function registerReset(program2) {
14176
14550
  }
14177
14551
 
14178
14552
  // src/commands/skills.ts
14179
- import { existsSync as existsSync17, mkdirSync as mkdirSync18, statSync as statSync7, writeFileSync as writeFileSync17 } from "fs";
14180
- import { join as join23 } from "path";
14553
+ import { existsSync as existsSync17, mkdirSync as mkdirSync18, statSync as statSync8, writeFileSync as writeFileSync17 } from "fs";
14554
+ import { join as join24 } from "path";
14181
14555
  function filenameFromDisposition(header) {
14182
14556
  if (!header) return void 0;
14183
14557
  const m = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i.exec(header);
@@ -14185,11 +14559,11 @@ function filenameFromDisposition(header) {
14185
14559
  }
14186
14560
  function resolveOutputPath(output, serverFilename) {
14187
14561
  const filename = serverFilename || "skills.zip";
14188
- if (!output) return join23(process.cwd(), filename);
14189
- const looksLikeDir = output.endsWith("/") || existsSync17(output) && statSync7(output).isDirectory();
14562
+ if (!output) return join24(process.cwd(), filename);
14563
+ const looksLikeDir = output.endsWith("/") || existsSync17(output) && statSync8(output).isDirectory();
14190
14564
  if (looksLikeDir) {
14191
14565
  mkdirSync18(output, { recursive: true });
14192
- return join23(output, filename);
14566
+ return join24(output, filename);
14193
14567
  }
14194
14568
  return output;
14195
14569
  }
@@ -14235,6 +14609,7 @@ Examples:
14235
14609
  $ sechroom skills install --scope project write them to ./.claude/skills instead
14236
14610
  $ sechroom skills list what's materialised on disk
14237
14611
  $ sechroom skills clean remove the materialised skill files
14612
+ $ sechroom skills clean --prune-orphans also sweep skills orphaned by an earlier rename
14238
14613
  $ sechroom skills preview --workspace wsp_abc render a draft bundle from source (no install)
14239
14614
  $ sechroom skills package my-bundle download the installed bundle's skills as a zip
14240
14615
  $ sechroom skills package --from-source --workspace wsp_abc -o ./dist zip a draft from source
@@ -14248,7 +14623,7 @@ clients select all; an unconfigured machine preserves the legacy Claude default.
14248
14623
  );
14249
14624
  skills.command("install").description("Materialise your installed skills to disk (the already-installed bundle \u2014 no server install)").option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--client <client>", "claude, codex, or all (default: configured/detected clients)").option("--dry-run", "print what would be written; write nothing").option("--json", "machine output").action((opts, cmd) => runInstall(SKILL_SPEC, cmd, opts));
14250
14625
  skills.command("list").description("List the skills materialised on disk (per resolved config dir)").option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--client <client>", "claude, codex, or all (default: configured/detected clients)").option("--json", "machine output").action((opts, cmd) => runList(SKILL_SPEC, cmd, opts));
14251
- skills.command("clean [slug]").description(`Remove skill files materialised to disk (default ${DEFAULT_SKILLS_SLUG})`).option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--client <client>", "claude, codex, or all (default: configured/detected clients)").option("--json", "machine output").action((slugArg, opts, cmd) => runClean(SKILL_SPEC, cmd, opts, slugArg));
14626
+ skills.command("clean [slug]").description(`Remove skill files materialised to disk (default ${DEFAULT_SKILLS_SLUG})`).option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--client <client>", "claude, codex, or all (default: configured/detected clients)").option("--prune-orphans", "also remove sechroom-installed skill dirs that no lock entry claims (renamed/removed upstream)").option("--json", "machine output").action((slugArg, opts, cmd) => runClean(SKILL_SPEC, cmd, opts, slugArg));
14252
14627
  skills.command("preview").description("Render a workspace's draft bundle from source (no publish/install) and report the components").requiredOption("--workspace <id>", "workspace holding the draft bundle sources (wsp_\u2026)").option("--slug <slug>", "override the derived bundle slug").option("--title <title>", "override the derived bundle title").option("--version <version>", "override the derived bundle version").option("--default-install-parent <path>", "override the derived default install parent").option("--json", "machine output (the full RenderBundlePreviewResponse)").action(async (opts, cmd) => {
14253
14628
  const json = Boolean(opts.json) || Boolean(cmd.optsWithGlobals().json);
14254
14629
  const client = await makeClient(resolveConfig(cmd.optsWithGlobals()));
@@ -14395,8 +14770,8 @@ clients select all; an unconfigured machine preserves the legacy Claude default.
14395
14770
 
14396
14771
  // src/commands/sweep.ts
14397
14772
  import { existsSync as existsSync18 } from "fs";
14398
- import { dirname as dirname16, join as join24, resolve as resolve8 } from "path";
14399
- var DEFAULT_MANIFEST = join24(".sechroom", "repos.json");
14773
+ import { dirname as dirname16, join as join25, resolve as resolve8 } from "path";
14774
+ var DEFAULT_MANIFEST = join25(".sechroom", "repos.json");
14400
14775
  function planEntry(entry, root) {
14401
14776
  const dir = resolveChildDir(entry.path, root);
14402
14777
  if (!existsSync18(dir)) {
@@ -14900,7 +15275,7 @@ async function readStdin4() {
14900
15275
  function resolveVersion() {
14901
15276
  try {
14902
15277
  const pkg = JSON.parse(
14903
- readFileSync19(new URL("../package.json", import.meta.url), "utf8")
15278
+ readFileSync20(new URL("../package.json", import.meta.url), "utf8")
14904
15279
  );
14905
15280
  return pkg.version ?? "0.0.0";
14906
15281
  } catch {