@mutmutco/cli 3.64.0 → 3.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.cjs +262 -49
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -5165,16 +5165,20 @@ function validateDeadWorktreeDirContent(planned, inspected) {
5165
5165
  return { ok: false, reason: "unknown dead worktree cleanup kind" };
5166
5166
  }
5167
5167
  function validateBranchCleanupHead(planned, currentHeads) {
5168
+ const currentHeadOid = currentHeads.find((h) => h.branch === planned.branch)?.oid;
5169
+ if (planned.containedInBase && planned.plannedHeadOid) {
5170
+ if (!currentHeadOid) return { ok: false, reason: "local branch head could not be read" };
5171
+ return currentHeadOid === planned.plannedHeadOid ? { ok: true } : { ok: false, reason: `branch moved since planning (${currentHeadOid} != ${planned.plannedHeadOid})` };
5172
+ }
5168
5173
  const reviewedHeadOids = planned.reviewedHeadOids?.filter(Boolean) ?? [];
5169
5174
  if (!reviewedHeadOids.length) {
5170
5175
  return { ok: false, reason: "planned branch head was not verified against a PR head" };
5171
5176
  }
5172
- const currentHead = currentHeads.find((h) => h.branch === planned.branch)?.oid;
5173
- if (!currentHead) {
5177
+ if (!currentHeadOid) {
5174
5178
  return { ok: false, reason: "local branch head could not be verified against the PR head" };
5175
5179
  }
5176
- if (!reviewedHeadOids.includes(currentHead)) {
5177
- return { ok: false, reason: `${currentHead} != ${reviewedHeadOids.join("|")}` };
5180
+ if (!reviewedHeadOids.includes(currentHeadOid)) {
5181
+ return { ok: false, reason: `${currentHeadOid} != ${reviewedHeadOids.join("|")}` };
5178
5182
  }
5179
5183
  return { ok: true };
5180
5184
  }
@@ -5274,6 +5278,7 @@ function buildGcPlan(inputs) {
5274
5278
  const unpushedWorktrees = new Set((inputs.worktrees ?? []).filter((w) => w.unpushed).map((w) => w.branch));
5275
5279
  const preservedBranches2 = new Set(inputs.preservedBranches ?? []);
5276
5280
  const preservedWorktrees = new Set((inputs.worktrees ?? []).filter((w) => w.preserved).map((w) => w.branch));
5281
+ const mergedIntoBase = new Set((inputs.mergedIntoBase ?? []).map((b) => b.trim()).filter(Boolean));
5277
5282
  const skipped = [];
5278
5283
  const branches = [];
5279
5284
  const skipTrackingBranches = /* @__PURE__ */ new Set();
@@ -5313,7 +5318,8 @@ function buildGcPlan(inputs) {
5313
5318
  continue;
5314
5319
  }
5315
5320
  const localHead = branchHeads?.get(branch);
5316
- if (worktree2?.unpushed || branchHeads && (!localHead || !state.headOids.length || !state.headOids.includes(localHead))) {
5321
+ const containedInBase = mergedIntoBase.has(branch);
5322
+ if (!containedInBase && (worktree2?.unpushed || branchHeads && (!localHead || !state.headOids.length || !state.headOids.includes(localHead)))) {
5317
5323
  const detail = worktree2?.unpushed ? worktree2.path : localHead && state.headOids.length ? `${localHead} != ${state.headOids.join("|")}` : "local branch head could not be verified against the PR head";
5318
5324
  skipped.push({ branch, reason: "unpushed-branch", detail });
5319
5325
  skipTrackingBranches.add(branch);
@@ -5324,7 +5330,8 @@ function buildGcPlan(inputs) {
5324
5330
  prState: state.state,
5325
5331
  prNumbers: state.numbers,
5326
5332
  worktreePath: worktree2?.path,
5327
- ...state.headOids.length ? { reviewedHeadOids: state.headOids } : {}
5333
+ ...state.headOids.length ? { reviewedHeadOids: state.headOids } : {},
5334
+ ...containedInBase ? { containedInBase: true, ...localHead ? { plannedHeadOid: localHead } : {} } : {}
5328
5335
  });
5329
5336
  }
5330
5337
  const trackingRefs = [...new Set(inputs.staleTrackingRefs ?? [])].map((ref) => {
@@ -6121,6 +6128,32 @@ var DEFAULT_SURFACE = "claude";
6121
6128
  function activityLogPath(cwd) {
6122
6129
  return repoRuntimeStatePath(cwd, "hooks", "activity.jsonl");
6123
6130
  }
6131
+ var REDACTOR_WINDOW_MS = 48 * 60 * 60 * 1e3;
6132
+ var REDACTOR_SCAN_BYTES = 512 * 1024;
6133
+ function redactorLivenessProbe(cwd, now = /* @__PURE__ */ new Date()) {
6134
+ try {
6135
+ const raw = (0, import_node_fs10.readFileSync)(activityLogPath(cwd), "utf8");
6136
+ const tail = raw.length > REDACTOR_SCAN_BYTES ? raw.slice(raw.length - REDACTOR_SCAN_BYTES) : raw;
6137
+ const floor = now.getTime() - REDACTOR_WINDOW_MS;
6138
+ let failed = 0;
6139
+ let lastTs;
6140
+ for (const line of tail.split("\n")) {
6141
+ if (!line.includes('"secret-redact"') || !line.includes('"failed"')) continue;
6142
+ try {
6143
+ const row = JSON.parse(line);
6144
+ if (row.script !== "secret-redact" || row.outcome !== "failed") continue;
6145
+ const ts = row.ts ? Date.parse(row.ts) : Number.NaN;
6146
+ if (Number.isNaN(ts) || ts < floor) continue;
6147
+ failed += 1;
6148
+ if (!lastTs || row.ts > lastTs) lastTs = row.ts;
6149
+ } catch {
6150
+ }
6151
+ }
6152
+ return { failed, ...lastTs ? { lastTs } : {} };
6153
+ } catch {
6154
+ return void 0;
6155
+ }
6156
+ }
6124
6157
  function appendHookActivity(cwd, entry) {
6125
6158
  try {
6126
6159
  const path2 = activityLogPath(cwd);
@@ -10811,7 +10844,7 @@ function resolveExplicitScanRoot(explicitRoot, repoRoot2) {
10811
10844
  return explicitRepoWorktreesRoot(explicitRoot, repoRoot2, rootDirs);
10812
10845
  }
10813
10846
  async function gcPlan(remote, limit, opts = {}) {
10814
- const [branches, heads, current, stale, prs, worktrees, siblingDirs, preserved] = await Promise.all([
10847
+ const [branches, heads, current, stale, prs, worktrees, siblingDirs, preserved, mergedIntoBase] = await Promise.all([
10815
10848
  gitOut(["branch", "--format=%(refname:short)"]),
10816
10849
  localBranchHeads(),
10817
10850
  gitOut(["rev-parse", "--abbrev-ref", "HEAD"]),
@@ -10821,7 +10854,8 @@ async function gcPlan(remote, limit, opts = {}) {
10821
10854
  ghPrs(limit),
10822
10855
  worktreeBranches(),
10823
10856
  siblingWorktreeDirs(opts.root),
10824
- preservedBranches()
10857
+ preservedBranches(),
10858
+ branchesMergedIntoBase(remote)
10825
10859
  ]);
10826
10860
  return buildGcPlan({
10827
10861
  localBranches: branches.split(/\r?\n/).map((b) => b.trim()).filter(Boolean),
@@ -10832,9 +10866,23 @@ async function gcPlan(remote, limit, opts = {}) {
10832
10866
  pullRequests: prs,
10833
10867
  worktrees,
10834
10868
  remote,
10835
- preservedBranches: preserved
10869
+ preservedBranches: preserved,
10870
+ mergedIntoBase
10836
10871
  });
10837
10872
  }
10873
+ async function branchesMergedIntoBase(remote) {
10874
+ const out = /* @__PURE__ */ new Set();
10875
+ for (const base of ["development", "main", "master"]) {
10876
+ let listed;
10877
+ try {
10878
+ listed = await gitOut(["branch", "--merged", `${remote}/${base}`, "--format=%(refname:short)"]);
10879
+ } catch {
10880
+ continue;
10881
+ }
10882
+ for (const b of listed.split(/\r?\n/).map((x) => x.trim()).filter(Boolean)) out.add(b);
10883
+ }
10884
+ return [...out];
10885
+ }
10838
10886
  var STRAY_ROOT_WALK_BUDGET_MS = 4e3;
10839
10887
  function measureWorktreeRoot(root, deadline) {
10840
10888
  let dirs = 0;
@@ -16375,7 +16423,17 @@ var import_node_path17 = require("node:path");
16375
16423
  var POLICY_FILE = "test-policy.json";
16376
16424
  var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
16377
16425
  var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
16426
+ var TRAILER_KEY = "Test-Policy-Override";
16378
16427
  var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
16428
+ var REC = "";
16429
+ var FLD = "";
16430
+ var WAIVABLE_KINDS = [
16431
+ "mandatory-zone-untested",
16432
+ "unrequested-test-file",
16433
+ "protected-removed",
16434
+ "stale-protected-entry",
16435
+ "stale-satisfied-by"
16436
+ ];
16379
16437
  function translate(glob) {
16380
16438
  let out = "";
16381
16439
  for (let i = 0; i < glob.length; i++) {
@@ -16429,20 +16487,37 @@ function readFileOrNull2(path2) {
16429
16487
  return null;
16430
16488
  }
16431
16489
  }
16432
- function classify(changed, policy) {
16490
+ function removedPaths(changed) {
16491
+ return new Set(
16492
+ changed.map((f) => f.status === "D" ? f.path : f.status === "R" || f.status === "C" ? f.from : void 0).filter((p) => typeof p === "string")
16493
+ );
16494
+ }
16495
+ function classify(changed, policy, present = () => false) {
16433
16496
  const matchers = (policy.mandatory ?? []).map((m) => ({ ...m, re: globToRegExp(m.glob) }));
16434
16497
  const mandatoryHits = changed.filter((f) => matchers.some((m) => m.re.test(f.path)));
16435
16498
  const testChanges = changed.filter((f) => isTestPath(f.path));
16436
16499
  const addedTests = testChanges.filter((f) => f.status === "A");
16500
+ const removed = removedPaths(changed);
16501
+ const discharged = (m) => (m.satisfiedBy?.length ?? 0) > 0 && m.satisfiedBy.every((p) => present(p) && !removed.has(p));
16502
+ const untestedHits = changed.filter((f) => matchers.some((m) => m.re.test(f.path) && !discharged(m)));
16437
16503
  const protectedBy = new Map((policy.protected ?? []).map((p) => [p.path, p.why ?? ""]));
16438
- const removedProtected = changed.map((f) => f.status === "D" ? f.path : f.status === "R" || f.status === "C" ? f.from : void 0).filter((p) => typeof p === "string" && protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
16439
- return { mandatoryHits, testChanges, addedTests, removedProtected };
16504
+ for (const m of policy.mandatory ?? []) {
16505
+ for (const p of m.satisfiedBy ?? []) {
16506
+ if (!protectedBy.has(p)) protectedBy.set(p, `the standing coverage \`${m.glob}\` is discharged by. Removing it silently empties that glob.`);
16507
+ }
16508
+ }
16509
+ const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
16510
+ return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
16440
16511
  }
16441
16512
  function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs19.existsSync)(path2)) {
16442
16513
  return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path17.join)(root, p)));
16443
16514
  }
16444
- function evaluate(changed, policy) {
16445
- const { mandatoryHits, testChanges, addedTests, removedProtected } = classify(changed, policy);
16515
+ function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs19.existsSync)(path2)) {
16516
+ const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
16517
+ return [...new Set(declared)].filter((p) => !exists((0, import_node_path17.join)(root, p)));
16518
+ }
16519
+ function evaluate(changed, policy, present = () => false) {
16520
+ const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
16446
16521
  const findings = [];
16447
16522
  if (removedProtected.length > 0) {
16448
16523
  findings.push({
@@ -16453,12 +16528,12 @@ function evaluate(changed, policy) {
16453
16528
  ${f.why}`).join("\n") + "\n If you are removing one because it looked unrequired, read the reason above first \u2014 it is\n the answer to exactly that question. If this removal was asked for, add a commit trailer:\n Test-Policy-Override: <reason>"
16454
16529
  });
16455
16530
  }
16456
- if (mandatoryHits.length > 0 && testChanges.length === 0) {
16531
+ if (untestedHits.length > 0 && testChanges.length === 0) {
16457
16532
  findings.push({
16458
16533
  kind: "mandatory-zone-untested",
16459
- paths: mandatoryHits.map((f) => f.path),
16460
- detail: `MANDATORY ZONE UNTESTED \u2014 this change touches ${mandatoryHits.length} path(s) in the mandatory zone but changes no test:
16461
- ` + mandatoryHits.map((f) => ` ${f.path}`).join("\n") + "\n These are the paths where a silent failure costs real money, real data, a security breach,\n or production. Add or update a test that would fail if the guarded property broke."
16534
+ paths: untestedHits.map((f) => f.path),
16535
+ detail: `MANDATORY ZONE UNTESTED \u2014 this change touches ${untestedHits.length} path(s) in the mandatory zone but changes no test:
16536
+ ` + untestedHits.map((f) => ` ${f.path}`).join("\n") + "\n These are the paths where a silent failure costs real money, real data, a security breach,\n or production. Add or update a test that would fail if the guarded property broke."
16462
16537
  });
16463
16538
  }
16464
16539
  if (policy.declared !== false && addedTests.length > 0 && mandatoryHits.length === 0) {
@@ -16484,6 +16559,89 @@ function resolveBase(cwd, explicit) {
16484
16559
  }
16485
16560
  return null;
16486
16561
  }
16562
+ function isShallowRepository(cwd) {
16563
+ try {
16564
+ return git(["rev-parse", "--is-shallow-repository"], cwd).trim() !== "false";
16565
+ } catch {
16566
+ return true;
16567
+ }
16568
+ }
16569
+ function untrustworthyRange(cwd, base) {
16570
+ if (!base) {
16571
+ return {
16572
+ kind: "unresolvable-base",
16573
+ paths: [],
16574
+ detail: "NO COMPARISON BASE \u2014 none of --base, TEST_POLICY_BASE, origin/development or origin/main resolved.\n With no base there is no change set, and a gate that cannot see the change set has nothing it\n can honestly pass. Fetch the default branch, or name the base with --base / TEST_POLICY_BASE."
16575
+ };
16576
+ }
16577
+ if (!isShallowRepository(cwd)) return null;
16578
+ return {
16579
+ kind: "untrusted-range",
16580
+ paths: [],
16581
+ detail: `UNTRUSTWORTHY RANGE \u2014 this clone is SHALLOW, so ${base.slice(0, 8)}..HEAD is not the change set.
16582
+ A grafted commit has no visible parents, so excluding the base excludes the base ALONE and the
16583
+ range silently widens to everything reachable from HEAD. Measured in the fleet (#3628): an
16584
+ earlier CI step ran \`git fetch --depth=1\`, and a Test-Policy-Override trailer from a commit
16585
+ merged the previous day then passed every PR green. A gate that cannot identify the change set
16586
+ must not evaluate one, and must never waive one.
16587
+ Fetch full history \u2014 actions/checkout with \`fetch-depth: 0\`, or \`git fetch --unshallow\`.`
16588
+ };
16589
+ }
16590
+ function parseScope(value) {
16591
+ const scoped = /^\[([^\]]*)\]\s*([\s\S]*)$/.exec(value);
16592
+ if (!scoped) return { kinds: [...WAIVABLE_KINDS], reason: value, unknown: [] };
16593
+ const named = scoped[1].split(",").map((k) => k.trim()).filter(Boolean);
16594
+ return {
16595
+ kinds: named,
16596
+ reason: scoped[2].trim(),
16597
+ unknown: named.filter((k) => !WAIVABLE_KINDS.includes(k))
16598
+ };
16599
+ }
16600
+ function invisibleTrailer(sha, line) {
16601
+ return {
16602
+ kind: "malformed-override-trailer",
16603
+ paths: [],
16604
+ detail: `OVERRIDE TRAILER GIT CANNOT SEE \u2014 commit ${sha.slice(0, 8)} carries
16605
+ ${line}
16606
+ but \`git log --format='%(trailers:key=${TRAILER_KEY})'\` reports nothing for it, so the waiver would
16607
+ exist only to the regex that granted it. The trailer was chosen over a flag because it can be
16608
+ AUDITED, and a reason the auditor cannot see is not a reason this gate accepts (#3628).
16609
+ Git reads trailers from the LAST paragraph only, and every line of that paragraph must be a
16610
+ trailer or an indented continuation \u2014 one bare line such as \`Closes #123\` disqualifies the whole
16611
+ block. Indent the continuation lines, and leave nothing but trailers in that paragraph.`
16612
+ };
16613
+ }
16614
+ function unknownScope(sha, unknown) {
16615
+ return {
16616
+ kind: "malformed-override-trailer",
16617
+ paths: [],
16618
+ detail: `UNKNOWN OVERRIDE SCOPE \u2014 commit ${sha.slice(0, 8)} scopes its waiver to ${unknown.join(", ")}, which this
16619
+ gate cannot report.
16620
+ Waivable kinds: ${WAIVABLE_KINDS.join(", ")}.
16621
+ Refused rather than widened: treating a typo as "waive everything" is how a scoped waiver turns
16622
+ into a blanket exemption without anyone deciding it should.`
16623
+ };
16624
+ }
16625
+ function readOverride(base, cwd) {
16626
+ const format = `%H${FLD}%(trailers:key=${TRAILER_KEY},valueonly,unfold)${FLD}%B${REC}`;
16627
+ const refusals = [];
16628
+ let override = null;
16629
+ for (const record of git(["log", `${base}..HEAD`, `--format=${format}`], cwd).split(REC)) {
16630
+ const [sha, trailer, body] = record.replace(/^\s+/, "").split(FLD);
16631
+ if (!sha) continue;
16632
+ const value = (trailer ?? "").trim();
16633
+ if (!value) {
16634
+ const shaped = OVERRIDE_RE.exec(body ?? "");
16635
+ if (shaped) refusals.push(invisibleTrailer(sha, shaped[0].trim()));
16636
+ continue;
16637
+ }
16638
+ if (override) continue;
16639
+ const { kinds, reason, unknown } = parseScope(value);
16640
+ if (unknown.length > 0) refusals.push(unknownScope(sha, unknown));
16641
+ else override = { sha, reason, kinds };
16642
+ }
16643
+ return { override, refusals };
16644
+ }
16487
16645
  function changedFilesSince(base, cwd) {
16488
16646
  const raw = git(["diff", "--name-status", "-M", `${base}...HEAD`], cwd).trim();
16489
16647
  if (!raw) return [];
@@ -16499,29 +16657,45 @@ function runTestPolicy(root, deps = {}) {
16499
16657
  const policy = deps.policy ?? loadPolicy(root);
16500
16658
  const exists = deps.exists ?? ((path2) => (0, import_node_fs19.existsSync)(path2));
16501
16659
  const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
16502
- const unresolved = unresolvedProtectedEntries(policy, root, exists);
16660
+ const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
16661
+ const refusal = deps.changed ? null : untrustworthyRange(root, base);
16662
+ const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
16663
+ const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
16664
+ const present = (path2) => exists((0, import_node_path17.join)(root, path2));
16665
+ const removedByThisDiff = removedPaths(changed);
16666
+ const staleFindings = [];
16667
+ const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
16503
16668
  if (unresolved.length > 0) {
16504
- return {
16505
- ok: false,
16506
- base: null,
16507
- changedCount: 0,
16508
- ...counts,
16509
- findings: [{
16510
- kind: "stale-protected-entry",
16511
- paths: unresolved,
16512
- detail: `STALE PROTECTED ENTRY \u2014 test-policy.json protects ${unresolved.length} path(s) that do not exist:
16669
+ staleFindings.push({
16670
+ kind: "stale-protected-entry",
16671
+ paths: unresolved,
16672
+ detail: `STALE PROTECTED ENTRY \u2014 test-policy.json protects ${unresolved.length} path(s) that do not exist:
16513
16673
  ` + unresolved.map((p) => ` ${p}`).join("\n") + "\n An entry naming a missing file reads as protection while protecting nothing.\n Restore the file, or remove its entry."
16514
- }]
16515
- };
16674
+ });
16516
16675
  }
16517
- const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
16518
- if (!deps.changed && !base) return { ok: true, findings: [], changedCount: 0, base: null, ...counts };
16519
- const changed = deps.changed ?? changedFilesSince(base, root);
16520
- if (changed.length === 0) return { ok: true, findings: [], changedCount: 0, base, ...counts };
16521
- const override = deps.overrideReason !== void 0 ? deps.overrideReason : OVERRIDE_RE.exec(git(["log", `${base}..HEAD`, "--format=%B"], root))?.[1]?.trim() ?? null;
16522
- if (override) return { ok: true, findings: [], changedCount: changed.length, base, overriddenBy: override, ...counts };
16523
- const findings = evaluate(changed, policy);
16524
- return { ok: findings.length === 0, findings, changedCount: changed.length, base, ...counts };
16676
+ const staleSatisfiers = unresolvedSatisfiers(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
16677
+ if (staleSatisfiers.length > 0) {
16678
+ staleFindings.push({
16679
+ kind: "stale-satisfied-by",
16680
+ paths: staleSatisfiers,
16681
+ detail: `STALE STANDING COVERAGE \u2014 a mandatory glob claims ${staleSatisfiers.length} path(s) as satisfiedBy that do not exist:
16682
+ ` + staleSatisfiers.map((p) => ` ${p}`).join("\n") + "\n A glob discharged by coverage that is not there is a glob enforcing nothing, quietly.\n Restore the file, or drop it from satisfiedBy so the glob asks for a test again."
16683
+ });
16684
+ }
16685
+ const override = lookup.override;
16686
+ const waived = [];
16687
+ const sift = (found) => {
16688
+ waived.push(...found.filter((f) => override?.kinds.includes(f.kind)));
16689
+ return found.filter((f) => !override?.kinds.includes(f.kind));
16690
+ };
16691
+ const blocking = sift([...refusal ? [refusal] : [], ...lookup.refusals, ...staleFindings]);
16692
+ const findings = blocking.length > 0 ? blocking : sift(evaluate(changed, policy, present));
16693
+ const result = { ok: findings.length === 0, findings, changedCount: changed.length, base, ...counts };
16694
+ if (override) {
16695
+ result.overriddenBy = override;
16696
+ result.waived = waived;
16697
+ }
16698
+ return result;
16525
16699
  }
16526
16700
 
16527
16701
  // src/docs-audit-command.ts
@@ -21875,7 +22049,18 @@ function recordGcWorktreeRemoval(primaryRoot, actor, owners, path2, branch) {
21875
22049
  });
21876
22050
  dropWorktreeOwner(primaryRoot, path2);
21877
22051
  }
21878
- function renderGcApplyResult(result) {
22052
+ function renderPrLandCleanupLines(cleanup) {
22053
+ const report = cleanup;
22054
+ const wt = report?.worktree;
22055
+ if (!wt?.path) return [];
22056
+ const lines = [];
22057
+ if (wt.status === "removed") lines.push(`pr land: removed worktree ${wt.path} \u2014 that directory is gone; cd elsewhere before your next command`);
22058
+ else if (wt.status === "deferred") lines.push(`pr land: worktree ${wt.path} could not be removed yet and is registered for a later sweep`);
22059
+ else lines.push(`pr land: worktree ${wt.path} \u2014 ${wt.status ?? "unknown"}${wt.reason ? ` (${wt.reason})` : ""}`);
22060
+ if (wt.deferredNote) lines.push(`pr land: ${wt.deferredNote}`);
22061
+ return lines;
22062
+ }
22063
+ function renderGcApplyResult(result, skipped = []) {
21879
22064
  const lines = ["worktree gc apply result:"];
21880
22065
  lines.push(` branches removed: ${result.removedBranches.length ? result.removedBranches.join(", ") : "none"}`);
21881
22066
  lines.push(` remote branches removed: ${result.removedRemoteBranches.length ? result.removedRemoteBranches.join(", ") : "none"}`);
@@ -21892,9 +22077,15 @@ function renderGcApplyResult(result) {
21892
22077
  }
21893
22078
  const removedNothing = result.removedBranches.length === 0 && result.removedRemoteBranches.length === 0 && result.removedTrackingRefs.length === 0 && result.removedWorktreeDirs.length === 0;
21894
22079
  if (removedNothing && !result.refused.length) {
21895
- lines.push(" NOTHING WAS REMOVED. A stale worktree whose directory still exists is not reachable by");
21896
- lines.push(" this verb \u2014 remove it from inside it with `mmi-cli worktree land --apply`, or delete the");
21897
- lines.push(" directory and re-run to clear its registration.");
22080
+ const actionable = skipped.filter((s) => s.reason !== "protected" && s.reason !== "current-branch");
22081
+ if (actionable.length) {
22082
+ lines.push(` NOTHING WAS REMOVED \u2014 every candidate was skipped for a stated reason (${actionable.length}):`);
22083
+ for (const s of actionable) lines.push(` - ${s.branch}: ${s.reason}${s.detail ? ` (${s.detail})` : ""}`);
22084
+ } else {
22085
+ lines.push(" NOTHING WAS REMOVED. A stale worktree whose directory still exists is not reachable by");
22086
+ lines.push(" this verb \u2014 remove it from inside it with `mmi-cli worktree land --apply`, or delete the");
22087
+ lines.push(" directory and re-run to clear its registration.");
22088
+ }
21898
22089
  }
21899
22090
  return lines.join("\n");
21900
22091
  }
@@ -24877,6 +25068,22 @@ function checkDocsAudit(probe) {
24877
25068
  }
24878
25069
  return { ok: true, id: "docs-audit", label: "docs-audit", detail: probe.detail, verbose: [probe.detail] };
24879
25070
  }
25071
+ function checkRedactorLiveness(probe) {
25072
+ if (!probe) return null;
25073
+ const evidence = [`secret-redact failed rows in the last 48h: ${probe.failed}${probe.lastTs ? ` (last ${probe.lastTs})` : ""}`];
25074
+ if (probe.failed === 0) {
25075
+ return { ok: true, id: "redactor-liveness", label: "secret-redact liveness", verbose: evidence };
25076
+ }
25077
+ return {
25078
+ ok: false,
25079
+ id: "redactor-liveness",
25080
+ label: "secret-redact liveness",
25081
+ reportOnly: true,
25082
+ detail: `${probe.failed} crash marker${probe.failed === 1 ? "" : "s"} in 48h`,
25083
+ fix: "the redactor is crashing during scans \u2014 read `.git/mmi-runtime/hooks/activity.jsonl` for the error and fix it; every crash is a scan that never ran",
25084
+ verbose: evidence
25085
+ };
25086
+ }
24880
25087
  function checkSessionPayload(probe) {
24881
25088
  if (!probe) return null;
24882
25089
  const { chars } = probe;
@@ -25054,6 +25261,10 @@ async function runDoctorClean(opts, io, deps) {
25054
25261
  const sched = checkSchedules(probe);
25055
25262
  if (sched) checks.push(sched);
25056
25263
  }
25264
+ if (!opts.fast && !opts.banner && !opts.preflight && deps.redactorLiveness) {
25265
+ const redactor = checkRedactorLiveness(deps.redactorLiveness());
25266
+ if (redactor) checks.push(redactor);
25267
+ }
25057
25268
  if (!opts.fast && !opts.banner && !opts.preflight && deps.docsAudit) {
25058
25269
  const probe = await deps.docsAudit().catch((e) => ({
25059
25270
  armed: true,
@@ -25494,6 +25705,9 @@ function mmiDoctorDeps(opts = {}) {
25494
25705
  // #3470: what the SessionStart hook LAST emitted, measured at emission by the session-start verb below.
25495
25706
  // A local record read — cheap enough for every lane, including the banner.
25496
25707
  sessionPayload: () => readSessionPayload(process.cwd()),
25708
+ // #3630: recent secret-redact crash markers from the shared hook-activity trace — with the Stop
25709
+ // hook retired, the full/scheduled doctor is the trace's only reader. Local tail-scan, fail-soft.
25710
+ redactorLiveness: () => redactorLivenessProbe(process.cwd()),
25497
25711
  // #3485 items 9 and 8: why the MMI plugin has never printed an "updated — please restart" notice, and
25498
25712
  // which branch it would pick one up from. Two local file reads, no network, fail-soft to no rows.
25499
25713
  marketplaceRows: () => {
@@ -25848,7 +26062,7 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
25848
26062
  console.log(formatGcPlan(plan, false));
25849
26063
  } else {
25850
26064
  if (applyResult) console.log(`
25851
- ${renderGcApplyResult(applyResult)}`);
26065
+ ${renderGcApplyResult(applyResult, plan.skipped)}`);
25852
26066
  }
25853
26067
  if (applyResult?.failed.length) process.exitCode = 1;
25854
26068
  } catch (e) {
@@ -26190,12 +26404,10 @@ tests.command("policy").description("enforce this repo's test-policy.json agains
26190
26404
  if (!result.ok) process.exitCode = 1;
26191
26405
  return;
26192
26406
  }
26193
- if (result.base === null && result.ok) {
26194
- console.log("tests policy: no comparison base (origin/development or origin/main) \u2014 skipping.");
26195
- return;
26196
- }
26197
- if (result.overriddenBy) {
26198
- console.log(`tests policy: overridden by commit trailer \u2014 ${result.overriddenBy}`);
26407
+ if (result.overriddenBy && result.ok) {
26408
+ const { sha, reason, kinds } = result.overriddenBy;
26409
+ const scope = (result.waived ?? []).map((f) => f.kind).join(", ") || "nothing";
26410
+ console.log(`tests policy: overridden by ${sha.slice(0, 8)} (waiving ${scope}; scope ${kinds.join(", ")}) \u2014 ${reason}`);
26199
26411
  return;
26200
26412
  }
26201
26413
  if (result.ok) {
@@ -27268,6 +27480,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
27268
27480
  if (o.json) printLine(JSON.stringify(result));
27269
27481
  else {
27270
27482
  printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
27483
+ for (const line of renderPrLandCleanupLines(result.cleanup)) printLine(line);
27271
27484
  if (result.cleanupError) printLine(`pr land cleanup: ${result.cleanupError}`);
27272
27485
  }
27273
27486
  if (result.status === "failed" || result.cleanupError) process.exitCode = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.64.0",
3
+ "version": "3.66.0",
4
4
  "description": "MMI Future CLI — the org dev toolbox (board, registry, keyless secrets, release train, bootstrap, doctor) and the cross-IDE engine the plugin's session-start hook drives.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",