@mutmutco/cli 4.6.13 → 4.6.14
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 +173 -24
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -13880,6 +13880,49 @@ function boardClaimStatusFlagRecovery(flag, canonicalPath, argv) {
|
|
|
13880
13880
|
corrected: `mmi-cli ${kept.join(" ")}`
|
|
13881
13881
|
};
|
|
13882
13882
|
}
|
|
13883
|
+
function boardClaimLaneFlagRecovery(flag, canonicalPath, argv) {
|
|
13884
|
+
if (canonicalPath !== "oracle board claim") return void 0;
|
|
13885
|
+
if (!/^--(?:agent|actor|on-behalf)(?:=|$)/.test(flag)) return void 0;
|
|
13886
|
+
const baseFlag = flag.split("=")[0];
|
|
13887
|
+
const kept = [];
|
|
13888
|
+
for (let i = 0; i < argv.length; i++) {
|
|
13889
|
+
const tok = argv[i];
|
|
13890
|
+
if (tok.startsWith(`${baseFlag}=`)) {
|
|
13891
|
+
kept.push(`--for=${tok.slice(baseFlag.length + 1)}`);
|
|
13892
|
+
continue;
|
|
13893
|
+
}
|
|
13894
|
+
if (tok === baseFlag) {
|
|
13895
|
+
kept.push("--for");
|
|
13896
|
+
if (i + 1 >= argv.length || argv[i + 1].startsWith("-")) kept.push("<login>");
|
|
13897
|
+
continue;
|
|
13898
|
+
}
|
|
13899
|
+
kept.push(tok);
|
|
13900
|
+
}
|
|
13901
|
+
return {
|
|
13902
|
+
message: `unknown option '${baseFlag}' \u2014 \`oracle board claim\` declares lane attribution as \`--for <login>\` ('assign to this login instead of @me \u2014 agent claims on behalf of the master')`,
|
|
13903
|
+
corrected: `mmi-cli ${kept.join(" ")}`
|
|
13904
|
+
};
|
|
13905
|
+
}
|
|
13906
|
+
var BOARD_CLAIM_AGENT_FLAGS = /* @__PURE__ */ new Set(["--agent", "--lane", "--by"]);
|
|
13907
|
+
function boardClaimAgentFlagRecovery(flag, canonicalPath, argv) {
|
|
13908
|
+
if (canonicalPath !== "oracle board claim") return void 0;
|
|
13909
|
+
const bare = flag.split("=")[0];
|
|
13910
|
+
if (!BOARD_CLAIM_AGENT_FLAGS.has(bare)) return void 0;
|
|
13911
|
+
const kept = [];
|
|
13912
|
+
for (let i = 0; i < argv.length; i++) {
|
|
13913
|
+
const tok = argv[i];
|
|
13914
|
+
if (tok.startsWith(`${bare}=`)) continue;
|
|
13915
|
+
if (tok === bare) {
|
|
13916
|
+
if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) i++;
|
|
13917
|
+
continue;
|
|
13918
|
+
}
|
|
13919
|
+
kept.push(tok);
|
|
13920
|
+
}
|
|
13921
|
+
return {
|
|
13922
|
+
message: `unknown option '${bare}' \u2014 \`oracle board claim\` stamps the claiming session itself \u2014 drop ${bare} and its value; \`--for <login>\` assigns on behalf of another login`,
|
|
13923
|
+
corrected: `mmi-cli ${kept.join(" ")}`
|
|
13924
|
+
};
|
|
13925
|
+
}
|
|
13883
13926
|
function issueCommentAppendOnlyRecovery(flag, canonicalPath, argv) {
|
|
13884
13927
|
if (canonicalPath !== "oracle issue comment") return void 0;
|
|
13885
13928
|
const hasEditVerb = argv.some((t, idx) => idx >= 3 && !t.startsWith("-") && /^(?:edit|update|patch|modify)$/i.test(t));
|
|
@@ -18996,9 +19039,10 @@ function surfaceInferenceSuffix(inferred) {
|
|
|
18996
19039
|
}
|
|
18997
19040
|
function uncertainSurfaceRefusal(inferred, command = "issue create") {
|
|
18998
19041
|
if (!inferred.uncertain) return void 0;
|
|
19042
|
+
const bareName = inferred.label.toLowerCase().startsWith(SURFACE_PREFIX) ? inferred.label.slice(SURFACE_PREFIX.length) : inferred.label;
|
|
18999
19043
|
const alternatives = inferred.alternatives?.length ? ` Other candidates: ${inferred.alternatives.join(", ")}.` : "";
|
|
19000
19044
|
return {
|
|
19001
|
-
message: `${command}: surface inference is uncertain (${inferred.reason}) \u2014 pass --surface <value> to choose explicitly or --no-surface only when the filing is genuinely exempt.${alternatives}`,
|
|
19045
|
+
message: `${command}: surface inference is uncertain (${inferred.reason}) \u2014 pass --surface <value> to choose explicitly or --no-surface only when the filing is genuinely exempt. The closest match was ${inferred.label} \u2014 re-run the same create with --surface ${bareName}.${alternatives}`,
|
|
19002
19046
|
payload: {
|
|
19003
19047
|
code: ERROR_CODES.ERR_MISSING_FLAG,
|
|
19004
19048
|
offending_flag: "--surface",
|
|
@@ -25472,7 +25516,7 @@ var BLESSED_RUNNER_GATE_SHA = "a19e9392e5ad9b0675f02e4304d68dd933fad60f";
|
|
|
25472
25516
|
var BLESSED_KIT_AGENT_FILES_SHA = "e976df208d891b215844386c1cac0f161946b6fa";
|
|
25473
25517
|
var BLESSED_JEV_FAILURES_SHA = "d5278c6133318b33727483ddfdfddc8f841e3ef6";
|
|
25474
25518
|
var BLESSED_VERDICT_RECEIPT_SHA = "";
|
|
25475
|
-
var BLESSED_RUNNER_CHOICE_SHA = "
|
|
25519
|
+
var BLESSED_RUNNER_CHOICE_SHA = "cf35db6cdf053746bc5b2cd75f241a7d93cf7f48";
|
|
25476
25520
|
var BLESSED_RUNBOOK_GUARD_SHA = "e661e0aedc0e58b73c738bf6cdfeac65e5914584";
|
|
25477
25521
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
25478
25522
|
var LOCAL_USE = "./.github/actions/run-with-budget";
|
|
@@ -27702,10 +27746,10 @@ var rollout_plan_default = {
|
|
|
27702
27746
|
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)."
|
|
27703
27747
|
},
|
|
27704
27748
|
baseline: {
|
|
27705
|
-
version: "4.6.
|
|
27706
|
-
tag: "v4.6.
|
|
27707
|
-
commit: "
|
|
27708
|
-
npm: "@mutmutco/cli@4.6.
|
|
27749
|
+
version: "4.6.14",
|
|
27750
|
+
tag: "v4.6.14",
|
|
27751
|
+
commit: "5bbc73222ade",
|
|
27752
|
+
npm: "@mutmutco/cli@4.6.14"
|
|
27709
27753
|
},
|
|
27710
27754
|
exitCriterion: "fleet-n-of-n",
|
|
27711
27755
|
hubOnlyShortcut: "forbidden",
|
|
@@ -27722,14 +27766,14 @@ var rollout_plan_default = {
|
|
|
27722
27766
|
repo: "mutmutco/mmi-hub",
|
|
27723
27767
|
role: "canary",
|
|
27724
27768
|
schedule: "train",
|
|
27725
|
-
v3Target: "v4.6.
|
|
27769
|
+
v3Target: "v4.6.14"
|
|
27726
27770
|
}
|
|
27727
27771
|
],
|
|
27728
27772
|
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.",
|
|
27729
27773
|
rollback: {
|
|
27730
27774
|
independent: true,
|
|
27731
|
-
mechanism: "npm dist-tag latest -> 4.6.
|
|
27732
|
-
v3Target: "v4.6.
|
|
27775
|
+
mechanism: "npm dist-tag latest -> 4.6.14 and redeploy the Hub Lambda from tag v4.6.14 (5bbc73222ade); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
27776
|
+
v3Target: "v4.6.14 (@mutmutco/cli@4.6.14, tag commit 5bbc73222ade \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
27733
27777
|
}
|
|
27734
27778
|
},
|
|
27735
27779
|
{
|
|
@@ -32308,7 +32352,7 @@ var STANDARD_HOSTED_ALLOWLIST = [
|
|
|
32308
32352
|
["mutmutco/MMI-Hub", ".github/workflows/launcher-build.yml", "build", "windows-latest"],
|
|
32309
32353
|
["mutmutco/Jerv-JervCode", ".github/workflows/release-archives.yml", "release-platform-arm64", "windows-11-arm"]
|
|
32310
32354
|
];
|
|
32311
|
-
var HOSTED_SPILL_RE = /^\$\{\{\s*(?:matrix\.[\w-]+\s*==\s*'[\w-]+'\s*&&\s*'([^']+)'\s*\|\|\s*)?fromJSON\(needs\.([\w-]+)\.outputs\.(
|
|
32355
|
+
var HOSTED_SPILL_RE = /^\$\{\{\s*(?:matrix\.[\w-]+\s*==\s*'[\w-]+'\s*&&\s*(?:'(?<leg>[^']+)'|fromJSON\(needs\.(?<legDecider>[\w-]+)\.outputs\.(?<legOutput>\w+_runs_on)\s*\|\|\s*(?<legOwned>'[^']+')\))\s*\|\|\s*)?fromJSON\(needs\.(?<decider>[\w-]+)\.outputs\.(?<output>\w+_runs_on)\s*\|\|\s*(?<owned>'[^']+')\)\s*\}\}$/;
|
|
32312
32356
|
var RUNNER_CHOICE_ACTION = "mutmutco/MMI-Hub/.github/actions/runner-choice";
|
|
32313
32357
|
function runnerChoiceProduces(job, decider, output, source) {
|
|
32314
32358
|
const needs = job.needs;
|
|
@@ -32338,8 +32382,11 @@ function classifyJobRunnerCost(job, allowance2, source) {
|
|
|
32338
32382
|
if (approved(raw) || allowance2 === "windows-11-arm" && raw === allowance2) return "standard-hosted";
|
|
32339
32383
|
const fallback = /^\$\{\{\s*vars\.MMI_RUNNER_FALLBACK\s*==\s*'hosted'\s*&&\s*'([^']+)'\s*\|\|\s*(fromJSON\('[^']+'\))\s*\}\}$/.exec(raw);
|
|
32340
32384
|
if (fallback && approved(fallback[1]) && classifyRunnerCost("${{ " + fallback[2] + " }}") === "self-hosted") return "standard-hosted";
|
|
32341
|
-
const
|
|
32342
|
-
if (
|
|
32385
|
+
const layerZero = /^\$\{\{\s*vars\.[\w-]+\s*==\s*'off'\s*&&\s*(fromJSON\('[^']+'\))\s*\|\|\s*'([^']+)'\s*\}\}$/.exec(raw);
|
|
32386
|
+
if (layerZero && classifyRunnerCost("${{ " + layerZero[1] + " }}") === "self-hosted" && STANDARD_HOSTED_LABEL_RE.test(layerZero[2])) return "standard-hosted";
|
|
32387
|
+
const spill = HOSTED_SPILL_RE.exec(raw)?.groups;
|
|
32388
|
+
const legOk = (g) => g.legDecider ? classifyRunnerCost(`\${{ fromJSON(${g.legOwned}) }}`) === "self-hosted" && Boolean(source) && runnerChoiceProduces(job, g.legDecider, g.legOutput, source) : !g.leg || STANDARD_HOSTED_LABEL_RE.test(g.leg);
|
|
32389
|
+
if (spill && source && legOk(spill) && classifyRunnerCost(`\${{ fromJSON(${spill.owned}) }}`) === "self-hosted" && runnerChoiceProduces(job, spill.decider, spill.output, source)) return "standard-hosted";
|
|
32343
32390
|
const direct = classifyRunnerCost(raw);
|
|
32344
32391
|
const matrix = /^\$\{\{\s*matrix\.([\w-]+)(?:\.([\w-]+))?\s*\}\}$/.exec(raw);
|
|
32345
32392
|
if (!matrix || direct === "hosted") return direct;
|
|
@@ -39219,6 +39266,92 @@ function auditRunnerFallbacks(repo, files, closure) {
|
|
|
39219
39266
|
remediation: f.cost === "hosted" || f.cost === "unknown" ? "Resolve source reads and unsupported selectors/callees; preserve checks and artifacts. See gh-runner-runbook.md." : void 0
|
|
39220
39267
|
}));
|
|
39221
39268
|
}
|
|
39269
|
+
var yamlStrings = (value) => typeof value === "string" ? [value] : value && typeof value === "object" ? Object.values(value).flatMap(yamlStrings) : [];
|
|
39270
|
+
var survivesDeciderFailure = (job) => {
|
|
39271
|
+
const guard = typeof job.if === "string" ? job.if.replace(/^\s*\$\{\{\s*|\s*\}\}\s*$/g, "").trim() : "";
|
|
39272
|
+
return (guard === "!cancelled()" || guard.startsWith("!cancelled() &&") || /\balways\(\)/.test(guard)) && !/\bsuccess\(\)/.test(guard);
|
|
39273
|
+
};
|
|
39274
|
+
function auditDeciderSkipTrap(repo, files) {
|
|
39275
|
+
const checks = [];
|
|
39276
|
+
for (const { path: path2, body } of files) {
|
|
39277
|
+
let doc;
|
|
39278
|
+
try {
|
|
39279
|
+
doc = (0, import_yaml2.parse)(body);
|
|
39280
|
+
} catch {
|
|
39281
|
+
continue;
|
|
39282
|
+
}
|
|
39283
|
+
const jobs = plainRecord(doc) && plainRecord(doc.jobs) ? Object.entries(doc.jobs).filter(([, j]) => plainRecord(j)) : [];
|
|
39284
|
+
const deciders = /* @__PURE__ */ new Set();
|
|
39285
|
+
for (const [id, job] of jobs) {
|
|
39286
|
+
const selector = [
|
|
39287
|
+
typeof job["runs-on"] === "string" ? job["runs-on"] : "",
|
|
39288
|
+
...typeof job.uses === "string" ? yamlStrings(job.with) : []
|
|
39289
|
+
].join("\n");
|
|
39290
|
+
const found = [...new Set([...selector.matchAll(/needs\.([\w-]+)\.outputs\.\w+_runs_on/g)].map((m) => m[1]))];
|
|
39291
|
+
if (!found.length) continue;
|
|
39292
|
+
for (const d of found) deciders.add(d);
|
|
39293
|
+
const guard = typeof job.if === "string" ? job.if.replace(/^\s*\$\{\{\s*|\s*\}\}\s*$/g, "").trim() : "";
|
|
39294
|
+
const guarded = survivesDeciderFailure(job);
|
|
39295
|
+
checks.push({
|
|
39296
|
+
ok: guarded,
|
|
39297
|
+
label: `job reading decider ${found.map((d) => `needs.${d}`).join(", ")} outputs survives a failed decider (#9670): ${repo}/${path2}:${id}`,
|
|
39298
|
+
detail: guarded ? void 0 : `${id}: ${guard ? `if: \`${guard}\`` : "no if:"} lets a failed decider SKIP this job, and a skipped required check counts as passing`,
|
|
39299
|
+
remediation: guarded ? void 0 : `Start the job's if: with \`!cancelled() &&\` (or use \`always()\`), never \`success()\`, so it runs on the owned fallback when ${found[0]} fails. See gh-runner-runbook.md.`
|
|
39300
|
+
});
|
|
39301
|
+
if (typeof job.uses === "string") {
|
|
39302
|
+
const uses = job.uses;
|
|
39303
|
+
const callee = uses.startsWith("./.github/workflows/") ? files.find((file) => file.path === uses.slice(2)) : void 0;
|
|
39304
|
+
let target;
|
|
39305
|
+
if (callee) {
|
|
39306
|
+
try {
|
|
39307
|
+
target = (0, import_yaml2.parse)(callee.body);
|
|
39308
|
+
} catch {
|
|
39309
|
+
}
|
|
39310
|
+
}
|
|
39311
|
+
const targetJobs = plainRecord(target) && plainRecord(target.jobs) ? target.jobs : void 0;
|
|
39312
|
+
if (!targetJobs) checks.push({
|
|
39313
|
+
ok: false,
|
|
39314
|
+
label: `unverified decider consumer (#9678): ${repo}/${path2}:${id}`,
|
|
39315
|
+
detail: `${id}: reusable workflow ${job.uses} is unresolved; decider runner inputs cannot be verified`
|
|
39316
|
+
});
|
|
39317
|
+
else {
|
|
39318
|
+
const inputs = plainRecord(job.with) ? Object.entries(job.with).filter(([, value]) => yamlStrings(value).some((text) => /needs\.[\w-]+\.outputs\.\w+_runs_on/.test(text))).map(([name]) => name) : [];
|
|
39319
|
+
for (const [targetId, targetJob] of Object.entries(targetJobs)) {
|
|
39320
|
+
if (!plainRecord(targetJob) || typeof targetJob["runs-on"] !== "string") continue;
|
|
39321
|
+
if (!inputs.some((name) => new RegExp(`inputs\\.${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w-])`).test(targetJob["runs-on"]))) continue;
|
|
39322
|
+
const ok = survivesDeciderFailure(targetJob);
|
|
39323
|
+
checks.push({
|
|
39324
|
+
ok,
|
|
39325
|
+
label: `reusable decider consumer survives a failed decider (#9678): ${repo}/${callee.path}:${targetId}`,
|
|
39326
|
+
detail: ok ? void 0 : `${id} -> ${targetId}: callee runner input needs !cancelled() or always(), never success()`
|
|
39327
|
+
});
|
|
39328
|
+
}
|
|
39329
|
+
}
|
|
39330
|
+
}
|
|
39331
|
+
}
|
|
39332
|
+
if (deciders.size) {
|
|
39333
|
+
const possibleResult = /\btoJSON\s*\(\s*needs\s*\)|\bneeds\s*\.\s*\*\s*\.\s*result\b|\bneeds(?:\.[\w-]+)?\s*\[\s*(?!['"][\w-]+['"]\s*\])/;
|
|
39334
|
+
for (const [id, job] of jobs) if (yamlStrings(job).some((text) => possibleResult.test(text))) {
|
|
39335
|
+
checks.push({
|
|
39336
|
+
ok: false,
|
|
39337
|
+
label: `possible decider result read (#9678): ${repo}/${path2}:${id}`,
|
|
39338
|
+
detail: `${id}: toJSON(needs), wildcard result or dynamic needs read may expose a failed decider result; use only explicit decider outputs`
|
|
39339
|
+
});
|
|
39340
|
+
}
|
|
39341
|
+
}
|
|
39342
|
+
for (const decider of deciders) {
|
|
39343
|
+
const read = new RegExp(String.raw`needs(?:\.${decider}|\[\s*['"]${decider}['"]\s*\])(?:\.result|\[\s*['"]result['"]\s*\])`);
|
|
39344
|
+
const readers = jobs.filter(([, job]) => yamlStrings(job).some((text) => read.test(text))).map(([id]) => id);
|
|
39345
|
+
checks.push({
|
|
39346
|
+
ok: readers.length === 0,
|
|
39347
|
+
label: `needs.${decider}.result is never read (#9670): ${repo}/${path2}`,
|
|
39348
|
+
detail: readers.length ? `${readers.join(", ")} read needs.${decider}.result; a decider that failed to start has no result to trust` : void 0,
|
|
39349
|
+
remediation: readers.length ? `Remove the needs.${decider}.result read from ${readers.join(", ")}; the empty ${decider} output already selects the owned fallback. See gh-runner-runbook.md.` : void 0
|
|
39350
|
+
});
|
|
39351
|
+
}
|
|
39352
|
+
}
|
|
39353
|
+
return checks;
|
|
39354
|
+
}
|
|
39222
39355
|
var LIVE_SELF_HOSTED_LINUX_LABELS = /* @__PURE__ */ new Set([
|
|
39223
39356
|
"self-hosted",
|
|
39224
39357
|
"linux",
|
|
@@ -40194,6 +40327,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
40194
40327
|
} catch (e) {
|
|
40195
40328
|
checks.push({ ok: false, label: "runner workflow source is readable (#8958)", detail: e instanceof WorkflowSourceError ? e.message : "workflow source: read-failed" });
|
|
40196
40329
|
}
|
|
40330
|
+
checks.push(...auditDeciderSkipTrap(repo, workflows));
|
|
40197
40331
|
checks.push(...auditSelfHostedRunnerHygiene(workflows));
|
|
40198
40332
|
checks.push(...auditJevOutcomes(workflows));
|
|
40199
40333
|
checks.push(...auditVerdictReceipt(workflows));
|
|
@@ -41972,11 +42106,13 @@ async function runPrLandOnce(prNumber, options, deps) {
|
|
|
41972
42106
|
const preflight2 = await deps.preflightQueue(prNumber, repo, pinned);
|
|
41973
42107
|
if (preflight2 && preflight2.mergeStatus === "failed") {
|
|
41974
42108
|
const rawError = preflight2.error ?? "queue preparation needed";
|
|
42109
|
+
const nextAction = preflight2.preparation?.nextAction;
|
|
42110
|
+
const error = nextAction ? rawError.includes(nextAction) ? rawError : `${rawError} \u2014 ${nextAction}` : !rawError.includes("read pr queue") ? `${rawError} \u2014 ${prQueueReadHint(prNumber, repo)}` : rawError;
|
|
41975
42111
|
return {
|
|
41976
42112
|
...base,
|
|
41977
42113
|
mergeStatus: "failed",
|
|
41978
42114
|
...preflight2.preparation ? { preparation: preflight2.preparation } : {},
|
|
41979
|
-
error
|
|
42115
|
+
error
|
|
41980
42116
|
};
|
|
41981
42117
|
}
|
|
41982
42118
|
}
|
|
@@ -42030,7 +42166,8 @@ async function runPrLandOnce(prNumber, options, deps) {
|
|
|
42030
42166
|
}
|
|
42031
42167
|
if (merge.mergeStatus === "failed") {
|
|
42032
42168
|
const rawError = merge.error ?? "merge failed";
|
|
42033
|
-
const
|
|
42169
|
+
const nextAction = merge.preparation?.nextAction;
|
|
42170
|
+
const error = nextAction ? rawError.includes(nextAction) ? rawError : `${rawError} \u2014 ${nextAction}` : hasQueue && !rawError.includes("read pr queue") ? `${rawError} \u2014 ${prQueueReadHint(prNumber, repo)}` : rawError;
|
|
42034
42171
|
return { ...base, ...merge.preparation ? { preparation: merge.preparation } : {}, ...merge.outcomeUnknown ? { outcomeUnknown: true } : {}, error };
|
|
42035
42172
|
}
|
|
42036
42173
|
if (hasQueue) {
|
|
@@ -44417,7 +44554,7 @@ function refuseNotClaimable(e, json) {
|
|
|
44417
44554
|
process.exitCode = 1;
|
|
44418
44555
|
return true;
|
|
44419
44556
|
}
|
|
44420
|
-
var BOARD_READ_HELP = "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n\nWhen using Windows PowerShell 5.1, avoid shell redirects: PowerShell 5.1's `> file.json` is Out-File,\nwhich writes UTF-16LE with a BOM, and Node reading it as 'utf8' then fails JSON.parse at position 1\n(#5802). Use --out instead \u2014 the CLI writes the file itself as UTF-8:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n\nThe --json report is an OBJECT \u2014 never an array, and there is no top-level `items` list:\n {project, viewer, repo, primary, secondary, warnings, partial[, source, builtAt, eventCursor, ...]}\n`primary` is this repo and `secondary` the other repos in scope; each is a bucket object\n {userOwned, claimable, taken, unownedInFlight}\nand each bucket is an array of items. Filter the BUCKET, not the report:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n node -e \"const b=require('./.jerv/tmp/board.json');console.log(b.primary.claimable.length)\"\n\nAdd --flat with --json for one array across both scopes: {ref,number,title,status,assignees,labels,bucket,claim,openPullRequests}\n(#9595: claim is the parsed mmi-claim evidence object, null when unclaimed; openPullRequests lists the OPEN\n PRs whose closing-issue body links name the item. On the FULL JSON object that list lives at\n `report.pullRequests` \u2014 there is no top-level `openPullRequests`, and #9596 hand-written projections\n that assume one crash on `.filter`.)\n\nA cached snapshot read renders `source: cache \xB7 built <timestamp> \xB7 fresh|stale` (#9584) and carries\n`builtAt` under --json, so a same-turn re-read of the same builtAt buys nothing new.\n\nUnder --json, parse stdout ONLY and check the exit code first. A failure still prints a\nhuman-readable reason on stderr, so never pipe 2>&1 into a JSON parser (#9151) \u2014 the merged\ntext is not JSON and hides the real board failure.\n";
|
|
44557
|
+
var BOARD_READ_HELP = "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n\nWhen using Windows PowerShell 5.1, avoid shell redirects: PowerShell 5.1's `> file.json` is Out-File,\nwhich writes UTF-16LE with a BOM, and Node reading it as 'utf8' then fails JSON.parse at position 1\n(#5802). Use --out instead \u2014 the CLI writes the file itself as UTF-8:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n\nThe --json report is an OBJECT \u2014 never an array, and there is no top-level `items` list:\n {project, viewer, repo, primary, secondary, warnings, partial[, source, builtAt, eventCursor, ...]}\n`primary` is this repo and `secondary` the other repos in scope; each is a bucket object\n {userOwned, claimable, taken, unownedInFlight}\nand each bucket is an array of items. Filter the BUCKET, not the report:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n node -e \"const b=require('./.jerv/tmp/board.json');console.log(b.primary.claimable.length)\"\n\nAdd --flat with --json for one array across both scopes: {ref,number,title,status,assignees,labels,bucket,claim,openPullRequests}\n(#9595: claim is the parsed mmi-claim evidence object, null when unclaimed; openPullRequests lists the OPEN\n PRs whose closing-issue body links name the item. On the FULL JSON object that list lives at\n `report.pullRequests` \u2014 there is no top-level `openPullRequests`, and #9596 hand-written projections\n that assume one crash on `.filter`.)\n\nA cached snapshot read renders `source: cache \xB7 built <timestamp> \xB7 fresh|stale` (#9584) and carries\n`builtAt` under --json, so a same-turn re-read of the same builtAt buys nothing new.\n\nUnder --json, parse stdout ONLY and check the exit code first. A failure still prints a\nhuman-readable reason on stderr, so never pipe 2>&1 into a JSON parser (#9151) \u2014 the merged\ntext is not JSON and hides the real board failure.\n\nA zero-byte or empty .jerv/tmp/*.json means the read FAILED \u2014 `Unexpected end of JSON input` is\nthe symptom of shell-redirected empty stdout, not malformed JSON \u2014 re-run with --out (which writes\nthe file only on success) and check the exit code before parsing.\n\nChanging an existing item's Status is a move, never a hand-written `gh api graphql`\n`updateProjectV2ItemFieldValue` mutation with copied `PVT_`/`PVTI_` node ids and a manual Status\nfield-id lookup:\n mmi-cli oracle board move <status> <ref>\n";
|
|
44421
44558
|
function flattenBoardReport(report) {
|
|
44422
44559
|
const closingByRef = /* @__PURE__ */ new Map();
|
|
44423
44560
|
for (const pull of report.pullRequests ?? []) {
|
|
@@ -66222,12 +66359,12 @@ async function readRunLog(id, o, runner) {
|
|
|
66222
66359
|
const base = `repos/${repo}/actions/runs/${runId}`;
|
|
66223
66360
|
const pages = JSON.parse(await readAppApi(`${base}${attempt === void 0 ? "" : `/attempts/${attempt}`}/jobs`, runner));
|
|
66224
66361
|
const jobs = pages.flatMap((page) => page.jobs);
|
|
66225
|
-
const selected = jobs.filter((job) => jobName === void 0 ? FAILED2.has(job.conclusion ?? "") : job.name === jobName);
|
|
66362
|
+
const selected = jobs.filter((job) => jobName === void 0 ? FAILED2.has(job.conclusion ?? "") : job.name === jobName || String(job.id) === jobName);
|
|
66226
66363
|
const result = { runId, repo, jobs: [] };
|
|
66227
66364
|
if (!selected.length && jobName === void 0) return { result, exitCode: 1, message: `read run log: no matching job for run ${runId} in ${repo}; conclusions seen: ${[...new Set(jobs.map((j) => j.conclusion ?? "pending"))].join(", ") || "none"}` };
|
|
66228
66365
|
if (!selected.length) {
|
|
66229
66366
|
const listed = jobs.slice(0, 30).map((j) => `${j.name} (${j.id})`).join(", ");
|
|
66230
|
-
return { result, exitCode: 1, message: `read run log: --job takes the exact job name
|
|
66367
|
+
return { result, exitCode: 1, message: `read run log: --job takes the exact job name or numeric job id; jobs in run ${runId}: ${listed || "none"}${jobs.length > 30 ? `, \u2026+${jobs.length - 30} more` : ""}` };
|
|
66231
66368
|
}
|
|
66232
66369
|
for (const job of selected) {
|
|
66233
66370
|
const text = await readAppApi(`repos/${repo}/actions/jobs/${parseBrokerId(String(job.id), "job id")}/logs`, runner);
|
|
@@ -66236,7 +66373,7 @@ async function readRunLog(id, o, runner) {
|
|
|
66236
66373
|
return { result, exitCode: 0 };
|
|
66237
66374
|
}
|
|
66238
66375
|
function registerRunLogRead(run, runner) {
|
|
66239
|
-
run.command("log").description("read workflow job logs on the App pool: error lines with \xB13 context and bounded tail (#8136); defaults to failed jobs, never personal").requiredOption("--repo <owner/repo>", "target repo on github.com", logRepo).addOption(new Option("--failed", "select failure/cancelled/timed_out jobs (default)").conflicts("job")).addOption(new Option("--job <name>", "select jobs with this exact name").argParser(logJob).conflicts("failed")).option("--tail <n>", "last lines per job (1-2000; default 200)", (v) => String(parseBrokerInt(v, "--tail", 1, 2e3)), "200").option("--attempt <n>", "run attempt (1-1000; default latest)", (v) => String(parseBrokerInt(v, "--attempt", 1, 1e3))).option("--json", "emit runId, repo and jobs with failedStep/errorLines/tail").argument("<runId>", "workflow run number", (v) => parseBrokerId(v, "run id")).action(async (id, o) => {
|
|
66376
|
+
run.command("log").description("read workflow job logs on the App pool: error lines with \xB13 context and bounded tail (#8136); defaults to failed jobs, never personal").requiredOption("--repo <owner/repo>", "target repo on github.com", logRepo).addOption(new Option("--failed", "select failure/cancelled/timed_out jobs (default)").conflicts("job")).addOption(new Option("--job <name>", "select jobs with this exact name or numeric job id").argParser(logJob).conflicts("failed")).option("--tail <n>", "last lines per job (1-2000; default 200)", (v) => String(parseBrokerInt(v, "--tail", 1, 2e3)), "200").option("--attempt <n>", "run attempt (1-1000; default latest)", (v) => String(parseBrokerInt(v, "--attempt", 1, 1e3))).option("--json", "emit runId, repo and jobs with failedStep/errorLines/tail").argument("<runId>", "workflow run number", (v) => parseBrokerId(v, "run id")).action(async (id, o) => {
|
|
66240
66377
|
requireBrokerApp("read run log");
|
|
66241
66378
|
const env = brokerChildEnv();
|
|
66242
66379
|
try {
|
|
@@ -66324,6 +66461,11 @@ function localJqEnv(base = process.env) {
|
|
|
66324
66461
|
}
|
|
66325
66462
|
return env;
|
|
66326
66463
|
}
|
|
66464
|
+
function formatJqFailureMessage(code, stderr) {
|
|
66465
|
+
const base = `--jq expression failed (jq exit ${code}): ${stderr.trim()}`;
|
|
66466
|
+
if (!/Invalid escape/i.test(stderr)) return base;
|
|
66467
|
+
return `${base} \u2014 the expression is handed to jq verbatim (mmi-cli never re-quotes it), so if a shell or seat layer collapsed a backslash, use an escape-free form such as \`test("bundle[.]css")\` or \`contains("bundle.css")\`, or drop --jq and filter the JSON locally`;
|
|
66468
|
+
}
|
|
66327
66469
|
var systemJqRunner = (expr, input, env) => new Promise((resolveOut, reject2) => {
|
|
66328
66470
|
const child2 = (0, import_node_child_process20.spawn)("jq", [...process.platform === "win32" ? ["--binary"] : [], "-r", "-c", expr], { env, stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
66329
66471
|
let stdout = "";
|
|
@@ -66335,7 +66477,7 @@ var systemJqRunner = (expr, input, env) => new Promise((resolveOut, reject2) =>
|
|
|
66335
66477
|
stderr += chunk;
|
|
66336
66478
|
});
|
|
66337
66479
|
child2.on("error", (e) => reject2(e.code === "ENOENT" ? new Error("--jq needs the jq binary on PATH and none was found \u2014 install jq, or drop --jq and filter the --json output locally") : e));
|
|
66338
|
-
child2.on("close", (code) => code === 0 ? resolveOut(stdout) : reject2(new Error(
|
|
66480
|
+
child2.on("close", (code) => code === 0 ? resolveOut(stdout) : reject2(new Error(formatJqFailureMessage(code, stderr))));
|
|
66339
66481
|
child2.stdin.on("error", () => {
|
|
66340
66482
|
});
|
|
66341
66483
|
child2.stdin.end(input);
|
|
@@ -66949,7 +67091,7 @@ var RELEASE_LATEST_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/latest$`);
|
|
|
66949
67091
|
var RELEASE_TAG_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/tags/(${NAME})$`);
|
|
66950
67092
|
var RELEASE_ASSET_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/assets/\\d+$`);
|
|
66951
67093
|
var API_GET_ALLOWED_SHAPES = "repos/{owner}/{repo}/issues/comments/{id}, repos/{owner}/{repo}/issues/{n}/comments[?per_page=1-100&page=<n>, each key at most once], repos/{owner}/{repo}/pulls/{n}/files, repos/{owner}/{repo}/compare/{a}...{b}, repos/{owner}/{repo}/contents/{path}[?ref=<branch>], repos/{owner}/{repo}/actions/runs[?per_page=1-100&page=<n>&status=&branch=&event=&head_sha=<40-hex>&created=<date-range>&actor=, each key at most once], repos/{owner}/{repo}/actions/runs/{id}, repos/{owner}/{repo}/actions/runs/{id}/jobs[?per_page=1-100&page=<n>, each key at most once], repos/{owner}/{repo}/actions/runs/{id}/attempts/{n}/jobs[?per_page=1-100&page=<n>, each key at most once], repos/{owner}/{repo}/actions/runs/{id}/artifacts[?per_page=1-100&page=<n>, each key at most once], repos/{owner}/{repo}/actions/artifacts/{id}/zip (needs --out <file>), repos/{owner}/{repo}/actions/jobs/{id}/logs (needs --out <file>), repos/{owner}/{repo}/releases[?per_page=1-100&page=<n>, each key at most once], repos/{owner}/{repo}/releases/latest, repos/{owner}/{repo}/releases/tags/{tag}, repos/{owner}/{repo}/releases/assets/{id} (needs --out <file>)";
|
|
66952
|
-
var API_GET_RECOVERY = " No other GET read exists \u2014 do not retry path variants; use a dedicated mmi-cli subcommand or ask a human token holder. This refusal exits 1 on stderr with nothing on stdout: check the exit code and never merge stderr into stdout (2>&1) before JSON-parsing.";
|
|
67094
|
+
var API_GET_RECOVERY = " No other GET read exists \u2014 do not retry path variants; use a dedicated mmi-cli subcommand or ask a human token holder. This refusal exits 1 on stderr with nothing on stdout: check the exit code and never merge stderr into stdout (2>&1) before JSON-parsing. Piping empty stdout into JSON.parse hides the stderr refusal; check the exit code before parsing.";
|
|
66953
67095
|
var TYPED_READ_ROUTE_RE = new RegExp(`^repos/(${NAME})/(${NAME})/(issues|pulls)(?:/(comments/\\d+|\\d+)(?:/([a-z_]+))?)?$`);
|
|
66954
67096
|
function typedReadHint(path2) {
|
|
66955
67097
|
const match = TYPED_READ_ROUTE_RE.exec(path2.split("?")[0]);
|
|
@@ -66970,7 +67112,7 @@ function typedReadHint(path2) {
|
|
|
66970
67112
|
}
|
|
66971
67113
|
if (!item) return say(`mmi-cli devops read pr list --repo ${slug} --json number,title,state,headRefName`);
|
|
66972
67114
|
if (item.startsWith("comments/")) return say(`mmi-cli devops read pr view <pr> --repo ${slug} --json comments,reviews`);
|
|
66973
|
-
return say(`mmi-cli devops read pr view ${item} --repo ${slug} --json title,state,comments,reviews,commits,statusCheckRollup`);
|
|
67115
|
+
return say(`mmi-cli devops read pr view ${item} --repo ${slug} --json title,state,comments,reviews,commits,statusCheckRollup,mergedAt,mergeCommit,headRefOid`, "; `mergedAt`, `mergeCommit` and `headRefOid` carry the merge and head evidence, and `--json` is a field list callers may narrow or extend");
|
|
66974
67116
|
}
|
|
66975
67117
|
function runsReadHint(path2) {
|
|
66976
67118
|
const q = path2.indexOf("?");
|
|
@@ -67015,11 +67157,18 @@ function checkRunsReadHint(path2) {
|
|
|
67015
67157
|
const slug = `${match[1]}/${match[2]}`;
|
|
67016
67158
|
return ` Check-suite and check-run routes are deliberately outside the App allowlist \u2014 read per-check / per-job conclusions with the allowlisted \`MMI_ACTOR=app mmi-cli devops api get "repos/${slug}/actions/runs/<id>/jobs?per_page=100"\` or \`mmi-cli devops read run view <id> --repo ${slug} --json databaseId,workflowName,event,status,conclusion,jobs\`; check-run annotations and output text have no App-token read (ask a human token holder).`;
|
|
67017
67159
|
}
|
|
67160
|
+
function workflowRunsReadHint(path2) {
|
|
67161
|
+
const match = new RegExp(`^repos/(${NAME})/(${NAME})/actions/workflows(?:/([^/?#]+)(?:/runs)?)?(?:[/?#]|$)`).exec(path2.split("?")[0]);
|
|
67162
|
+
if (!match) return "";
|
|
67163
|
+
const [, owner, repo, workflow] = match;
|
|
67164
|
+
const slug = `${owner}/${repo}`;
|
|
67165
|
+
return ` Workflow routes are outside the App allowlist \u2014 use the typed workflow runs read \`MMI_ACTOR=app mmi-cli devops read run list --repo ${slug} --workflow ${workflow ?? "<workflow.yml>"} --branch <branch> --limit <n> --json databaseId,workflowName,event,status,conclusion\`, or the allowlisted REST runs list \`MMI_ACTOR=app mmi-cli devops api get "repos/${slug}/actions/runs?branch=<branch>&per_page=<n>"\`.`;
|
|
67166
|
+
}
|
|
67018
67167
|
function parseApiGetPath(rawPath) {
|
|
67019
67168
|
const path2 = rawPath.trim();
|
|
67020
67169
|
const refuse = () => {
|
|
67021
67170
|
const hint = /^[A-Za-z]:[/\\]/.test(path2) ? " Git Bash/MSYS may have rewritten a leading-slash argument; retry with MSYS_NO_PATHCONV=1 and the slash-free repos/... path. The endpoint must still be allowlisted." : "";
|
|
67022
|
-
const routeHints = `${typedReadHint(path2)}${runsReadHint(path2)}${orgScopedHint(path2)}${tagsReadHint(path2)}${branchRulesReadHint(path2)}${commitsReadHint(path2)}${checkRunsReadHint(path2)}${hint}`;
|
|
67171
|
+
const routeHints = `${typedReadHint(path2)}${runsReadHint(path2)}${orgScopedHint(path2)}${tagsReadHint(path2)}${branchRulesReadHint(path2)}${commitsReadHint(path2)}${checkRunsReadHint(path2)}${workflowRunsReadHint(path2)}${hint}`;
|
|
67023
67172
|
return new Error(`api get: refusing '${rawPath}' \u2014${routeHints}${API_GET_RECOVERY} Only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}.`);
|
|
67024
67173
|
};
|
|
67025
67174
|
if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
|
|
@@ -67079,7 +67228,7 @@ function registerApiReadCommands(program3, overrides) {
|
|
|
67079
67228
|
if (!zip && o.out !== void 0) {
|
|
67080
67229
|
return fail(`${shapes} \u2014 drop --out and let the JSON body go to stdout (pipe it into node/jq, or use --jq <expr>), or use the typed read (e.g. \`mmi-cli devops read run list --repo <owner/repo> --json databaseId,workflowName,event,status,conclusion\`). This refusal exits 1 on stderr with nothing written: check the exit code and never discard stderr (2>&1 / >/dev/null) before parsing.`);
|
|
67081
67230
|
}
|
|
67082
|
-
return fail(shapes);
|
|
67231
|
+
return fail(`${shapes} \u2014 append \`--out <file>\` to this command (e.g. \`MMI_ACTOR=app mmi-cli devops api get ${req.path} --out .jerv/tmp/job.log\`), or for job logs use the typed read \`MMI_ACTOR=app mmi-cli devops read run log <runId> --repo <owner/repo> --job <name|id>\`. Shell redirection (\`>\` / \`2>&1\`) is not a substitute: this refusal exits 1 on stderr with nothing written to stdout, so \`>\` leaves a zero-byte file \u2014 delete it and check the exit code.`);
|
|
67083
67232
|
}
|
|
67084
67233
|
const appRoute = req.kind.startsWith("actions-") || req.kind.startsWith("release-");
|
|
67085
67234
|
if (appRoute) requireBrokerApp("api get");
|
|
@@ -70650,7 +70799,7 @@ function envelopeAwareWriteErr(str2) {
|
|
|
70650
70799
|
const invoked = resolveCommandFromArgv(program2, argv);
|
|
70651
70800
|
const invokedPath = invoked ? canonicalPathFor(commandPath3(invoked)) ?? commandPath3(invoked) : void 0;
|
|
70652
70801
|
const canonicalArgv = canonicalArgvFor(argv);
|
|
70653
|
-
const claimStatus = invoked ? boardClaimStatusFlagRecovery(flag, invokedPath, canonicalArgv) ?? issueCommentAppendOnlyRecovery(flag, invokedPath, canonicalArgv) ?? trainPlanRedundantFlagRecovery(flag, invokedPath, canonicalArgv) : void 0;
|
|
70802
|
+
const claimStatus = invoked ? boardClaimStatusFlagRecovery(flag, invokedPath, canonicalArgv) ?? boardClaimLaneFlagRecovery(flag, invokedPath, canonicalArgv) ?? boardClaimAgentFlagRecovery(flag, invokedPath, canonicalArgv) ?? issueCommentAppendOnlyRecovery(flag, invokedPath, canonicalArgv) ?? trainPlanRedundantFlagRecovery(flag, invokedPath, canonicalArgv) : void 0;
|
|
70654
70803
|
if (claimStatus) {
|
|
70655
70804
|
if (argvWantsJson3()) {
|
|
70656
70805
|
process.stderr.write(
|
package/package.json
CHANGED