@mutmutco/cli 4.0.11 → 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 +229 -193
- 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);
|
|
@@ -23624,6 +23635,7 @@ function shardRepoIndexV4(envelope) {
|
|
|
23624
23635
|
|
|
23625
23636
|
// src/repo-index-cloud-client.ts
|
|
23626
23637
|
var RETRY_ATTEMPTS2 = 3;
|
|
23638
|
+
var WARMUP_MAX_PASSES = 3;
|
|
23627
23639
|
async function probeRepoIndexV4ReadinessCloud(queries, deps) {
|
|
23628
23640
|
if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
|
|
23629
23641
|
const token = await deps.token();
|
|
@@ -23631,6 +23643,7 @@ async function probeRepoIndexV4ReadinessCloud(queries, deps) {
|
|
|
23631
23643
|
const baseUrl = deps.baseUrl.replace(/\/$/, "");
|
|
23632
23644
|
const headers = { ...clientVersionHeaders(), Authorization: ["Bearer", token].join(" "), "content-type": "application/json" };
|
|
23633
23645
|
const shadowPass = async (readinessProbe) => {
|
|
23646
|
+
const ms = {};
|
|
23634
23647
|
for (const query of queries) {
|
|
23635
23648
|
const res = await fetchWithRetry(deps.fetch ?? fetch, `${baseUrl}/repo-index/v4/shadow`, {
|
|
23636
23649
|
method: "POST",
|
|
@@ -23639,12 +23652,18 @@ async function probeRepoIndexV4ReadinessCloud(queries, deps) {
|
|
|
23639
23652
|
}, { attempts: RETRY_ATTEMPTS2, timeoutMs: 12e4, sleep: deps.retrySleep });
|
|
23640
23653
|
const body = await res.json().catch(() => ({}));
|
|
23641
23654
|
if (!res.ok) return { ok: false, error: `${query.id}: ${body.error ?? `v4 readiness probe HTTP ${res.status}`}${body.errorClass ? ` (${body.errorClass})` : ""}`, status: res.status };
|
|
23655
|
+
if (typeof body.v4?.ms === "number") ms[query.id] = body.v4.ms;
|
|
23642
23656
|
}
|
|
23643
|
-
return { ok: true };
|
|
23657
|
+
return { ok: true, ms };
|
|
23644
23658
|
};
|
|
23659
|
+
const steady = (ms) => queries.every((query) => query.latencyMs === void 0 || ms[query.id] === void 0 || ms[query.id] <= query.latencyMs);
|
|
23645
23660
|
try {
|
|
23646
|
-
|
|
23661
|
+
let warmup = await shadowPass(false);
|
|
23647
23662
|
if (!warmup.ok) return warmup;
|
|
23663
|
+
for (let pass = 2; pass <= WARMUP_MAX_PASSES && !steady(warmup.ms); pass++) {
|
|
23664
|
+
warmup = await shadowPass(false);
|
|
23665
|
+
if (!warmup.ok) return warmup;
|
|
23666
|
+
}
|
|
23648
23667
|
const verdict = await shadowPass(true);
|
|
23649
23668
|
if (!verdict.ok) return verdict;
|
|
23650
23669
|
const res = await fetchWithRetry(deps.fetch ?? fetch, `${baseUrl}/repo-index/status`, {
|
|
@@ -24256,15 +24275,21 @@ function formatRepoIndexCloudStatus(status) {
|
|
|
24256
24275
|
return `repo-index: cloud ${repo} v4 ${state}${commit}; ${readiness}`;
|
|
24257
24276
|
}
|
|
24258
24277
|
function defaultV4ReadinessSuite() {
|
|
24259
|
-
const
|
|
24260
|
-
const
|
|
24261
|
-
|
|
24278
|
+
const raw = repo_index_golden_queries_v4_default;
|
|
24279
|
+
const queries = (raw.queries ?? []).map(({ id, query, mode, latency }) => ({
|
|
24280
|
+
id,
|
|
24281
|
+
query,
|
|
24282
|
+
mode,
|
|
24283
|
+
...typeof latency?.p95Ms === "number" ? { latencyMs: latency.p95Ms } : {}
|
|
24284
|
+
}));
|
|
24285
|
+
const modes = new Set(queries.map((query) => query.mode));
|
|
24286
|
+
if (raw.schemaVersion !== 4 || !Array.isArray(raw.queries) || !["lexical", "semantic", "hybrid"].every((mode) => modes.has(mode))) {
|
|
24262
24287
|
throw new Error("invalid bundled repo-index-golden-queries-v4.json");
|
|
24263
24288
|
}
|
|
24264
|
-
return
|
|
24289
|
+
return { schemaVersion: 4, queries };
|
|
24265
24290
|
}
|
|
24266
24291
|
async function runRepoIndexV4ReadinessGate(opts) {
|
|
24267
|
-
const queries = opts.suite.queries.map(({ id, query, mode }) => ({ id, query, mode }));
|
|
24292
|
+
const queries = opts.suite.queries.map(({ id, query, mode, latencyMs }) => ({ id, query, mode, latencyMs }));
|
|
24268
24293
|
const probed = await opts.probe(queries);
|
|
24269
24294
|
if (!probed.ok) return { ok: false, findings: [{ ok: false, code: "v4-probe-error", detail: probed.error }] };
|
|
24270
24295
|
const ok = probed.readiness.verdict === "ready";
|
|
@@ -30460,6 +30485,175 @@ async function checkDocsIndexAtHead(opts, deps) {
|
|
|
30460
30485
|
// src/issue-commands.ts
|
|
30461
30486
|
var import_node_fs38 = require("node:fs");
|
|
30462
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
|
|
30463
30657
|
var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
|
|
30464
30658
|
var ReparentConflictError = class extends Error {
|
|
30465
30659
|
constructor(message, payload) {
|
|
@@ -30583,6 +30777,13 @@ async function closeIssue(client, options, deps = {}) {
|
|
|
30583
30777
|
if (options.evidence !== void 0) {
|
|
30584
30778
|
await verifyCloseEvidence(client, options.evidence, repo, parsed.number, reason === "duplicate-of" ? options.duplicateOf : void 0);
|
|
30585
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
|
+
}
|
|
30586
30787
|
await client.rest("PATCH", `repos/${repo}/issues/${parsed.number}`, {
|
|
30587
30788
|
body: { state: "closed", state_reason: stateReason }
|
|
30588
30789
|
});
|
|
@@ -32292,173 +32493,6 @@ function registerSessionReport(program3) {
|
|
|
32292
32493
|
});
|
|
32293
32494
|
}
|
|
32294
32495
|
|
|
32295
|
-
// src/learning-closure-rate.ts
|
|
32296
|
-
var CLOSURE_RATE_TIMEOUT_MS = 2e4;
|
|
32297
|
-
var LOOP_ITEM_CAP = 100;
|
|
32298
|
-
var EVIDENCE_CONCURRENCY = 4;
|
|
32299
|
-
var RED_TEAM_FINDING_LABEL2 = "red-team";
|
|
32300
|
-
var RULING_LOOP_STATE_LABELS = ["loop-state:closed-deferred", "loop-state:closed-invalid"];
|
|
32301
|
-
var CLOSED_MERGED_LOOP_STATE_LABEL = "loop-state:closed-merged";
|
|
32302
|
-
var LOOP_KINDS2 = [
|
|
32303
|
-
{ kind: "friction", label: REPORT_LABEL },
|
|
32304
|
-
{ kind: "lesson", label: SKILL_LESSON_LABEL },
|
|
32305
|
-
{ kind: "red-team", label: RED_TEAM_FINDING_LABEL2 }
|
|
32306
|
-
];
|
|
32307
|
-
function prIsMerged(pr2) {
|
|
32308
|
-
return String(pr2.state ?? "").toUpperCase() === "MERGED";
|
|
32309
|
-
}
|
|
32310
|
-
function mergedPrsOf(linkedPrs) {
|
|
32311
|
-
return linkedPrs.filter(prIsMerged);
|
|
32312
|
-
}
|
|
32313
|
-
function hasRecordedRuling(labels) {
|
|
32314
|
-
return labels.some((label) => RULING_LOOP_STATE_LABELS.includes(label));
|
|
32315
|
-
}
|
|
32316
|
-
function classifyLoopClosure(item, evidence) {
|
|
32317
|
-
if (String(item.state ?? "").toUpperCase() !== "CLOSED") return "open";
|
|
32318
|
-
if (mergedPrsOf(evidence.linkedPrs).length > 0) return "closed";
|
|
32319
|
-
if (item.labels.includes(CLOSED_MERGED_LOOP_STATE_LABEL)) return "closed";
|
|
32320
|
-
if (hasRecordedRuling(item.labels)) return "closed";
|
|
32321
|
-
return "closed-no-evidence";
|
|
32322
|
-
}
|
|
32323
|
-
var round3 = (x) => Math.round(x * 1e3) / 1e3;
|
|
32324
|
-
function computeLoopKindRate(spec, verdicts) {
|
|
32325
|
-
const numerator = verdicts.filter((v) => v === "closed").length;
|
|
32326
|
-
const unknownEvidence = verdicts.filter((v) => v === "closed-no-evidence").length;
|
|
32327
|
-
const denominator = verdicts.length;
|
|
32328
|
-
return {
|
|
32329
|
-
kind: spec.kind,
|
|
32330
|
-
label: spec.label,
|
|
32331
|
-
numerator,
|
|
32332
|
-
denominator,
|
|
32333
|
-
unknownEvidence,
|
|
32334
|
-
rate: denominator > 0 ? round3(numerator / denominator) : null
|
|
32335
|
-
};
|
|
32336
|
-
}
|
|
32337
|
-
function fmtRate(rate) {
|
|
32338
|
-
return rate === null ? "\u2014" : rate.toFixed(3);
|
|
32339
|
-
}
|
|
32340
|
-
function formatClosureSummary(report) {
|
|
32341
|
-
const kindWidth = Math.max(...report.kinds.map((k) => k.kind.length));
|
|
32342
|
-
const labelWidth = Math.max(...report.kinds.map((k) => k.label.length));
|
|
32343
|
-
const lines = [`learning closure rate \u2014 ${report.repo} (computed at read; #4440)`];
|
|
32344
|
-
for (const k of report.kinds) {
|
|
32345
|
-
const head = `${k.kind.padEnd(kindWidth)} (label ${k.label.padEnd(labelWidth)})`;
|
|
32346
|
-
const body = k.denominator === 0 ? "no loop items" : `${k.numerator}/${k.denominator} closed \xB7 ${k.unknownEvidence} unknown evidence \xB7 rate ${fmtRate(k.rate)}`;
|
|
32347
|
-
lines.push(` ${head}: ${body}`);
|
|
32348
|
-
}
|
|
32349
|
-
const t = report.totals;
|
|
32350
|
-
lines.push(
|
|
32351
|
-
` ${"total".padEnd(kindWidth)}: ${t.numerator}/${t.denominator} closed \xB7 ${t.unknownEvidence} unknown evidence \xB7 rate ${fmtRate(t.rate)}`,
|
|
32352
|
-
"A closed loop without a merged PR or a recorded ruling reads as open."
|
|
32353
|
-
);
|
|
32354
|
-
if (report.evidencePartial) {
|
|
32355
|
-
lines.push("note: at least one closed item had an unreadable linked-PR read \u2014 counted open (unknown evidence).");
|
|
32356
|
-
}
|
|
32357
|
-
return lines.join("\n");
|
|
32358
|
-
}
|
|
32359
|
-
function buildClosureReport(repo, perKind, evidencePartial) {
|
|
32360
|
-
const kinds = perKind.map(({ spec, verdicts }) => computeLoopKindRate(spec, verdicts));
|
|
32361
|
-
const numerator = kinds.reduce((acc, k) => acc + k.numerator, 0);
|
|
32362
|
-
const denominator = kinds.reduce((acc, k) => acc + k.denominator, 0);
|
|
32363
|
-
const unknownEvidence = kinds.reduce((acc, k) => acc + k.unknownEvidence, 0);
|
|
32364
|
-
const report = {
|
|
32365
|
-
repo,
|
|
32366
|
-
kinds,
|
|
32367
|
-
totals: {
|
|
32368
|
-
numerator,
|
|
32369
|
-
denominator,
|
|
32370
|
-
unknownEvidence,
|
|
32371
|
-
rate: denominator > 0 ? round3(numerator / denominator) : null
|
|
32372
|
-
},
|
|
32373
|
-
evidencePartial,
|
|
32374
|
-
summary: ""
|
|
32375
|
-
};
|
|
32376
|
-
report.summary = formatClosureSummary(report);
|
|
32377
|
-
return report;
|
|
32378
|
-
}
|
|
32379
|
-
function splitRepo2(repo) {
|
|
32380
|
-
const parts = repo.split("/");
|
|
32381
|
-
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
32382
|
-
throw new QueryReadError("BAD_INPUT", `invalid repo "${repo}" (expected owner/repo)`);
|
|
32383
|
-
}
|
|
32384
|
-
return { owner: parts[0], name: parts[1] };
|
|
32385
|
-
}
|
|
32386
|
-
async function mapPooled(items, limit, worker) {
|
|
32387
|
-
const out = new Array(items.length);
|
|
32388
|
-
let next = 0;
|
|
32389
|
-
const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
32390
|
-
while (next < items.length) {
|
|
32391
|
-
const index = next;
|
|
32392
|
-
next += 1;
|
|
32393
|
-
out[index] = await worker(items[index]);
|
|
32394
|
-
}
|
|
32395
|
-
});
|
|
32396
|
-
await Promise.all(runners);
|
|
32397
|
-
return out;
|
|
32398
|
-
}
|
|
32399
|
-
async function readEvidence(deps, owner, name, item) {
|
|
32400
|
-
try {
|
|
32401
|
-
const resp = await deps.ghJson(prForIssueGraphqlArgs(owner, name, item.number), CLOSURE_RATE_TIMEOUT_MS);
|
|
32402
|
-
return { linkedPrs: extractPrForIssueResponse(resp), readFailed: false };
|
|
32403
|
-
} catch (e) {
|
|
32404
|
-
if (e instanceof QueryReadError && e.code === "NOT_FOUND") {
|
|
32405
|
-
return { linkedPrs: [], readFailed: false };
|
|
32406
|
-
}
|
|
32407
|
-
return { linkedPrs: [], readFailed: true };
|
|
32408
|
-
}
|
|
32409
|
-
}
|
|
32410
|
-
async function runLearningClosureRate(deps, opts = {}) {
|
|
32411
|
-
const repo = opts.repo ?? HUB_REPO;
|
|
32412
|
-
const { owner, name } = splitRepo2(repo);
|
|
32413
|
-
let evidencePartial = false;
|
|
32414
|
-
const perKind = [];
|
|
32415
|
-
for (const spec of LOOP_KINDS2) {
|
|
32416
|
-
const rows = await deps.ghJson(
|
|
32417
|
-
buildIssueListArgs({ label: spec.label, state: "all", limit: LOOP_ITEM_CAP }, repo),
|
|
32418
|
-
CLOSURE_RATE_TIMEOUT_MS
|
|
32419
|
-
);
|
|
32420
|
-
const items = shapeIssueList(rows);
|
|
32421
|
-
const closed = items.filter((item) => String(item.state ?? "").toUpperCase() === "CLOSED");
|
|
32422
|
-
const evidence = await mapPooled(closed, EVIDENCE_CONCURRENCY, (item) => readEvidence(deps, owner, name, item));
|
|
32423
|
-
if (evidence.some((ev) => ev.readFailed)) evidencePartial = true;
|
|
32424
|
-
const evidenceByNumber = new Map(closed.map((item, i) => [item.number, evidence[i]]));
|
|
32425
|
-
const verdicts = items.map(
|
|
32426
|
-
(item) => classifyLoopClosure(item, evidenceByNumber.get(item.number) ?? { linkedPrs: [], readFailed: false })
|
|
32427
|
-
);
|
|
32428
|
-
perKind.push({ spec, verdicts });
|
|
32429
|
-
}
|
|
32430
|
-
return buildClosureReport(repo, perKind, evidencePartial);
|
|
32431
|
-
}
|
|
32432
|
-
function closureRateFail(e) {
|
|
32433
|
-
if (e instanceof QueryReadError) {
|
|
32434
|
-
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;
|
|
32435
|
-
return fail(`closure-rate: ${e.message}`, code === ERROR_CODES.ERR_BAD_ENUM ? void 0 : { code });
|
|
32436
|
-
}
|
|
32437
|
-
const err = e;
|
|
32438
|
-
return fail(`closure-rate: ${(err.stderr || err.message || String(e)).trim()}`);
|
|
32439
|
-
}
|
|
32440
|
-
function registerLearningClosureRateCommand(program3) {
|
|
32441
|
-
const deps = queryDeps();
|
|
32442
|
-
withExamples(
|
|
32443
|
-
program3.command("closure-rate").description(
|
|
32444
|
-
"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)"
|
|
32445
|
-
).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) => {
|
|
32446
|
-
try {
|
|
32447
|
-
const report = await runLearningClosureRate(deps, { repo: o.repo });
|
|
32448
|
-
if (o.json) console.log(JSON.stringify(report, null, 2));
|
|
32449
|
-
else console.log(report.summary);
|
|
32450
|
-
} catch (e) {
|
|
32451
|
-
closureRateFail(e);
|
|
32452
|
-
}
|
|
32453
|
-
}),
|
|
32454
|
-
[
|
|
32455
|
-
"mmi-cli learning closure-rate",
|
|
32456
|
-
"mmi-cli learning closure-rate --repo mutmutco/MMI-Hub --json"
|
|
32457
|
-
],
|
|
32458
|
-
"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"
|
|
32459
|
-
);
|
|
32460
|
-
}
|
|
32461
|
-
|
|
32462
32496
|
// ../infra/src/fleet-health.ts
|
|
32463
32497
|
function uniqueRepos(repos) {
|
|
32464
32498
|
const unique = /* @__PURE__ */ new Map();
|
|
@@ -37947,7 +37981,7 @@ pr.command("ci-policy").description("report merge CI policy: wait-for-checks vs
|
|
|
37947
37981
|
if (o.json) return printLine(JSON.stringify(result));
|
|
37948
37982
|
printLine(`merge CI policy: ${result.policy} (${result.reason})`);
|
|
37949
37983
|
});
|
|
37950
|
-
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) => {
|
|
37951
37985
|
let timeoutMs;
|
|
37952
37986
|
if (o.timeout !== void 0) {
|
|
37953
37987
|
const minutes = Number(o.timeout);
|
|
@@ -37962,10 +37996,12 @@ pr.command("checks-wait <number>").description(`bounded wait for PR checks; skip
|
|
|
37962
37996
|
const snapshot = snapshotRead.snapshot;
|
|
37963
37997
|
const baseBranch = snapshot.baseRef;
|
|
37964
37998
|
const ciHeadRef = !snapshot.headIsFork && snapshot.headRef ? snapshot.headRef : void 0;
|
|
37965
|
-
const requiredContexts = await fetchRequiredCheckContexts(repo, baseBranch).catch(() => null);
|
|
37966
37999
|
const result = await waitForPrChecks({
|
|
37967
38000
|
resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef),
|
|
37968
|
-
|
|
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),
|
|
37969
38005
|
pollMergeable: () => pollRestPrMergeable(number, repo),
|
|
37970
38006
|
pollRateLimit: () => waitLoopCorePool("pr checks-wait"),
|
|
37971
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