@mutmutco/cli 4.3.38 → 4.3.40
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/README.md +6 -4
- package/dist/main.cjs +310 -124
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,21 +6,23 @@ This package is published from [mutmutco/MMI-Hub](https://github.com/mutmutco/MM
|
|
|
6
6
|
|
|
7
7
|
The CLI carries the org **Hub endpoint** intrinsically (override with the `MMI_HUB_URL` env var), so a product repo needs **no committed control-plane config** to reach the Hub — board coords, deploy coordinates, OAuth, and the secrets layout are all discovered from the Hub registry at runtime.
|
|
8
8
|
|
|
9
|
+
Local stage recipes and printed plans use Bash on every platform. Windows requires Git Bash in its standard system or user installation, or `SHELL` set to the absolute `bash.exe` path. A missing Bash installation fails explicitly. Native Windows administration helpers remain independent of the stage shell.
|
|
10
|
+
|
|
9
11
|
## Install
|
|
10
12
|
|
|
11
|
-
```
|
|
13
|
+
```bash
|
|
12
14
|
npm install -g @mutmutco/cli
|
|
13
15
|
```
|
|
14
16
|
|
|
15
17
|
Authenticate GitHub once for Hub session issuance and Project board operations:
|
|
16
18
|
|
|
17
|
-
```
|
|
19
|
+
```bash
|
|
18
20
|
gh auth login --hostname github.com --git-protocol https --web --scopes "project"
|
|
19
21
|
```
|
|
20
22
|
|
|
21
23
|
Then verify the installed command:
|
|
22
24
|
|
|
23
|
-
```
|
|
25
|
+
```bash
|
|
24
26
|
mmi-cli --version
|
|
25
27
|
mmi-cli doctor --json
|
|
26
28
|
```
|
|
@@ -79,7 +81,7 @@ they don't get re-derived or re-litigated per session:
|
|
|
79
81
|
|
|
80
82
|
When working inside an `MMI-Hub` checkout before npm is available, use the committed bundle directly:
|
|
81
83
|
|
|
82
|
-
```
|
|
84
|
+
```bash
|
|
83
85
|
node cli/dist/index.cjs --version
|
|
84
86
|
node cli/dist/index.cjs doctor --json
|
|
85
87
|
```
|
package/dist/main.cjs
CHANGED
|
@@ -4149,6 +4149,20 @@ var HOUSE_MAP = {
|
|
|
4149
4149
|
onboard: "core"
|
|
4150
4150
|
// repo-readiness orientation (front door — torn toward oracle, kept core)
|
|
4151
4151
|
};
|
|
4152
|
+
var HOUSE_ALIASES = {
|
|
4153
|
+
issue: ["devops"],
|
|
4154
|
+
// filing/managing issues is oracle, but reads as shipping work
|
|
4155
|
+
"issue create": ["devops"]
|
|
4156
|
+
};
|
|
4157
|
+
function isDeclaredHouseAlias(houseToken, lookupPath) {
|
|
4158
|
+
const segments = lookupPath.split(" ");
|
|
4159
|
+
for (let end = segments.length; end > 0; end -= 1) {
|
|
4160
|
+
const key = segments.slice(0, end).join(" ");
|
|
4161
|
+
const aliases = HOUSE_ALIASES[key];
|
|
4162
|
+
if (aliases) return aliases.includes(houseToken);
|
|
4163
|
+
}
|
|
4164
|
+
return false;
|
|
4165
|
+
}
|
|
4152
4166
|
function houseForPath(path2) {
|
|
4153
4167
|
if (path2 === "") return "core";
|
|
4154
4168
|
const segments = path2.split(" ");
|
|
@@ -5256,6 +5270,39 @@ function unknownCommandDomainGuide(parentPath, token) {
|
|
|
5256
5270
|
return GUIDES.get(`${parentPath}:${token}`);
|
|
5257
5271
|
}
|
|
5258
5272
|
|
|
5273
|
+
// src/verb-alias.ts
|
|
5274
|
+
var VERB_ALIAS_TABLE = [
|
|
5275
|
+
// #6489: `board` has no `status` leaf (read|claim|show|move|done|doctor only); the read that
|
|
5276
|
+
// answers "what is this issue/PR's board column" is `board show <ref>`.
|
|
5277
|
+
{ typed: ["oracle", "board", "status"], canonical: ["oracle", "board", "show"] },
|
|
5278
|
+
{ typed: ["board", "status"], canonical: ["board", "show"] },
|
|
5279
|
+
// #6494: there is no top-level or housed `registry` command; org/registry project metadata is
|
|
5280
|
+
// `oracle org project get`.
|
|
5281
|
+
{ typed: ["registry"], canonical: ["oracle", "org", "project", "get"] },
|
|
5282
|
+
// #6486: filing/managing an issue reads as devops work (it ships something) but issues are oracle's
|
|
5283
|
+
// live truth; the real command is `oracle issue create`. (A narrower house-token-only reroute for
|
|
5284
|
+
// this same case may also exist elsewhere — this entry keeps the general table a complete answer on
|
|
5285
|
+
// its own, not dependent on that mechanism landing.)
|
|
5286
|
+
{ typed: ["devops", "issue", "create"], canonical: ["oracle", "issue", "create"] }
|
|
5287
|
+
];
|
|
5288
|
+
var TABLE_BY_LENGTH = [...VERB_ALIAS_TABLE].sort((a, b) => b.typed.length - a.typed.length);
|
|
5289
|
+
function matchesAt(argv, start, tokens2) {
|
|
5290
|
+
if (start + tokens2.length > argv.length) return false;
|
|
5291
|
+
return tokens2.every((tok, i) => argv[start + i] === tok);
|
|
5292
|
+
}
|
|
5293
|
+
function applyVerbAlias(argv) {
|
|
5294
|
+
const entry = TABLE_BY_LENGTH.find((e) => matchesAt(argv, 2, e.typed));
|
|
5295
|
+
if (!entry) return void 0;
|
|
5296
|
+
argv.splice(2, entry.typed.length, ...entry.canonical);
|
|
5297
|
+
return { typed: entry.typed.join(" "), canonical: entry.canonical.join(" ") };
|
|
5298
|
+
}
|
|
5299
|
+
function resolveVerbAliasShim(argv) {
|
|
5300
|
+
const hit = applyVerbAlias(argv);
|
|
5301
|
+
if (!hit) return;
|
|
5302
|
+
process.stderr.write(`mmi-cli: '${hit.typed}' is '${hit.canonical}' \u2014 ran it for you
|
|
5303
|
+
`);
|
|
5304
|
+
}
|
|
5305
|
+
|
|
5259
5306
|
// src/command-composition.ts
|
|
5260
5307
|
var import_node_os22 = require("node:os");
|
|
5261
5308
|
var import_node_path47 = require("node:path");
|
|
@@ -7195,6 +7242,50 @@ async function readRepoSurfaceLabels(repo, deps = {}) {
|
|
|
7195
7242
|
return void 0;
|
|
7196
7243
|
}
|
|
7197
7244
|
}
|
|
7245
|
+
function surfaceWords(label) {
|
|
7246
|
+
return label.slice(SURFACE_PREFIX.length).toLowerCase().split(/[-_]+/).filter(Boolean);
|
|
7247
|
+
}
|
|
7248
|
+
function pathWords(text) {
|
|
7249
|
+
const words = [];
|
|
7250
|
+
for (const hit of text.match(/[\w.-]+(?:\/[\w.-]+)+/g) ?? []) {
|
|
7251
|
+
for (const segment of hit.split("/")) words.push(...segment.toLowerCase().split(/[-_.]+/).filter(Boolean));
|
|
7252
|
+
}
|
|
7253
|
+
return words;
|
|
7254
|
+
}
|
|
7255
|
+
function proseWords(text) {
|
|
7256
|
+
return text.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
7257
|
+
}
|
|
7258
|
+
function inferSurface(candidates, context = {}) {
|
|
7259
|
+
const sorted = [...candidates].sort();
|
|
7260
|
+
if (sorted.length === 0) throw new Error("inferSurface: no surface:* candidates to choose from");
|
|
7261
|
+
const pWords = new Set(pathWords(context.body ?? ""));
|
|
7262
|
+
const tWords = /* @__PURE__ */ new Set([...proseWords(context.title ?? ""), ...proseWords(context.body ?? "")]);
|
|
7263
|
+
let best = sorted[0];
|
|
7264
|
+
let bestScore = 0;
|
|
7265
|
+
let bestFromPath = false;
|
|
7266
|
+
for (const label of sorted) {
|
|
7267
|
+
const words = surfaceWords(label);
|
|
7268
|
+
if (!words.length) continue;
|
|
7269
|
+
const pathHits = words.filter((w) => pWords.has(w)).length;
|
|
7270
|
+
const proseHits = words.filter((w) => tWords.has(w)).length;
|
|
7271
|
+
const score = pathHits * 2 + proseHits;
|
|
7272
|
+
if (score > bestScore) {
|
|
7273
|
+
bestScore = score;
|
|
7274
|
+
best = label;
|
|
7275
|
+
bestFromPath = pathHits > 0;
|
|
7276
|
+
}
|
|
7277
|
+
}
|
|
7278
|
+
if (bestScore === 0) {
|
|
7279
|
+
return {
|
|
7280
|
+
label: sorted[0],
|
|
7281
|
+
reason: `no path or title/body word matched any of ${sorted.length} surface label(s) \u2014 defaulted to the alphabetically-first`
|
|
7282
|
+
};
|
|
7283
|
+
}
|
|
7284
|
+
return {
|
|
7285
|
+
label: best,
|
|
7286
|
+
reason: bestFromPath ? "matched a path mentioned in the issue body" : "matched a word in the issue title/body"
|
|
7287
|
+
};
|
|
7288
|
+
}
|
|
7198
7289
|
async function checkSurfaceRequirement(input, deps = {}) {
|
|
7199
7290
|
if (input.waiver) {
|
|
7200
7291
|
const reason = input.waiver.reason.trim();
|
|
@@ -7226,20 +7317,7 @@ async function checkSurfaceRequirement(input, deps = {}) {
|
|
|
7226
7317
|
}
|
|
7227
7318
|
if (known.length === 0) return { enforcing: false, taxonomyAbsent: true };
|
|
7228
7319
|
if (labelsCarrySurface(input.labels)) return { enforcing: true };
|
|
7229
|
-
|
|
7230
|
-
const where = input.rowLabel ? `${input.rowLabel}: ` : "";
|
|
7231
|
-
return {
|
|
7232
|
-
enforcing: true,
|
|
7233
|
-
refusal: {
|
|
7234
|
-
message: `${command}: ${where}${input.repo} requires every open issue to carry exactly one surface:* label, and this one sets none \u2014 filing it unlabeled reds the board check on every open PR in that repo. Pass --surface <value>, or --no-surface if this filing is genuinely exempt`,
|
|
7235
|
-
payload: {
|
|
7236
|
-
code: ERROR_CODES.ERR_MISSING_FLAG,
|
|
7237
|
-
offending_flag: "--surface",
|
|
7238
|
-
// Advisory, not a closed enum: the board rule is "exactly one surface:* label", whatever its value.
|
|
7239
|
-
expected: [...known].sort()
|
|
7240
|
-
}
|
|
7241
|
-
}
|
|
7242
|
-
};
|
|
7320
|
+
return { enforcing: true, inferred: inferSurface(known, { title: input.title, body: input.body }) };
|
|
7243
7321
|
}
|
|
7244
7322
|
function conflictingSurfaceInputs(surfaceFlag, labels) {
|
|
7245
7323
|
if (!surfaceFlag) return void 0;
|
|
@@ -12120,10 +12198,9 @@ async function postIssueComment(client, input) {
|
|
|
12120
12198
|
|
|
12121
12199
|
// src/board-claim-move.ts
|
|
12122
12200
|
var CLAIM_CONCURRENCY = 5;
|
|
12123
|
-
function
|
|
12124
|
-
if (platform2 !== "win32") return message2;
|
|
12201
|
+
function withGatedWriteChainHint(message2) {
|
|
12125
12202
|
return `${message2}
|
|
12126
|
-
note:
|
|
12203
|
+
note: run refused writes separately or join dependent commands with '&&'; a ';' chain continues after failure`;
|
|
12127
12204
|
}
|
|
12128
12205
|
function isArchivedItemRefusal(message2) {
|
|
12129
12206
|
return /The item is archived and cannot be updated/i.test(message2);
|
|
@@ -12309,7 +12386,7 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
12309
12386
|
}
|
|
12310
12387
|
if (!options.force) {
|
|
12311
12388
|
const refusal = laneContestMessage(item.ref, contest, "claim");
|
|
12312
|
-
throw new Error(options.bulk ? refusal :
|
|
12389
|
+
throw new Error(options.bulk ? refusal : withGatedWriteChainHint(refusal));
|
|
12313
12390
|
}
|
|
12314
12391
|
previousHolder = displaced();
|
|
12315
12392
|
};
|
|
@@ -12493,7 +12570,7 @@ async function unclaimBoardIssue(options, deps = {}) {
|
|
|
12493
12570
|
const toStatus = options.toStatus ?? "Todo";
|
|
12494
12571
|
if (!options.force && item.contentType === "Issue") {
|
|
12495
12572
|
const contest = await checkLaneContest(client, item);
|
|
12496
|
-
if (contest.contested) throw new Error(
|
|
12573
|
+
if (contest.contested) throw new Error(withGatedWriteChainHint(laneContestMessage(item.ref, contest, "unclaim")));
|
|
12497
12574
|
}
|
|
12498
12575
|
try {
|
|
12499
12576
|
await client.rest("DELETE", `repos/${item.repository}/issues/${item.number}/assignees`, {
|
|
@@ -15617,10 +15694,10 @@ var rollout_plan_default = {
|
|
|
15617
15694
|
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)."
|
|
15618
15695
|
},
|
|
15619
15696
|
baseline: {
|
|
15620
|
-
version: "4.3.
|
|
15621
|
-
tag: "v4.3.
|
|
15622
|
-
commit: "
|
|
15623
|
-
npm: "@mutmutco/cli@4.3.
|
|
15697
|
+
version: "4.3.40",
|
|
15698
|
+
tag: "v4.3.40",
|
|
15699
|
+
commit: "487948fabf28",
|
|
15700
|
+
npm: "@mutmutco/cli@4.3.40"
|
|
15624
15701
|
},
|
|
15625
15702
|
exitCriterion: "fleet-n-of-n",
|
|
15626
15703
|
hubOnlyShortcut: "forbidden",
|
|
@@ -15637,14 +15714,14 @@ var rollout_plan_default = {
|
|
|
15637
15714
|
repo: "mutmutco/mmi-hub",
|
|
15638
15715
|
role: "canary",
|
|
15639
15716
|
schedule: "train",
|
|
15640
|
-
v3Target: "v4.3.
|
|
15717
|
+
v3Target: "v4.3.40"
|
|
15641
15718
|
}
|
|
15642
15719
|
],
|
|
15643
15720
|
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.",
|
|
15644
15721
|
rollback: {
|
|
15645
15722
|
independent: true,
|
|
15646
|
-
mechanism: "npm dist-tag latest -> 4.3.
|
|
15647
|
-
v3Target: "v4.3.
|
|
15723
|
+
mechanism: "npm dist-tag latest -> 4.3.40 and redeploy the Hub Lambda from tag v4.3.40 (487948fabf28); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
15724
|
+
v3Target: "v4.3.40 (@mutmutco/cli@4.3.40, tag commit 487948fabf28 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
15648
15725
|
}
|
|
15649
15726
|
},
|
|
15650
15727
|
{
|
|
@@ -21645,13 +21722,9 @@ async function runTrainDoctor(input) {
|
|
|
21645
21722
|
add(linked ? { code: "worktree-isolated", severity: "blocker", source: "local", title: `this is a linked worktree on ${currentBranch2 || "(detached)"}, not the lane's start branch ${startBranch} (#2770)`, remedy: `run the ${lane} lane from the primary checkout on ${startBranch}; a linked worktree never carries the train` } : { code: "branch-mismatch", severity: "blocker", source: "local", title: `on ${currentBranch2 || "(detached)"}; the ${lane} lane starts from ${startBranch}`, remedy: `git checkout ${startBranch}, then rerun; --heal never switches branches \u2014 the branch you stand on names the lane you meant, and healing it would run the train off a branch you did not pick (#6432)` });
|
|
21646
21723
|
}
|
|
21647
21724
|
const stage = laneStage(lane);
|
|
21648
|
-
|
|
21649
|
-
const workflows = deps.readWorkflows(cwd, workflowsRef);
|
|
21650
|
-
if (workflows === null) {
|
|
21651
|
-
add({ code: "workflows-unreadable", severity: "blocker", source: "local", title: `.github/workflows at ${workflowsRef} could not be read from ${cwd} \u2014 the tag-addressability (#5428) and npm-major (#5666) preflights did not run`, remedy: `run from a checkout whose git can read ${workflowsRef} (fetch origin first), then rerun; an unread workflow set is unverified, not green` });
|
|
21652
|
-
}
|
|
21725
|
+
let missing = [];
|
|
21653
21726
|
if (hints) {
|
|
21654
|
-
|
|
21727
|
+
missing = [
|
|
21655
21728
|
...!hints.hasDevelopmentBranch && lane !== "hotfix" ? ["development"] : [],
|
|
21656
21729
|
...!hints.hasMainBranch ? ["main"] : [],
|
|
21657
21730
|
// #6318: a `--dev` release never reads, merges or tags rc — a missing rc branch cannot block it.
|
|
@@ -21659,30 +21732,38 @@ async function runTrainDoctor(input) {
|
|
|
21659
21732
|
];
|
|
21660
21733
|
if (missing.length) add({ code: "bootstrap-gap", severity: "blocker", source: "origin", title: `train branch(es) missing on origin: ${missing.join(", ")}`, remedy: `bootstrap the repo train: \`mmi-cli devops bootstrap apply ${repo} --execute\` (from the MMI-Hub root), then rerun` });
|
|
21661
21734
|
}
|
|
21662
|
-
|
|
21663
|
-
|
|
21664
|
-
|
|
21665
|
-
|
|
21666
|
-
}
|
|
21667
|
-
|
|
21668
|
-
|
|
21669
|
-
|
|
21670
|
-
|
|
21671
|
-
|
|
21672
|
-
|
|
21673
|
-
|
|
21674
|
-
|
|
21675
|
-
|
|
21676
|
-
|
|
21677
|
-
|
|
21678
|
-
|
|
21679
|
-
|
|
21680
|
-
|
|
21681
|
-
|
|
21735
|
+
const startBranchMissing = startBranch != null && missing.includes(startBranch);
|
|
21736
|
+
const workflowsRef = `origin/${startBranch ?? "main"}`;
|
|
21737
|
+
const workflows = startBranchMissing ? null : deps.readWorkflows(cwd, workflowsRef);
|
|
21738
|
+
if (workflows === null && !startBranchMissing) {
|
|
21739
|
+
add({ code: "workflows-unreadable", severity: "blocker", source: "local", title: `.github/workflows at ${workflowsRef} could not be read from ${cwd} \u2014 the tag-addressability (#5428) and npm-major (#5666) preflights did not run`, remedy: `run from a checkout whose git can read ${workflowsRef} (fetch origin first), then rerun; an unread workflow set is unverified, not green` });
|
|
21740
|
+
}
|
|
21741
|
+
if (!startBranchMissing) {
|
|
21742
|
+
try {
|
|
21743
|
+
const required = await discoverRequiredCheckContexts(train, ctx, stage);
|
|
21744
|
+
if (required.length === 0) {
|
|
21745
|
+
add({ code: "bootstrap-gap", severity: "warning", source: "origin", title: `no ruleset requires a status check on ${stage} \u2014 the train tags without a check wait (the GitHub push gate is the backstop)`, remedy: `activate the product ruleset: \`mmi-cli devops bootstrap apply ${repo} --execute\` (from the MMI-Hub root), or \`mmi-cli devops ci audit --repo ${repo}\`` });
|
|
21746
|
+
} else {
|
|
21747
|
+
try {
|
|
21748
|
+
assertTagAddressableRequiredContexts({ readWorkflows: () => workflows }, required, repo);
|
|
21749
|
+
} catch (e) {
|
|
21750
|
+
add({ code: "contexts-not-tag-addressable", severity: "blocker", source: "local", title: `required ${stage} check(s) cannot materialize on a tag push (#5428)`, remedy: message(e) });
|
|
21751
|
+
}
|
|
21752
|
+
if (startBranch) {
|
|
21753
|
+
const tip = await git3(["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${startBranch}`]);
|
|
21754
|
+
const checkRuns = JSON.parse(await train.run("gh", ["api", `repos/${repo}/commits/${tip}/check-runs`, "--jq", TRAIN_CHECK_RUNS_JQ]));
|
|
21755
|
+
const statuses = JSON.parse(await train.run("gh", ["api", `repos/${repo}/commits/${tip}/status`, "--jq", TRAIN_COMMIT_STATUS_JQ]));
|
|
21756
|
+
const observed = required.filter((c) => checkRuns.some((r) => r.name === c) || statuses.some((s) => s.context === c));
|
|
21757
|
+
const red = observed.filter((c) => resolveContextState(c, checkRuns, statuses) === "failed");
|
|
21758
|
+
const inFlight = observed.filter((c) => resolveContextState(c, checkRuns, statuses) === "pending");
|
|
21759
|
+
const at = `${startBranch}@${tip.slice(0, 12)}`;
|
|
21760
|
+
if (red.length) add({ code: "required-check-red", severity: "blocker", source: "origin", title: `required ${stage} check(s) failed on the lane's start branch tip: ${red.join(", ")} at ${at}`, remedy: `--heal never clears this one \u2014 a red trunk needs a fix, not a heal: land the failure's fix on ${startBranch} through a CI-gated PR, then rerun once its run is green (#6461)` });
|
|
21761
|
+
else if (inFlight.length) add({ code: "required-check-red", severity: "warning", source: "origin", title: `required ${stage} check(s) still in flight on the lane's start branch tip: ${inFlight.join(", ")} at ${at}`, remedy: `wait for the run to conclude and rerun \u2014 the tip's verdict is not known yet, and the train would tag before it is` });
|
|
21762
|
+
}
|
|
21682
21763
|
}
|
|
21764
|
+
} catch (e) {
|
|
21765
|
+
unverified(`required status checks on ${stage}`, e);
|
|
21683
21766
|
}
|
|
21684
|
-
} catch (e) {
|
|
21685
|
-
unverified(`required status checks on ${stage}`, e);
|
|
21686
21767
|
}
|
|
21687
21768
|
let ledgerTag;
|
|
21688
21769
|
let ledgerPath;
|
|
@@ -24606,7 +24687,7 @@ function ciReconcileExitFailed(input) {
|
|
|
24606
24687
|
}
|
|
24607
24688
|
return input.applyResults.some((result) => result.errors.length > 0 || result.postApply == null || result.postApply.state !== "clean" && result.postApply.state !== "repaired");
|
|
24608
24689
|
}
|
|
24609
|
-
async function authoritativeSafetyHold(authority, deps, repo, baseBranch) {
|
|
24690
|
+
async function authoritativeSafetyHold(authority, deps, repo, baseBranch, meta) {
|
|
24610
24691
|
if (authority.coversReleaseBranches) {
|
|
24611
24692
|
const prOnly = authority.contexts.filter((context) => TRAIN_PR_ONLY_CONTEXTS.has(context));
|
|
24612
24693
|
if (prOnly.length > 0) {
|
|
@@ -24620,6 +24701,22 @@ async function authoritativeSafetyHold(authority, deps, repo, baseBranch) {
|
|
|
24620
24701
|
if (missing.length > 0) {
|
|
24621
24702
|
return `${authority.source} requires context(s) with no pull_request workflow emitter [${missing.join(", ")}]; no source or live mutation was made`;
|
|
24622
24703
|
}
|
|
24704
|
+
if (authority.coversReleaseBranches) {
|
|
24705
|
+
const track = resolveReleaseTrack(meta, void 0, repo);
|
|
24706
|
+
const releaseBranches = branchesForTrack(track).filter((b) => b !== baseBranch);
|
|
24707
|
+
const ignore = /* @__PURE__ */ new Set([...AGENT_PR_BOOKKEEPING_CONTEXTS, ...TRAIN_PR_ONLY_CONTEXTS]);
|
|
24708
|
+
const requiredForReleaseBranches = authority.contexts.filter((context) => !ignore.has(context));
|
|
24709
|
+
for (const branch of releaseBranches) {
|
|
24710
|
+
if (await branchPresence(deps, repo, branch) !== true) continue;
|
|
24711
|
+
const emittedThere = await resolveEmittedPrContexts(deps, repo, branch);
|
|
24712
|
+
if (emittedThere === void 0) continue;
|
|
24713
|
+
const emittedThereSet = new Set(emittedThere);
|
|
24714
|
+
const unreachable = requiredForReleaseBranches.filter((context) => !emittedThereSet.has(context));
|
|
24715
|
+
if (unreachable.length > 0) {
|
|
24716
|
+
return `${authority.source} requires context(s) [${unreachable.join(", ")}] on ${branch}, but ${branch}'s own workflows emit none of them \u2014 a base-${branch} PR (e.g. a hotfix) could never satisfy the gate; no source or live mutation was made. Remove [${unreachable.join(", ")}] from ${branch} requirements, or add the emitting workflow to ${branch}`;
|
|
24717
|
+
}
|
|
24718
|
+
}
|
|
24719
|
+
}
|
|
24623
24720
|
return null;
|
|
24624
24721
|
}
|
|
24625
24722
|
async function applyCiReconcileRepo(repo, deps) {
|
|
@@ -24636,7 +24733,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
24636
24733
|
empty.errors.push(`${PRODUCT_RULESET_REF} is not parseable: ${e.message}`);
|
|
24637
24734
|
return finalizeCiReconcile(repo, deps, empty, report);
|
|
24638
24735
|
}
|
|
24639
|
-
const hold = await authoritativeSafetyHold(authority, deps, repo, baseBranch);
|
|
24736
|
+
const hold = await authoritativeSafetyHold(authority, deps, repo, baseBranch, meta);
|
|
24640
24737
|
if (hold) {
|
|
24641
24738
|
empty.skipped.push(hold);
|
|
24642
24739
|
return finalizeCiReconcile(repo, deps, empty, report, hold);
|
|
@@ -24644,7 +24741,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
24644
24741
|
} else if (report.class === "deployable" && !report.explicitNoCi) {
|
|
24645
24742
|
const registryAuthority = registryAuthorityWithoutReference(meta, repo);
|
|
24646
24743
|
if (registryAuthority) {
|
|
24647
|
-
const hold = await authoritativeSafetyHold(registryAuthority, deps, repo, baseBranch);
|
|
24744
|
+
const hold = await authoritativeSafetyHold(registryAuthority, deps, repo, baseBranch, meta);
|
|
24648
24745
|
if (hold) {
|
|
24649
24746
|
empty.skipped.push(hold);
|
|
24650
24747
|
return finalizeCiReconcile(repo, deps, empty, report, hold);
|
|
@@ -24673,7 +24770,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
24673
24770
|
result.errors.push(`${PRODUCT_RULESET_REF} is not parseable: ${e.message}`);
|
|
24674
24771
|
return finalizeCiReconcile(repo, deps, result, report);
|
|
24675
24772
|
}
|
|
24676
|
-
const hold = await authoritativeSafetyHold(authority, deps, repo, baseBranch);
|
|
24773
|
+
const hold = await authoritativeSafetyHold(authority, deps, repo, baseBranch, meta);
|
|
24677
24774
|
if (hold) {
|
|
24678
24775
|
result.skipped.push(hold);
|
|
24679
24776
|
return finalizeCiReconcile(repo, deps, result, report, hold);
|
|
@@ -25859,7 +25956,7 @@ function registerBoardCommands(program3) {
|
|
|
25859
25956
|
return `Claimed ${ref} for ${holder} - In Progress${reclaimed}`;
|
|
25860
25957
|
}
|
|
25861
25958
|
const board = program3.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
|
|
25862
|
-
board.command("read", { isDefault: true }).alias("list").description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--direct", "bypass the Hub snapshot and read the live board through direct GitHub GraphQL").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").option("--out <path>", "write the output to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5802)").addHelpText("after", "\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\
|
|
25959
|
+
board.command("read", { isDefault: true }).alias("list").description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--direct", "bypass the Hub snapshot and read the live board through direct GitHub GraphQL").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").option("--out <path>", "write the output to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5802)").addHelpText("after", "\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").action((o) => runBoardRead(o));
|
|
25863
25960
|
withExamples(mutating(
|
|
25864
25961
|
board.command("claim <issues...>").description("claim issues: assign them and move their Project v2 Status to In Progress \u2014 idempotent, so an item already yours and In Progress succeeds unchanged (one or more refs); the board scan rides the Hub snapshot, same as board read").addHelpText("after", "\nclaim reads the board through the same Hub snapshot leg as `board read` (the App-installation\ncredential, never your personal GraphQL pool); the direct user-auth read is an emergency fallback\nand is named in a Warning line after the verdict (#6162).\n\nevery claim stamps a lane-identity marker comment on the issue (`<!-- mmi-claim: \u2026 -->`,\nsurface/session@host) so other agents can attribute the hold (#3727). The session is the\nhost-exported id when the surface provides one, otherwise a per-process `synth-` fallback \u2014\na claim is never anonymous (#5245). `board show`, doctor and unclaim read the latest marker.\n\nsame-owner resume (#6035): when the prior marker was posted by YOUR login on THIS host and its\nlocal session is verifiably dead (transcript probe), the claim proceeds as a resume without\n--force and names the evidence. A live, foreign-host, or unprobeable prior lane still refuses.\n\nreclaim from In Review (#6339): an In Review item with no holder, or one held by the claiming\nlogin, is claimable \u2014 it moves back to In Progress and the receipt names the status it came from.\nAn In Review item another login holds is refused (ask the holder, or wait for the review to land),\nand Done stays refused. The board status is the authority; no PR state is consulted.\n").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--for <login>", "assign to this login instead of @me \u2014 agent claims on behalf of the master").option("--force", "take an item already claimed by another lane that shows live evidence of active work (#3727)").option("--check", "read-only: run every claim gate and report the verdict, writing nothing \u2014 exits 1 with the same refusal a real claim would raise (#4511)").option("--allow-partial", "return success JSON if assignment succeeds but the status move fails"),
|
|
25865
25962
|
(_opts, args) => ({ command: "oracle board claim", issues: args[0] ?? [] })
|
|
@@ -29253,8 +29350,11 @@ function parseOauthVar(raw) {
|
|
|
29253
29350
|
const out = {};
|
|
29254
29351
|
for (const [key, value] of Object.entries(map)) {
|
|
29255
29352
|
if (key === "subdomains" || key === "domains") {
|
|
29256
|
-
|
|
29257
|
-
|
|
29353
|
+
const allowEmpty = key === "subdomains";
|
|
29354
|
+
if (!Array.isArray(value) || value.some((v) => typeof v !== "string" || v.trim() === "" && !(allowEmpty && v === ""))) {
|
|
29355
|
+
throw new Error(
|
|
29356
|
+
allowEmpty ? `org project set: oauth.${key} must be an array of strings ("" selects the apex \u2014 the domain itself)` : `org project set: oauth.${key} must be an array of non-empty strings`
|
|
29357
|
+
);
|
|
29258
29358
|
}
|
|
29259
29359
|
out[key] = value.map((v) => v.trim());
|
|
29260
29360
|
} else if (key === "callbackPath") {
|
|
@@ -29996,6 +30096,18 @@ function stageComposeFileEnv(files) {
|
|
|
29996
30096
|
// src/stage-runner.ts
|
|
29997
30097
|
var execFileP3 = (0, import_node_util5.promisify)(import_node_child_process11.execFile);
|
|
29998
30098
|
var DOCKER_TIMEOUT_MS = 15e3;
|
|
30099
|
+
function stageBash() {
|
|
30100
|
+
if (process.platform !== "win32") return "bash";
|
|
30101
|
+
const configured = process.env.SHELL;
|
|
30102
|
+
if (configured && (0, import_node_path27.isAbsolute)(configured) && /[/\\]bash(?:\.exe)?$/i.test(configured) && !/[/\\](?:System32|Sysnative)[/\\]bash(?:\.exe)?$/i.test(configured) && (0, import_node_fs28.existsSync)(configured)) return configured;
|
|
30103
|
+
const candidates = [
|
|
30104
|
+
(0, import_node_path27.join)(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
|
|
30105
|
+
...process.env.LOCALAPPDATA ? [(0, import_node_path27.join)(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")] : []
|
|
30106
|
+
];
|
|
30107
|
+
const bash = candidates.find((path2) => (0, import_node_fs28.existsSync)(path2));
|
|
30108
|
+
if (!bash) throw new Error("Local stages require Git Bash on Windows. Install Git for Windows or set SHELL to its absolute bash.exe path.");
|
|
30109
|
+
return bash;
|
|
30110
|
+
}
|
|
29999
30111
|
var EARLY_EXIT_GRACE_MS = 2e3;
|
|
30000
30112
|
function earlyExitGraceMs() {
|
|
30001
30113
|
if (process.env.NODE_ENV === "test") {
|
|
@@ -30186,24 +30298,6 @@ function mergeEnvSecretsIntoFile(content, secrets) {
|
|
|
30186
30298
|
return body.endsWith("\n") ? body : `${body}
|
|
30187
30299
|
`;
|
|
30188
30300
|
}
|
|
30189
|
-
var POSIX_ONLY_VERBS = ["cp", "mv", "rm", "ln", "cat", "touch", "chmod", "export"];
|
|
30190
|
-
function posixOnlyShellProblems(command, field, platform2 = process.platform) {
|
|
30191
|
-
if (platform2 !== "win32" || !command?.trim()) return [];
|
|
30192
|
-
const problems = [];
|
|
30193
|
-
if (/(^|&&|\||;)\s*[A-Za-z_][A-Za-z0-9_]*=\S/.test(command)) {
|
|
30194
|
-
problems.push(
|
|
30195
|
-
`stage.${field} uses POSIX inline env assignment (VAR=value command) which fails in cmd.exe on Windows; use 'set VAR=value && command' or a cross-platform launcher`
|
|
30196
|
-
);
|
|
30197
|
-
}
|
|
30198
|
-
for (const verb of POSIX_ONLY_VERBS) {
|
|
30199
|
-
if (new RegExp(`(^|&&|\\||;|\\()\\s*${verb}\\b`).test(command)) {
|
|
30200
|
-
problems.push(
|
|
30201
|
-
`stage.${field} calls POSIX '${verb}' which does not exist in cmd.exe on Windows; use the cmd/PowerShell equivalent or a cross-platform script`
|
|
30202
|
-
);
|
|
30203
|
-
}
|
|
30204
|
-
}
|
|
30205
|
-
return problems;
|
|
30206
|
-
}
|
|
30207
30301
|
function validateStageConfig(config = {}, action) {
|
|
30208
30302
|
const problems = [];
|
|
30209
30303
|
if (action === "run" && !config.build?.trim() && !config.ensureEnv) problems.push("stage.build is required for stage run");
|
|
@@ -30211,8 +30305,6 @@ function validateStageConfig(config = {}, action) {
|
|
|
30211
30305
|
if (config.healthUrl != null && config.healthUrl.trim() && !/^https?:\/\//.test(config.healthUrl.trim())) {
|
|
30212
30306
|
problems.push("stage.healthUrl must be an http(s) URL");
|
|
30213
30307
|
}
|
|
30214
|
-
if (action === "run") problems.push(...posixOnlyShellProblems(config.build, "build"));
|
|
30215
|
-
problems.push(...posixOnlyShellProblems(config.up, "up"));
|
|
30216
30308
|
if (config.portRange != null) {
|
|
30217
30309
|
const r = config.portRange;
|
|
30218
30310
|
const ok = Array.isArray(r) && r.length === 2 && r.every((n) => Number.isInteger(n) && n >= 1024 && n <= 65535) && r[0] <= r[1];
|
|
@@ -30237,14 +30329,27 @@ function isPortFree(port) {
|
|
|
30237
30329
|
});
|
|
30238
30330
|
}
|
|
30239
30331
|
async function shell(command, cwd, timeoutMs, env) {
|
|
30240
|
-
|
|
30332
|
+
const execution = execFileP3(stageBash(), ["-c", command], {
|
|
30241
30333
|
cwd,
|
|
30242
|
-
|
|
30243
|
-
timeout: timeoutMs,
|
|
30334
|
+
timeout: process.platform === "win32" ? timeoutMs + 5e3 : timeoutMs,
|
|
30244
30335
|
windowsHide: true,
|
|
30245
30336
|
maxBuffer: 1024 * 1024 * 4,
|
|
30246
30337
|
...env ? { env: { ...process.env, ...env } } : {}
|
|
30247
30338
|
});
|
|
30339
|
+
let cleanup;
|
|
30340
|
+
const timer = process.platform === "win32" ? setTimeout(() => {
|
|
30341
|
+
cleanup = killTree(execution.child.pid ?? 0);
|
|
30342
|
+
}, timeoutMs) : void 0;
|
|
30343
|
+
try {
|
|
30344
|
+
await execution;
|
|
30345
|
+
if (cleanup) throw new Error(`stage command timed out after ${timeoutMs}ms`);
|
|
30346
|
+
} catch (error) {
|
|
30347
|
+
if (cleanup) throw new Error(`stage command timed out after ${timeoutMs}ms`, { cause: error });
|
|
30348
|
+
throw error;
|
|
30349
|
+
} finally {
|
|
30350
|
+
clearTimeout(timer);
|
|
30351
|
+
await cleanup;
|
|
30352
|
+
}
|
|
30248
30353
|
}
|
|
30249
30354
|
async function listDockerContainers() {
|
|
30250
30355
|
const { stdout } = await execFileP3("docker", ["container", "ls", "--format", "{{json .}}"], {
|
|
@@ -30484,7 +30589,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd, currentEn
|
|
|
30484
30589
|
async function killTree(pid) {
|
|
30485
30590
|
if (!Number.isInteger(pid) || pid <= 0) return;
|
|
30486
30591
|
if (process.platform === "win32") {
|
|
30487
|
-
await execFileP3("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true }).catch(() => void 0);
|
|
30592
|
+
await execFileP3("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true, timeout: 5e3 }).catch(() => void 0);
|
|
30488
30593
|
return;
|
|
30489
30594
|
}
|
|
30490
30595
|
try {
|
|
@@ -30550,6 +30655,7 @@ async function stopStage(opts = {}) {
|
|
|
30550
30655
|
};
|
|
30551
30656
|
}
|
|
30552
30657
|
async function startStage(config = {}, opts = {}) {
|
|
30658
|
+
const bash = stageBash();
|
|
30553
30659
|
const problems = validateStageConfig(config, "start");
|
|
30554
30660
|
if (problems.length) throw new Error(problems.join("; "));
|
|
30555
30661
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -30571,9 +30677,8 @@ async function startStage(config = {}, opts = {}) {
|
|
|
30571
30677
|
let up = sub(config.up.trim());
|
|
30572
30678
|
if (opts.forceRecreate) up = appendForceRecreate(up);
|
|
30573
30679
|
const identity = await resolveStageIdentity(cwd);
|
|
30574
|
-
const child2 = (0, import_node_child_process11.spawn)(up, {
|
|
30680
|
+
const child2 = (0, import_node_child_process11.spawn)(bash, ["-c", up], {
|
|
30575
30681
|
cwd,
|
|
30576
|
-
shell: true,
|
|
30577
30682
|
// POSIX-only: the process group exists for the group-kill in stopStage. On win32 teardown is
|
|
30578
30683
|
// `taskkill /T /F` (no group needed), and detached+shell defeats windowsHide — every spawn would
|
|
30579
30684
|
// flash a Windows Terminal window (0x800700e8) on dev machines.
|
|
@@ -30627,6 +30732,7 @@ function stageEnvRefusal(cwd) {
|
|
|
30627
30732
|
return `stage refuses to run: .env file(s) present in the repo (${envFiles.join(", ")}) \u2014 secrets belong in the Hub vault; delete the file(s) and re-run (#5908)`;
|
|
30628
30733
|
}
|
|
30629
30734
|
async function runStage(config = {}, opts = {}) {
|
|
30735
|
+
stageBash();
|
|
30630
30736
|
const problems = validateStageConfig(config, "run");
|
|
30631
30737
|
if (problems.length) throw new Error(problems.join("; "));
|
|
30632
30738
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -33492,18 +33598,33 @@ async function preflightBatchSurfaces(validated, rowRepo, options) {
|
|
|
33492
33598
|
}
|
|
33493
33599
|
if (options.noSurface) return [];
|
|
33494
33600
|
const errors = [];
|
|
33495
|
-
const
|
|
33601
|
+
const censusCache = /* @__PURE__ */ new Map();
|
|
33602
|
+
const censusFor = (repo) => {
|
|
33603
|
+
let pending = censusCache.get(repo);
|
|
33604
|
+
if (!pending) {
|
|
33605
|
+
pending = readRepoSurfaceLabels(repo);
|
|
33606
|
+
censusCache.set(repo, pending);
|
|
33607
|
+
}
|
|
33608
|
+
return pending;
|
|
33609
|
+
};
|
|
33496
33610
|
for (const { row, spec } of validated) {
|
|
33611
|
+
if (labelsCarrySurface(spec.labels)) continue;
|
|
33497
33612
|
const repo = rowRepo(spec);
|
|
33498
|
-
const
|
|
33499
|
-
|
|
33500
|
-
|
|
33501
|
-
|
|
33502
|
-
|
|
33503
|
-
|
|
33504
|
-
|
|
33613
|
+
const known = await censusFor(repo);
|
|
33614
|
+
if (known === void 0) {
|
|
33615
|
+
process.stderr.write(
|
|
33616
|
+
`warning: could not read ${repo}'s labels, so row ${row}'s surface-label requirement was not checked; if that board enforces one surface:* label per open issue, add one with \`mmi-cli oracle issue edit <n> --add-label surface:<value>\`
|
|
33617
|
+
`
|
|
33618
|
+
);
|
|
33619
|
+
continue;
|
|
33505
33620
|
}
|
|
33506
|
-
if (
|
|
33621
|
+
if (known.length === 0) continue;
|
|
33622
|
+
const inferred = inferSurface(known, { title: spec.title, body: spec.body });
|
|
33623
|
+
spec.labels = [...spec.labels ?? [], inferred.label];
|
|
33624
|
+
process.stderr.write(
|
|
33625
|
+
`mmi-cli: no --surface given \u2014 row ${row} filed under ${inferred.label} (${inferred.reason}). Pass --surface to choose.
|
|
33626
|
+
`
|
|
33627
|
+
);
|
|
33507
33628
|
}
|
|
33508
33629
|
return errors;
|
|
33509
33630
|
}
|
|
@@ -35597,8 +35718,8 @@ function registerSecretsCommands(program3) {
|
|
|
35597
35718
|
const secrets = program3.command("secrets").description("project vault \u2014 project-admins self-serve their own repo's full tree (stageless + dev/rc/main); org-infra namespaces are master-gated");
|
|
35598
35719
|
secrets.command("where").description("print where this repo's secrets live \u2014 the two-tier vault layout + well-known keys (no values)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsWhere(d, o)));
|
|
35599
35720
|
secrets.command("list").description("list secret NAMES + tier for THIS repo (never values). To locate a key you cannot place \u2014 including org-infra (_org) \u2014 use the cross-slug view: `mmi-cli oracle org access capabilities` (#3436)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsList(d, o)));
|
|
35600
|
-
secrets.command("find <intent
|
|
35601
|
-
if (!await secretsFind(d,
|
|
35721
|
+
secrets.command("find <intent...>").description("resolve a plain-language intent to canonical secret names + the exact keyless-use command (master-only, #2244); an unquoted multi-word intent is joined with spaces (#6495)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((intentWords, o) => withSecrets(async (d) => {
|
|
35722
|
+
if (!await secretsFind(d, intentWords.join(" "), o)) process.exitCode = 1;
|
|
35602
35723
|
}));
|
|
35603
35724
|
secrets.command("catalog").description("the secret catalog \u2014 grouped, with each secret's exact keyless-use command (master-only, #2244)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--drift", "include the bidirectional reconciler drift report").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsCatalog(d, o)));
|
|
35604
35725
|
secrets.command("doctor").description("vault drift report \u2014 declared-missing / orphan / off-scheme / duplicate (master-only, #2244)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets(async (d) => {
|
|
@@ -35922,8 +36043,8 @@ var import_node_fs34 = require("node:fs");
|
|
|
35922
36043
|
var import_node_path31 = require("node:path");
|
|
35923
36044
|
|
|
35924
36045
|
// src/stage-default.ts
|
|
35925
|
-
function shellFor(
|
|
35926
|
-
return
|
|
36046
|
+
function shellFor() {
|
|
36047
|
+
return "bash";
|
|
35927
36048
|
}
|
|
35928
36049
|
function isCentralContainerModel(model) {
|
|
35929
36050
|
return model === "tenant-container" || model === "solo-container";
|
|
@@ -36394,7 +36515,7 @@ function registerStageCommands(program3) {
|
|
|
36394
36515
|
fail(`stage stop: ${e.message}`);
|
|
36395
36516
|
}
|
|
36396
36517
|
});
|
|
36397
|
-
stage.command("start").description("start the configured local stage process and optionally wait for health").option("--json", "machine-readable output").option("--apply", "start the configured stage.up process").option("--port <port>", "loopback port for this worktree stage (1024..65535)").option("--timeout-ms <ms>", "bounded health timeout", "60000").option("--allow-stale-env", "start despite a stale ensureEnv target file").action(async () => {
|
|
36518
|
+
stage.command("start").description("start the configured local stage process and optionally wait for health").option("--json", "machine-readable output").option("--apply", "start the configured stage.up process in Bash (Git Bash on Windows)").option("--port <port>", "loopback port for this worktree stage (1024..65535)").option("--timeout-ms <ms>", "bounded health timeout", "60000").option("--allow-stale-env", "start despite a stale ensureEnv target file").action(async () => {
|
|
36398
36519
|
const o = { json: rawFlag("--json"), apply: rawFlag("--apply"), timeoutMs: rawValue("--timeout-ms", "60000"), allowStaleEnv: rawFlag("--allow-stale-env") };
|
|
36399
36520
|
const { resolution: res, project: project2, cfg: stageCfg } = await resolveStage();
|
|
36400
36521
|
if (!o.apply) {
|
|
@@ -37080,8 +37201,20 @@ function matchedMandatoryGlobs(paths, mandatory) {
|
|
|
37080
37201
|
return list.some((path2) => re.test(path2));
|
|
37081
37202
|
});
|
|
37082
37203
|
}
|
|
37083
|
-
function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, override = null, addedPaths = [] } = {}) {
|
|
37204
|
+
function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, override = null, addedPaths = [], structuralRefusal = false } = {}) {
|
|
37084
37205
|
const configuredMandatoryCount = mandatoryGlobList(mandatory).length;
|
|
37206
|
+
if (structuralRefusal) {
|
|
37207
|
+
return {
|
|
37208
|
+
configuredMandatoryCount,
|
|
37209
|
+
matchedMandatoryGlobs: [],
|
|
37210
|
+
matchedMandatoryCount: 0,
|
|
37211
|
+
testCommandsAllowed: false,
|
|
37212
|
+
editsExistingTest: false,
|
|
37213
|
+
reasonId: "test-command-outside-mandatory-zone",
|
|
37214
|
+
testCommandNote: null,
|
|
37215
|
+
commandClasses: { allowed: [], refused: [TEST_COMMAND_CLASS] }
|
|
37216
|
+
};
|
|
37217
|
+
}
|
|
37085
37218
|
if (!regulated) {
|
|
37086
37219
|
return {
|
|
37087
37220
|
configuredMandatoryCount: 0,
|
|
@@ -37090,25 +37223,30 @@ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, overrid
|
|
|
37090
37223
|
testCommandsAllowed: true,
|
|
37091
37224
|
editsExistingTest: false,
|
|
37092
37225
|
reasonId: null,
|
|
37226
|
+
testCommandNote: null,
|
|
37093
37227
|
commandClasses: { allowed: [TEST_COMMAND_CLASS], refused: [] }
|
|
37094
37228
|
};
|
|
37095
37229
|
}
|
|
37096
37230
|
const matched = matchedMandatoryGlobs(paths, mandatory);
|
|
37097
37231
|
const overrideAuthorizes = Array.isArray(override?.kinds) && override.kinds.includes(TEST_WORK_KIND);
|
|
37098
37232
|
const existingTestEdited = editsExistingTest(paths, addedPaths);
|
|
37099
|
-
const
|
|
37233
|
+
const inMandatoryZone = matched.length > 0 || overrideAuthorizes || existingTestEdited;
|
|
37234
|
+
const testCommandsAllowed = true;
|
|
37100
37235
|
return {
|
|
37101
37236
|
configuredMandatoryCount,
|
|
37102
37237
|
matchedMandatoryGlobs: matched,
|
|
37103
37238
|
matchedMandatoryCount: matched.length,
|
|
37104
37239
|
testCommandsAllowed,
|
|
37105
|
-
/** #5842: true when the diff edits a test file that already existed — one of the facts that
|
|
37106
|
-
* authorize the class,
|
|
37240
|
+
/** #5842: true when the diff edits a test file that already existed — one of the facts that used
|
|
37241
|
+
* to authorize the class, kept for callers that still branch on it. */
|
|
37107
37242
|
editsExistingTest: existingTestEdited,
|
|
37108
|
-
reasonId:
|
|
37243
|
+
reasonId: null,
|
|
37244
|
+
/** Informational only (#1166): set when the diff is outside the mandatory zone, so a caller can
|
|
37245
|
+
* note that CI does not require a test here — never a reason to refuse the run. */
|
|
37246
|
+
testCommandNote: inMandatoryZone ? null : "test-command-outside-mandatory-zone",
|
|
37109
37247
|
commandClasses: {
|
|
37110
|
-
allowed:
|
|
37111
|
-
refused:
|
|
37248
|
+
allowed: [TEST_COMMAND_CLASS],
|
|
37249
|
+
refused: []
|
|
37112
37250
|
}
|
|
37113
37251
|
};
|
|
37114
37252
|
}
|
|
@@ -37778,7 +37916,12 @@ function runTestPolicy(root, deps = {}) {
|
|
|
37778
37916
|
addedPaths: changed.filter((f) => f.status === "A" || f.status === "R").map((f) => f.path),
|
|
37779
37917
|
mandatory: policy.mandatory,
|
|
37780
37918
|
regulated: policy.declared !== false || policyRefusals.length > 0,
|
|
37781
|
-
override: override && lookup.refusals.length === 0 ? { kinds: override.kinds } : null
|
|
37919
|
+
override: override && lookup.refusals.length === 0 ? { kinds: override.kinds } : null,
|
|
37920
|
+
// #1166: a still-blocking pre-diff finding (untrusted range, unresolvable base, a missing policy
|
|
37921
|
+
// on the hotfix lane, a malformed trailer, an unwaived stale entry) means the diff itself was
|
|
37922
|
+
// never evaluated — the mandatory-zone question this evaluator now answers permissively was
|
|
37923
|
+
// never reached, so the refusal stands exactly as before.
|
|
37924
|
+
structuralRefusal: blocking.length > 0
|
|
37782
37925
|
});
|
|
37783
37926
|
const result = {
|
|
37784
37927
|
ok: findings.length === 0,
|
|
@@ -37792,6 +37935,7 @@ function runTestPolicy(root, deps = {}) {
|
|
|
37792
37935
|
matchedMandatoryCount: commandPolicy.matchedMandatoryCount,
|
|
37793
37936
|
testCommandsAllowed: commandPolicy.testCommandsAllowed,
|
|
37794
37937
|
testCommandReasonId: commandPolicy.reasonId,
|
|
37938
|
+
testCommandNote: commandPolicy.testCommandNote,
|
|
37795
37939
|
commandClasses: commandPolicy.commandClasses
|
|
37796
37940
|
};
|
|
37797
37941
|
if (override) {
|
|
@@ -39808,7 +39952,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39808
39952
|
}
|
|
39809
39953
|
const issue = program3.command("issue").description("issues \u2014 create and view with structured JSON (view; show, get and read alias view for board-verb/gh-shaped callers). Claims are board mutations: use `oracle board claim`, never `oracle issue claim` or the flat `board claim`");
|
|
39810
39954
|
withExamples(mutating(
|
|
39811
|
-
issue.command("create").description("create an issue (type \u2014 label) and print {number,url,label} JSON").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").choices([...ISSUE_TYPES])).option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file. `-` (stdin) needs a heredoc, which the agent inline-body guard denies (#1473/#2125) \u2014 prefer a real path; a title with backticks needs --title-file for the same reason (#3381)").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only \u2014 never a priority:* label, #416)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--label <label...>", "extra label(s) to attach (repeatable; auto-created if missing)").option("--surface <surface>", "issue surface, with or without the surface: prefix (#3789).
|
|
39955
|
+
issue.command("create").description("create an issue (type \u2014 label) and print {number,url,label} JSON").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").choices([...ISSUE_TYPES])).option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file. `-` (stdin) needs a heredoc, which the agent inline-body guard denies (#1473/#2125) \u2014 prefer a real path; a title with backticks needs --title-file for the same reason (#3381)").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only \u2014 never a priority:* label, #416)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--label <label...>", "extra label(s) to attach (repeatable; auto-created if missing)").option("--surface <surface>", "issue surface, with or without the surface: prefix (#3789). Any value satisfies the one-surface-label board rule, so this is not a closed enum. Omit it on an enforcing repo and one is inferred from the title/body (#1164) \u2014 the pick is printed and included in --json as surface_inferred/surface/surface_reason").option("--no-surface", "file without a surface label on a repo that requires one \u2014 for a genuinely exempt filing (e.g. a coop proof issue that spans every surface). Suppresses inference too").option("--parent <ref>", "file as a native sub-issue of this parent (#123, owner/repo#123, or URL)").option("--no-related", "skip the auto related-issues comment"),
|
|
39812
39956
|
// --dry-run/--validate-only plan: resolve the same title source and validate the same type, priority,
|
|
39813
39957
|
// and surface contract as the real action. A plan that echoes the title-file PATH instead of its value
|
|
39814
39958
|
// is not a plan of the mutation that will run (#3914).
|
|
@@ -39832,14 +39976,24 @@ function registerCollaborationCommands(program3) {
|
|
|
39832
39976
|
if (clash) fail(clash.message, clash.payload);
|
|
39833
39977
|
const planRepo = opts.batch || surfaceWaived() ? void 0 : await resolveRepo(opts.repo);
|
|
39834
39978
|
const surface = resolveCreateSurface(opts);
|
|
39979
|
+
let planInferred;
|
|
39835
39980
|
if (planRepo) {
|
|
39836
|
-
const { refusal, warn } = await checkSurfaceRequirement({
|
|
39981
|
+
const { refusal, warn, inferred } = await checkSurfaceRequirement({
|
|
39837
39982
|
repo: planRepo,
|
|
39838
|
-
labels: [...planLabels, ...surface ? [surface] : []]
|
|
39983
|
+
labels: [...planLabels, ...surface ? [surface] : []],
|
|
39984
|
+
title,
|
|
39985
|
+
body: opts.body
|
|
39839
39986
|
});
|
|
39840
39987
|
if (warn) process.stderr.write(`${warn}
|
|
39841
39988
|
`);
|
|
39842
39989
|
if (refusal) fail(refusal.message, refusal.payload);
|
|
39990
|
+
if (inferred && !surface) {
|
|
39991
|
+
planInferred = inferred;
|
|
39992
|
+
process.stderr.write(
|
|
39993
|
+
`mmi-cli: no --surface given \u2014 filed under ${inferred.label} (${inferred.reason}). Pass --surface to choose.
|
|
39994
|
+
`
|
|
39995
|
+
);
|
|
39996
|
+
}
|
|
39843
39997
|
}
|
|
39844
39998
|
return {
|
|
39845
39999
|
command: "issue create",
|
|
@@ -39847,7 +40001,8 @@ function registerCollaborationCommands(program3) {
|
|
|
39847
40001
|
title,
|
|
39848
40002
|
priority,
|
|
39849
40003
|
repo: opts.repo,
|
|
39850
|
-
...surface ? { surface } : {}
|
|
40004
|
+
...surface ? { surface } : {},
|
|
40005
|
+
...planInferred ? { surface_inferred: true, surface: planInferred.label, surface_reason: planInferred.reason } : {}
|
|
39851
40006
|
};
|
|
39852
40007
|
}
|
|
39853
40008
|
).action(async (o) => {
|
|
@@ -39859,6 +40014,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39859
40014
|
let extraLabels = [];
|
|
39860
40015
|
let targetRepo2;
|
|
39861
40016
|
let surfaceFlagLabel;
|
|
40017
|
+
let surfaceInferred;
|
|
39862
40018
|
try {
|
|
39863
40019
|
issueType = resolveCreateType(o.type, "issue create", o.label);
|
|
39864
40020
|
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
|
|
@@ -39890,8 +40046,8 @@ function registerCollaborationCommands(program3) {
|
|
|
39890
40046
|
return fail(`issue create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
|
|
39891
40047
|
}
|
|
39892
40048
|
{
|
|
39893
|
-
const surfaceCheck = await checkSurfaceRequirement({ repo: targetRepo2, labels: extraLabels });
|
|
39894
|
-
const { refusal, warn } = surfaceCheck;
|
|
40049
|
+
const surfaceCheck = await checkSurfaceRequirement({ repo: targetRepo2, labels: extraLabels, title, body });
|
|
40050
|
+
const { refusal, warn, inferred } = surfaceCheck;
|
|
39895
40051
|
if (warn) process.stderr.write(`${warn}
|
|
39896
40052
|
`);
|
|
39897
40053
|
if (shouldWithdrawSurfaceFlag(surfaceCheck) && surfaceFlagLabel) {
|
|
@@ -39906,6 +40062,22 @@ function registerCollaborationCommands(program3) {
|
|
|
39906
40062
|
});
|
|
39907
40063
|
process.stderr.write(
|
|
39908
40064
|
`warning: --surface ${surfaceFlagLabel} was dropped \u2014 ${targetRepo2} defines no surface:* labels, and creating one here would switch the one-surface-label rule on for every later filing in it. Use --label ${surfaceFlagLabel} if you really mean to start that taxonomy.
|
|
40065
|
+
`
|
|
40066
|
+
);
|
|
40067
|
+
}
|
|
40068
|
+
if (inferred && !surfaceFlagLabel && !surfaceWaived()) {
|
|
40069
|
+
extraLabels = [...extraLabels, inferred.label];
|
|
40070
|
+
args = buildIssueArgs({
|
|
40071
|
+
type: issueType,
|
|
40072
|
+
title,
|
|
40073
|
+
body,
|
|
40074
|
+
priority,
|
|
40075
|
+
repo: targetRepo2,
|
|
40076
|
+
labels: extraLabels.length ? extraLabels : void 0
|
|
40077
|
+
});
|
|
40078
|
+
surfaceInferred = inferred;
|
|
40079
|
+
process.stderr.write(
|
|
40080
|
+
`mmi-cli: no --surface given \u2014 filed under ${inferred.label} (${inferred.reason}). Pass --surface to choose.
|
|
39909
40081
|
`
|
|
39910
40082
|
);
|
|
39911
40083
|
}
|
|
@@ -39946,7 +40118,9 @@ function registerCollaborationCommands(program3) {
|
|
|
39946
40118
|
...attached.resetAt ? { resetAt: attached.resetAt } : {},
|
|
39947
40119
|
...attached.resetEpochSeconds !== void 0 ? { resetEpochSeconds: attached.resetEpochSeconds } : {}
|
|
39948
40120
|
} : {},
|
|
39949
|
-
...parentLinkFields(parent, parentLinkError)
|
|
40121
|
+
...parentLinkFields(parent, parentLinkError),
|
|
40122
|
+
// #1164: surfaced so a caller can see (and override with --surface) a pick it never asked for.
|
|
40123
|
+
...surfaceInferred ? { surface_inferred: true, surface: surfaceInferred.label, surface_reason: surfaceInferred.reason } : {}
|
|
39950
40124
|
}));
|
|
39951
40125
|
}), [
|
|
39952
40126
|
'mmi-cli oracle issue create --type task --title "Wire the schema"',
|
|
@@ -42091,7 +42265,7 @@ function registerDeveloperCommands(program3) {
|
|
|
42091
42265
|
}
|
|
42092
42266
|
});
|
|
42093
42267
|
const tests = program3.command("tests").description("a repo's test-policy.json \u2014 the opt-in test contract and its enforcement");
|
|
42094
|
-
tests.command("policy").description("enforce this repo's test-policy.json against the diff: a mandatory-zone change must carry a test, an unrequested new test file is refused, a `protected` test file may not be deleted or renamed away, and a `protected` entry naming a missing file is refused. Override any of them with a `Test-Policy-Override: <reason>` commit trailer (#3605)").option("--json", "machine-readable result: { ok, base, policySource: { ref, sha, path }, root, changedCount, mandatoryCount, matchedMandatoryCount, matchedMandatoryGlobs, testCommandsAllowed, commandClasses, findings[] }").option("--base <ref>", "comparison base (default: TEST_POLICY_BASE, then origin/development, then origin/main)").option("--policy-ref <commit>", "load test-policy.json from the exact fetched origin/development commit and audit its protected/satisfiedBy paths against that commit's tree, not this checkout; valid only with --base origin/main").addOption(new Option("--repo <owner/repo>", "not accepted \u2014 tests policy reads the current checkout").hideHelp()).action(async (o) => {
|
|
42268
|
+
tests.command("policy").description("enforce this repo's test-policy.json against the diff: a mandatory-zone change must carry a test, an unrequested new test file is refused, a `protected` test file may not be deleted or renamed away, and a `protected` entry naming a missing file is refused. Override any of them with a `Test-Policy-Override: <reason>` commit trailer (#3605)").option("--json", "machine-readable result: { ok, base, policySource: { ref, sha, path }, root, changedCount, mandatoryCount, matchedMandatoryCount, matchedMandatoryGlobs, testCommandsAllowed, testCommandNote, commandClasses, findings[] }").option("--base <ref>", "comparison base (default: TEST_POLICY_BASE, then origin/development, then origin/main)").option("--policy-ref <commit>", "load test-policy.json from the exact fetched origin/development commit and audit its protected/satisfiedBy paths against that commit's tree, not this checkout; valid only with --base origin/main").addOption(new Option("--repo <owner/repo>", "not accepted \u2014 tests policy reads the current checkout").hideHelp()).action(async (o) => {
|
|
42095
42269
|
try {
|
|
42096
42270
|
if (o.repo) {
|
|
42097
42271
|
const rerun = `mmi-cli tests policy${o.base ? ` --base ${o.base}` : ""}${o.policyRef ? ` --policy-ref ${o.policyRef}` : ""}${o.json ? " --json" : ""}`;
|
|
@@ -42114,7 +42288,7 @@ function registerDeveloperCommands(program3) {
|
|
|
42114
42288
|
return;
|
|
42115
42289
|
}
|
|
42116
42290
|
if (result.ok) {
|
|
42117
|
-
const commandVerdict = result.testCommandsAllowed ? "test commands: allowed" : `test commands: refused [${result.testCommandReasonId}] \u2014 resolved repo: ${result.root}; if that is not the repo under test, start the command with a literal Set-Location "<your worktree>"`;
|
|
42291
|
+
const commandVerdict = result.testCommandsAllowed ? result.testCommandNote ? `test commands: allowed (informational [${result.testCommandNote}] \u2014 CI does not require a test here)` : "test commands: allowed" : `test commands: refused [${result.testCommandReasonId}] \u2014 resolved repo: ${result.root}; if that is not the repo under test, start the command with a literal Set-Location "<your worktree>"`;
|
|
42118
42292
|
console.log(
|
|
42119
42293
|
`tests policy: OK (${result.changedCount} changed file(s); ${result.matchedMandatoryCount} of ${result.mandatoryCount} mandatory glob(s) matched, ${result.protectedCount} protected file(s); ${commandVerdict}).`
|
|
42120
42294
|
);
|
|
@@ -45050,8 +45224,7 @@ project.command("sync-info [owner/repo]").description("synchronize the owning Gi
|
|
|
45050
45224
|
return failGraceful(e.message);
|
|
45051
45225
|
}
|
|
45052
45226
|
});
|
|
45053
|
-
|
|
45054
|
-
projectDeploy.command("get [owner/repo]").description("read nonsecret DEPLOY# facts for one project; defaults to the current repo").addOption(new Option("--stage <stage>", "dev | rc | main").choices(["dev", "rc", "main"])).option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
45227
|
+
async function runProjectDeployGet(repoOrSlug, o) {
|
|
45055
45228
|
const cfg = await loadConfig();
|
|
45056
45229
|
let target;
|
|
45057
45230
|
try {
|
|
@@ -45065,7 +45238,9 @@ projectDeploy.command("get [owner/repo]").description("read nonsecret DEPLOY# fa
|
|
|
45065
45238
|
if (stage && !["dev", "rc", "main"].includes(stage)) return fail("org project deploy get: --stage must be dev, rc, or main");
|
|
45066
45239
|
const payload = stage ? { slug: out.slug, stage, deploy: out.stages[stage] ?? null } : out;
|
|
45067
45240
|
console.log(JSON.stringify(payload));
|
|
45068
|
-
}
|
|
45241
|
+
}
|
|
45242
|
+
var projectDeploy = project.command("deploy").description("read nonsecret DEPLOY# facts (domain, port, deploy path, substrate, host presence)").argument("[ownerRepo]", "shorthand for `get <owner/repo>`").addOption(new Option("--stage <stage>", "dev | rc | main").choices(["dev", "rc", "main"])).option("--json", "machine-readable output").action(async (ownerRepo, o) => runProjectDeployGet(ownerRepo, o));
|
|
45243
|
+
projectDeploy.command("get [owner/repo]").description("read nonsecret DEPLOY# facts for one project; defaults to the current repo").addOption(new Option("--stage <stage>", "dev | rc | main").choices(["dev", "rc", "main"])).option("--json", "machine-readable output").action(async (repoOrSlug, o) => runProjectDeployGet(repoOrSlug, o));
|
|
45069
45244
|
projectDeploy.command("doctor").description("read-only estate scan for duplicate Hetzner (sshHost, port) DEPLOY# coordinates (#5431); master-only; never auto-reassigns ports").option("--json", "machine-readable output").action(async (o) => {
|
|
45070
45245
|
const cfg = await loadConfig();
|
|
45071
45246
|
const report = await fetchDeployPortCollisions(registryClientDeps(cfg));
|
|
@@ -45313,11 +45488,13 @@ oauth.command("plan", { isDefault: true }).description("print the canonical JS o
|
|
|
45313
45488
|
oc = parseOauthConfig(meta ?? {}, slug);
|
|
45314
45489
|
} catch (e) {
|
|
45315
45490
|
const message2 = e.message;
|
|
45491
|
+
const declareCommand = `mmi-cli oracle org project set ${o.repo ?? `mutmutco/${slug}`} --var 'oauth={"subdomains":["${defaultSubdomain(slug)}"],"callbackPath":"${DEFAULT_CALLBACK_PATH}"}'`;
|
|
45316
45492
|
if (/^oauth is not configured for /.test(message2)) {
|
|
45317
|
-
return
|
|
45493
|
+
return failGracefulEnvelope(
|
|
45318
45494
|
`org oauth plan: ${message2}. Declare it in the registry META first:
|
|
45319
|
-
|
|
45320
|
-
New repo? The GCP project and its Google Auth Platform consent screen must exist too \u2014 docs/Guides/oauth-provision.md \u2014 New repo
|
|
45495
|
+
${declareCommand}
|
|
45496
|
+
New repo? The GCP project and its Google Auth Platform consent screen must exist too \u2014 docs/Guides/oauth-provision.md \u2014 New repo.`,
|
|
45497
|
+
{ code: ERROR_CODES.ERR_STATE_CONFLICT, corrected_command: declareCommand }
|
|
45321
45498
|
);
|
|
45322
45499
|
}
|
|
45323
45500
|
return failGraceful(`org oauth plan: ${message2}`);
|
|
@@ -45704,6 +45881,14 @@ function resolveHouseShim(argv) {
|
|
|
45704
45881
|
argv.splice(2, 1);
|
|
45705
45882
|
return;
|
|
45706
45883
|
}
|
|
45884
|
+
if (actual && isDeclaredHouseAlias(houseToken, lookupPath)) {
|
|
45885
|
+
process.stderr.write(
|
|
45886
|
+
`mmi-cli: '${lookupPath}' lives in house '${actual}', not '${houseToken}' \u2014 routed to \`mmi-cli ${canonicalPathFor(lookupPath)}\`
|
|
45887
|
+
`
|
|
45888
|
+
);
|
|
45889
|
+
argv.splice(2, 1);
|
|
45890
|
+
return;
|
|
45891
|
+
}
|
|
45707
45892
|
if (actual) {
|
|
45708
45893
|
refuseHouseShim(
|
|
45709
45894
|
lookupPath,
|
|
@@ -45732,6 +45917,7 @@ function printHouseRootHelp(house) {
|
|
|
45732
45917
|
);
|
|
45733
45918
|
consoleIo.log(lines2.join("\n"));
|
|
45734
45919
|
}
|
|
45920
|
+
resolveVerbAliasShim(process.argv);
|
|
45735
45921
|
resolveHouseShim(process.argv);
|
|
45736
45922
|
program2.parseAsync(process.argv).then(() => finishCliRun()).catch((e) => failGraceful(e.message));
|
|
45737
45923
|
// Annotate the CommonJS export names for ESM import in node:
|
package/package.json
CHANGED