@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 +208 -186
- package/dist/repo-index-v4.cjs +13 -5
- package/package.json +1 -1
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.
|
|
10831
|
-
tag: "v4.0.
|
|
10832
|
-
commit: "
|
|
10833
|
-
npm: "@mutmutco/cli@4.0.
|
|
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.
|
|
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.
|
|
10857
|
-
v3Target: "v4.0.
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
package/dist/repo-index-v4.cjs
CHANGED
|
@@ -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(
|
|
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 {
|
|
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