@mutmutco/cli 4.0.12 → 4.0.13

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 CHANGED
@@ -10827,10 +10827,10 @@ var rollout_plan_default = {
10827
10827
  note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
10828
10828
  },
10829
10829
  baseline: {
10830
- version: "4.0.12",
10831
- tag: "v4.0.12",
10832
- commit: "0ead67177a73",
10833
- npm: "@mutmutco/cli@4.0.12"
10830
+ version: "4.0.13",
10831
+ tag: "v4.0.13",
10832
+ commit: "8491a7532553",
10833
+ npm: "@mutmutco/cli@4.0.13"
10834
10834
  },
10835
10835
  exitCriterion: "fleet-n-of-n",
10836
10836
  hubOnlyShortcut: "forbidden",
@@ -10847,14 +10847,14 @@ var rollout_plan_default = {
10847
10847
  repo: "mutmutco/mmi-hub",
10848
10848
  role: "canary",
10849
10849
  schedule: "train",
10850
- v3Target: "v4.0.12"
10850
+ v3Target: "v4.0.13"
10851
10851
  }
10852
10852
  ],
10853
10853
  rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
10854
10854
  rollback: {
10855
10855
  independent: true,
10856
- mechanism: "npm dist-tag latest -> 4.0.12 and redeploy the Hub Lambda from tag v4.0.12 (0ead67177a73); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
10857
- v3Target: "v4.0.12 (@mutmutco/cli@4.0.12, tag commit 0ead67177a73 \u2014 last known-good release carrying the repo-index v4-only contract)"
10856
+ mechanism: "npm dist-tag latest -> 4.0.13 and redeploy the Hub Lambda from tag v4.0.13 (8491a7532553); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
10857
+ v3Target: "v4.0.13 (@mutmutco/cli@4.0.13, tag commit 8491a7532553 \u2014 last known-good release carrying the repo-index v4-only contract)"
10858
10858
  }
10859
10859
  },
10860
10860
  {
@@ -17412,14 +17412,16 @@ async function filterDependencyBlockedClaimables(items, client, opts = {}) {
17412
17412
  }
17413
17413
 
17414
17414
  // src/closing-keyword-guard.ts
17415
- var CLOSING_MENTION_RE = /\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/gi;
17415
+ var CLOSING_MENTION_RE = /\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+(?:#(\d+)|\[#(\d+)\]\([^)\s]+\))/gi;
17416
17416
  var NEGATION_RE = /\b(?:not|never|cannot|can't|don't|doesn't|didn't|won't|wouldn't|shouldn't|mustn't|without)\b/i;
17417
17417
  var CLAUSE_WINDOW = 120;
17418
- var NEGATED_CLOSING_PHRASE_RE = /\b(?:(?:does|do|did|will|would|should|must|can)\s+not|(?:is|was|are|were)\s+not|doesn't|don't|didn't|won't|wouldn't|shouldn't|mustn't|can't|cannot|never|without|not)\s+(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/gi;
17418
+ var NEGATED_CLOSING_PHRASE_RE = /\b(?:(?:does|do|did|will|would|should|must|can)\s+not|(?:is|was|are|were)\s+not|doesn't|don't|didn't|won't|wouldn't|shouldn't|mustn't|can't|cannot|never|without|not)\s+(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+(?:#(\d+)|\[#(\d+)\]\([^)\s]+\))/gi;
17419
17419
  function rewriteNegatedClosingPhrases(text) {
17420
17420
  if (!text.includes("#")) return text;
17421
17421
  const segments = text.split(/(```[\s\S]*?```|~~~[\s\S]*?~~~|`[^`\n]*`)/g);
17422
- return segments.map((seg, i) => i % 2 === 1 ? seg : seg.replace(NEGATED_CLOSING_PHRASE_RE, (_m, n) => `leaves #${n} open`)).join("");
17422
+ return segments.map(
17423
+ (seg, i) => i % 2 === 1 ? seg : seg.replace(NEGATED_CLOSING_PHRASE_RE, (_m, plain, linked) => `leaves #${plain ?? linked} open`)
17424
+ ).join("");
17423
17425
  }
17424
17426
  function findClosingMentions(text) {
17425
17427
  const mentions = [];
@@ -17436,7 +17438,8 @@ function findClosingMentions(text) {
17436
17438
  const clause = boundary === -1 ? windowText : windowText.slice(boundary + 1);
17437
17439
  mentions.push({
17438
17440
  keyword: (match[1] ?? "").toLowerCase(),
17439
- issue: Number(match[2]),
17441
+ issue: Number(match[2] ?? match[3]),
17442
+ // plain `#N` vs Markdown-linked `[#N](URL)` (#5338)
17440
17443
  negated: NEGATION_RE.test(clause)
17441
17444
  });
17442
17445
  }
@@ -23478,16 +23481,23 @@ function runEmbedderOnce(cwd, chunks, modelDirectory, createdAt) {
23478
23481
  const result = (0, import_node_child_process13.spawnSync)(process.execPath, [file], { input: JSON.stringify(request), encoding: "utf8", windowsHide: true, timeout: V4_EMBED_TIMEOUT_MS, maxBuffer: V4_MAX_ARTIFACT_BYTES, env });
23479
23482
  if (result.error || result.status !== 0) {
23480
23483
  const cleanExit2 = result.error === void 0 && result.signal === void 0 && typeof result.status === "number" && result.status !== 0;
23484
+ const stdout = String(result.stdout).trim();
23485
+ const stderrTail = String(result.stderr).trim().slice(-400) || void 0;
23481
23486
  let code;
23482
- let stderrTail;
23483
23487
  if (cleanExit2) {
23484
23488
  try {
23485
- code = JSON.parse(String(result.stdout))?.code;
23489
+ code = JSON.parse(stdout)?.code;
23486
23490
  } catch {
23487
23491
  }
23488
- stderrTail = String(result.stderr).trim().slice(-400) || void 0;
23489
23492
  }
23490
- return { ok: false, code, stderrTail, cleanExit: cleanExit2, detail: result.error?.message ?? result.signal ?? `exit status ${result.status ?? "unknown"}` };
23493
+ return {
23494
+ ok: false,
23495
+ code,
23496
+ stdoutTail: code ? void 0 : stdout.slice(-400) || void 0,
23497
+ stderrTail,
23498
+ cleanExit: cleanExit2,
23499
+ detail: result.error?.message ?? result.signal ?? `exit status ${result.status ?? "unknown"}`
23500
+ };
23491
23501
  }
23492
23502
  try {
23493
23503
  const response = JSON.parse(result.stdout);
@@ -23517,8 +23527,9 @@ function runEmbedder(cwd, chunks, modelDirectory, createdAt) {
23517
23527
  }
23518
23528
  const runner = last;
23519
23529
  const code = runner.code ? ` code=${runner.code}` : "";
23530
+ const stdout = runner.stdoutTail ? ` stdout=${runner.stdoutTail}` : "";
23520
23531
  const stderr = runner.stderrTail ? ` stderr=${runner.stderrTail}` : "";
23521
- throw new Error(`repo-index v4 embedding runner failed: ${runner.detail ?? "unknown"}${code}${stderr}`);
23532
+ throw new Error(`repo-index v4 embedding runner failed: ${runner.detail ?? "unknown"}${code}${stdout}${stderr}`);
23522
23533
  }
23523
23534
  async function buildRepoIndexV4(cwd, repo, opts = {}) {
23524
23535
  const { commit, defaultBranch, createdAt } = gitInfo(cwd);
@@ -30474,6 +30485,175 @@ async function checkDocsIndexAtHead(opts, deps) {
30474
30485
  // src/issue-commands.ts
30475
30486
  var import_node_fs38 = require("node:fs");
30476
30487
  var import_node_crypto13 = require("node:crypto");
30488
+
30489
+ // src/learning-closure-rate.ts
30490
+ var CLOSURE_RATE_TIMEOUT_MS = 2e4;
30491
+ var LOOP_ITEM_CAP = 100;
30492
+ var EVIDENCE_CONCURRENCY = 4;
30493
+ var RED_TEAM_FINDING_LABEL2 = "red-team";
30494
+ var RULING_LOOP_STATE_LABELS = ["loop-state:closed-deferred", "loop-state:closed-invalid"];
30495
+ var CLOSED_MERGED_LOOP_STATE_LABEL = "loop-state:closed-merged";
30496
+ var LOOP_KINDS2 = [
30497
+ { kind: "friction", label: REPORT_LABEL },
30498
+ { kind: "lesson", label: SKILL_LESSON_LABEL },
30499
+ { kind: "red-team", label: RED_TEAM_FINDING_LABEL2 }
30500
+ ];
30501
+ function prIsMerged(pr2) {
30502
+ return String(pr2.state ?? "").toUpperCase() === "MERGED";
30503
+ }
30504
+ function mergedPrsOf(linkedPrs) {
30505
+ return linkedPrs.filter(prIsMerged);
30506
+ }
30507
+ function hasRecordedRuling(labels) {
30508
+ return labels.some((label) => RULING_LOOP_STATE_LABELS.includes(label));
30509
+ }
30510
+ function classifyLoopClosure(item, evidence) {
30511
+ if (String(item.state ?? "").toUpperCase() !== "CLOSED") return "open";
30512
+ if (mergedPrsOf(evidence.linkedPrs).length > 0) return "closed";
30513
+ if (item.labels.includes(CLOSED_MERGED_LOOP_STATE_LABEL)) return "closed";
30514
+ if (hasRecordedRuling(item.labels)) return "closed";
30515
+ return "closed-no-evidence";
30516
+ }
30517
+ var round3 = (x) => Math.round(x * 1e3) / 1e3;
30518
+ function computeLoopKindRate(spec, verdicts) {
30519
+ const numerator = verdicts.filter((v) => v === "closed").length;
30520
+ const unknownEvidence = verdicts.filter((v) => v === "closed-no-evidence").length;
30521
+ const denominator = verdicts.length;
30522
+ return {
30523
+ kind: spec.kind,
30524
+ label: spec.label,
30525
+ numerator,
30526
+ denominator,
30527
+ unknownEvidence,
30528
+ rate: denominator > 0 ? round3(numerator / denominator) : null
30529
+ };
30530
+ }
30531
+ function fmtRate(rate) {
30532
+ return rate === null ? "\u2014" : rate.toFixed(3);
30533
+ }
30534
+ function formatClosureSummary(report) {
30535
+ const kindWidth = Math.max(...report.kinds.map((k) => k.kind.length));
30536
+ const labelWidth = Math.max(...report.kinds.map((k) => k.label.length));
30537
+ const lines = [`learning closure rate \u2014 ${report.repo} (computed at read; #4440)`];
30538
+ for (const k of report.kinds) {
30539
+ const head = `${k.kind.padEnd(kindWidth)} (label ${k.label.padEnd(labelWidth)})`;
30540
+ const body = k.denominator === 0 ? "no loop items" : `${k.numerator}/${k.denominator} closed \xB7 ${k.unknownEvidence} unknown evidence \xB7 rate ${fmtRate(k.rate)}`;
30541
+ lines.push(` ${head}: ${body}`);
30542
+ }
30543
+ const t = report.totals;
30544
+ lines.push(
30545
+ ` ${"total".padEnd(kindWidth)}: ${t.numerator}/${t.denominator} closed \xB7 ${t.unknownEvidence} unknown evidence \xB7 rate ${fmtRate(t.rate)}`,
30546
+ "A closed loop without a merged PR or a recorded ruling reads as open."
30547
+ );
30548
+ if (report.evidencePartial) {
30549
+ lines.push("note: at least one closed item had an unreadable linked-PR read \u2014 counted open (unknown evidence).");
30550
+ }
30551
+ return lines.join("\n");
30552
+ }
30553
+ function buildClosureReport(repo, perKind, evidencePartial) {
30554
+ const kinds = perKind.map(({ spec, verdicts }) => computeLoopKindRate(spec, verdicts));
30555
+ const numerator = kinds.reduce((acc, k) => acc + k.numerator, 0);
30556
+ const denominator = kinds.reduce((acc, k) => acc + k.denominator, 0);
30557
+ const unknownEvidence = kinds.reduce((acc, k) => acc + k.unknownEvidence, 0);
30558
+ const report = {
30559
+ repo,
30560
+ kinds,
30561
+ totals: {
30562
+ numerator,
30563
+ denominator,
30564
+ unknownEvidence,
30565
+ rate: denominator > 0 ? round3(numerator / denominator) : null
30566
+ },
30567
+ evidencePartial,
30568
+ summary: ""
30569
+ };
30570
+ report.summary = formatClosureSummary(report);
30571
+ return report;
30572
+ }
30573
+ function splitRepo2(repo) {
30574
+ const parts = repo.split("/");
30575
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
30576
+ throw new QueryReadError("BAD_INPUT", `invalid repo "${repo}" (expected owner/repo)`);
30577
+ }
30578
+ return { owner: parts[0], name: parts[1] };
30579
+ }
30580
+ async function mapPooled(items, limit, worker) {
30581
+ const out = new Array(items.length);
30582
+ let next = 0;
30583
+ const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {
30584
+ while (next < items.length) {
30585
+ const index = next;
30586
+ next += 1;
30587
+ out[index] = await worker(items[index]);
30588
+ }
30589
+ });
30590
+ await Promise.all(runners);
30591
+ return out;
30592
+ }
30593
+ async function readEvidence(deps, owner, name, item) {
30594
+ try {
30595
+ const resp = await deps.ghJson(prForIssueGraphqlArgs(owner, name, item.number), CLOSURE_RATE_TIMEOUT_MS);
30596
+ return { linkedPrs: extractPrForIssueResponse(resp), readFailed: false };
30597
+ } catch (e) {
30598
+ if (e instanceof QueryReadError && e.code === "NOT_FOUND") {
30599
+ return { linkedPrs: [], readFailed: false };
30600
+ }
30601
+ return { linkedPrs: [], readFailed: true };
30602
+ }
30603
+ }
30604
+ async function runLearningClosureRate(deps, opts = {}) {
30605
+ const repo = opts.repo ?? HUB_REPO;
30606
+ const { owner, name } = splitRepo2(repo);
30607
+ let evidencePartial = false;
30608
+ const perKind = [];
30609
+ for (const spec of LOOP_KINDS2) {
30610
+ const rows = await deps.ghJson(
30611
+ buildIssueListArgs({ label: spec.label, state: "all", limit: LOOP_ITEM_CAP }, repo),
30612
+ CLOSURE_RATE_TIMEOUT_MS
30613
+ );
30614
+ const items = shapeIssueList(rows);
30615
+ const closed = items.filter((item) => String(item.state ?? "").toUpperCase() === "CLOSED");
30616
+ const evidence = await mapPooled(closed, EVIDENCE_CONCURRENCY, (item) => readEvidence(deps, owner, name, item));
30617
+ if (evidence.some((ev) => ev.readFailed)) evidencePartial = true;
30618
+ const evidenceByNumber = new Map(closed.map((item, i) => [item.number, evidence[i]]));
30619
+ const verdicts = items.map(
30620
+ (item) => classifyLoopClosure(item, evidenceByNumber.get(item.number) ?? { linkedPrs: [], readFailed: false })
30621
+ );
30622
+ perKind.push({ spec, verdicts });
30623
+ }
30624
+ return buildClosureReport(repo, perKind, evidencePartial);
30625
+ }
30626
+ function closureRateFail(e) {
30627
+ if (e instanceof QueryReadError) {
30628
+ const code = e.code === "NOT_FOUND" ? ERROR_CODES.ERR_NOT_FOUND : e.code === "NO_AUTH" ? ERROR_CODES.ERR_NO_AUTH : ERROR_CODES.ERR_BAD_ENUM;
30629
+ return fail(`closure-rate: ${e.message}`, code === ERROR_CODES.ERR_BAD_ENUM ? void 0 : { code });
30630
+ }
30631
+ const err = e;
30632
+ return fail(`closure-rate: ${(err.stderr || err.message || String(e)).trim()}`);
30633
+ }
30634
+ function registerLearningClosureRateCommand(program3) {
30635
+ const deps = queryDeps();
30636
+ withExamples(
30637
+ program3.command("closure-rate").description(
30638
+ "learning closure rate per loop kind (friction, lesson, red-team) computed at read from board + linked-PR/ruling evidence \u2014 a closed loop without a merged PR or a recorded ruling reads as open (#4440)"
30639
+ ).option("--repo <owner/repo>", `repo holding the loop items (defaults to ${HUB_REPO}, the central Hub board)`).option("--json", "print the structured LearningClosureReport JSON (default is the human summary)").action(async (o) => {
30640
+ try {
30641
+ const report = await runLearningClosureRate(deps, { repo: o.repo });
30642
+ if (o.json) console.log(JSON.stringify(report, null, 2));
30643
+ else console.log(report.summary);
30644
+ } catch (e) {
30645
+ closureRateFail(e);
30646
+ }
30647
+ }),
30648
+ [
30649
+ "mmi-cli learning closure-rate",
30650
+ "mmi-cli learning closure-rate --repo mutmutco/MMI-Hub --json"
30651
+ ],
30652
+ "loop items are read newest-first with a bounded cap of 100 per kind \u2014 a rate over a longer history needs the cap raised, never an unbounded scan"
30653
+ );
30654
+ }
30655
+
30656
+ // src/issue-commands.ts
30477
30657
  var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
30478
30658
  var ReparentConflictError = class extends Error {
30479
30659
  constructor(message, payload) {
@@ -30597,6 +30777,13 @@ async function closeIssue(client, options, deps = {}) {
30597
30777
  if (options.evidence !== void 0) {
30598
30778
  await verifyCloseEvidence(client, options.evidence, repo, parsed.number, reason === "duplicate-of" ? options.duplicateOf : void 0);
30599
30779
  }
30780
+ if (reason === "completed" && options.evidence !== void 0) {
30781
+ const ensureLabels = deps.ensureLabels ?? ensureLabelsExist;
30782
+ await ensureLabels([CLOSED_MERGED_LOOP_STATE_LABEL], repo);
30783
+ await client.rest("POST", `repos/${repo}/issues/${parsed.number}/labels`, {
30784
+ body: { labels: [CLOSED_MERGED_LOOP_STATE_LABEL] }
30785
+ });
30786
+ }
30600
30787
  await client.rest("PATCH", `repos/${repo}/issues/${parsed.number}`, {
30601
30788
  body: { state: "closed", state_reason: stateReason }
30602
30789
  });
@@ -32306,173 +32493,6 @@ function registerSessionReport(program3) {
32306
32493
  });
32307
32494
  }
32308
32495
 
32309
- // src/learning-closure-rate.ts
32310
- var CLOSURE_RATE_TIMEOUT_MS = 2e4;
32311
- var LOOP_ITEM_CAP = 100;
32312
- var EVIDENCE_CONCURRENCY = 4;
32313
- var RED_TEAM_FINDING_LABEL2 = "red-team";
32314
- var RULING_LOOP_STATE_LABELS = ["loop-state:closed-deferred", "loop-state:closed-invalid"];
32315
- var CLOSED_MERGED_LOOP_STATE_LABEL = "loop-state:closed-merged";
32316
- var LOOP_KINDS2 = [
32317
- { kind: "friction", label: REPORT_LABEL },
32318
- { kind: "lesson", label: SKILL_LESSON_LABEL },
32319
- { kind: "red-team", label: RED_TEAM_FINDING_LABEL2 }
32320
- ];
32321
- function prIsMerged(pr2) {
32322
- return String(pr2.state ?? "").toUpperCase() === "MERGED";
32323
- }
32324
- function mergedPrsOf(linkedPrs) {
32325
- return linkedPrs.filter(prIsMerged);
32326
- }
32327
- function hasRecordedRuling(labels) {
32328
- return labels.some((label) => RULING_LOOP_STATE_LABELS.includes(label));
32329
- }
32330
- function classifyLoopClosure(item, evidence) {
32331
- if (String(item.state ?? "").toUpperCase() !== "CLOSED") return "open";
32332
- if (mergedPrsOf(evidence.linkedPrs).length > 0) return "closed";
32333
- if (item.labels.includes(CLOSED_MERGED_LOOP_STATE_LABEL)) return "closed";
32334
- if (hasRecordedRuling(item.labels)) return "closed";
32335
- return "closed-no-evidence";
32336
- }
32337
- var round3 = (x) => Math.round(x * 1e3) / 1e3;
32338
- function computeLoopKindRate(spec, verdicts) {
32339
- const numerator = verdicts.filter((v) => v === "closed").length;
32340
- const unknownEvidence = verdicts.filter((v) => v === "closed-no-evidence").length;
32341
- const denominator = verdicts.length;
32342
- return {
32343
- kind: spec.kind,
32344
- label: spec.label,
32345
- numerator,
32346
- denominator,
32347
- unknownEvidence,
32348
- rate: denominator > 0 ? round3(numerator / denominator) : null
32349
- };
32350
- }
32351
- function fmtRate(rate) {
32352
- return rate === null ? "\u2014" : rate.toFixed(3);
32353
- }
32354
- function formatClosureSummary(report) {
32355
- const kindWidth = Math.max(...report.kinds.map((k) => k.kind.length));
32356
- const labelWidth = Math.max(...report.kinds.map((k) => k.label.length));
32357
- const lines = [`learning closure rate \u2014 ${report.repo} (computed at read; #4440)`];
32358
- for (const k of report.kinds) {
32359
- const head = `${k.kind.padEnd(kindWidth)} (label ${k.label.padEnd(labelWidth)})`;
32360
- const body = k.denominator === 0 ? "no loop items" : `${k.numerator}/${k.denominator} closed \xB7 ${k.unknownEvidence} unknown evidence \xB7 rate ${fmtRate(k.rate)}`;
32361
- lines.push(` ${head}: ${body}`);
32362
- }
32363
- const t = report.totals;
32364
- lines.push(
32365
- ` ${"total".padEnd(kindWidth)}: ${t.numerator}/${t.denominator} closed \xB7 ${t.unknownEvidence} unknown evidence \xB7 rate ${fmtRate(t.rate)}`,
32366
- "A closed loop without a merged PR or a recorded ruling reads as open."
32367
- );
32368
- if (report.evidencePartial) {
32369
- lines.push("note: at least one closed item had an unreadable linked-PR read \u2014 counted open (unknown evidence).");
32370
- }
32371
- return lines.join("\n");
32372
- }
32373
- function buildClosureReport(repo, perKind, evidencePartial) {
32374
- const kinds = perKind.map(({ spec, verdicts }) => computeLoopKindRate(spec, verdicts));
32375
- const numerator = kinds.reduce((acc, k) => acc + k.numerator, 0);
32376
- const denominator = kinds.reduce((acc, k) => acc + k.denominator, 0);
32377
- const unknownEvidence = kinds.reduce((acc, k) => acc + k.unknownEvidence, 0);
32378
- const report = {
32379
- repo,
32380
- kinds,
32381
- totals: {
32382
- numerator,
32383
- denominator,
32384
- unknownEvidence,
32385
- rate: denominator > 0 ? round3(numerator / denominator) : null
32386
- },
32387
- evidencePartial,
32388
- summary: ""
32389
- };
32390
- report.summary = formatClosureSummary(report);
32391
- return report;
32392
- }
32393
- function splitRepo2(repo) {
32394
- const parts = repo.split("/");
32395
- if (parts.length !== 2 || !parts[0] || !parts[1]) {
32396
- throw new QueryReadError("BAD_INPUT", `invalid repo "${repo}" (expected owner/repo)`);
32397
- }
32398
- return { owner: parts[0], name: parts[1] };
32399
- }
32400
- async function mapPooled(items, limit, worker) {
32401
- const out = new Array(items.length);
32402
- let next = 0;
32403
- const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {
32404
- while (next < items.length) {
32405
- const index = next;
32406
- next += 1;
32407
- out[index] = await worker(items[index]);
32408
- }
32409
- });
32410
- await Promise.all(runners);
32411
- return out;
32412
- }
32413
- async function readEvidence(deps, owner, name, item) {
32414
- try {
32415
- const resp = await deps.ghJson(prForIssueGraphqlArgs(owner, name, item.number), CLOSURE_RATE_TIMEOUT_MS);
32416
- return { linkedPrs: extractPrForIssueResponse(resp), readFailed: false };
32417
- } catch (e) {
32418
- if (e instanceof QueryReadError && e.code === "NOT_FOUND") {
32419
- return { linkedPrs: [], readFailed: false };
32420
- }
32421
- return { linkedPrs: [], readFailed: true };
32422
- }
32423
- }
32424
- async function runLearningClosureRate(deps, opts = {}) {
32425
- const repo = opts.repo ?? HUB_REPO;
32426
- const { owner, name } = splitRepo2(repo);
32427
- let evidencePartial = false;
32428
- const perKind = [];
32429
- for (const spec of LOOP_KINDS2) {
32430
- const rows = await deps.ghJson(
32431
- buildIssueListArgs({ label: spec.label, state: "all", limit: LOOP_ITEM_CAP }, repo),
32432
- CLOSURE_RATE_TIMEOUT_MS
32433
- );
32434
- const items = shapeIssueList(rows);
32435
- const closed = items.filter((item) => String(item.state ?? "").toUpperCase() === "CLOSED");
32436
- const evidence = await mapPooled(closed, EVIDENCE_CONCURRENCY, (item) => readEvidence(deps, owner, name, item));
32437
- if (evidence.some((ev) => ev.readFailed)) evidencePartial = true;
32438
- const evidenceByNumber = new Map(closed.map((item, i) => [item.number, evidence[i]]));
32439
- const verdicts = items.map(
32440
- (item) => classifyLoopClosure(item, evidenceByNumber.get(item.number) ?? { linkedPrs: [], readFailed: false })
32441
- );
32442
- perKind.push({ spec, verdicts });
32443
- }
32444
- return buildClosureReport(repo, perKind, evidencePartial);
32445
- }
32446
- function closureRateFail(e) {
32447
- if (e instanceof QueryReadError) {
32448
- const code = e.code === "NOT_FOUND" ? ERROR_CODES.ERR_NOT_FOUND : e.code === "NO_AUTH" ? ERROR_CODES.ERR_NO_AUTH : ERROR_CODES.ERR_BAD_ENUM;
32449
- return fail(`closure-rate: ${e.message}`, code === ERROR_CODES.ERR_BAD_ENUM ? void 0 : { code });
32450
- }
32451
- const err = e;
32452
- return fail(`closure-rate: ${(err.stderr || err.message || String(e)).trim()}`);
32453
- }
32454
- function registerLearningClosureRateCommand(program3) {
32455
- const deps = queryDeps();
32456
- withExamples(
32457
- program3.command("closure-rate").description(
32458
- "learning closure rate per loop kind (friction, lesson, red-team) computed at read from board + linked-PR/ruling evidence \u2014 a closed loop without a merged PR or a recorded ruling reads as open (#4440)"
32459
- ).option("--repo <owner/repo>", `repo holding the loop items (defaults to ${HUB_REPO}, the central Hub board)`).option("--json", "print the structured LearningClosureReport JSON (default is the human summary)").action(async (o) => {
32460
- try {
32461
- const report = await runLearningClosureRate(deps, { repo: o.repo });
32462
- if (o.json) console.log(JSON.stringify(report, null, 2));
32463
- else console.log(report.summary);
32464
- } catch (e) {
32465
- closureRateFail(e);
32466
- }
32467
- }),
32468
- [
32469
- "mmi-cli learning closure-rate",
32470
- "mmi-cli learning closure-rate --repo mutmutco/MMI-Hub --json"
32471
- ],
32472
- "loop items are read newest-first with a bounded cap of 100 per kind \u2014 a rate over a longer history needs the cap raised, never an unbounded scan"
32473
- );
32474
- }
32475
-
32476
32496
  // ../infra/src/fleet-health.ts
32477
32497
  function uniqueRepos(repos) {
32478
32498
  const unique = /* @__PURE__ */ new Map();
@@ -37961,7 +37981,7 @@ pr.command("ci-policy").description("report merge CI policy: wait-for-checks vs
37961
37981
  if (o.json) return printLine(JSON.stringify(result));
37962
37982
  printLine(`merge CI policy: ${result.policy} (${result.reason})`);
37963
37983
  });
37964
- pr.command("checks-wait <number>").description(`bounded wait for PR checks; skips immediately on no-ci repos (#1432), fails immediately on a CONFLICTING PR \u2014 GitHub never queues checks for one (#2970). REST-only polling with a pool floor (#3024). Default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m; --timeout raises it. Exit 1 = a check FAILED or the PR is CONFLICTING, exit ${PR_CHECKS_TIMEOUT_EXIT_CODE} = the wait window expired or the API pool ran dry (re-arm)`).option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--timeout <minutes>", `wait budget in minutes (default ${PR_CHECKS_TIMEOUT_MS / 6e4}) \u2014 raise it for serial self-hosted e2e queues`).action(async (number, o) => {
37984
+ pr.command("checks-wait <number>").description(`bounded wait for ALL checks on the PR head, required or not (#5336); skips immediately on no-ci repos (#1432), fails immediately on a CONFLICTING PR \u2014 GitHub never queues checks for one (#2970). REST-only polling with a pool floor (#3024). Default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m; --timeout raises it. Exit 1 = a check FAILED or the PR is CONFLICTING, exit ${PR_CHECKS_TIMEOUT_EXIT_CODE} = the wait window expired or the API pool ran dry (re-arm)`).option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--timeout <minutes>", `wait budget in minutes (default ${PR_CHECKS_TIMEOUT_MS / 6e4}) \u2014 raise it for serial self-hosted e2e queues`).action(async (number, o) => {
37965
37985
  let timeoutMs;
37966
37986
  if (o.timeout !== void 0) {
37967
37987
  const minutes = Number(o.timeout);
@@ -37976,10 +37996,12 @@ pr.command("checks-wait <number>").description(`bounded wait for PR checks; skip
37976
37996
  const snapshot = snapshotRead.snapshot;
37977
37997
  const baseBranch = snapshot.baseRef;
37978
37998
  const ciHeadRef = !snapshot.headIsFork && snapshot.headRef ? snapshot.headRef : void 0;
37979
- const requiredContexts = await fetchRequiredCheckContexts(repo, baseBranch).catch(() => null);
37980
37999
  const result = await waitForPrChecks({
37981
38000
  resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef),
37982
- pollChecks: () => pollRestPrChecks(number, repo, void 0, requiredContexts),
38001
+ // #5336: ALL checks on the PR head — no required-status scoping here. A non-required check still
38002
+ // pending must hold the wait open, and a non-required red must fail it; #4396's scoping is the
38003
+ // merge paths' contract (`pr land`, `pr merge --wait`), which mirror the GitHub merge button.
38004
+ pollChecks: () => pollRestPrChecks(number, repo),
37983
38005
  pollMergeable: () => pollRestPrMergeable(number, repo),
37984
38006
  pollRateLimit: () => waitLoopCorePool("pr checks-wait"),
37985
38007
  // #3388: on a confirmed red, read the failing runs' annotations so a wall-clock budget kill stops
@@ -451,16 +451,23 @@ function runEmbedderOnce(cwd, chunks, modelDirectory, createdAt) {
451
451
  const result = (0, import_node_child_process3.spawnSync)(process.execPath, [file], { input: JSON.stringify(request), encoding: "utf8", windowsHide: true, timeout: V4_EMBED_TIMEOUT_MS, maxBuffer: V4_MAX_ARTIFACT_BYTES, env });
452
452
  if (result.error || result.status !== 0) {
453
453
  const cleanExit = result.error === void 0 && result.signal === void 0 && typeof result.status === "number" && result.status !== 0;
454
+ const stdout = String(result.stdout).trim();
455
+ const stderrTail = String(result.stderr).trim().slice(-400) || void 0;
454
456
  let code;
455
- let stderrTail;
456
457
  if (cleanExit) {
457
458
  try {
458
- code = JSON.parse(String(result.stdout))?.code;
459
+ code = JSON.parse(stdout)?.code;
459
460
  } catch {
460
461
  }
461
- stderrTail = String(result.stderr).trim().slice(-400) || void 0;
462
462
  }
463
- return { ok: false, code, stderrTail, cleanExit, detail: result.error?.message ?? result.signal ?? `exit status ${result.status ?? "unknown"}` };
463
+ return {
464
+ ok: false,
465
+ code,
466
+ stdoutTail: code ? void 0 : stdout.slice(-400) || void 0,
467
+ stderrTail,
468
+ cleanExit,
469
+ detail: result.error?.message ?? result.signal ?? `exit status ${result.status ?? "unknown"}`
470
+ };
464
471
  }
465
472
  try {
466
473
  const response = JSON.parse(result.stdout);
@@ -490,8 +497,9 @@ function runEmbedder(cwd, chunks, modelDirectory, createdAt) {
490
497
  }
491
498
  const runner = last;
492
499
  const code = runner.code ? ` code=${runner.code}` : "";
500
+ const stdout = runner.stdoutTail ? ` stdout=${runner.stdoutTail}` : "";
493
501
  const stderr = runner.stderrTail ? ` stderr=${runner.stderrTail}` : "";
494
- throw new Error(`repo-index v4 embedding runner failed: ${runner.detail ?? "unknown"}${code}${stderr}`);
502
+ throw new Error(`repo-index v4 embedding runner failed: ${runner.detail ?? "unknown"}${code}${stdout}${stderr}`);
495
503
  }
496
504
  async function buildRepoIndexV4(cwd, repo, opts = {}) {
497
505
  const { commit, defaultBranch, createdAt } = gitInfo(cwd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.0.12",
3
+ "version": "4.0.13",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",