@mutmutco/claude-plugin 4.6.37 → 4.6.39
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/cli/dist/main.cjs
CHANGED
|
@@ -15691,6 +15691,9 @@ var APP_SUBPROCESS_ROUTED_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
15691
15691
|
// #9871: queue enqueue + auto-merge fallback ride ghFilingAppEnv (#9490/#9528; receipt credential app_installation).
|
|
15692
15692
|
"pr land"
|
|
15693
15693
|
]);
|
|
15694
|
+
function ineligibleActorNote(commandPath4) {
|
|
15695
|
+
return commandPath4 === "board move" ? "app actor: board move retains the existing process identity; status-write credential is selected separately and reported in its receipt" : `app actor: note, not a failure \u2014 '${commandPath4}' is personal-only (viewer/@me/authorship), so it ran on the personal token despite ${APP_ACTOR_ENV}=app`;
|
|
15696
|
+
}
|
|
15694
15697
|
async function activateAppActor(commandPath4, env, mint, note = (line) => process.stderr.write(`${line}
|
|
15695
15698
|
`)) {
|
|
15696
15699
|
activeQuotaScope = void 0;
|
|
@@ -15703,7 +15706,7 @@ async function activateAppActor(commandPath4, env, mint, note = (line) => proces
|
|
|
15703
15706
|
if (!APP_ELIGIBLE_COMMANDS.has(commandPath4) && !APP_WRITE_COMMANDS.has(commandPath4)) {
|
|
15704
15707
|
if (requested === "app" && !notedIneligibleCommand && !APP_SUBPROCESS_ROUTED_COMMANDS.has(commandPath4)) {
|
|
15705
15708
|
notedIneligibleCommand = true;
|
|
15706
|
-
note(
|
|
15709
|
+
note(ineligibleActorNote(commandPath4));
|
|
15707
15710
|
}
|
|
15708
15711
|
return "personal";
|
|
15709
15712
|
}
|
|
@@ -22767,23 +22770,8 @@ var BOARD_WRITE_REFUSAL_TYPES = /* @__PURE__ */ new Set(["INSUFFICIENT_SCOPES",
|
|
|
22767
22770
|
async function updateItemSingleSelect(client, projectId, itemId, fieldId, optionId) {
|
|
22768
22771
|
if (process.env.MMI_ACTOR?.trim() === "app") {
|
|
22769
22772
|
const { writeHubBoardField: writeHubBoardField2 } = await Promise.resolve().then(() => (init_board_write(), board_write_exports));
|
|
22770
|
-
|
|
22771
|
-
|
|
22772
|
-
return { credential: "app_installation" };
|
|
22773
|
-
} catch (error) {
|
|
22774
|
-
const refusal = error;
|
|
22775
|
-
if (refusal?.rateLimited !== true || refusal.status !== 503 && refusal.status !== 429) throw error;
|
|
22776
|
-
try {
|
|
22777
|
-
const identity = await githubKeyringTokenRead();
|
|
22778
|
-
if (identity.state !== "ok") throw error;
|
|
22779
|
-
const owner = createGitHubClient({ token: async () => identity.token });
|
|
22780
|
-
await owner.graphql(UPDATE_ITEM_FIELD_MUTATION, { projectId, itemId, fieldId, optionId });
|
|
22781
|
-
} catch {
|
|
22782
|
-
throw error;
|
|
22783
|
-
}
|
|
22784
|
-
process.stderr.write("board write: Hub App allowance exhausted \u2014 wrote with your own GitHub login (#9053)\n");
|
|
22785
|
-
return { credential: "user" };
|
|
22786
|
-
}
|
|
22773
|
+
await writeHubBoardField2({ projectId, itemId, fieldId, optionId });
|
|
22774
|
+
return { credential: "app_installation" };
|
|
22787
22775
|
}
|
|
22788
22776
|
try {
|
|
22789
22777
|
await client.graphql(UPDATE_ITEM_FIELD_MUTATION, { projectId, itemId, fieldId, optionId });
|
|
@@ -22928,19 +22916,20 @@ function renderBoardItem(item) {
|
|
|
22928
22916
|
}
|
|
22929
22917
|
return lines2.join("\n");
|
|
22930
22918
|
}
|
|
22919
|
+
var CACHED_CONFIRM_CLAUSE = " \xB7 items closed/created after the build may still differ \u2014 confirm with --direct";
|
|
22931
22920
|
function renderCachedBoardSource(report) {
|
|
22932
22921
|
if (typeof report.stale?.ageMs === "number") {
|
|
22933
22922
|
const ageSeconds = Math.floor(report.stale.ageMs / 1e3);
|
|
22934
|
-
return `source: cache \xB7 built ${report.builtAt ? `${report.builtAt} (${ageSeconds}s old)` : `${ageSeconds}s ago`} \xB7 stale`;
|
|
22923
|
+
return `source: cache \xB7 built ${report.builtAt ? `${report.builtAt} (${ageSeconds}s old)` : `${ageSeconds}s ago`} \xB7 stale${CACHED_CONFIRM_CLAUSE}`;
|
|
22935
22924
|
}
|
|
22936
22925
|
if (report.builtAt) {
|
|
22937
22926
|
const builtMs = Date.parse(report.builtAt);
|
|
22938
22927
|
if (Number.isFinite(builtMs)) {
|
|
22939
|
-
return `source: cache \xB7 built ${report.builtAt} (${Math.max(0, Math.floor((Date.now() - builtMs) / 1e3))}s old) \xB7 fresh`;
|
|
22928
|
+
return `source: cache \xB7 built ${report.builtAt} (${Math.max(0, Math.floor((Date.now() - builtMs) / 1e3))}s old) \xB7 fresh${CACHED_CONFIRM_CLAUSE}`;
|
|
22940
22929
|
}
|
|
22941
|
-
return `source: cache \xB7 built ${report.builtAt} \xB7 fresh`;
|
|
22930
|
+
return `source: cache \xB7 built ${report.builtAt} \xB7 fresh${CACHED_CONFIRM_CLAUSE}`;
|
|
22942
22931
|
}
|
|
22943
|
-
return
|
|
22932
|
+
return `source: cache${CACHED_CONFIRM_CLAUSE}`;
|
|
22944
22933
|
}
|
|
22945
22934
|
function renderBoardReport(report) {
|
|
22946
22935
|
const lines2 = [`Board \xB7 ${report.project.title} \xB7 @${report.viewer}`, report.source === "cache" ? renderCachedBoardSource(report) : renderBoardSource()];
|
|
@@ -25235,6 +25224,20 @@ function upsertManagedSeedBlock(current, desired, begin, end) {
|
|
|
25235
25224
|
action: "replace"
|
|
25236
25225
|
};
|
|
25237
25226
|
}
|
|
25227
|
+
async function headStrictlyBehind(execGit, head, remoteSha, defaultBranch) {
|
|
25228
|
+
try {
|
|
25229
|
+
await execGit(["fetch", "--no-tags", "--quiet", "origin", `refs/heads/${defaultBranch}`]);
|
|
25230
|
+
} catch (e) {
|
|
25231
|
+
return { superseded: false, unknown: `could not fetch origin/${defaultBranch} to check ancestry (${e.message})` };
|
|
25232
|
+
}
|
|
25233
|
+
try {
|
|
25234
|
+
await execGit(["merge-base", "--is-ancestor", head, remoteSha]);
|
|
25235
|
+
return { superseded: true };
|
|
25236
|
+
} catch (e) {
|
|
25237
|
+
if (e.code === 1) return { superseded: false };
|
|
25238
|
+
return { superseded: false, unknown: `could not prove whether this checkout is an ancestor of origin/${defaultBranch} (${e.message}) \u2014 a shallow checkout cannot prove ancestry, so fetch full history` };
|
|
25239
|
+
}
|
|
25240
|
+
}
|
|
25238
25241
|
async function resolveHubSeedSource(execGit, defaultBranch = "development") {
|
|
25239
25242
|
let status;
|
|
25240
25243
|
try {
|
|
@@ -25262,9 +25265,19 @@ remedy (non-destructive): run this command from a clean dedicated worktree \u201
|
|
|
25262
25265
|
return { ok: false, reason: `\`git ls-remote origin refs/heads/${defaultBranch}\` returned nothing \u2014 refusing to read seeds without a known upstream revision` };
|
|
25263
25266
|
}
|
|
25264
25267
|
if (head !== remoteSha) {
|
|
25268
|
+
const behind = await headStrictlyBehind(execGit, head, remoteSha, defaultBranch);
|
|
25269
|
+
if (behind.superseded) {
|
|
25270
|
+
return {
|
|
25271
|
+
ok: false,
|
|
25272
|
+
superseded: true,
|
|
25273
|
+
head,
|
|
25274
|
+
remoteSha,
|
|
25275
|
+
reason: `this checkout (${head}) is behind origin/${defaultBranch} (${remoteSha}) \u2014 a newer commit overtook this run, whose own queued run owns the tick; seeding nothing and exiting green`
|
|
25276
|
+
};
|
|
25277
|
+
}
|
|
25265
25278
|
return {
|
|
25266
25279
|
ok: false,
|
|
25267
|
-
reason: `this checkout (${head}) is not origin/${defaultBranch} (${remoteSha}) \u2014 fast-forward to origin/${defaultBranch} first; a stale or ahead checkout must not seed the fleet`
|
|
25280
|
+
reason: `this checkout (${head}) is not origin/${defaultBranch} (${remoteSha}) \u2014 fast-forward to origin/${defaultBranch} first; a stale or ahead checkout must not seed the fleet${behind.unknown ? ` (${behind.unknown})` : ""}`
|
|
25268
25281
|
};
|
|
25269
25282
|
}
|
|
25270
25283
|
return { ok: true, sha: head };
|
|
@@ -28392,10 +28405,10 @@ var rollout_plan_default = {
|
|
|
28392
28405
|
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)."
|
|
28393
28406
|
},
|
|
28394
28407
|
baseline: {
|
|
28395
|
-
version: "4.6.
|
|
28396
|
-
tag: "v4.6.
|
|
28397
|
-
commit: "
|
|
28398
|
-
npm: "@mutmutco/cli@4.6.
|
|
28408
|
+
version: "4.6.39",
|
|
28409
|
+
tag: "v4.6.39",
|
|
28410
|
+
commit: "2669c496b69e",
|
|
28411
|
+
npm: "@mutmutco/cli@4.6.39"
|
|
28399
28412
|
},
|
|
28400
28413
|
exitCriterion: "fleet-n-of-n",
|
|
28401
28414
|
hubOnlyShortcut: "forbidden",
|
|
@@ -28412,14 +28425,14 @@ var rollout_plan_default = {
|
|
|
28412
28425
|
repo: "mutmutco/mmi-hub",
|
|
28413
28426
|
role: "canary",
|
|
28414
28427
|
schedule: "train",
|
|
28415
|
-
v3Target: "v4.6.
|
|
28428
|
+
v3Target: "v4.6.39"
|
|
28416
28429
|
}
|
|
28417
28430
|
],
|
|
28418
28431
|
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.",
|
|
28419
28432
|
rollback: {
|
|
28420
28433
|
independent: true,
|
|
28421
|
-
mechanism: "npm dist-tag latest -> 4.6.
|
|
28422
|
-
v3Target: "v4.6.
|
|
28434
|
+
mechanism: "npm dist-tag latest -> 4.6.39 and redeploy the Hub Lambda from tag v4.6.39 (2669c496b69e); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
28435
|
+
v3Target: "v4.6.39 (@mutmutco/cli@4.6.39, tag commit 2669c496b69e \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
28423
28436
|
}
|
|
28424
28437
|
},
|
|
28425
28438
|
{
|
|
@@ -44002,7 +44015,7 @@ async function submitQueueAdmission(pr, repo, head, read, write, options, resume
|
|
|
44002
44015
|
for (let readAttempt = 0; readAttempt < maxReads; readAttempt++) {
|
|
44003
44016
|
if (readAttempt) await sleep3(readAttempt * 1e3);
|
|
44004
44017
|
try {
|
|
44005
|
-
const observed = await readPrMergeQueue(pr, repo, read);
|
|
44018
|
+
const observed = await readPrMergeQueue(pr, repo, read, options.sleep);
|
|
44006
44019
|
if (observed.state === "MERGED" && observed.headRefOid === head) return markReenqueued({ mergeStatus: "merged" });
|
|
44007
44020
|
if (observed.isInMergeQueue && observed.headRefOid === head) {
|
|
44008
44021
|
const entry = observed.mergeQueueEntry;
|
|
@@ -44044,13 +44057,13 @@ async function submitQueueAdmission(pr, repo, head, read, write, options, resume
|
|
|
44044
44057
|
if (receipt && (receipt.state === "enqueued" || receipt.state === "merged")) {
|
|
44045
44058
|
if (receipt.head.toLowerCase() !== head.toLowerCase()) {
|
|
44046
44059
|
try {
|
|
44047
|
-
const observed = await readPrMergeQueue(pr, repo, read);
|
|
44060
|
+
const observed = await readPrMergeQueue(pr, repo, read, options.sleep);
|
|
44048
44061
|
superseded = observed.state === "OPEN" && !observed.isInMergeQueue;
|
|
44049
44062
|
} catch {
|
|
44050
44063
|
}
|
|
44051
44064
|
} else {
|
|
44052
44065
|
try {
|
|
44053
|
-
const observed = await readPrMergeQueue(pr, repo, read);
|
|
44066
|
+
const observed = await readPrMergeQueue(pr, repo, read, options.sleep);
|
|
44054
44067
|
if (observed.state === "OPEN" && !observed.isInMergeQueue && observed.headRefOid?.toLowerCase() === receipt.head.toLowerCase()) {
|
|
44055
44068
|
const boundMs = receipt.queueEntry ? Date.parse(receipt.queueEntry.enqueuedAt) : receipt.observedAt + UNWITNESSED_EJECTION_GRACE_MS;
|
|
44056
44069
|
const events = await readArrayPages(
|
|
@@ -44253,6 +44266,15 @@ ${list}
|
|
|
44253
44266
|
Co-authored-by: GitHub <noreply@github.com>
|
|
44254
44267
|
`;
|
|
44255
44268
|
}
|
|
44269
|
+
var WAIVER_KEYS = /^(?:Test-Policy-Override|Runbook-Unaffected)\s*:/i;
|
|
44270
|
+
function clipTrailer(trailer) {
|
|
44271
|
+
return trailer.length > 120 ? `${trailer.slice(0, 117)}...` : trailer;
|
|
44272
|
+
}
|
|
44273
|
+
function describeWaiverText(label, message2, cwd) {
|
|
44274
|
+
const block = parseTrailers(message2, cwd).filter((trailer) => WAIVER_KEYS.test(trailer));
|
|
44275
|
+
const found = block.length ? `final trailer block holds ${block.map((trailer) => `\`${clipTrailer(trailer)}\``).join(" and ")}` : "git parses no Test-Policy-Override or Runbook-Unaffected trailer there";
|
|
44276
|
+
return `${label}: ${found}`;
|
|
44277
|
+
}
|
|
44256
44278
|
function checkQueueSquashOverride(title, pr, messages, root) {
|
|
44257
44279
|
if (messages.length <= 1 || !messages.some((message2) => /^\s*Test-Policy-Override\s*:/im.test(`${message2.headline}
|
|
44258
44280
|
${message2.body}`)) || readMessageOverride2(githubQueueSquashMessage(title, pr, messages), root)) return void 0;
|
|
@@ -44386,7 +44408,8 @@ ${pull.body}`;
|
|
|
44386
44408
|
}
|
|
44387
44409
|
const requiredBody = options.bodyText ?? composedBody;
|
|
44388
44410
|
const prBodyPreservesOverrides = requiredBody != null && method === "SQUASH" && policy.body === "PR_BODY" && options.bodyText == null && prBodyPreservesCommitOverrides(messages, headline, pull.body, process.cwd());
|
|
44389
|
-
|
|
44411
|
+
const queueKeepsWaiver = options.bodyText == null && method !== "SQUASH" && Boolean(readMessageOverride2(actualMessage, process.cwd()));
|
|
44412
|
+
if (requiredBody && !prBodyPreservesOverrides && !queueKeepsWaiver && (method !== "SQUASH" || requiredBody.trim() !== body.trim())) {
|
|
44390
44413
|
if (method === "SQUASH" && policy.body === "PR_BODY" && options.bodyText == null) {
|
|
44391
44414
|
return {
|
|
44392
44415
|
kind: "refused",
|
|
@@ -44398,7 +44421,9 @@ ${pull.body}`;
|
|
|
44398
44421
|
issueTargets: [],
|
|
44399
44422
|
nextAction: `PR author: this repository squashes with PR_BODY, so place the required Test-Policy-Override trailer(s) in the PR body final trailer block (e.g. \`mmi-cli devops pr edit ${pr} --repo ${repo} --body-file .jerv/tmp/pr-${pr}-body.md\`) without a body override, then rerun \`pr land\`. Keep repository settings unchanged.`
|
|
44400
44423
|
},
|
|
44401
|
-
error:
|
|
44424
|
+
error: `queue preparation needed: repository squash policy is PR_BODY, and the PR body does not carry the required Test-Policy-Override trailer(s) in its final Git-recognized trailer block \u2014 ${describeWaiverText("the PR body", pull.body, process.cwd())}; ${describeWaiverText("the source commit messages", messages.map((message2) => `${message2.headline}
|
|
44425
|
+
|
|
44426
|
+
${message2.body}`).join("\n\n"), process.cwd())}`
|
|
44402
44427
|
}
|
|
44403
44428
|
};
|
|
44404
44429
|
}
|
|
@@ -44407,9 +44432,12 @@ ${pull.body}`;
|
|
|
44407
44432
|
return keys.length ? keys.join(", ") : "none";
|
|
44408
44433
|
};
|
|
44409
44434
|
const source = options.bodyText != null ? "the explicit body override" : "the body composed from the source commit messages";
|
|
44435
|
+
const sentLabel = method === "SQUASH" ? `squash ${policy.body ?? "unknown"}, ${messages.length} commit message${messages.length === 1 ? "" : "s"}` : `${method}${repository.merge_commit_message === "PR_BODY" ? " + PR body" : ""}`;
|
|
44410
44436
|
const detail = `the requested sanitized body or trailer arrangement cannot be sent to queue admission: queue merge method ${method}, squash body policy ${policy.body ?? "unknown"}; required body is ${source} (trailer keys: ${trailerKeys(`${headline}
|
|
44411
44437
|
|
|
44412
|
-
${requiredBody}`)}), but the message the queue would send carries trailer keys: ${trailerKeys(actualMessage)}
|
|
44438
|
+
${requiredBody}`)}), but the message the queue would send carries trailer keys: ${trailerKeys(actualMessage)}. ${describeWaiverText(`the message the queue sends (${sentLabel})`, actualMessage, process.cwd())}; ${describeWaiverText("the source commit messages", messages.map((message2) => `${message2.headline}
|
|
44439
|
+
|
|
44440
|
+
${message2.body}`).join("\n\n"), process.cwd())}.`;
|
|
44413
44441
|
const next = options.bodyText != null ? `PR author: a merge queue accepts no body override; drop --body/--squash-body-file and place the required trailer(s) where the queue's ${method}${method === "SQUASH" ? ` (${policy.body})` : ""} message reads them, then rerun \`mmi-cli devops pr land ${pr} --repo ${repo}\`. Keep repository settings unchanged.` : method !== "SQUASH" ? `PR author: the queue's ${method} method keeps the original commit messages, so the required trailer(s) must sit in a source commit message's final trailer block; amend the commit, push, re-run checks and \`mmi-cli devops pr land ${pr} --repo ${repo}\`. Keep repository settings unchanged.` : policy.body === "BLANK" ? `PR author: squash body policy BLANK sends an empty body, so no trailer from the commits or PR body reaches the queue commit; the waiver cannot survive queue admission with unchanged repository settings \u2014 remove the need for the override or ask a maintainer. Keep repository settings unchanged.` : `PR author: squash body policy ${policy.body} does not reproduce the composed body; consolidate the required trailer(s) into one source commit message's final trailer block, push, re-run checks and \`mmi-cli devops pr land ${pr} --repo ${repo}\`. Keep repository settings unchanged.`;
|
|
44414
44442
|
const caveat = " GitHub may append coauthors even to one source commit, so source consolidation alone does not prove the generated message passes test policy; the combined queue candidate must pass with the shared trailer reader.";
|
|
44415
44443
|
return { kind: "refused", result: preparation("queue-message-override", [], detail, next + caveat) };
|
|
@@ -44459,7 +44487,7 @@ async function abandonQueueAdmission(pr, repo, options = {}) {
|
|
|
44459
44487
|
}
|
|
44460
44488
|
let snapshot;
|
|
44461
44489
|
try {
|
|
44462
|
-
snapshot = await readPrMergeQueue(pr, repo, options.read ?? actorReadApi);
|
|
44490
|
+
snapshot = await readPrMergeQueue(pr, repo, options.read ?? actorReadApi, options.sleep);
|
|
44463
44491
|
} catch (error) {
|
|
44464
44492
|
return refuse(`the PR and queue state could not be read (${error.message}); the admission is unchanged`);
|
|
44465
44493
|
}
|
|
@@ -44484,13 +44512,19 @@ function decideQueueMergeRoute(hasQueue, method, headRef) {
|
|
|
44484
44512
|
if (!hasQueue) return "normal";
|
|
44485
44513
|
return method === "--merge" && /^(release-align|hotfix-fold)\//.test(headRef) ? "admin-true-merge" : "pr-land";
|
|
44486
44514
|
}
|
|
44487
|
-
async function readPrMergeQueue(pr, repo, gh2 = actorReadApi) {
|
|
44515
|
+
async function readPrMergeQueue(pr, repo, gh2 = actorReadApi, sleep2 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms))) {
|
|
44488
44516
|
if (!/^\d+$/.test(pr)) throw new Error("merge queue read requires a PR number");
|
|
44489
|
-
const
|
|
44490
|
-
|
|
44491
|
-
|
|
44492
|
-
|
|
44493
|
-
|
|
44517
|
+
const fields = `pullRequest(number:${pr}) { state isInMergeQueue mergeQueueEntry { state position enqueuedAt headCommit { oid } } autoMergeRequest { enabledAt } headRefOid }`;
|
|
44518
|
+
let result;
|
|
44519
|
+
for (let attempt = 0; ; attempt++) {
|
|
44520
|
+
try {
|
|
44521
|
+
result = await queueGraphql(repo, fields, gh2);
|
|
44522
|
+
break;
|
|
44523
|
+
} catch (error) {
|
|
44524
|
+
if (attempt >= 2 || !isUpstreamGitHubFault(execErrorText(error))) throw error;
|
|
44525
|
+
await sleep2(2e3 * (attempt + 1));
|
|
44526
|
+
}
|
|
44527
|
+
}
|
|
44494
44528
|
if (!result.pullRequest) throw new Error(`merge queue PR #${pr} not found`);
|
|
44495
44529
|
const entry = result.pullRequest.mergeQueueEntry;
|
|
44496
44530
|
if (entry && (entry.enqueuedAt !== void 0 && !Number.isFinite(Date.parse(entry.enqueuedAt)) || entry.headCommit && !/^[a-f0-9]{40}$/i.test(entry.headCommit.oid))) throw new Error("queue entry identity is unreadable");
|
|
@@ -44919,7 +44953,7 @@ async function dispatchWorkflow(repo, workflow, ref, inputs, deps) {
|
|
|
44919
44953
|
const fields = ["-f", `ref=${ref}`];
|
|
44920
44954
|
const names = /* @__PURE__ */ new Set();
|
|
44921
44955
|
for (const input of inputs) {
|
|
44922
|
-
const match = /^([A-Za-z_][A-Za-z0-9_]{0,63})=([^\r\n]{0,2048})$/.exec(input);
|
|
44956
|
+
const match = /^([A-Za-z_][A-Za-z0-9_-]{0,63})=([^\r\n]{0,2048})$/.exec(input);
|
|
44923
44957
|
if (!match || names.has(match[1])) throw new Error("run dispatch: invalid or duplicate input");
|
|
44924
44958
|
names.add(match[1]);
|
|
44925
44959
|
fields.push("-f", `inputs[${match[1]}]=${match[2]}`);
|
|
@@ -46446,7 +46480,7 @@ function refuseNotClaimable(e, json) {
|
|
|
46446
46480
|
process.exitCode = 1;
|
|
46447
46481
|
return true;
|
|
46448
46482
|
}
|
|
46449
|
-
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:\n {project, viewer, repo, primary, secondary, items, 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 for ownership context:\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\n`items` (#9974) is that same flat row array, already on the full report: {ref,number,title,status,assignees,labels,bucket,claim,openPullRequests}\n node -e \"const b=require('./.jerv/tmp/board.json');for(const i of b.items)console.log(i.number,i.status,i.title)\"\nIt is built from the same flattener as --flat, so `const rows = j.items || j` is now safe on either form.\n\nAdd --flat with --json for that array as the WHOLE output (no project/viewer/warnings envelope):\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\nNever sniff the envelope \u2014 read `report.items` (or `primary`/`secondary`) by name. `j.items ?? j.rows ?? j`,\n`Object.values(j)` and any generic \"find the list\" wrapper are still guesses: before `items` existed they\nthrew `items is not iterable` before any row was read (#9956), and `j.rows` has never been a key. For a\nnumber|status|claim|title projection across non-Done rows, read --json --flat --out .jerv/tmp/board.json\nand filter that array locally (identical rows to `report.items`):\n mmi-cli oracle board read --json --flat --out .jerv/tmp/board.json\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";
|
|
46483
|
+
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:\n {project, viewer, repo, primary, secondary, items, 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 for ownership context:\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\n`items` (#9974) is that same flat row array, already on the full report: {ref,number,title,status,assignees,labels,bucket,claim,openPullRequests}\n node -e \"const b=require('./.jerv/tmp/board.json');for(const i of b.items)console.log(i.number,i.status,i.title)\"\nIt is built from the same flattener as --flat, so `const rows = j.items || j` is now safe on either form.\n\nAdd --flat with --json for that array as the WHOLE output (no project/viewer/warnings envelope):\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\nNever sniff the envelope \u2014 read `report.items` (or `primary`/`secondary`) by name. `j.items ?? j.rows ?? j`,\n`Object.values(j)` and any generic \"find the list\" wrapper are still guesses: before `items` existed they\nthrew `items is not iterable` before any row was read (#9956), and `j.rows` has never been a key. For a\nnumber|status|claim|title projection across non-Done rows, read --json --flat --out .jerv/tmp/board.json\nand filter that array locally (identical rows to `report.items`):\n mmi-cli oracle board read --json --flat --out .jerv/tmp/board.json\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. That age grades the\nCOPY, not its content: the line ends `\xB7 items closed/created after the build may still differ \u2014 confirm\nwith --direct` (#10028), so re-read with --direct before treating a row as still open or still present.\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";
|
|
46450
46484
|
function flattenBoardReport(report) {
|
|
46451
46485
|
const closingByRef = /* @__PURE__ */ new Map();
|
|
46452
46486
|
for (const pull of report.pullRequests ?? []) {
|
|
@@ -51148,6 +51182,13 @@ var declaredProjectAdmins = (meta) => {
|
|
|
51148
51182
|
return Array.isArray(list) ? list.filter((entry) => typeof entry === "string") : [];
|
|
51149
51183
|
};
|
|
51150
51184
|
var execGitForSeedSource = async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
51185
|
+
function reportSuperseded(prefix, source, json) {
|
|
51186
|
+
const head = source.head.slice(0, 8);
|
|
51187
|
+
const remote = source.remoteSha.slice(0, 8);
|
|
51188
|
+
const summary = `${prefix}: superseded \u2014 this checkout (${head}) is behind origin/development (${remote}); a newer run owns this tick, nothing seeded`;
|
|
51189
|
+
if (json) console.log(JSON.stringify({ status: "superseded", checkoutSha: source.head, remoteSha: source.remoteSha, summary }, null, 2));
|
|
51190
|
+
else console.log(summary);
|
|
51191
|
+
}
|
|
51151
51192
|
async function gateRenderDiff(current, rendered) {
|
|
51152
51193
|
const dir = (0, import_node_fs40.mkdtempSync)((0, import_node_path37.join)((0, import_node_os21.tmpdir)(), "mmi-gate-diff-"));
|
|
51153
51194
|
const before = (0, import_node_path37.join)(dir, "committed-gate.yml");
|
|
@@ -51235,7 +51276,10 @@ async function propagationScope(classes, what) {
|
|
|
51235
51276
|
}
|
|
51236
51277
|
async function propagatePins(apply, json) {
|
|
51237
51278
|
const source = await resolveHubSeedSource(execGitForSeedSource);
|
|
51238
|
-
if (apply && !source.ok)
|
|
51279
|
+
if (apply && !source.ok) {
|
|
51280
|
+
if (source.superseded) return reportSuperseded("bootstrap propagate --pins --apply", source, json);
|
|
51281
|
+
return fail(`bootstrap propagate --pins --apply: ${source.reason}`);
|
|
51282
|
+
}
|
|
51239
51283
|
const { classOf, inClassRepos, canarySlug, sourcePilot } = await propagationScope(null, "hub action pins");
|
|
51240
51284
|
const gh2 = async (args) => execFileP("gh", args, { timeout: 2e4 });
|
|
51241
51285
|
const repos = [];
|
|
@@ -52360,7 +52404,10 @@ LIVE apply to ${repo}:
|
|
|
52360
52404
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
52361
52405
|
if (!(0, import_node_fs40.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
|
|
52362
52406
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
52363
|
-
if (!seedSource.ok)
|
|
52407
|
+
if (!seedSource.ok) {
|
|
52408
|
+
if (seedSource.superseded) return reportSuperseded("bootstrap propagate", seedSource, o.json);
|
|
52409
|
+
return fail(`bootstrap propagate: ${seedSource.reason}`);
|
|
52410
|
+
}
|
|
52364
52411
|
const manifest = loadBootstrapSeeds((0, import_node_fs40.readFileSync)(manifestPath, "utf8"));
|
|
52365
52412
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
52366
52413
|
if (!o.target) {
|
|
@@ -53359,7 +53406,7 @@ async function stageStatus(member, stage, deps) {
|
|
|
53359
53406
|
if (pin) {
|
|
53360
53407
|
served = pin;
|
|
53361
53408
|
source = "registry-pin";
|
|
53362
|
-
} else
|
|
53409
|
+
} else {
|
|
53363
53410
|
try {
|
|
53364
53411
|
const status = await deps.deploy(member.repo, stage);
|
|
53365
53412
|
deployStatus = status;
|
|
@@ -53505,10 +53552,12 @@ async function newestGreen(deps, repo) {
|
|
|
53505
53552
|
async function readSuiteDev(deps) {
|
|
53506
53553
|
const report = await deps.status();
|
|
53507
53554
|
return Promise.all(report.members.map(async (m) => {
|
|
53508
|
-
const
|
|
53555
|
+
const track = deps.releaseTrack ? await deps.releaseTrack(m.repo).catch(() => void 0) : void 0;
|
|
53556
|
+
const stage = track === "single" ? "main" : "dev";
|
|
53557
|
+
const st = m.stages.find((s) => s.stage === stage);
|
|
53509
53558
|
const row = { repo: m.repo, head: st.head, served: st.served };
|
|
53510
|
-
if (st.source === "n/a") return { ...row, state: "converged", note:
|
|
53511
|
-
if (st.lag === false) return { ...row, state: "converged", note:
|
|
53559
|
+
if (st.source === "n/a") return { ...row, state: "converged", note: `no ${stage} stage` };
|
|
53560
|
+
if (st.lag === false) return { ...row, state: "converged", note: `${stage} serves the development head` };
|
|
53512
53561
|
let green;
|
|
53513
53562
|
try {
|
|
53514
53563
|
green = await newestGreen(deps, m.repo);
|
|
@@ -53516,10 +53565,10 @@ async function readSuiteDev(deps) {
|
|
|
53516
53565
|
return { ...row, state: "unknown", note: `checks unreadable (${e.message})` };
|
|
53517
53566
|
}
|
|
53518
53567
|
const last = st.deployStatus?.lastRun;
|
|
53519
|
-
if (st.served === "unknown") return { ...row, green, state: "unknown", note:
|
|
53520
|
-
if (same(st.served, green)) return { ...row, green, state: "converged", note:
|
|
53568
|
+
if (st.served === "unknown") return { ...row, green, state: "unknown", note: `${stage} version unproven (health probe failed or no deploy record)${last?.url ? `; last deploy ${last.conclusion ?? last.status}: ${last.url}` : ""}` };
|
|
53569
|
+
if (same(st.served, green)) return { ...row, green, state: "converged", note: `${stage} serves the newest green SHA; head ${short2(st.head)} is not green yet` };
|
|
53521
53570
|
if (!green) return { ...row, state: "blocked", note: `no green SHA in the last ${WALK} development commits` };
|
|
53522
|
-
if (last?.conclusion === "failure") return { ...row, green, state: "blocked", note: `last
|
|
53571
|
+
if (last?.conclusion === "failure") return { ...row, green, state: "blocked", note: `last ${stage} deploy failed: ${last.url ?? "no run url"}` };
|
|
53523
53572
|
return { ...row, green, state: "lagging", note: `newest green ${short2(green)} not deployed yet; the dev auto-deploy sweep (every 15 min) picks it up` };
|
|
53524
53573
|
}));
|
|
53525
53574
|
}
|
|
@@ -58738,6 +58787,11 @@ ${lines2}`);
|
|
|
58738
58787
|
if (deps.attach) {
|
|
58739
58788
|
const attached = await deps.attach(row.number, rowRepo(spec), rowPriority);
|
|
58740
58789
|
row.onBoard = attached.onBoard;
|
|
58790
|
+
if (attached.prioritySet === false) {
|
|
58791
|
+
row.status = "partial";
|
|
58792
|
+
row.prioritySet = false;
|
|
58793
|
+
row.priorityError = attached.priorityError;
|
|
58794
|
+
}
|
|
58741
58795
|
if (attached.projectItemId) row.projectItemId = attached.projectItemId;
|
|
58742
58796
|
if (attached.boardAttach) {
|
|
58743
58797
|
row.boardAttach = attached.boardAttach;
|
|
@@ -63536,8 +63590,10 @@ async function attachToProject(issueNumber, repo, priority) {
|
|
|
63536
63590
|
await setBoardItemPriority(defaultGitHubClient(), cfg, projectItemId2, priority);
|
|
63537
63591
|
} catch (e) {
|
|
63538
63592
|
const err = e;
|
|
63539
|
-
|
|
63593
|
+
const priorityError = (err.stderr || err.message || String(e)).trim();
|
|
63594
|
+
process.stderr.write(`warning: issue #${issueNumber} board Priority not set: ${priorityError}; issue and board attachment already exist; do not recreate the issue
|
|
63540
63595
|
`);
|
|
63596
|
+
return { projectItemId: projectItemId2, onBoard: true, credential: "app_installation", prioritySet: false, priorityError };
|
|
63541
63597
|
}
|
|
63542
63598
|
}
|
|
63543
63599
|
return { projectItemId: projectItemId2, onBoard: true, credential: "app_installation" };
|
|
@@ -63887,12 +63943,13 @@ function registerCollaborationCommands(program3) {
|
|
|
63887
63943
|
`);
|
|
63888
63944
|
const linkRecovery = parentRecoveryRef !== void 0 && !parent ? `mmi-cli oracle issue link-child ${parentRecoveryRef} ${childRef}` : void 0;
|
|
63889
63945
|
console.log(JSON.stringify({
|
|
63890
|
-
status: boardRecovery || linkRecovery ? "partial" : "created",
|
|
63946
|
+
status: boardRecovery || linkRecovery || attached.prioritySet === false ? "partial" : "created",
|
|
63891
63947
|
...created,
|
|
63892
63948
|
label: issueType,
|
|
63893
63949
|
priority,
|
|
63894
63950
|
projectItemId,
|
|
63895
63951
|
onBoard,
|
|
63952
|
+
...attached.prioritySet === false ? { prioritySet: false, priorityError: attached.priorityError } : {},
|
|
63896
63953
|
...attachCredential ? { credential: attachCredential } : {},
|
|
63897
63954
|
// #5489: partial receipt when the GitHub issue landed but Project v2 attach hit GraphQL quota.
|
|
63898
63955
|
...!onBoard ? {
|
|
@@ -64279,8 +64336,9 @@ ${list}`);
|
|
|
64279
64336
|
}
|
|
64280
64337
|
}
|
|
64281
64338
|
const created = requireGhCreateOk(await ghCreate(args), "skill-lesson");
|
|
64282
|
-
const
|
|
64283
|
-
|
|
64339
|
+
const attached = await attachToProject(created.number, targetRepo2, priority);
|
|
64340
|
+
const { projectItemId, onBoard } = attached;
|
|
64341
|
+
console.log(JSON.stringify({ ...created, projectItemId, onBoard, ...attached.prioritySet === false ? { status: "partial", prioritySet: false, priorityError: attached.priorityError } : {} }));
|
|
64284
64342
|
});
|
|
64285
64343
|
const pr = program3.command("pr").description("pull requests \u2014 reliable create with structured output");
|
|
64286
64344
|
withExamples(pr.command("create").description("create a PR and print {number,url} JSON").option("--title <title>", "PR title").option("--title-file <path|->", "read the PR title from a UTF-8 file, or from stdin with -").option("--body <body>", "PR body (markdown)").option("--body-file <path|->", "read PR body from a UTF-8 file, or from stdin with -").option("--base <branch>", "base branch (defaults to the repo default)").option("--head <branch>", "head branch (defaults to the current branch)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--draft", "open the PR in draft state (#2667)").option("--no-zeroci", "compatibility option; pr create does not mint receipts").option("--json", "machine-readable output (default; accepted for parity)").action(async (o) => {
|
|
@@ -67123,6 +67181,8 @@ function registerSuiteCommands(program3) {
|
|
|
67123
67181
|
const status = suiteStatusDeps(github, registry2);
|
|
67124
67182
|
const deps = {
|
|
67125
67183
|
status: () => readSuiteStatus(status),
|
|
67184
|
+
// #10020: a single-track member's one live stage is `main`; judge that row instead of the dev one.
|
|
67185
|
+
releaseTrack: status.releaseTrack,
|
|
67126
67186
|
commits: async (repo, limit) => (await github.rest("GET", `repos/${repo}/commits?sha=development&per_page=${limit}`) ?? []).map((c) => c.sha),
|
|
67127
67187
|
checkRuns: async (repo, sha) => (await github.rest("GET", `repos/${repo}/commits/${sha}/check-runs?per_page=100`))?.check_runs ?? [],
|
|
67128
67188
|
requiredChecks: async (repo) => {
|
package/cli/package.json
CHANGED
package/package.json
CHANGED
|
@@ -83,7 +83,9 @@ jobs:
|
|
|
83
83
|
# that runs the full job; main must never become skippable via bootstrap vars alone while the
|
|
84
84
|
# org ruleset still requires `gate` on main.
|
|
85
85
|
if: ${{ github.event_name != 'push' || github.ref_name == '{{GATE_FULL_RUN_BRANCH}}' || github.ref_name == 'main' || github.ref_type == 'tag' }}
|
|
86
|
-
|
|
86
|
+
# #9248: included GitHub-hosted capacity, first choice for CI.
|
|
87
|
+
# #9668: GATE_HOSTED_SPILL_QUEUE=off routes it to the owned mmi-live lanes at Actions run-out.
|
|
88
|
+
runs-on: ${{ vars.GATE_HOSTED_SPILL_QUEUE == 'off' && fromJSON('["self-hosted","linux","x64","mmi-live"]') || 'ubuntu-latest' }}
|
|
87
89
|
defaults:
|
|
88
90
|
run: { working-directory: {{GATE_WORKDIR}} }{{GATE_SERVICES_YAML}}
|
|
89
91
|
steps:{{GATE_VERDICT_STEP_YAML}}
|
|
@@ -21,7 +21,8 @@ jobs:
|
|
|
21
21
|
# the earlier "included Ubuntu for light pre-gates" permission; it creates no new runner class — standard
|
|
22
22
|
# included hosted labels are allowed (`ubuntu-*`, `windows-*`, `macos-*`, ARM variants; #8977); only
|
|
23
23
|
# larger/paid images, GPU and custom runner groups stay refused.
|
|
24
|
-
|
|
24
|
+
# #9668: GATE_HOSTED_SPILL_QUEUE=off routes it to the owned mmi-live lanes at Actions run-out.
|
|
25
|
+
runs-on: ${{ vars.GATE_HOSTED_SPILL_QUEUE == 'off' && fromJSON('["self-hosted","linux","x64","mmi-live"]') || 'ubuntu-latest' }}
|
|
25
26
|
timeout-minutes: 10
|
|
26
27
|
steps:{{GATE_VERDICT_STEP_YAML}}
|
|
27
28
|
- if: ${{ steps.development-gate.outputs.reuse != 'true' }}
|