@mutmutco/cli 3.65.0 → 3.67.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 +162 -15
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -6128,6 +6128,32 @@ var DEFAULT_SURFACE = "claude";
6128
6128
  function activityLogPath(cwd) {
6129
6129
  return repoRuntimeStatePath(cwd, "hooks", "activity.jsonl");
6130
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
+ }
6131
6157
  function appendHookActivity(cwd, entry) {
6132
6158
  try {
6133
6159
  const path2 = activityLogPath(cwd);
@@ -16397,7 +16423,17 @@ var import_node_path17 = require("node:path");
16397
16423
  var POLICY_FILE = "test-policy.json";
16398
16424
  var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
16399
16425
  var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
16426
+ var TRAILER_KEY = "Test-Policy-Override";
16400
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
+ ];
16401
16437
  function translate(glob) {
16402
16438
  let out = "";
16403
16439
  for (let i = 0; i < glob.length; i++) {
@@ -16523,6 +16559,89 @@ function resolveBase(cwd, explicit) {
16523
16559
  }
16524
16560
  return null;
16525
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
+ }
16526
16645
  function changedFilesSince(base, cwd) {
16527
16646
  const raw = git(["diff", "--name-status", "-M", `${base}...HEAD`], cwd).trim();
16528
16647
  if (!raw) return [];
@@ -16539,9 +16658,9 @@ function runTestPolicy(root, deps = {}) {
16539
16658
  const exists = deps.exists ?? ((path2) => (0, import_node_fs19.existsSync)(path2));
16540
16659
  const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
16541
16660
  const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
16542
- const changed = deps.changed ?? (base ? changedFilesSince(base, root) : []);
16543
- const override = deps.overrideReason !== void 0 ? deps.overrideReason : !deps.changed && base ? OVERRIDE_RE.exec(git(["log", `${base}..HEAD`, "--format=%B"], root))?.[1]?.trim() ?? null : null;
16544
- if (override) return { ok: true, findings: [], changedCount: changed.length, base, overriddenBy: override, ...counts };
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: [] };
16545
16664
  const present = (path2) => exists((0, import_node_path17.join)(root, path2));
16546
16665
  const removedByThisDiff = removedPaths(changed);
16547
16666
  const staleFindings = [];
@@ -16563,13 +16682,20 @@ function runTestPolicy(root, deps = {}) {
16563
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."
16564
16683
  });
16565
16684
  }
16566
- if (staleFindings.length > 0) {
16567
- return { ok: false, base, changedCount: changed.length, ...counts, findings: staleFindings };
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;
16568
16697
  }
16569
- if (!deps.changed && !base) return { ok: true, findings: [], changedCount: 0, base: null, ...counts };
16570
- if (changed.length === 0) return { ok: true, findings: [], changedCount: 0, base, ...counts };
16571
- const findings = evaluate(changed, policy, present);
16572
- return { ok: findings.length === 0, findings, changedCount: changed.length, base, ...counts };
16698
+ return result;
16573
16699
  }
16574
16700
 
16575
16701
  // src/docs-audit-command.ts
@@ -24942,6 +25068,22 @@ function checkDocsAudit(probe) {
24942
25068
  }
24943
25069
  return { ok: true, id: "docs-audit", label: "docs-audit", detail: probe.detail, verbose: [probe.detail] };
24944
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
+ }
24945
25087
  function checkSessionPayload(probe) {
24946
25088
  if (!probe) return null;
24947
25089
  const { chars } = probe;
@@ -25119,6 +25261,10 @@ async function runDoctorClean(opts, io, deps) {
25119
25261
  const sched = checkSchedules(probe);
25120
25262
  if (sched) checks.push(sched);
25121
25263
  }
25264
+ if (!opts.fast && !opts.banner && !opts.preflight && deps.redactorLiveness) {
25265
+ const redactor = checkRedactorLiveness(deps.redactorLiveness());
25266
+ if (redactor) checks.push(redactor);
25267
+ }
25122
25268
  if (!opts.fast && !opts.banner && !opts.preflight && deps.docsAudit) {
25123
25269
  const probe = await deps.docsAudit().catch((e) => ({
25124
25270
  armed: true,
@@ -25559,6 +25705,9 @@ function mmiDoctorDeps(opts = {}) {
25559
25705
  // #3470: what the SessionStart hook LAST emitted, measured at emission by the session-start verb below.
25560
25706
  // A local record read — cheap enough for every lane, including the banner.
25561
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()),
25562
25711
  // #3485 items 9 and 8: why the MMI plugin has never printed an "updated — please restart" notice, and
25563
25712
  // which branch it would pick one up from. Two local file reads, no network, fail-soft to no rows.
25564
25713
  marketplaceRows: () => {
@@ -26255,12 +26404,10 @@ tests.command("policy").description("enforce this repo's test-policy.json agains
26255
26404
  if (!result.ok) process.exitCode = 1;
26256
26405
  return;
26257
26406
  }
26258
- if (result.base === null && result.ok) {
26259
- console.log("tests policy: no comparison base (origin/development or origin/main) \u2014 skipping.");
26260
- return;
26261
- }
26262
- if (result.overriddenBy) {
26263
- 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}`);
26264
26411
  return;
26265
26412
  }
26266
26413
  if (result.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.65.0",
3
+ "version": "3.67.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",