@mutmutco/claude-plugin 4.6.34 → 4.6.36
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/.claude-plugin/plugin.json +1 -1
- package/cli/dist/main.cjs +211 -39
- package/cli/package.json +1 -1
- package/package.json +1 -1
- package/skills/board/SKILL.md +10 -6
- package/skills/board/reference.md +6 -3
- package/skills/bootstrap/seeds/readme-mmi-developer-environment.block.md +3 -0
- package/skills/bootstrap/seeds/runbook-unaffected.json +4 -0
- package/skills/org-guide/references/work-and-releases.md +17 -0
package/cli/dist/main.cjs
CHANGED
|
@@ -15366,13 +15366,6 @@ async function fetchHubBoardSnapshot(request, deps) {
|
|
|
15366
15366
|
// src/board-read.ts
|
|
15367
15367
|
init_github_quota();
|
|
15368
15368
|
|
|
15369
|
-
// src/claim-marker-client.ts
|
|
15370
|
-
init_github_client();
|
|
15371
|
-
init_github_quota();
|
|
15372
|
-
|
|
15373
|
-
// src/pr-checks-rest.ts
|
|
15374
|
-
init_cli_shared();
|
|
15375
|
-
|
|
15376
15369
|
// ../shared/github-quota-scope.ts
|
|
15377
15370
|
var import_node_crypto4 = require("node:crypto");
|
|
15378
15371
|
function appQuotaScope(appId, installationId, origin = "https://api.github.com", resource = "graphql") {
|
|
@@ -15497,6 +15490,7 @@ var APP_WRITE_COMMANDS = /* @__PURE__ */ new Map([
|
|
|
15497
15490
|
["run cancel", { metadata: "read", actions: "write" }],
|
|
15498
15491
|
// #9587: one named run rerun (all or failed jobs) — same actions:write profile, nothing broader.
|
|
15499
15492
|
["run rerun", { metadata: "read", actions: "write" }],
|
|
15493
|
+
["run dispatch", { metadata: "read", actions: "write" }],
|
|
15500
15494
|
// #9780: repository Actions variables — get/list mint only Variables:read; create/update/delete
|
|
15501
15495
|
// Variables:write. Repository scope only; no secret store, no rename, no workflow dispatch.
|
|
15502
15496
|
// #9790: the token key is `actions_variables` (as ci-reconcile.yml mints); `variables` was refused 422.
|
|
@@ -15736,6 +15730,13 @@ async function activateAppActor(commandPath4, env, mint, note = (line) => proces
|
|
|
15736
15730
|
return "app";
|
|
15737
15731
|
}
|
|
15738
15732
|
|
|
15733
|
+
// src/claim-marker-client.ts
|
|
15734
|
+
init_github_client();
|
|
15735
|
+
init_github_quota();
|
|
15736
|
+
|
|
15737
|
+
// src/pr-checks-rest.ts
|
|
15738
|
+
init_cli_shared();
|
|
15739
|
+
|
|
15739
15740
|
// src/merge-ci-policy.ts
|
|
15740
15741
|
function resolveMergeCiPolicy(input) {
|
|
15741
15742
|
if (input.registryCi === "none") {
|
|
@@ -17173,9 +17174,11 @@ async function fetchRestCorePool(gh2 = defaultGhApi, nowMs = Date.now()) {
|
|
|
17173
17174
|
// src/claim-marker-client.ts
|
|
17174
17175
|
var claimWriteTokenProvider;
|
|
17175
17176
|
var claimWriteTokenOnce;
|
|
17177
|
+
var notedPersonalClaimWrite = false;
|
|
17176
17178
|
function setClaimWriteTokenProvider(provider) {
|
|
17177
17179
|
claimWriteTokenProvider = provider;
|
|
17178
17180
|
claimWriteTokenOnce = void 0;
|
|
17181
|
+
notedPersonalClaimWrite = false;
|
|
17179
17182
|
}
|
|
17180
17183
|
async function selectClaimWriteClient(client, appClient) {
|
|
17181
17184
|
if (appClient) return appClient;
|
|
@@ -17186,15 +17189,26 @@ async function selectClaimWriteClient(client, appClient) {
|
|
|
17186
17189
|
token = await claimWriteTokenOnce;
|
|
17187
17190
|
} catch (e) {
|
|
17188
17191
|
claimWriteTokenOnce = void 0;
|
|
17192
|
+
if (e instanceof AppActorError && e.code === "vault-denied" && process.env.MMI_ACTOR?.trim() !== "app") return personalClaimWrite(client);
|
|
17189
17193
|
const detail = e instanceof Error ? e.message : String(e);
|
|
17190
17194
|
throw new Error(`${detail} \u2014 refusing personal-token fallback for claim write (#9511)`);
|
|
17191
17195
|
}
|
|
17192
17196
|
if (!token) {
|
|
17193
17197
|
claimWriteTokenOnce = void 0;
|
|
17194
|
-
|
|
17198
|
+
if (process.env.MMI_ACTOR?.trim() === "app") {
|
|
17199
|
+
throw new Error("app actor: claim write token unavailable \u2014 refusing personal-token fallback for claim write (#9511)");
|
|
17200
|
+
}
|
|
17201
|
+
return personalClaimWrite(client);
|
|
17195
17202
|
}
|
|
17196
17203
|
return createGitHubClient({ token: async () => token });
|
|
17197
17204
|
}
|
|
17205
|
+
function personalClaimWrite(client) {
|
|
17206
|
+
if (!notedPersonalClaimWrite) {
|
|
17207
|
+
notedPersonalClaimWrite = true;
|
|
17208
|
+
process.stderr.write("note: no App route on this host (no MMI-Hub vault access) \u2014 claim writes go as your own GitHub login\n");
|
|
17209
|
+
}
|
|
17210
|
+
return client;
|
|
17211
|
+
}
|
|
17198
17212
|
async function selectLivenessClient(client, appClient) {
|
|
17199
17213
|
if (appClient) return appClient;
|
|
17200
17214
|
let token;
|
|
@@ -22744,6 +22758,7 @@ mutation($projectId: ID!, $itemId: ID!) {
|
|
|
22744
22758
|
item { id }
|
|
22745
22759
|
}
|
|
22746
22760
|
}`;
|
|
22761
|
+
var BOARD_WRITE_REFUSAL_TYPES = /* @__PURE__ */ new Set(["INSUFFICIENT_SCOPES", "FORBIDDEN", "NOT_FOUND"]);
|
|
22747
22762
|
async function updateItemSingleSelect(client, projectId, itemId, fieldId, optionId) {
|
|
22748
22763
|
if (process.env.MMI_ACTOR?.trim() === "app") {
|
|
22749
22764
|
const { writeHubBoardField: writeHubBoardField2 } = await Promise.resolve().then(() => (init_board_write(), board_write_exports));
|
|
@@ -22770,10 +22785,14 @@ async function updateItemSingleSelect(client, projectId, itemId, fieldId, option
|
|
|
22770
22785
|
return { credential: "user" };
|
|
22771
22786
|
} catch (error) {
|
|
22772
22787
|
const errors = error.graphqlErrors;
|
|
22773
|
-
if (errors?.length && errors.every((entry) => entry.type
|
|
22788
|
+
if (errors?.length && errors.every((entry) => entry.type !== void 0 && BOARD_WRITE_REFUSAL_TYPES.has(entry.type))) {
|
|
22774
22789
|
const { writeHubBoardField: writeHubBoardField2 } = await Promise.resolve().then(() => (init_board_write(), board_write_exports));
|
|
22775
|
-
|
|
22776
|
-
|
|
22790
|
+
try {
|
|
22791
|
+
await writeHubBoardField2({ projectId, itemId, fieldId, optionId });
|
|
22792
|
+
return { credential: "app_installation" };
|
|
22793
|
+
} catch {
|
|
22794
|
+
throw error;
|
|
22795
|
+
}
|
|
22777
22796
|
}
|
|
22778
22797
|
if (isGitHubRateLimitError(error)) {
|
|
22779
22798
|
const { writeHubBoardField: writeHubBoardField2 } = await Promise.resolve().then(() => (init_board_write(), board_write_exports));
|
|
@@ -28366,10 +28385,10 @@ var rollout_plan_default = {
|
|
|
28366
28385
|
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)."
|
|
28367
28386
|
},
|
|
28368
28387
|
baseline: {
|
|
28369
|
-
version: "4.6.
|
|
28370
|
-
tag: "v4.6.
|
|
28371
|
-
commit: "
|
|
28372
|
-
npm: "@mutmutco/cli@4.6.
|
|
28388
|
+
version: "4.6.36",
|
|
28389
|
+
tag: "v4.6.36",
|
|
28390
|
+
commit: "d1c15e58d6e5",
|
|
28391
|
+
npm: "@mutmutco/cli@4.6.36"
|
|
28373
28392
|
},
|
|
28374
28393
|
exitCriterion: "fleet-n-of-n",
|
|
28375
28394
|
hubOnlyShortcut: "forbidden",
|
|
@@ -28386,14 +28405,14 @@ var rollout_plan_default = {
|
|
|
28386
28405
|
repo: "mutmutco/mmi-hub",
|
|
28387
28406
|
role: "canary",
|
|
28388
28407
|
schedule: "train",
|
|
28389
|
-
v3Target: "v4.6.
|
|
28408
|
+
v3Target: "v4.6.36"
|
|
28390
28409
|
}
|
|
28391
28410
|
],
|
|
28392
28411
|
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.",
|
|
28393
28412
|
rollback: {
|
|
28394
28413
|
independent: true,
|
|
28395
|
-
mechanism: "npm dist-tag latest -> 4.6.
|
|
28396
|
-
v3Target: "v4.6.
|
|
28414
|
+
mechanism: "npm dist-tag latest -> 4.6.36 and redeploy the Hub Lambda from tag v4.6.36 (d1c15e58d6e5); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
28415
|
+
v3Target: "v4.6.36 (@mutmutco/cli@4.6.36, tag commit d1c15e58d6e5 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
28397
28416
|
}
|
|
28398
28417
|
},
|
|
28399
28418
|
{
|
|
@@ -30698,6 +30717,8 @@ var CORRELATE_ATTEMPTS = 5;
|
|
|
30698
30717
|
var CORRELATE_DELAY_MS = 1500;
|
|
30699
30718
|
var CORRELATE_SKEW_SLACK_MS = 1e4;
|
|
30700
30719
|
var CORRELATE_PAGE_LIMIT = 50;
|
|
30720
|
+
var GET_PUBLISH_CORRELATE_ATTEMPTS = 12;
|
|
30721
|
+
var GET_PUBLISH_CORRELATE_DELAY_MS = 2500;
|
|
30701
30722
|
var RUN_CONFIRM_ATTEMPTS = 3;
|
|
30702
30723
|
var RUN_CONFIRM_DELAY_MS = 1e3;
|
|
30703
30724
|
var defaultSleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
@@ -30744,11 +30765,13 @@ async function correlateRun(deps, args) {
|
|
|
30744
30765
|
const sleep2 = resolveSleep(deps);
|
|
30745
30766
|
const threshold = args.since - CORRELATE_SKEW_SLACK_MS;
|
|
30746
30767
|
const repo = args.mode === "workflow" ? args.repo ?? HUB_REPO2 : HUB_REPO2;
|
|
30768
|
+
const attempts = args.mode === "dispatch" ? args.attempts ?? CORRELATE_ATTEMPTS : CORRELATE_ATTEMPTS;
|
|
30769
|
+
const delayMs = args.mode === "dispatch" ? args.delayMs ?? CORRELATE_DELAY_MS : CORRELATE_DELAY_MS;
|
|
30747
30770
|
let lastError;
|
|
30748
30771
|
let parsedAnyResponse = false;
|
|
30749
30772
|
let truncatedWindow = false;
|
|
30750
|
-
for (let attempt = 0; attempt <
|
|
30751
|
-
if (attempt > 0) await sleep2(
|
|
30773
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
30774
|
+
if (attempt > 0) await sleep2(delayMs);
|
|
30752
30775
|
const listArgs = [
|
|
30753
30776
|
"run",
|
|
30754
30777
|
"list",
|
|
@@ -30791,7 +30814,7 @@ async function correlateRun(deps, args) {
|
|
|
30791
30814
|
if (!parsedAnyResponse) {
|
|
30792
30815
|
return {
|
|
30793
30816
|
read: "failed",
|
|
30794
|
-
error: `\`gh run list --workflow ${args.workflow} --repo ${repo}\` failed on all ${
|
|
30817
|
+
error: `\`gh run list --workflow ${args.workflow} --repo ${repo}\` failed on all ${attempts} attempts${lastError ? ` (${describeReadError(lastError)})` : ""}`
|
|
30795
30818
|
};
|
|
30796
30819
|
}
|
|
30797
30820
|
if (truncatedWindow) {
|
|
@@ -30806,7 +30829,7 @@ function correlateTenantRun(deps, since, titleIncludes) {
|
|
|
30806
30829
|
return correlateRun(deps, { workflow: "tenant-deploy.yml", since, mode: "dispatch", titleIncludes });
|
|
30807
30830
|
}
|
|
30808
30831
|
function correlateGetPublishRun(deps, since, product, mode) {
|
|
30809
|
-
return correlateRun(deps, { workflow: "get-publish.yml", since, mode: "dispatch", titleIncludes: [product, ...mode ? [mode] : []], ...mode ? { requireTitleMatch: true } : {} });
|
|
30832
|
+
return correlateRun(deps, { workflow: "get-publish.yml", since, mode: "dispatch", titleIncludes: [product, ...mode ? [mode] : []], ...mode ? { requireTitleMatch: true } : {}, attempts: GET_PUBLISH_CORRELATE_ATTEMPTS, delayMs: GET_PUBLISH_CORRELATE_DELAY_MS });
|
|
30810
30833
|
}
|
|
30811
30834
|
function correlateControlRun(deps, since, titleIncludes) {
|
|
30812
30835
|
return correlateRun(deps, { workflow: "tenant-control.yml", since, mode: "dispatch", titleIncludes, titleFieldsFrom: 2, ...titleIncludes?.length ? { requireTitleMatch: true } : {} });
|
|
@@ -31937,7 +31960,7 @@ async function fetchServedChannelVersion(url, fetchImpl = fetch) {
|
|
|
31937
31960
|
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
31938
31961
|
}
|
|
31939
31962
|
}
|
|
31940
|
-
async function findPriorGreenDispatch(deps, repo, workflow, sha, titleTokens) {
|
|
31963
|
+
async function findPriorGreenDispatch(deps, repo, workflow, sha, titleTokens, opts = {}) {
|
|
31941
31964
|
if (!/^[0-9a-f]{40}$/i.test(sha)) return void 0;
|
|
31942
31965
|
try {
|
|
31943
31966
|
const rows = JSON.parse(await deps.run("gh", [
|
|
@@ -31956,12 +31979,18 @@ async function findPriorGreenDispatch(deps, repo, workflow, sha, titleTokens) {
|
|
|
31956
31979
|
"--json",
|
|
31957
31980
|
"databaseId,url,status,conclusion,displayTitle,headSha"
|
|
31958
31981
|
]));
|
|
31959
|
-
const
|
|
31960
|
-
|
|
31982
|
+
const titleMatch = (r) => titleTokens.every((t) => (r.displayTitle ?? "").split(/[^\w.-]+/).includes(t));
|
|
31983
|
+
const atSha = (r) => r.headSha?.toLowerCase() === sha.toLowerCase();
|
|
31984
|
+
const hit = rows.find((r) => typeof r.databaseId === "number" && atSha(r) && r.status === "completed" && r.conclusion === "success" && titleMatch(r));
|
|
31985
|
+
if (hit) return { runId: hit.databaseId, ...hit.url ? { runUrl: hit.url } : {} };
|
|
31986
|
+
if (!opts.includeActive) return void 0;
|
|
31987
|
+
const active = rows.find((r) => typeof r.databaseId === "number" && atSha(r) && PRIOR_DISPATCH_ACTIVE_STATUSES.has(r.status ?? "") && titleMatch(r));
|
|
31988
|
+
return active ? { runId: active.databaseId, ...active.url ? { runUrl: active.url } : {}, active: true } : void 0;
|
|
31961
31989
|
} catch {
|
|
31962
31990
|
return void 0;
|
|
31963
31991
|
}
|
|
31964
31992
|
}
|
|
31993
|
+
var PRIOR_DISPATCH_ACTIVE_STATUSES = /* @__PURE__ */ new Set(["in_progress", "queued", "waiting", "requested", "pending"]);
|
|
31965
31994
|
function planeFailureRow(workflow, evidence) {
|
|
31966
31995
|
return { workflow, runUrlNote: evidence, conclusion: "failure" };
|
|
31967
31996
|
}
|
|
@@ -32040,10 +32069,13 @@ async function appendMmiGetRedeploy(deps, input, dispatch) {
|
|
|
32040
32069
|
async function appendServedChannelStage(deps, input, dispatch) {
|
|
32041
32070
|
if (!isHubControlRepo(input.repo)) return dispatch;
|
|
32042
32071
|
const remedy = `${getPublishDispatchCommand(input.repo)} -f mode=stage`;
|
|
32043
|
-
const prior = await findPriorGreenDispatch(deps, input.repo, "get-publish.yml", input.releaseSha, [HUB_SERVED_PRODUCT, "stage"]);
|
|
32072
|
+
const prior = await findPriorGreenDispatch(deps, input.repo, "get-publish.yml", input.releaseSha, [HUB_SERVED_PRODUCT, "stage"], { includeActive: true });
|
|
32044
32073
|
let row;
|
|
32045
|
-
if (prior) {
|
|
32074
|
+
if (prior && !prior.active) {
|
|
32046
32075
|
row = { workflow: "get-publish.yml", runId: prior.runId, ...prior.runUrl ? { runUrl: prior.runUrl } : {}, conclusion: "success" };
|
|
32076
|
+
} else if (prior?.active) {
|
|
32077
|
+
row = { workflow: "get-publish.yml", runId: prior.runId, ...prior.runUrl ? { runUrl: prior.runUrl } : {}, conclusion: "pending" };
|
|
32078
|
+
if (input.watch) row.conclusion = await watchTenantRun(deps, prior.runId, input.repo);
|
|
32047
32079
|
} else {
|
|
32048
32080
|
const since = (deps.now ?? Date.now)();
|
|
32049
32081
|
try {
|
|
@@ -32058,7 +32090,7 @@ async function appendServedChannelStage(deps, input, dispatch) {
|
|
|
32058
32090
|
};
|
|
32059
32091
|
}
|
|
32060
32092
|
const correlated = await correlateGetPublishRun(deps, since, HUB_SERVED_PRODUCT, "stage");
|
|
32061
|
-
if (correlated.read
|
|
32093
|
+
if (correlated.read === "failed") {
|
|
32062
32094
|
return {
|
|
32063
32095
|
...dispatch,
|
|
32064
32096
|
note: `${dispatch.note}; get-publish stage dispatched but its run could not be READ \u2014 check Actions; recover with \`${remedy}\``,
|
|
@@ -32066,6 +32098,14 @@ async function appendServedChannelStage(deps, input, dispatch) {
|
|
|
32066
32098
|
workflowRuns: [...dispatch.workflowRuns ?? [], planeFailureRow("get-publish.yml", "stage dispatched, but its run could not be read")]
|
|
32067
32099
|
};
|
|
32068
32100
|
}
|
|
32101
|
+
if (correlated.read === "absent") {
|
|
32102
|
+
const dispatchedAt = new Date(since).toISOString();
|
|
32103
|
+
return {
|
|
32104
|
+
...dispatch,
|
|
32105
|
+
note: `${dispatch.note}; get-publish stage dispatched at ${dispatchedAt} but its run is not listed yet \u2014 recorded PENDING; \`devops release --resume\` re-correlates it by title and time`,
|
|
32106
|
+
workflowRuns: [...dispatch.workflowRuns ?? [], { workflow: "get-publish.yml", runUrlNote: `stage dispatched ${dispatchedAt}; run not listed within the correlation window`, conclusion: "pending", dispatchedAt, titleTokens: [HUB_SERVED_PRODUCT, "stage"] }]
|
|
32107
|
+
};
|
|
32108
|
+
}
|
|
32069
32109
|
row = { workflow: "get-publish.yml", runId: correlated.runId, ...correlated.runUrl ? { runUrl: correlated.runUrl } : {}, conclusion: "pending" };
|
|
32070
32110
|
if (input.watch) row.conclusion = await watchTenantRun(deps, correlated.runId, input.repo);
|
|
32071
32111
|
}
|
|
@@ -32090,12 +32130,15 @@ async function appendServedChannelPublish(deps, input, dispatch, redeployed) {
|
|
|
32090
32130
|
if (input.publishStatus !== "success") {
|
|
32091
32131
|
return withNote(`served channel NOT moved (publish did not succeed) \u2014 after the publish is green, run \`${remedy}\``);
|
|
32092
32132
|
}
|
|
32093
|
-
const prior = input.releaseSha ? await findPriorGreenDispatch(deps, input.repo, "get-publish.yml", input.releaseSha, [HUB_SERVED_PRODUCT, "pointer"]) : void 0;
|
|
32133
|
+
const prior = input.releaseSha ? await findPriorGreenDispatch(deps, input.repo, "get-publish.yml", input.releaseSha, [HUB_SERVED_PRODUCT, "pointer"], { includeActive: true }) : void 0;
|
|
32094
32134
|
let runRow;
|
|
32095
32135
|
let runStatus;
|
|
32096
|
-
if (prior) {
|
|
32136
|
+
if (prior && !prior.active) {
|
|
32097
32137
|
runRow = { workflow: "get-publish.yml", runId: prior.runId, ...prior.runUrl ? { runUrl: prior.runUrl } : {} };
|
|
32098
32138
|
runStatus = "success";
|
|
32139
|
+
} else if (prior?.active) {
|
|
32140
|
+
runRow = { workflow: "get-publish.yml", runId: prior.runId, ...prior.runUrl ? { runUrl: prior.runUrl } : {} };
|
|
32141
|
+
runStatus = input.watch ? await watchTenantRun(deps, prior.runId, input.repo) : "pending";
|
|
32099
32142
|
} else {
|
|
32100
32143
|
const version = input.tag?.replace(/^v/, "") ?? (deps.readLauncherVersion ?? readLocalLauncherVersion)();
|
|
32101
32144
|
if (!version) return withNote("the local launcher version could not be read \u2014 nothing was compared, so the move is unverified");
|
|
@@ -32107,9 +32150,17 @@ async function appendServedChannelPublish(deps, input, dispatch, redeployed) {
|
|
|
32107
32150
|
return withNote(`get-publish dispatch FAILED: ${msg}. The release itself landed, but the SERVED CHANNEL IS UNCHANGED \u2014 recover with \`${pointerRemedy}\``, "failure", planeFailureRow("get-publish.yml", `dispatch refused: ${msg}`));
|
|
32108
32151
|
}
|
|
32109
32152
|
const correlated = await correlateGetPublishRun(deps, since, HUB_SERVED_PRODUCT, "pointer");
|
|
32110
|
-
if (correlated.read
|
|
32153
|
+
if (correlated.read === "failed") {
|
|
32111
32154
|
return withNote(`get-publish dispatched but its run could not be READ: ${correlated.error ?? "no matching run appeared"}. Check the Actions tab; if it never ran, recover with \`${pointerRemedy}\``, "failure", planeFailureRow("get-publish.yml", `dispatched, but its run could not be read`));
|
|
32112
32155
|
}
|
|
32156
|
+
if (correlated.read === "absent") {
|
|
32157
|
+
const dispatchedAt = new Date(since).toISOString();
|
|
32158
|
+
return {
|
|
32159
|
+
...dispatch,
|
|
32160
|
+
note: `${dispatch.note}; get-publish pointer dispatched at ${dispatchedAt} but its run is not listed yet \u2014 recorded PENDING; \`devops release --resume\` re-correlates it by title and time`,
|
|
32161
|
+
workflowRuns: [...dispatch.workflowRuns ?? [], { workflow: "get-publish.yml", runUrlNote: `pointer dispatched ${dispatchedAt}; run not listed within the correlation window`, conclusion: "pending", dispatchedAt, titleTokens: [HUB_SERVED_PRODUCT, "pointer"] }]
|
|
32162
|
+
};
|
|
32163
|
+
}
|
|
32113
32164
|
const { runId, runUrl } = correlated;
|
|
32114
32165
|
runRow = { workflow: "get-publish.yml", ...runId ? { runId } : {}, ...runUrl ? { runUrl } : {} };
|
|
32115
32166
|
if (!input.watch) {
|
|
@@ -32121,7 +32172,7 @@ async function appendServedChannelPublish(deps, input, dispatch, redeployed) {
|
|
|
32121
32172
|
}
|
|
32122
32173
|
runStatus = await watchTenantRun(deps, runId, input.repo);
|
|
32123
32174
|
}
|
|
32124
|
-
if (runStatus
|
|
32175
|
+
if (runStatus === "failure") {
|
|
32125
32176
|
return {
|
|
32126
32177
|
...dispatch,
|
|
32127
32178
|
note: `${dispatch.note}; get-publish.yml ${runStatus} \u2014 the SERVED CHANNEL IS UNCHANGED; recover with \`${pointerRemedy}\``,
|
|
@@ -32129,6 +32180,13 @@ async function appendServedChannelPublish(deps, input, dispatch, redeployed) {
|
|
|
32129
32180
|
deployStatus: "failure"
|
|
32130
32181
|
};
|
|
32131
32182
|
}
|
|
32183
|
+
if (runStatus === "pending") {
|
|
32184
|
+
return {
|
|
32185
|
+
...dispatch,
|
|
32186
|
+
note: `${dispatch.note}; get-publish.yml pointer run is still unverified (pending) \u2014 the served channel move is not yet proven; \`devops release --resume\` re-reads it`,
|
|
32187
|
+
workflowRuns: [...dispatch.workflowRuns ?? [], { ...runRow, conclusion: "pending" }]
|
|
32188
|
+
};
|
|
32189
|
+
}
|
|
32132
32190
|
const expected = input.tag ? input.tag.replace(/^v/, "") : (deps.readLauncherVersion ?? readLocalLauncherVersion)();
|
|
32133
32191
|
const runs2 = [...dispatch.workflowRuns ?? [], { ...runRow, conclusion: "success" }];
|
|
32134
32192
|
if (!channelUrl) {
|
|
@@ -32269,6 +32327,14 @@ async function appendInstallHomePublish(deps, input, dispatch) {
|
|
|
32269
32327
|
deployStatus: "failure"
|
|
32270
32328
|
};
|
|
32271
32329
|
}
|
|
32330
|
+
if (correlated.read === "absent") {
|
|
32331
|
+
const dispatchedAt = new Date(since).toISOString();
|
|
32332
|
+
return {
|
|
32333
|
+
...dispatch,
|
|
32334
|
+
note: note(`installer home publish for ${product} dispatched at ${dispatchedAt} but its run is not listed yet \u2014 recorded PENDING; \`devops release --resume\` re-correlates it by title and time`),
|
|
32335
|
+
workflowRuns: [...dispatch.workflowRuns ?? [], { workflow: "get-publish.yml", runUrlNote: `installer home (${product}) dispatched ${dispatchedAt}; run not listed within the correlation window`, conclusion: "pending", dispatchedAt, titleTokens: [product] }]
|
|
32336
|
+
};
|
|
32337
|
+
}
|
|
32272
32338
|
const row = { workflow: "get-publish.yml", ...correlated.runId ? { runId: correlated.runId } : {}, ...correlated.runUrl ? { runUrl: correlated.runUrl } : {} };
|
|
32273
32339
|
if (!input.watch) {
|
|
32274
32340
|
return {
|
|
@@ -32278,7 +32344,7 @@ async function appendInstallHomePublish(deps, input, dispatch) {
|
|
|
32278
32344
|
};
|
|
32279
32345
|
}
|
|
32280
32346
|
const status = await watchTenantRun(deps, correlated.runId, HUB_REPO2);
|
|
32281
|
-
if (status
|
|
32347
|
+
if (status === "failure") {
|
|
32282
32348
|
return {
|
|
32283
32349
|
...dispatch,
|
|
32284
32350
|
note: note(`get-publish.yml (product=${product}) ${status} \u2014 the INSTALLER HOME IS UNCHANGED; recover with \`${remedy}\``),
|
|
@@ -32286,6 +32352,13 @@ async function appendInstallHomePublish(deps, input, dispatch) {
|
|
|
32286
32352
|
deployStatus: "failure"
|
|
32287
32353
|
};
|
|
32288
32354
|
}
|
|
32355
|
+
if (status === "pending") {
|
|
32356
|
+
return {
|
|
32357
|
+
...dispatch,
|
|
32358
|
+
note: note(`get-publish.yml (product=${product}) run is still unverified (pending) \u2014 the installer home is not yet proven published; \`devops release --resume\` re-reads it`),
|
|
32359
|
+
workflowRuns: [...dispatch.workflowRuns ?? [], { ...row, conclusion: "pending" }]
|
|
32360
|
+
};
|
|
32361
|
+
}
|
|
32289
32362
|
return {
|
|
32290
32363
|
...dispatch,
|
|
32291
32364
|
note: note(`installer home published for ${product} (get-publish.yml green)`),
|
|
@@ -32324,8 +32397,10 @@ async function appendServedChannelLegs(deps, input, dispatch) {
|
|
|
32324
32397
|
]);
|
|
32325
32398
|
return mergeServedLegs(dispatch, home2, proof2);
|
|
32326
32399
|
}
|
|
32327
|
-
const
|
|
32328
|
-
const
|
|
32400
|
+
const stageRows = (prepared.workflowRuns ?? []).filter((r) => r.workflow === "get-publish.yml");
|
|
32401
|
+
const stageGreen = stageRows.every((r) => r.conclusion === "success");
|
|
32402
|
+
const stagePending = !stageGreen && stageRows.every((r) => r.conclusion !== "failure");
|
|
32403
|
+
const channel = stageGreen || !isHubControlRepo(input.repo) || publishStatus !== "success" ? await appendServedChannelPublish(deps, { repo: input.repo, publishStatus, watch: input.watch, releaseSha: input.releaseSha, tag: input.tag }, dispatch, Promise.resolve(prepared)) : { ...dispatch, note: `${dispatch.note}; served channel NOT moved (stage ${stagePending ? "still pending \u2014 `devops release --resume` re-correlates the stage run and moves the pointer" : "not green"}) \u2014 recover with \`${getPublishDispatchCommand(input.repo)} -f mode=stage\`, then \`${getPublishDispatchCommand(input.repo)} -f mode=pointer -f version=${input.tag.replace(/^v/, "")}\`` };
|
|
32329
32404
|
const out = mergeServedLegs(dispatch, {
|
|
32330
32405
|
...dispatch,
|
|
32331
32406
|
note: dispatch.note + prepared.note,
|
|
@@ -44822,6 +44897,56 @@ function registerRunRerun(run, writeToken, projects) {
|
|
|
44822
44897
|
});
|
|
44823
44898
|
}
|
|
44824
44899
|
|
|
44900
|
+
// src/run-dispatch.ts
|
|
44901
|
+
init_cli_shared();
|
|
44902
|
+
async function dispatchWorkflow(repo, workflow, ref, inputs, deps) {
|
|
44903
|
+
if (!/^[\w.-]+\/[\w.-]+$/.test(repo)) throw new Error("run dispatch: invalid repository");
|
|
44904
|
+
if (!/^[A-Za-z0-9_.-]+\.ya?ml$/.test(workflow)) throw new Error("run dispatch: workflow must be a YAML filename");
|
|
44905
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._/-]{0,199}$/.test(ref) || ref.includes("..") || ref.includes("//") || ref.endsWith("/")) throw new Error("run dispatch: invalid ref");
|
|
44906
|
+
if (inputs.length > 10) throw new Error("run dispatch: at most 10 inputs");
|
|
44907
|
+
const fields = ["-f", `ref=${ref}`];
|
|
44908
|
+
const names = /* @__PURE__ */ new Set();
|
|
44909
|
+
for (const input of inputs) {
|
|
44910
|
+
const match = /^([A-Za-z_][A-Za-z0-9_]{0,63})=([^\r\n]{0,2048})$/.exec(input);
|
|
44911
|
+
if (!match || names.has(match[1])) throw new Error("run dispatch: invalid or duplicate input");
|
|
44912
|
+
names.add(match[1]);
|
|
44913
|
+
fields.push("-f", `inputs[${match[1]}]=${match[2]}`);
|
|
44914
|
+
}
|
|
44915
|
+
const projects = await deps.projects();
|
|
44916
|
+
if (!projects) throw new Error("run dispatch: registry unavailable; no dispatch attempted");
|
|
44917
|
+
if (!projects.some((p) => (p.repos ?? []).some((r) => typeof r === "string" && registryRepoKey(qualifyRegistryRepo(r)) === registryRepoKey(repo)))) {
|
|
44918
|
+
throw new Error("run dispatch: repository is not registered; no dispatch attempted");
|
|
44919
|
+
}
|
|
44920
|
+
let status;
|
|
44921
|
+
try {
|
|
44922
|
+
status = await deps.post(`repos/${repo}/actions/workflows/${workflow}/dispatches`, fields);
|
|
44923
|
+
} catch {
|
|
44924
|
+
throw new Error("run dispatch: outcome unknown; inspect GitHub before any retry");
|
|
44925
|
+
}
|
|
44926
|
+
if (status !== 204) throw new Error("run dispatch: outcome unknown; inspect GitHub before any retry");
|
|
44927
|
+
return { ok: true, repo, workflow, ref, credential: "app_installation", status: "dispatch-requested", httpStatus: status };
|
|
44928
|
+
}
|
|
44929
|
+
function appDispatchPost(writeToken, exec3 = execFileP) {
|
|
44930
|
+
return async (path2, fields) => {
|
|
44931
|
+
const token = await writeToken();
|
|
44932
|
+
const { stdout } = await exec3("gh", ["api", "--include", "--method", "POST", path2, ...fields], {
|
|
44933
|
+
timeout: 2e4,
|
|
44934
|
+
env: { ...process.env, GH_TOKEN: token, MMI_GH_POOL: "app" }
|
|
44935
|
+
});
|
|
44936
|
+
return Number(/^HTTP\/[\d.]+ (\d{3})/.exec(stdout)?.[1] ?? 0);
|
|
44937
|
+
};
|
|
44938
|
+
}
|
|
44939
|
+
function registerRunDispatch(run, writeToken, projects) {
|
|
44940
|
+
run.command("dispatch <workflow>").description("request one workflow dispatch using the org App; an ambiguous result is never retried").requiredOption("--repo <owner/repo>", "registered repository").requiredOption("--ref <ref>", "workflow ref").option("--input <name=value>", "workflow input (repeatable)", (v, values) => [...values, v], []).option("--json", "machine-readable receipt").action(async (workflow, options) => {
|
|
44941
|
+
try {
|
|
44942
|
+
const receipt = await dispatchWorkflow(options.repo, workflow, options.ref, options.input, { projects, post: appDispatchPost(writeToken) });
|
|
44943
|
+
console.log(options.json ? JSON.stringify(receipt) : `run dispatch: requested ${receipt.repo}/${receipt.workflow} at ${receipt.ref}`);
|
|
44944
|
+
} catch (error) {
|
|
44945
|
+
return fail(error.message);
|
|
44946
|
+
}
|
|
44947
|
+
});
|
|
44948
|
+
}
|
|
44949
|
+
|
|
44825
44950
|
// src/devops-vars.ts
|
|
44826
44951
|
var import_node_crypto13 = require("node:crypto");
|
|
44827
44952
|
init_github_client();
|
|
@@ -46042,6 +46167,15 @@ async function attachOnIntegrationRefusal(detail, cfg, contentNodeId, attach = a
|
|
|
46042
46167
|
return { warning: `board attach refused: the gh login is an App installation token without Projects access (${detail}) and the Hub App leg failed (${e.message}). If the Hub reports the same refusal, ${PROJECTS_PERMISSION_HINT}` };
|
|
46043
46168
|
}
|
|
46044
46169
|
}
|
|
46170
|
+
var ATTACH_AUTHORITY_REFUSAL = /FORBIDDEN|NOT_FOUND|Resource not accessible|missing required scopes|insufficient (?:permissions?|scope)|Could not resolve to a node|does not have (?:access|permission)/i;
|
|
46171
|
+
async function attachOnAuthorityRefusal(detail, cfg, contentNodeId, attach = attachViaHubApp) {
|
|
46172
|
+
if (!ATTACH_AUTHORITY_REFUSAL.test(detail)) return void 0;
|
|
46173
|
+
try {
|
|
46174
|
+
return { projectItemId: await attach(cfg, contentNodeId) };
|
|
46175
|
+
} catch (e) {
|
|
46176
|
+
return { warning: `${detail}; the Hub attach also refused (${e.message}) \u2014 attach needs org-owner, repo-admin or repository write authority on the board's repository` };
|
|
46177
|
+
}
|
|
46178
|
+
}
|
|
46045
46179
|
async function issueNodeIdForAttach(repo, number, ghView, client) {
|
|
46046
46180
|
try {
|
|
46047
46181
|
return (await ghView()).trim();
|
|
@@ -46300,7 +46434,7 @@ function refuseNotClaimable(e, json) {
|
|
|
46300
46434
|
process.exitCode = 1;
|
|
46301
46435
|
return true;
|
|
46302
46436
|
}
|
|
46303
|
-
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
|
|
46437
|
+
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";
|
|
46304
46438
|
function flattenBoardReport(report) {
|
|
46305
46439
|
const closingByRef = /* @__PURE__ */ new Map();
|
|
46306
46440
|
for (const pull of report.pullRequests ?? []) {
|
|
@@ -46322,6 +46456,9 @@ function flattenBoardReport(report) {
|
|
|
46322
46456
|
openPullRequests: closingByRef.get(ref) ?? []
|
|
46323
46457
|
}))));
|
|
46324
46458
|
}
|
|
46459
|
+
function withBoardItems(report) {
|
|
46460
|
+
return { ...report, items: flattenBoardReport(report) };
|
|
46461
|
+
}
|
|
46325
46462
|
function registerBoardCommands(program3) {
|
|
46326
46463
|
function withDiscoverMissDetail(message2) {
|
|
46327
46464
|
const miss = lastBoardDiscoverMiss();
|
|
@@ -46346,7 +46483,7 @@ function registerBoardCommands(program3) {
|
|
|
46346
46483
|
snapshotOnly: o.snapshotOnly,
|
|
46347
46484
|
...o.minCursor !== void 0 ? { minCursor: Number(o.minCursor) } : {}
|
|
46348
46485
|
}, o.direct ? {} : { snapshot: registryClientDeps(config) });
|
|
46349
|
-
const output = o.json ? JSON.stringify(o.flat ? flattenBoardReport(report) : report) : renderBoardReport(report);
|
|
46486
|
+
const output = o.json ? JSON.stringify(o.flat ? flattenBoardReport(report) : withBoardItems(report)) : renderBoardReport(report);
|
|
46350
46487
|
if (!o.out) console.log(output);
|
|
46351
46488
|
else console.log(`Wrote board read to ${o.out} (UTF-8, ${writeUtf8Receipt(o.out, output)} bytes)`);
|
|
46352
46489
|
} catch (e) {
|
|
@@ -63066,9 +63203,11 @@ function renderResidueSweepLines(report) {
|
|
|
63066
63203
|
const more = report.unrecorded.length > 5 ? `, \u2026 ${report.unrecorded.length - 5} more` : "";
|
|
63067
63204
|
lines2.push(`pr merge: ${report.unrecorded.length} unregistered folder(s) with content under the worktree root${where} were left untouched (not recorded by a merge): ${listed.map((u) => u.path).join(", ")}${more}`);
|
|
63068
63205
|
for (const u of listed) lines2.push(`pr merge: left untouched ${u.path} \u2014 ${provenanceText(u)}`);
|
|
63206
|
+
lines2.push(`pr merge: recovery: ${RESIDUE_RECOVERY_MOVE}`);
|
|
63069
63207
|
}
|
|
63070
63208
|
return lines2;
|
|
63071
63209
|
}
|
|
63210
|
+
var RESIDUE_RECOVERY_MOVE = `run \`git worktree list --porcelain\` in the primary checkout and confirm the path is absent and its provenance above shows no live lane (not registered; admin dir gone or .git pointer missing); nothing can record it for this sweep (only \`pr merge\` records its own merged worktree in <git-common-dir>/${RESIDUE_LEDGER_FILE}), so removing a non-empty unrecorded folder stays a human decision \u2014 hand the confirmed path to a human; this sweep never deletes it`;
|
|
63072
63211
|
function provenanceText(u) {
|
|
63073
63212
|
const pointer = u.gitdir ? `.git \u2192 ${u.gitdir}${u.gitdirPresent ? "" : " (admin dir gone)"}` : ".git pointer missing";
|
|
63074
63213
|
const registration = `worktrees/${u.registrationId} ${u.registrationPresent ? "registered" : "not registered"}`;
|
|
@@ -63226,6 +63365,24 @@ async function attachToProject(issueNumber, repo, priority) {
|
|
|
63226
63365
|
}
|
|
63227
63366
|
return boardAttachRateLimitedReceipt(resetEpochSeconds);
|
|
63228
63367
|
}
|
|
63368
|
+
const authority = contentId ? await attachOnAuthorityRefusal(detail, cfg, contentId) : void 0;
|
|
63369
|
+
if (authority && "projectItemId" in authority) {
|
|
63370
|
+
if (priority) {
|
|
63371
|
+
try {
|
|
63372
|
+
await setBoardItemPriority(defaultGitHubClient(), cfg, authority.projectItemId, priority);
|
|
63373
|
+
} catch (pe) {
|
|
63374
|
+
const perr = pe;
|
|
63375
|
+
process.stderr.write(`warning: issue #${issueNumber} board Priority not set: ${(perr.stderr || perr.message || String(pe)).trim()}
|
|
63376
|
+
`);
|
|
63377
|
+
}
|
|
63378
|
+
}
|
|
63379
|
+
return { projectItemId: authority.projectItemId, onBoard: true, credential: "app_installation" };
|
|
63380
|
+
}
|
|
63381
|
+
if (authority) {
|
|
63382
|
+
process.stderr.write(`warning: issue #${issueNumber} created but NOT added to the project board: ${authority.warning}
|
|
63383
|
+
`);
|
|
63384
|
+
return { onBoard: false };
|
|
63385
|
+
}
|
|
63229
63386
|
process.stderr.write(`warning: issue #${issueNumber} created but NOT added to the project board: ${detail}
|
|
63230
63387
|
`);
|
|
63231
63388
|
return { onBoard: false };
|
|
@@ -73836,7 +73993,16 @@ setGhWriteTokenProvider(() => {
|
|
|
73836
73993
|
throw e;
|
|
73837
73994
|
});
|
|
73838
73995
|
});
|
|
73839
|
-
|
|
73996
|
+
var claimWriteToken;
|
|
73997
|
+
setClaimWriteTokenProvider(() => {
|
|
73998
|
+
if (process.env.MMI_ACTOR?.trim() === "app") {
|
|
73999
|
+
return mintFilingWriteToken(appActorDeps()).then((t) => t.token);
|
|
74000
|
+
}
|
|
74001
|
+
return claimWriteToken ??= mintFilingWriteToken(appActorDeps()).then((t) => t.token, (e) => {
|
|
74002
|
+
if (e instanceof AppActorError && e.code === "vault-denied") return void 0;
|
|
74003
|
+
throw e;
|
|
74004
|
+
});
|
|
74005
|
+
});
|
|
73840
74006
|
setStatusWriteTokenProvider(() => mintStatusWriteToken(appActorDeps()));
|
|
73841
74007
|
var registerPreAction = Reflect.get(program2, ["h", "ook"].join(""));
|
|
73842
74008
|
registerPreAction.call(program2, "preAction", async (_thisCommand, actionCommand) => {
|
|
@@ -73848,6 +74014,7 @@ registerPreAction.call(program2, "preAction", async (_thisCommand, actionCommand
|
|
|
73848
74014
|
"ci cancel-orphans",
|
|
73849
74015
|
"run cancel",
|
|
73850
74016
|
"run rerun",
|
|
74017
|
+
"run dispatch",
|
|
73851
74018
|
// #9779/#9780: the vars verbs and archive-status validate --repo/name/value before the first
|
|
73852
74019
|
// dispatch, so their scoped App mint waits for the action handler like the run writes above.
|
|
73853
74020
|
"vars get",
|
|
@@ -74727,13 +74894,18 @@ ci.command("hosted-usage").description("read-only hosted tick and lane-demand al
|
|
|
74727
74894
|
});
|
|
74728
74895
|
registerCiChecksWait(ci);
|
|
74729
74896
|
registerCancelOrphans(ci, async () => (await mintCommandWriteToken(appActorDeps(), "ci cancel-orphans")).token);
|
|
74730
|
-
var runRoot = program2.command("run").description("bounded workflow-run writes
|
|
74897
|
+
var runRoot = program2.command("run").description("bounded workflow-run writes on the org App actions:write profile");
|
|
74731
74898
|
registerRunCancel(runRoot, async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), "run cancel")).token);
|
|
74732
74899
|
registerRunRerun(
|
|
74733
74900
|
runRoot,
|
|
74734
74901
|
async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), "run rerun")).token,
|
|
74735
74902
|
async () => fetchProjectsList(registryClientDeps(await loadConfig()))
|
|
74736
74903
|
);
|
|
74904
|
+
registerRunDispatch(
|
|
74905
|
+
runRoot,
|
|
74906
|
+
async () => (await mintCommandWriteToken(appActorDeps(), "run dispatch")).token,
|
|
74907
|
+
async () => fetchProjectsList(registryClientDeps(await loadConfig()))
|
|
74908
|
+
);
|
|
74737
74909
|
var varsAppClient = (commandPath4) => async () => (
|
|
74738
74910
|
// The concrete client always provides restWithStatus (optional on the interface only so hand-built
|
|
74739
74911
|
// test stubs of GitHubClient keep compiling).
|
package/cli/package.json
CHANGED
package/package.json
CHANGED
package/skills/board/SKILL.md
CHANGED
|
@@ -20,12 +20,16 @@ One call; `--flat` gives one array of rows to iterate. A routine read already ex
|
|
|
20
20
|
items, so no status post-filter is needed. The detailed form (`--json` without `--flat`) carries `viewer`,
|
|
21
21
|
`repo` and the project title; use its `primary` for this repo and `secondary` for other repos on the
|
|
22
22
|
Project. Without `--flat`, `board read --json` returns an object
|
|
23
|
-
`{project, viewer, repo, primary, secondary, warnings, partial}` whose `primary` and `secondary` are bucket
|
|
24
|
-
objects `{userOwned, claimable, taken, unownedInFlight}` of item arrays
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
`items
|
|
28
|
-
rows
|
|
23
|
+
`{project, viewer, repo, primary, secondary, items, warnings, partial}` whose `primary` and `secondary` are bucket
|
|
24
|
+
objects `{userOwned, claimable, taken, unownedInFlight}` of item arrays, and whose top-level `items` (#9974) is
|
|
25
|
+
the flat row array — the same rows `--flat` prints — so a number|status|title projection can iterate
|
|
26
|
+
`report.items` without a second read. Filter a bucket (`report.primary.claimable`) when ownership context
|
|
27
|
+
matters, or `report.items` / `--json --flat` for a projection. Never sniff
|
|
28
|
+
for the list (`j.items ?? j.rows ?? j`, `Object.values`, any generic "find the list" wrapper): `j.rows` is not a
|
|
29
|
+
key, and on an older report it threw
|
|
30
|
+
`items is not iterable` before any row was read. For a number|status|claim|title projection across non-Done
|
|
31
|
+
rows, read `report.items` or run `mmi-cli oracle board read --json --flat --out .jerv/tmp/board.json` and filter
|
|
32
|
+
that array locally.
|
|
29
33
|
`--flat` rows are `{ref,number,title,status,assignees,labels,bucket,claim,openPullRequests}` (`claim` is the
|
|
30
34
|
parsed mmi-claim evidence object, null when unclaimed; `openPullRequests` lists the OPEN PRs whose
|
|
31
35
|
closing-issue body links name the item — on the full JSON object that list lives at `report.pullRequests`,
|
|
@@ -21,9 +21,12 @@ rows to iterate. A routine read already excludes Done and closed items, so no st
|
|
|
21
21
|
One call. The detailed form (`--json` without `--flat`) carries `viewer`, `repo` and the project title — do not spend extra calls on
|
|
22
22
|
`gh api user` or `gh repo view`. Use `primary` for this repo and `secondary` for other repos on the same
|
|
23
23
|
Project. Without `--flat`, `board read --json` returns an object
|
|
24
|
-
`{project, viewer, repo, primary, secondary, warnings, partial}` whose `primary` and `secondary` are bucket
|
|
25
|
-
objects `{userOwned, claimable, taken, unownedInFlight}` of item arrays
|
|
26
|
-
|
|
24
|
+
`{project, viewer, repo, primary, secondary, items, warnings, partial}` whose `primary` and `secondary` are bucket
|
|
25
|
+
objects `{userOwned, claimable, taken, unownedInFlight}` of item arrays, and whose top-level `items` (#9974) holds
|
|
26
|
+
the flat rows — exactly what `--flat` prints — so iterate `report.items` instead of spending a second read.
|
|
27
|
+
Filter a bucket (`report.primary.claimable`) when ownership context matters. Never sniff for the list
|
|
28
|
+
(`j.items ?? j.rows ?? j` is only coincidentally safe now, `Object.values` is not): `j.rows` is not a key.
|
|
29
|
+
Partial reads
|
|
27
30
|
exit nonzero; pass `--allow-partial` only when the dev accepts an incomplete board.
|
|
28
31
|
|
|
29
32
|
Partition, as the CLI computes it:
|
|
@@ -2,4 +2,7 @@
|
|
|
2
2
|
## MMI developer environment
|
|
3
3
|
|
|
4
4
|
Use the [canonical MMI developer-environment guide](https://github.com/mutmutco/MMI-Hub/blob/main/docs/Architecture/agentic-dev-environment.md) for shared setup and daily agent work.
|
|
5
|
+
|
|
6
|
+
Daily loop: `mmi-cli oracle issue create` → `mmi-cli oracle board claim <n>` (stamps the mmi-claim lease) → `mmi-cli devops pr create` → `mmi-cli devops pr land <pr>`.
|
|
7
|
+
Any org member with repository write (developer) or repository admin (project admin) can run it, with no vault access; release trains stay org-owner only.
|
|
5
8
|
<!-- mmi-developer-environment:end -->
|
|
@@ -6,5 +6,9 @@
|
|
|
6
6
|
"scripts/next-version.mjs": {
|
|
7
7
|
"blob": "2c1985b59edc779695479559074e7654c1574a11",
|
|
8
8
|
"reason": "comment-only note that rc/release modes are unused in dev-only MM suite repos (#8282), no runtime or operator behaviour change"
|
|
9
|
+
},
|
|
10
|
+
"README.md": {
|
|
11
|
+
"blob": "768c5e35cdb48849c77bce6b334658adefe6a5d8",
|
|
12
|
+
"reason": "the developer-environment seed block now states the concrete mmi-cli loop (issue create, board claim, PR create, PR land) and who may run it — the README propagation is intended (#9987); no runbook step changes"
|
|
9
13
|
}
|
|
10
14
|
}
|
|
@@ -13,6 +13,23 @@ our self-hosted runners, dispatched by an org owner, never on a workstation (#91
|
|
|
13
13
|
A 503 `github-rate-limited` answer means the org App's hourly GitHub allowance is used up. It is
|
|
14
14
|
not a denial: wait for the reset time it names, then retry.
|
|
15
15
|
|
|
16
|
+
## Developer and project-admin access
|
|
17
|
+
|
|
18
|
+
A developer is an org member with GitHub write on a product repository. A project admin is that
|
|
19
|
+
repository's admin. Both run the full loop with mmi-cli and need no MMI-Hub vault access:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
mmi-cli oracle issue create --title "<title>" --body-file <path> # files and attaches the board card
|
|
23
|
+
mmi-cli oracle board claim <n> # stamps the mmi-claim lease
|
|
24
|
+
mmi-cli devops pr create --title "<title>" --base development
|
|
25
|
+
mmi-cli devops pr land <pr-number>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Board writes (attach, claim stamp, status move) gate on org owner, repo admin or repository write, so
|
|
29
|
+
these roles pass. Where the GitHub App route is absent the writes go as the person's own GitHub login
|
|
30
|
+
(one note says so); an agent host with the App route writes as the App. MMI-Hub's own merges and all
|
|
31
|
+
release trains stay org-owner only.
|
|
32
|
+
|
|
16
33
|
Start with `mmi-cli onboard` and the [board skill](../../board/SKILL.md).
|
|
17
34
|
Reading or discussing the board is read-only until the developer authorizes work.
|
|
18
35
|
Claim approved work with `mmi-cli oracle board claim <issue>`, then prepare an isolated task
|