@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.
- package/dist/main.cjs +162 -15
- 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
|
|
16543
|
-
const
|
|
16544
|
-
|
|
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
|
-
|
|
16567
|
-
|
|
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
|
-
|
|
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.
|
|
26259
|
-
|
|
26260
|
-
|
|
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.
|
|
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",
|