@mutmutco/cli 4.5.25 → 4.5.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +87 -15
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -1045,9 +1045,21 @@ function mutating(cmd, planFn) {
|
|
|
1045
1045
|
return cmd;
|
|
1046
1046
|
}
|
|
1047
1047
|
function installProcessBackstop() {
|
|
1048
|
+
installBrokenPipeGuard(process.stdout);
|
|
1049
|
+
installBrokenPipeGuard(process.stderr);
|
|
1048
1050
|
process.on("unhandledRejection", (reason) => void failGraceful(reason instanceof Error ? reason.message : String(reason)));
|
|
1049
1051
|
process.on("uncaughtException", (err) => void failGraceful(err instanceof Error ? err.message : String(err)));
|
|
1050
1052
|
}
|
|
1053
|
+
function installBrokenPipeGuard(stream) {
|
|
1054
|
+
stream.on("error", (err) => {
|
|
1055
|
+
if (err?.code !== "EPIPE") throw err;
|
|
1056
|
+
stream.write = (_chunk, enc, cb) => {
|
|
1057
|
+
const done = typeof enc === "function" ? enc : cb;
|
|
1058
|
+
if (typeof done === "function") queueMicrotask(() => done());
|
|
1059
|
+
return true;
|
|
1060
|
+
};
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1051
1063
|
function rawFlag(flag) {
|
|
1052
1064
|
return process.argv.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
|
|
1053
1065
|
}
|
|
@@ -16804,10 +16816,10 @@ var rollout_plan_default = {
|
|
|
16804
16816
|
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)."
|
|
16805
16817
|
},
|
|
16806
16818
|
baseline: {
|
|
16807
|
-
version: "4.5.
|
|
16808
|
-
tag: "v4.5.
|
|
16809
|
-
commit: "
|
|
16810
|
-
npm: "@mutmutco/cli@4.5.
|
|
16819
|
+
version: "4.5.27",
|
|
16820
|
+
tag: "v4.5.27",
|
|
16821
|
+
commit: "30f02884a0f7",
|
|
16822
|
+
npm: "@mutmutco/cli@4.5.27"
|
|
16811
16823
|
},
|
|
16812
16824
|
exitCriterion: "fleet-n-of-n",
|
|
16813
16825
|
hubOnlyShortcut: "forbidden",
|
|
@@ -16824,14 +16836,14 @@ var rollout_plan_default = {
|
|
|
16824
16836
|
repo: "mutmutco/mmi-hub",
|
|
16825
16837
|
role: "canary",
|
|
16826
16838
|
schedule: "train",
|
|
16827
|
-
v3Target: "v4.5.
|
|
16839
|
+
v3Target: "v4.5.27"
|
|
16828
16840
|
}
|
|
16829
16841
|
],
|
|
16830
16842
|
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.",
|
|
16831
16843
|
rollback: {
|
|
16832
16844
|
independent: true,
|
|
16833
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
16834
|
-
v3Target: "v4.5.
|
|
16845
|
+
mechanism: "npm dist-tag latest -> 4.5.27 and redeploy the Hub Lambda from tag v4.5.27 (30f02884a0f7); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
16846
|
+
v3Target: "v4.5.27 (@mutmutco/cli@4.5.27, tag commit 30f02884a0f7 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
16835
16847
|
}
|
|
16836
16848
|
},
|
|
16837
16849
|
{
|
|
@@ -17195,11 +17207,11 @@ var QueryReadError = class extends Error {
|
|
|
17195
17207
|
this.code = code;
|
|
17196
17208
|
}
|
|
17197
17209
|
};
|
|
17198
|
-
function clampLimit(n, dflt) {
|
|
17210
|
+
function clampLimit(n, dflt, max = MAX_LIMIT) {
|
|
17199
17211
|
if (n === void 0 || !Number.isFinite(n)) return dflt;
|
|
17200
17212
|
const v = Math.floor(n);
|
|
17201
17213
|
if (v < 1) return 1;
|
|
17202
|
-
if (v >
|
|
17214
|
+
if (v > max) return max;
|
|
17203
17215
|
return v;
|
|
17204
17216
|
}
|
|
17205
17217
|
function isGhNotFound(e) {
|
|
@@ -17226,10 +17238,12 @@ function loginsOf(v) {
|
|
|
17226
17238
|
function namesOf(v) {
|
|
17227
17239
|
return Array.isArray(v) ? v.map((a) => String(a?.name ?? "")).filter(Boolean) : [];
|
|
17228
17240
|
}
|
|
17241
|
+
var ISSUE_LIST_MAX_LIMIT = 1e3;
|
|
17229
17242
|
function buildIssueListArgs(opts, repo) {
|
|
17230
|
-
const limit = clampLimit(opts.limit, DEFAULT_LIMIT);
|
|
17243
|
+
const limit = clampLimit(opts.limit, DEFAULT_LIMIT, ISSUE_LIST_MAX_LIMIT);
|
|
17231
17244
|
const state = opts.state ?? "open";
|
|
17232
|
-
const
|
|
17245
|
+
const fields = opts.withBody ? `${ISSUE_LIST_FIELDS},body` : ISSUE_LIST_FIELDS;
|
|
17246
|
+
const args = ["issue", "list", "--repo", repo, "--state", state, "--limit", String(limit), "--json", fields];
|
|
17233
17247
|
if (opts.label) args.push("--label", opts.label);
|
|
17234
17248
|
if (opts.assignee) args.push("--assignee", opts.assignee);
|
|
17235
17249
|
if (opts.search) args.push("--search", opts.search);
|
|
@@ -17245,7 +17259,8 @@ function shapeIssueList(rows) {
|
|
|
17245
17259
|
state: String(row.state ?? ""),
|
|
17246
17260
|
url: String(row.url ?? ""),
|
|
17247
17261
|
assignees: loginsOf(row.assignees),
|
|
17248
|
-
labels: namesOf(row.labels)
|
|
17262
|
+
labels: namesOf(row.labels),
|
|
17263
|
+
...typeof row.body === "string" ? { body: row.body } : {}
|
|
17249
17264
|
};
|
|
17250
17265
|
});
|
|
17251
17266
|
}
|
|
@@ -17254,7 +17269,8 @@ async function runIssueList(deps, opts) {
|
|
|
17254
17269
|
if (!repo) {
|
|
17255
17270
|
throw new QueryReadError("BAD_INPUT", `could not resolve repo (pass --repo <owner/repo>)`);
|
|
17256
17271
|
}
|
|
17257
|
-
const
|
|
17272
|
+
const timeout = (opts.limit ?? 0) > MAX_LIMIT ? GH_LIST_TIMEOUT_MS * 4 : GH_LIST_TIMEOUT_MS;
|
|
17273
|
+
const rows = await deps.ghJson(buildIssueListArgs(opts, repo), timeout);
|
|
17258
17274
|
return shapeIssueList(rows);
|
|
17259
17275
|
}
|
|
17260
17276
|
function childrenGraphqlArgs(owner, name, number) {
|
|
@@ -17862,10 +17878,10 @@ function registerQueryCommands(program3) {
|
|
|
17862
17878
|
const project2 = program3.commands.find((c) => c.name() === "project");
|
|
17863
17879
|
if (!issue || !pr) return;
|
|
17864
17880
|
const deps = queryDeps();
|
|
17865
|
-
issue.command("list").description("bounded issue read \u2014 filter by text, label, state, or assignee; always prints JSON").option("--label <label>", "filter by label").option("--search <query>", "free-text issue search").addOption(caseInsensitiveChoices(new Option("--state <state>", "open | closed | all").default("open"), ISSUE_STATES)).option("--assignee <login>", "filter by assignee login").option("--limit <n>", "max issues to return (capped at 100)", (v) => Number(v), DEFAULT_LIMIT).option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output (already the default \u2014 accepted for contract uniformity)").action(async (o) => {
|
|
17881
|
+
issue.command("list").description("bounded issue read \u2014 filter by text, label, state, or assignee; always prints JSON").option("--label <label>", "filter by label").option("--search <query>", "free-text issue search").addOption(caseInsensitiveChoices(new Option("--state <state>", "open | closed | all").default("open"), ISSUE_STATES)).option("--assignee <login>", "filter by assignee login").option("--limit <n>", "max issues to return (capped at 1000; gh pages past 100)", (v) => Number(v), DEFAULT_LIMIT).option("--with-body", "include each issue's markdown body").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output (already the default \u2014 accepted for contract uniformity)").action(async (o) => {
|
|
17866
17882
|
try {
|
|
17867
17883
|
const state = validateEnum("--state", ISSUE_STATES, o.state, "issue list");
|
|
17868
|
-
const rows = await runIssueList(deps, { label: o.label, search: o.search, state, assignee: o.assignee, limit: o.limit, repo: o.repo });
|
|
17884
|
+
const rows = await runIssueList(deps, { label: o.label, search: o.search, state, assignee: o.assignee, limit: o.limit, repo: o.repo, withBody: o.withBody });
|
|
17869
17885
|
console.log(JSON.stringify(rows));
|
|
17870
17886
|
} catch (e) {
|
|
17871
17887
|
queryFail("issue list", e);
|
|
@@ -28753,6 +28769,50 @@ async function attachBoardIssues(selectors, repoOption, deps = {}) {
|
|
|
28753
28769
|
// src/board-commands.ts
|
|
28754
28770
|
init_clean_exit();
|
|
28755
28771
|
init_error_codes();
|
|
28772
|
+
|
|
28773
|
+
// src/board-events.ts
|
|
28774
|
+
init_fetch_retry();
|
|
28775
|
+
init_client_version();
|
|
28776
|
+
var BOARD_EVENTS_POLL_MS = 2e3;
|
|
28777
|
+
var BOARD_EVENTS_MAX_WAIT_S = 120;
|
|
28778
|
+
function isPage(b) {
|
|
28779
|
+
const p = b;
|
|
28780
|
+
return Boolean(p && Number.isInteger(p.cursor) && Array.isArray(p.events) && typeof p.resync === "boolean" && typeof p.more === "boolean");
|
|
28781
|
+
}
|
|
28782
|
+
async function readBoardEvents(since, deps) {
|
|
28783
|
+
if (!deps.baseUrl) throw new Error("no Hub API URL (set MMI_HUB_URL or use a current MMI CLI build)");
|
|
28784
|
+
const token = await deps.token();
|
|
28785
|
+
if (!token) throw new Error("no Hub session token (run `gh auth login`)");
|
|
28786
|
+
const query = since === void 0 ? "" : `?since=${since}`;
|
|
28787
|
+
const res = await fetchWithRetry(
|
|
28788
|
+
deps.fetch ?? fetch,
|
|
28789
|
+
`${deps.baseUrl.replace(/\/$/, "")}/board/events${query}`,
|
|
28790
|
+
{ method: "GET", headers: { ...clientVersionHeaders(), Authorization: `Bearer ${token}` } },
|
|
28791
|
+
{ attempts: 3, timeoutMs: 1e4 }
|
|
28792
|
+
);
|
|
28793
|
+
if (res.status === 426) throw new Error(upgradeRequiredError(res, await res.json().catch(() => null)));
|
|
28794
|
+
const body = await res.json().catch(() => null);
|
|
28795
|
+
if (!res.ok) {
|
|
28796
|
+
const detail = body?.error;
|
|
28797
|
+
throw new Error(`board events HTTP ${res.status}${detail ? `: ${detail}` : ""}`);
|
|
28798
|
+
}
|
|
28799
|
+
if (!isPage(body)) throw new Error("malformed board events response");
|
|
28800
|
+
return body;
|
|
28801
|
+
}
|
|
28802
|
+
async function waitBoardEvents(since, waitS, deps) {
|
|
28803
|
+
const now = deps.now ?? Date.now;
|
|
28804
|
+
const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
28805
|
+
const deadline = now() + Math.min(Math.max(waitS, 0), BOARD_EVENTS_MAX_WAIT_S) * 1e3;
|
|
28806
|
+
let cursor = since;
|
|
28807
|
+
for (; ; ) {
|
|
28808
|
+
const page = await readBoardEvents(cursor, deps);
|
|
28809
|
+
if (page.events.length || page.resync || page.more || now() + BOARD_EVENTS_POLL_MS > deadline) return page;
|
|
28810
|
+
cursor = page.cursor;
|
|
28811
|
+
await sleep2(BOARD_EVENTS_POLL_MS);
|
|
28812
|
+
}
|
|
28813
|
+
}
|
|
28814
|
+
|
|
28815
|
+
// src/board-commands.ts
|
|
28756
28816
|
function refuseRateLimited(e, json) {
|
|
28757
28817
|
if (!isRateLimitedError(e)) return false;
|
|
28758
28818
|
if (json) console.log(JSON.stringify(e.receipt));
|
|
@@ -28836,6 +28896,18 @@ function registerBoardCommands(program3) {
|
|
|
28836
28896
|
}
|
|
28837
28897
|
const board = program3.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
|
|
28838
28898
|
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("--snapshot-only", "fail instead of falling back to your personal GraphQL pool when the Hub snapshot is unavailable \u2014 for ambient readers such as sidebars (#7203)").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));
|
|
28899
|
+
board.command("events").description("content-free board/CI change records after a cursor, from the Hub event feed (no GitHub call) \u2014 for real-time sidebars (#7235)").option("--since <cursor>", "cursor from the previous answer; omit on first read (answers the head cursor with resync:true)").option("--wait <seconds>", "hold up to this long (max 120), polling every 2s, and return on the first event or resync", "0").option("--repo <owner/repo>", "repo whose Hub config to use (defaults to git origin)").option("--json", "machine-readable output (already the default)").addHelpText("after", "\nPrints {cursor, events, resync, more[, hidden]}. Pass the returned cursor as the next --since.\nresync:true means records were missed (retention ~1h, or a gap): re-read the board with\n`mmi-cli oracle board read --snapshot-only --json`.\n").action(async (o) => {
|
|
28900
|
+
try {
|
|
28901
|
+
if (o.since !== void 0 && !/^\d{1,15}$/.test(o.since)) throw new Error("--since must be a non-negative integer cursor");
|
|
28902
|
+
const wait = Number(o.wait ?? "0");
|
|
28903
|
+
if (!Number.isFinite(wait) || wait < 0) throw new Error("--wait must be a non-negative number of seconds");
|
|
28904
|
+
const config = await loadConfigForRepo(o.repo);
|
|
28905
|
+
const page = await waitBoardEvents(o.since === void 0 ? void 0 : Number(o.since), wait, registryClientDeps(config));
|
|
28906
|
+
console.log(JSON.stringify(page));
|
|
28907
|
+
} catch (e) {
|
|
28908
|
+
return failGraceful(`oracle board events failed: ${e.message}`);
|
|
28909
|
+
}
|
|
28910
|
+
});
|
|
28839
28911
|
withExamples(mutating(
|
|
28840
28912
|
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"),
|
|
28841
28913
|
(_opts, args) => ({ command: "oracle board claim", issues: args[0] ?? [] })
|
package/package.json
CHANGED