@mutmutco/cli 4.5.62 → 4.5.64
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 +480 -125
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -587,7 +587,147 @@ var init_github_client = __esm({
|
|
|
587
587
|
}
|
|
588
588
|
});
|
|
589
589
|
|
|
590
|
+
// src/github-quota.ts
|
|
591
|
+
function hubQuotaResetWaitMs(resetAt, nowMs, capMs = RATE_LIMIT_WAIT_CAP_MS, jitterMs = 0) {
|
|
592
|
+
if (typeof resetAt !== "string" || !resetAt) return void 0;
|
|
593
|
+
const resetMs = Date.parse(resetAt);
|
|
594
|
+
if (!Number.isFinite(resetMs)) return void 0;
|
|
595
|
+
const wait = rateLimitWaitMs(resetMs / 1e3, nowMs, capMs);
|
|
596
|
+
if (wait === void 0) return void 0;
|
|
597
|
+
return wait + Math.max(0, jitterMs);
|
|
598
|
+
}
|
|
599
|
+
function isRateLimitedError(e) {
|
|
600
|
+
return e instanceof RateLimitedError;
|
|
601
|
+
}
|
|
602
|
+
function isRateLimitText(text) {
|
|
603
|
+
return /API rate limit already exceeded|rate limit already exceeded|RATE_LIMITED|secondary rate|abuse detection|HTTP\/?\d*(?:\.\d)?\s+429\b|\b429\b.*rate limit/i.test(text) || /rate limit/i.test(text) && !/not rate.?limit/i.test(text);
|
|
604
|
+
}
|
|
605
|
+
function isRateLimitRefusal(e) {
|
|
606
|
+
if (e instanceof GitHubApiError && e.rateLimited) return true;
|
|
607
|
+
if (e instanceof GitHubApiError && e.status === 403) {
|
|
608
|
+
return /rate limit|abuse detection|secondary rate/i.test(e.message);
|
|
609
|
+
}
|
|
610
|
+
if (e instanceof RateLimitedError) return true;
|
|
611
|
+
const text = e instanceof Error ? `${e.message}
|
|
612
|
+
${e.stderr ?? ""}` : String(e ?? "");
|
|
613
|
+
return isRateLimitText(text);
|
|
614
|
+
}
|
|
615
|
+
function resetEpochFromError(e) {
|
|
616
|
+
if (e instanceof GitHubApiError && typeof e.rateLimitReset === "number") return e.rateLimitReset;
|
|
617
|
+
if (e instanceof RateLimitedError && typeof e.receipt.resetEpochSeconds === "number") {
|
|
618
|
+
return e.receipt.resetEpochSeconds;
|
|
619
|
+
}
|
|
620
|
+
return void 0;
|
|
621
|
+
}
|
|
622
|
+
function rateLimitWaitMs(resetEpochSeconds, nowMs, capMs = RATE_LIMIT_WAIT_CAP_MS) {
|
|
623
|
+
if (typeof resetEpochSeconds !== "number" || !Number.isFinite(resetEpochSeconds)) return void 0;
|
|
624
|
+
const wait = resetEpochSeconds * 1e3 - nowMs;
|
|
625
|
+
if (wait <= 0) return 0;
|
|
626
|
+
if (wait > capMs) return void 0;
|
|
627
|
+
return wait;
|
|
628
|
+
}
|
|
629
|
+
function rateLimitedReceipt(opts) {
|
|
630
|
+
const resetEpochSeconds = opts.resetEpochSeconds;
|
|
631
|
+
const resetAt = typeof resetEpochSeconds === "number" ? new Date(resetEpochSeconds * 1e3).toISOString() : void 0;
|
|
632
|
+
const note = rateLimitResetNote(resetEpochSeconds, opts.nowMs ?? Date.now());
|
|
633
|
+
const context = opts.context ?? "GitHub API";
|
|
634
|
+
const remaining = opts.remaining && (opts.remaining.graphql !== void 0 || opts.remaining.core !== void 0) ? opts.remaining : void 0;
|
|
635
|
+
return {
|
|
636
|
+
status: "rate_limited",
|
|
637
|
+
...resetEpochSeconds !== void 0 ? { resetEpochSeconds } : {},
|
|
638
|
+
...resetAt ? { resetAt } : {},
|
|
639
|
+
message: opts.message ?? `${context}: ${note}`,
|
|
640
|
+
retryCommand: opts.retryCommand,
|
|
641
|
+
...remaining ? { remaining } : {}
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
async function runWithRateLimitBackoff(operation, opts) {
|
|
645
|
+
const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
646
|
+
const now = opts.now ?? Date.now;
|
|
647
|
+
const log = opts.log ?? ((message2) => console.warn(message2));
|
|
648
|
+
const capMs = opts.capMs ?? RATE_LIMIT_WAIT_CAP_MS;
|
|
649
|
+
const tryOnce = async () => {
|
|
650
|
+
try {
|
|
651
|
+
return await operation();
|
|
652
|
+
} catch (e) {
|
|
653
|
+
if (!isRateLimitRefusal(e) || !opts.fallback) throw e;
|
|
654
|
+
try {
|
|
655
|
+
return await opts.fallback();
|
|
656
|
+
} catch (e2) {
|
|
657
|
+
if (isRateLimitRefusal(e2)) throw e2;
|
|
658
|
+
throw e2;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
try {
|
|
663
|
+
return await tryOnce();
|
|
664
|
+
} catch (e) {
|
|
665
|
+
if (!isRateLimitRefusal(e)) throw e;
|
|
666
|
+
const reset = resetEpochFromError(e);
|
|
667
|
+
const waitMs = rateLimitWaitMs(reset, now(), capMs);
|
|
668
|
+
if (waitMs === void 0) {
|
|
669
|
+
throw new RateLimitedError(rateLimitedReceipt({
|
|
670
|
+
resetEpochSeconds: reset,
|
|
671
|
+
retryCommand: opts.retryCommand,
|
|
672
|
+
context: opts.context,
|
|
673
|
+
nowMs: now()
|
|
674
|
+
}));
|
|
675
|
+
}
|
|
676
|
+
if (waitMs > 0) {
|
|
677
|
+
log(`${opts.context}: rate-limited \u2014 waiting ${Math.round(waitMs / 1e3)}s for pool reset`);
|
|
678
|
+
await sleep2(waitMs);
|
|
679
|
+
}
|
|
680
|
+
try {
|
|
681
|
+
return await tryOnce();
|
|
682
|
+
} catch (e2) {
|
|
683
|
+
if (!isRateLimitRefusal(e2)) throw e2;
|
|
684
|
+
throw new RateLimitedError(rateLimitedReceipt({
|
|
685
|
+
resetEpochSeconds: resetEpochFromError(e2) ?? reset,
|
|
686
|
+
retryCommand: opts.retryCommand,
|
|
687
|
+
context: opts.context,
|
|
688
|
+
nowMs: now()
|
|
689
|
+
}));
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
var RATE_LIMIT_WAIT_CAP_MS, RateLimitedError;
|
|
694
|
+
var init_github_quota = __esm({
|
|
695
|
+
"src/github-quota.ts"() {
|
|
696
|
+
"use strict";
|
|
697
|
+
init_github_client();
|
|
698
|
+
RATE_LIMIT_WAIT_CAP_MS = 12e4;
|
|
699
|
+
RateLimitedError = class extends Error {
|
|
700
|
+
receipt;
|
|
701
|
+
constructor(receipt) {
|
|
702
|
+
super(receipt.message);
|
|
703
|
+
this.name = "RateLimitedError";
|
|
704
|
+
this.receipt = receipt;
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
|
|
590
710
|
// src/fetch-retry.ts
|
|
711
|
+
async function hubQuota503(res, opts) {
|
|
712
|
+
if (res.status !== 503) return "pass";
|
|
713
|
+
let body;
|
|
714
|
+
try {
|
|
715
|
+
body = await res.clone().json();
|
|
716
|
+
} catch {
|
|
717
|
+
return "pass";
|
|
718
|
+
}
|
|
719
|
+
const code = body?.code;
|
|
720
|
+
if (code !== "github-rate-limited") return "pass";
|
|
721
|
+
const q = opts.hubQuotaWait;
|
|
722
|
+
const resetAt = body?.resetAt;
|
|
723
|
+
const wait = hubQuotaResetWaitMs(
|
|
724
|
+
typeof resetAt === "string" ? resetAt : void 0,
|
|
725
|
+
q?.now?.() ?? Date.now(),
|
|
726
|
+
q?.capMs ?? RATE_LIMIT_WAIT_CAP_MS,
|
|
727
|
+
(q?.jitterMs ?? (() => Math.floor(Math.random() * HUB_QUOTA_JITTER_MAX_MS)))()
|
|
728
|
+
);
|
|
729
|
+
return wait === void 0 ? "fail-fast" : { waitMs: wait };
|
|
730
|
+
}
|
|
591
731
|
async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
|
|
592
732
|
const attempts = opts.attempts ?? 3;
|
|
593
733
|
const baseDelayMs = opts.baseDelayMs ?? 250;
|
|
@@ -599,9 +739,17 @@ async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
|
|
|
599
739
|
const attemptInit = opts.timeoutMs ? { ...init, signal: AbortSignal.timeout(opts.timeoutMs) } : init;
|
|
600
740
|
try {
|
|
601
741
|
const res = await fetchImpl(url, attemptInit);
|
|
602
|
-
if (!isLast
|
|
603
|
-
|
|
604
|
-
|
|
742
|
+
if (!isLast) {
|
|
743
|
+
const quota = await hubQuota503(res, opts);
|
|
744
|
+
if (typeof quota === "object") {
|
|
745
|
+
await sleep2(quota.waitMs);
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
if (quota === "fail-fast") return res;
|
|
749
|
+
if (retryOn(res)) {
|
|
750
|
+
await sleep2(baseDelayMs * 2 ** i);
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
605
753
|
}
|
|
606
754
|
return res;
|
|
607
755
|
} catch (e) {
|
|
@@ -612,9 +760,12 @@ async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
|
|
|
612
760
|
}
|
|
613
761
|
throw lastErr;
|
|
614
762
|
}
|
|
763
|
+
var HUB_QUOTA_JITTER_MAX_MS;
|
|
615
764
|
var init_fetch_retry = __esm({
|
|
616
765
|
"src/fetch-retry.ts"() {
|
|
617
766
|
"use strict";
|
|
767
|
+
init_github_quota();
|
|
768
|
+
HUB_QUOTA_JITTER_MAX_MS = 5e3;
|
|
618
769
|
}
|
|
619
770
|
});
|
|
620
771
|
|
|
@@ -5568,114 +5719,12 @@ async function fetchHubBoardSnapshot(request, deps) {
|
|
|
5568
5719
|
return { state: "ok", snapshot: body };
|
|
5569
5720
|
}
|
|
5570
5721
|
|
|
5571
|
-
// src/
|
|
5572
|
-
|
|
5573
|
-
var RATE_LIMIT_WAIT_CAP_MS = 12e4;
|
|
5574
|
-
var RateLimitedError = class extends Error {
|
|
5575
|
-
receipt;
|
|
5576
|
-
constructor(receipt) {
|
|
5577
|
-
super(receipt.message);
|
|
5578
|
-
this.name = "RateLimitedError";
|
|
5579
|
-
this.receipt = receipt;
|
|
5580
|
-
}
|
|
5581
|
-
};
|
|
5582
|
-
function isRateLimitedError(e) {
|
|
5583
|
-
return e instanceof RateLimitedError;
|
|
5584
|
-
}
|
|
5585
|
-
function isRateLimitText(text) {
|
|
5586
|
-
return /API rate limit already exceeded|rate limit already exceeded|RATE_LIMITED|secondary rate|abuse detection|HTTP\/?\d*(?:\.\d)?\s+429\b|\b429\b.*rate limit/i.test(text) || /rate limit/i.test(text) && !/not rate.?limit/i.test(text);
|
|
5587
|
-
}
|
|
5588
|
-
function isRateLimitRefusal(e) {
|
|
5589
|
-
if (e instanceof GitHubApiError && e.rateLimited) return true;
|
|
5590
|
-
if (e instanceof GitHubApiError && e.status === 403) {
|
|
5591
|
-
return /rate limit|abuse detection|secondary rate/i.test(e.message);
|
|
5592
|
-
}
|
|
5593
|
-
if (e instanceof RateLimitedError) return true;
|
|
5594
|
-
const text = e instanceof Error ? `${e.message}
|
|
5595
|
-
${e.stderr ?? ""}` : String(e ?? "");
|
|
5596
|
-
return isRateLimitText(text);
|
|
5597
|
-
}
|
|
5598
|
-
function resetEpochFromError(e) {
|
|
5599
|
-
if (e instanceof GitHubApiError && typeof e.rateLimitReset === "number") return e.rateLimitReset;
|
|
5600
|
-
if (e instanceof RateLimitedError && typeof e.receipt.resetEpochSeconds === "number") {
|
|
5601
|
-
return e.receipt.resetEpochSeconds;
|
|
5602
|
-
}
|
|
5603
|
-
return void 0;
|
|
5604
|
-
}
|
|
5605
|
-
function rateLimitWaitMs(resetEpochSeconds, nowMs, capMs = RATE_LIMIT_WAIT_CAP_MS) {
|
|
5606
|
-
if (typeof resetEpochSeconds !== "number" || !Number.isFinite(resetEpochSeconds)) return void 0;
|
|
5607
|
-
const wait = resetEpochSeconds * 1e3 - nowMs;
|
|
5608
|
-
if (wait <= 0) return 0;
|
|
5609
|
-
if (wait > capMs) return void 0;
|
|
5610
|
-
return wait;
|
|
5611
|
-
}
|
|
5612
|
-
function rateLimitedReceipt(opts) {
|
|
5613
|
-
const resetEpochSeconds = opts.resetEpochSeconds;
|
|
5614
|
-
const resetAt = typeof resetEpochSeconds === "number" ? new Date(resetEpochSeconds * 1e3).toISOString() : void 0;
|
|
5615
|
-
const note = rateLimitResetNote(resetEpochSeconds, opts.nowMs ?? Date.now());
|
|
5616
|
-
const context = opts.context ?? "GitHub API";
|
|
5617
|
-
const remaining = opts.remaining && (opts.remaining.graphql !== void 0 || opts.remaining.core !== void 0) ? opts.remaining : void 0;
|
|
5618
|
-
return {
|
|
5619
|
-
status: "rate_limited",
|
|
5620
|
-
...resetEpochSeconds !== void 0 ? { resetEpochSeconds } : {},
|
|
5621
|
-
...resetAt ? { resetAt } : {},
|
|
5622
|
-
message: opts.message ?? `${context}: ${note}`,
|
|
5623
|
-
retryCommand: opts.retryCommand,
|
|
5624
|
-
...remaining ? { remaining } : {}
|
|
5625
|
-
};
|
|
5626
|
-
}
|
|
5627
|
-
async function runWithRateLimitBackoff(operation, opts) {
|
|
5628
|
-
const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
5629
|
-
const now = opts.now ?? Date.now;
|
|
5630
|
-
const log = opts.log ?? ((message2) => console.warn(message2));
|
|
5631
|
-
const capMs = opts.capMs ?? RATE_LIMIT_WAIT_CAP_MS;
|
|
5632
|
-
const tryOnce = async () => {
|
|
5633
|
-
try {
|
|
5634
|
-
return await operation();
|
|
5635
|
-
} catch (e) {
|
|
5636
|
-
if (!isRateLimitRefusal(e) || !opts.fallback) throw e;
|
|
5637
|
-
try {
|
|
5638
|
-
return await opts.fallback();
|
|
5639
|
-
} catch (e2) {
|
|
5640
|
-
if (isRateLimitRefusal(e2)) throw e2;
|
|
5641
|
-
throw e2;
|
|
5642
|
-
}
|
|
5643
|
-
}
|
|
5644
|
-
};
|
|
5645
|
-
try {
|
|
5646
|
-
return await tryOnce();
|
|
5647
|
-
} catch (e) {
|
|
5648
|
-
if (!isRateLimitRefusal(e)) throw e;
|
|
5649
|
-
const reset = resetEpochFromError(e);
|
|
5650
|
-
const waitMs = rateLimitWaitMs(reset, now(), capMs);
|
|
5651
|
-
if (waitMs === void 0) {
|
|
5652
|
-
throw new RateLimitedError(rateLimitedReceipt({
|
|
5653
|
-
resetEpochSeconds: reset,
|
|
5654
|
-
retryCommand: opts.retryCommand,
|
|
5655
|
-
context: opts.context,
|
|
5656
|
-
nowMs: now()
|
|
5657
|
-
}));
|
|
5658
|
-
}
|
|
5659
|
-
if (waitMs > 0) {
|
|
5660
|
-
log(`${opts.context}: rate-limited \u2014 waiting ${Math.round(waitMs / 1e3)}s for pool reset`);
|
|
5661
|
-
await sleep2(waitMs);
|
|
5662
|
-
}
|
|
5663
|
-
try {
|
|
5664
|
-
return await tryOnce();
|
|
5665
|
-
} catch (e2) {
|
|
5666
|
-
if (!isRateLimitRefusal(e2)) throw e2;
|
|
5667
|
-
throw new RateLimitedError(rateLimitedReceipt({
|
|
5668
|
-
resetEpochSeconds: resetEpochFromError(e2) ?? reset,
|
|
5669
|
-
retryCommand: opts.retryCommand,
|
|
5670
|
-
context: opts.context,
|
|
5671
|
-
nowMs: now()
|
|
5672
|
-
}));
|
|
5673
|
-
}
|
|
5674
|
-
}
|
|
5675
|
-
}
|
|
5722
|
+
// src/board-read.ts
|
|
5723
|
+
init_github_quota();
|
|
5676
5724
|
|
|
5677
5725
|
// src/claim-marker-client.ts
|
|
5678
5726
|
init_github_client();
|
|
5727
|
+
init_github_quota();
|
|
5679
5728
|
|
|
5680
5729
|
// src/pr-checks-rest.ts
|
|
5681
5730
|
init_cli_shared();
|
|
@@ -6519,6 +6568,7 @@ function withAppClaimMarkerRead(client, appClient) {
|
|
|
6519
6568
|
}
|
|
6520
6569
|
|
|
6521
6570
|
// src/board-dependency.ts
|
|
6571
|
+
init_github_quota();
|
|
6522
6572
|
var DEPENDS_ON_LINE = /^\s*[-*]?\s*\*\*Depends on:\*\*\s*(.+)$/im;
|
|
6523
6573
|
var ISSUE_REF = /([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)#(\d+)/g;
|
|
6524
6574
|
var DEPENDENCY_FETCH_CONCURRENCY = 8;
|
|
@@ -7209,6 +7259,9 @@ function jobNeeds(job) {
|
|
|
7209
7259
|
function unquote(s) {
|
|
7210
7260
|
return s.replace(/#.*$/, "").trim().replace(/^['"]|['"]$/g, "");
|
|
7211
7261
|
}
|
|
7262
|
+
function contextMatchesJobId(jobId, context) {
|
|
7263
|
+
return context === jobId || context.startsWith(`${jobId} / `);
|
|
7264
|
+
}
|
|
7212
7265
|
function unblockingGateJobs(workflows, required) {
|
|
7213
7266
|
const unblocking = /* @__PURE__ */ new Set();
|
|
7214
7267
|
for (const wf of workflows) {
|
|
@@ -7216,7 +7269,7 @@ function unblockingGateJobs(workflows, required) {
|
|
|
7216
7269
|
const jobs = parseWorkflowJobs(wf.body);
|
|
7217
7270
|
const needs = new Map(jobs.map((job) => [job.id, jobNeeds(job)]));
|
|
7218
7271
|
const covered = /* @__PURE__ */ new Set();
|
|
7219
|
-
const stack = jobs.map((job) => job.id).filter((id) => required.
|
|
7272
|
+
const stack = jobs.map((job) => job.id).filter((id) => [...required].some((context) => contextMatchesJobId(id, context)));
|
|
7220
7273
|
while (stack.length) {
|
|
7221
7274
|
const id = stack.pop();
|
|
7222
7275
|
if (covered.has(id)) continue;
|
|
@@ -8489,6 +8542,22 @@ function shouldWithdrawSurfaceFlag(result) {
|
|
|
8489
8542
|
|
|
8490
8543
|
// src/secrets-transport.ts
|
|
8491
8544
|
init_client_version();
|
|
8545
|
+
|
|
8546
|
+
// ../shared/quota-attribution.ts
|
|
8547
|
+
var ATTRIBUTION_MAX = 120;
|
|
8548
|
+
function sanitizeAttribution(value, max = ATTRIBUTION_MAX) {
|
|
8549
|
+
if (typeof value !== "string" || !Number.isInteger(max) || max < 1) return void 0;
|
|
8550
|
+
const cleaned = value.replace(/[^A-Za-z0-9 ._/@:-]/g, "").replace(/\s+/g, " ").trim().slice(0, max);
|
|
8551
|
+
return cleaned.length > 0 ? cleaned : void 0;
|
|
8552
|
+
}
|
|
8553
|
+
function attributionFields(attr) {
|
|
8554
|
+
if (!attr) return {};
|
|
8555
|
+
const command = sanitizeAttribution(attr.command);
|
|
8556
|
+
const via = sanitizeAttribution(attr.via);
|
|
8557
|
+
return { ...command ? { for: command } : {}, ...via ? { via } : {} };
|
|
8558
|
+
}
|
|
8559
|
+
|
|
8560
|
+
// src/secrets-transport.ts
|
|
8492
8561
|
var OWNER = "mutmutco";
|
|
8493
8562
|
var SSM_ROOT = "/mmi-future";
|
|
8494
8563
|
var PROJECT_TIER_SEGMENT = "dev";
|
|
@@ -8664,7 +8733,7 @@ async function fetchSecretValue(deps, key, opts, diagnose = false) {
|
|
|
8664
8733
|
const res = await fetchVaultRead(deps, `${deps.apiUrl}/secrets/get`, {
|
|
8665
8734
|
method: "POST",
|
|
8666
8735
|
headers: await deps.headers({ "content-type": "application/json" }),
|
|
8667
|
-
body: JSON.stringify({ repo, key, use: true, ...slug ? { slug } : {} })
|
|
8736
|
+
body: JSON.stringify({ repo, key, use: true, ...slug ? { slug } : {}, ...attributionFields(opts.attribution) })
|
|
8668
8737
|
});
|
|
8669
8738
|
if (!res.ok) {
|
|
8670
8739
|
if (diagnose) {
|
|
@@ -9158,6 +9227,7 @@ function isPriorityFieldConfigured(cfg) {
|
|
|
9158
9227
|
// src/gh-create.ts
|
|
9159
9228
|
init_cli_shared();
|
|
9160
9229
|
init_github_client();
|
|
9230
|
+
init_github_quota();
|
|
9161
9231
|
var ISSUE_TYPES = ["bug", "feature", "task"];
|
|
9162
9232
|
var GH_MUTATION_TIMEOUT_MS = 12e4;
|
|
9163
9233
|
function timeoutKillNote(err, timeoutMs) {
|
|
@@ -13467,9 +13537,11 @@ function ghError(e) {
|
|
|
13467
13537
|
|
|
13468
13538
|
// src/board-claim-move.ts
|
|
13469
13539
|
init_github_client();
|
|
13540
|
+
init_github_quota();
|
|
13470
13541
|
|
|
13471
13542
|
// src/sub-issue.ts
|
|
13472
13543
|
init_error_codes();
|
|
13544
|
+
init_github_quota();
|
|
13473
13545
|
function buildResolveIdArgs(ref) {
|
|
13474
13546
|
const args = ["issue", "view", String(ref.number), "--json", "id", "--jq", ".id"];
|
|
13475
13547
|
if (ref.repo) args.push("--repo", ref.repo);
|
|
@@ -17156,10 +17228,10 @@ var rollout_plan_default = {
|
|
|
17156
17228
|
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)."
|
|
17157
17229
|
},
|
|
17158
17230
|
baseline: {
|
|
17159
|
-
version: "4.5.
|
|
17160
|
-
tag: "v4.5.
|
|
17161
|
-
commit: "
|
|
17162
|
-
npm: "@mutmutco/cli@4.5.
|
|
17231
|
+
version: "4.5.64",
|
|
17232
|
+
tag: "v4.5.64",
|
|
17233
|
+
commit: "f201a5b12b79",
|
|
17234
|
+
npm: "@mutmutco/cli@4.5.64"
|
|
17163
17235
|
},
|
|
17164
17236
|
exitCriterion: "fleet-n-of-n",
|
|
17165
17237
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17176,14 +17248,14 @@ var rollout_plan_default = {
|
|
|
17176
17248
|
repo: "mutmutco/mmi-hub",
|
|
17177
17249
|
role: "canary",
|
|
17178
17250
|
schedule: "train",
|
|
17179
|
-
v3Target: "v4.5.
|
|
17251
|
+
v3Target: "v4.5.64"
|
|
17180
17252
|
}
|
|
17181
17253
|
],
|
|
17182
17254
|
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.",
|
|
17183
17255
|
rollback: {
|
|
17184
17256
|
independent: true,
|
|
17185
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17186
|
-
v3Target: "v4.5.
|
|
17257
|
+
mechanism: "npm dist-tag latest -> 4.5.64 and redeploy the Hub Lambda from tag v4.5.64 (f201a5b12b79); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17258
|
+
v3Target: "v4.5.64 (@mutmutco/cli@4.5.64, tag commit f201a5b12b79 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17187
17259
|
}
|
|
17188
17260
|
},
|
|
17189
17261
|
{
|
|
@@ -27966,7 +28038,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
27966
28038
|
if (hasRequiredChecks) {
|
|
27967
28039
|
const gateFiles = new Set(gateWorkflowFiles(prWorkflowPaths));
|
|
27968
28040
|
const gateWorkflows = (await getBranchWorkflows() ?? []).filter((wf) => gateFiles.has(wf.path.slice(wf.path.lastIndexOf("/") + 1)));
|
|
27969
|
-
const unblocking = unblockingGateJobs(gateWorkflows, new Set(liveContexts)).filter((job) => !AGENT_PR_BOOKKEEPING_CONTEXTS.has(job) && !branchPlan?.pending.some((entry) => entry.branch === baseBranch && entry.contexts.
|
|
28041
|
+
const unblocking = unblockingGateJobs(gateWorkflows, new Set(liveContexts)).filter((job) => !AGENT_PR_BOOKKEEPING_CONTEXTS.has(job) && !branchPlan?.pending.some((entry) => entry.branch === baseBranch && entry.contexts.some((context) => contextMatchesJobId(job, context))));
|
|
27970
28042
|
checks.push({
|
|
27971
28043
|
ok: unblocking.length === 0,
|
|
27972
28044
|
label: UNBLOCKING_GATE_JOBS_LABEL,
|
|
@@ -28051,7 +28123,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
28051
28123
|
const body = await fetchFileContent(deps, repo, baseBranch, path2);
|
|
28052
28124
|
if (body == null) continue;
|
|
28053
28125
|
if (isGateWorkflowPath(path2)) gateBodies.push({ path: path2, body });
|
|
28054
|
-
if (!/^\s*concurrency:/m.test(body) ||
|
|
28126
|
+
if (!/^\s*concurrency:/m.test(body) || !/^\s*cancel-in-progress:[ \t]*(?:true|\$\{\{\s*github\.event_name\s*==\s*(['"])pull_request\1\s*\}\})[ \t]*(?:#.*)?$/m.test(body)) {
|
|
28055
28127
|
if (NO_CANCEL_WAIVER.test(body)) waived.push(path2);
|
|
28056
28128
|
else missing.push(path2);
|
|
28057
28129
|
}
|
|
@@ -29667,6 +29739,7 @@ async function activateAppActor(commandPath3, env, mint, note = (line) => proces
|
|
|
29667
29739
|
init_cli_shared();
|
|
29668
29740
|
|
|
29669
29741
|
// src/attach-to-project.ts
|
|
29742
|
+
init_github_quota();
|
|
29670
29743
|
async function attachViaHubApp(cfg, contentNodeId) {
|
|
29671
29744
|
const { addIssueToProjectViaHub: addIssueToProjectViaHub2 } = await Promise.resolve().then(() => (init_board_write(), board_write_exports));
|
|
29672
29745
|
return addIssueToProjectViaHub2(cfg, contentNodeId);
|
|
@@ -29827,6 +29900,7 @@ async function waitBoardEvents(since, waitS, deps) {
|
|
|
29827
29900
|
}
|
|
29828
29901
|
|
|
29829
29902
|
// src/board-commands.ts
|
|
29903
|
+
init_github_quota();
|
|
29830
29904
|
function refuseRateLimited(e, json) {
|
|
29831
29905
|
if (!isRateLimitedError(e)) return false;
|
|
29832
29906
|
if (json) console.log(JSON.stringify(e.receipt));
|
|
@@ -35608,6 +35682,16 @@ async function suiteRcand(deps, opts) {
|
|
|
35608
35682
|
];
|
|
35609
35683
|
if (opts.dryRun) return { plan };
|
|
35610
35684
|
let record = readSuiteCut(deps.recordPath);
|
|
35685
|
+
if (record?.leadReleased && record.announcement === "pending") {
|
|
35686
|
+
const reason = await deps.announceSkipReason(record);
|
|
35687
|
+
if (!reason) throw new Error(`the finished cut ${record.leadRcTag} has an unverified announcement (pending); verify whether it was posted, then finish it with suite release --owner-go before a new cut (never post twice)`);
|
|
35688
|
+
record.announcement = "skipped";
|
|
35689
|
+
record.announcementNote = reason;
|
|
35690
|
+
writeSuiteCut(deps.recordPath, record);
|
|
35691
|
+
}
|
|
35692
|
+
if (record?.leadReleased && !record.announcement) {
|
|
35693
|
+
throw new Error(`the cut ${record.leadRcTag} already released the lead but never announced; finish it with suite release --owner-go before a new cut`);
|
|
35694
|
+
}
|
|
35611
35695
|
if ((record?.announcement === "sent" || record?.announcement === "skipped") && record.leadReleased) {
|
|
35612
35696
|
const archive = `${deps.recordPath}.${record.leadRcTag}.json`;
|
|
35613
35697
|
if ((0, import_node_fs29.existsSync)(archive)) throw new Error(`suite cut archive already exists at ${archive}; inspect before a new cut`);
|
|
@@ -35649,6 +35733,17 @@ async function suiteRcand(deps, opts) {
|
|
|
35649
35733
|
writeSuiteCut(deps.recordPath, record);
|
|
35650
35734
|
continue;
|
|
35651
35735
|
}
|
|
35736
|
+
const onHead = [];
|
|
35737
|
+
for (const tag of (await deps.rcTagsAt(member.repo, member.previousRc)).filter((t) => RC_TAG.test(t))) {
|
|
35738
|
+
if (!await deps.tagSha(member.repo, suiteReleaseTag(tag))) onHead.push(tag);
|
|
35739
|
+
}
|
|
35740
|
+
if (onHead.length > 1) throw new Error(`${member.repo}: several unreleased rc tags sit on its rc head (${onHead.join(", ")}); pin the one to reuse before the suite cut`);
|
|
35741
|
+
if (onHead.length === 1) {
|
|
35742
|
+
member.tag = onHead[0];
|
|
35743
|
+
member.sha = member.previousRc;
|
|
35744
|
+
writeSuiteCut(deps.recordPath, record);
|
|
35745
|
+
continue;
|
|
35746
|
+
}
|
|
35652
35747
|
quiet.push(member);
|
|
35653
35748
|
}
|
|
35654
35749
|
if (record.members.every((m) => m.carried || quiet.includes(m))) {
|
|
@@ -35811,6 +35906,194 @@ Each member's live release must serve <data>/main/module.js at its main pin path
|
|
|
35811
35906
|
return record;
|
|
35812
35907
|
}
|
|
35813
35908
|
|
|
35909
|
+
// src/suite-rc-data.ts
|
|
35910
|
+
var RC_DATA_VAULT_REPO = "mutmutco/MM-Work";
|
|
35911
|
+
var RC_DATA_VERCEL_PROJECT = "prj_2Q1UcZ6tscIxHr4lPEhSD3jU1ZGm";
|
|
35912
|
+
var RC_DATA_VERCEL_TEAM = "team_ntTdWEQ896Eh20RT5Ri5XJDB";
|
|
35913
|
+
var RC_DATA_ALIAS = "rc.mutmut.work";
|
|
35914
|
+
var RC_DATA_GIT_BRANCH = "rc";
|
|
35915
|
+
var RC_DATA_BOUND_KEYS = [
|
|
35916
|
+
{ envKey: "DATABASE_URL", liveVaultKey: "main/DATABASE_URL", rcVaultKey: "rc/DATABASE_URL" },
|
|
35917
|
+
{ envKey: "MIGRATION_DATABASE_URL", liveVaultKey: "main/MIGRATION_DATABASE_URL", rcVaultKey: "rc/MIGRATION_DATABASE_URL" },
|
|
35918
|
+
{ envKey: "MMW_DATA_KEY", liveVaultKey: "main/MMW_DATA_KEY", rcVaultKey: "rc/MMW_DATA_KEY" },
|
|
35919
|
+
{ envKey: "GOOGLE_CLIENT_ID", liveVaultKey: "GOOGLE_CLIENT_ID", rcVaultKey: null },
|
|
35920
|
+
{ envKey: "GOOGLE_CLIENT_SECRET", liveVaultKey: "GOOGLE_CLIENT_SECRET", rcVaultKey: null }
|
|
35921
|
+
];
|
|
35922
|
+
var POLL_ATTEMPTS = 60;
|
|
35923
|
+
var POLL_INTERVAL_MS = 5e3;
|
|
35924
|
+
function withTeam(path2) {
|
|
35925
|
+
const url = new URL(`https://api.vercel.com${path2}`);
|
|
35926
|
+
url.searchParams.set("teamId", RC_DATA_VERCEL_TEAM);
|
|
35927
|
+
return url.toString();
|
|
35928
|
+
}
|
|
35929
|
+
function envCollectionUrl2() {
|
|
35930
|
+
return withTeam(`/v10/projects/${encodeURIComponent(RC_DATA_VERCEL_PROJECT)}/env`);
|
|
35931
|
+
}
|
|
35932
|
+
function envItemUrl2(id) {
|
|
35933
|
+
return withTeam(`/v9/projects/${encodeURIComponent(RC_DATA_VERCEL_PROJECT)}/env/${encodeURIComponent(id)}`);
|
|
35934
|
+
}
|
|
35935
|
+
function aliasUrl() {
|
|
35936
|
+
return withTeam(`/v4/aliases/${RC_DATA_ALIAS}`);
|
|
35937
|
+
}
|
|
35938
|
+
function deploymentUrl(id) {
|
|
35939
|
+
return withTeam(`/v13/deployments/${encodeURIComponent(id)}`);
|
|
35940
|
+
}
|
|
35941
|
+
function redeployUrl() {
|
|
35942
|
+
const url = new URL("https://api.vercel.com/v13/deployments");
|
|
35943
|
+
url.searchParams.set("forceNew", "1");
|
|
35944
|
+
url.searchParams.set("teamId", RC_DATA_VERCEL_TEAM);
|
|
35945
|
+
return url.toString();
|
|
35946
|
+
}
|
|
35947
|
+
function isRcBranchEnv(row, key) {
|
|
35948
|
+
if (row.key !== key) return false;
|
|
35949
|
+
if (row.gitBranch !== RC_DATA_GIT_BRANCH) return false;
|
|
35950
|
+
const target = Array.isArray(row.target) ? row.target.filter((item) => typeof item === "string") : [];
|
|
35951
|
+
return target.includes("preview");
|
|
35952
|
+
}
|
|
35953
|
+
function envsOf2(body) {
|
|
35954
|
+
if (Array.isArray(body)) return body;
|
|
35955
|
+
if (body && typeof body === "object" && Array.isArray(body.envs)) {
|
|
35956
|
+
return body.envs;
|
|
35957
|
+
}
|
|
35958
|
+
return null;
|
|
35959
|
+
}
|
|
35960
|
+
function scrub(reason, secrets) {
|
|
35961
|
+
let out = reason.replace(/\s+/g, " ").trim().slice(0, 300);
|
|
35962
|
+
for (const secret of secrets) {
|
|
35963
|
+
if (secret) out = out.split(secret).join("[redacted]");
|
|
35964
|
+
}
|
|
35965
|
+
return out || "unknown error";
|
|
35966
|
+
}
|
|
35967
|
+
async function readJson(deps, label, url, secrets, init) {
|
|
35968
|
+
const token = secrets[0];
|
|
35969
|
+
const headers = { ...init?.headers ?? {}, ...token ? { Authorization: `Bearer ${token}` } : {} };
|
|
35970
|
+
const method = init?.method ?? "GET";
|
|
35971
|
+
let res;
|
|
35972
|
+
try {
|
|
35973
|
+
res = await deps.fetch(url, { ...init, method, headers });
|
|
35974
|
+
} catch (e) {
|
|
35975
|
+
throw new Error(`${label} failed: ${scrub(e instanceof Error ? e.message : String(e), secrets)}`);
|
|
35976
|
+
}
|
|
35977
|
+
let raw = "";
|
|
35978
|
+
try {
|
|
35979
|
+
raw = await res.text();
|
|
35980
|
+
} catch (e) {
|
|
35981
|
+
throw new Error(`${label} failed: ${scrub(e instanceof Error ? e.message : String(e), secrets)}`);
|
|
35982
|
+
}
|
|
35983
|
+
if (!res.ok) throw new Error(`${label} failed: HTTP ${res.status}${raw ? `: ${scrub(raw, secrets)}` : ""}`);
|
|
35984
|
+
if (!raw.trim()) return {};
|
|
35985
|
+
try {
|
|
35986
|
+
return JSON.parse(raw);
|
|
35987
|
+
} catch {
|
|
35988
|
+
throw new Error(`${label} failed: response was not JSON`);
|
|
35989
|
+
}
|
|
35990
|
+
}
|
|
35991
|
+
function deploymentReadyState(body) {
|
|
35992
|
+
if (body && typeof body === "object") {
|
|
35993
|
+
const row = body;
|
|
35994
|
+
if (typeof row.status === "string") return row.status;
|
|
35995
|
+
if (typeof row.readyState === "string") return row.readyState;
|
|
35996
|
+
}
|
|
35997
|
+
return null;
|
|
35998
|
+
}
|
|
35999
|
+
function deploymentName(body) {
|
|
36000
|
+
if (body && typeof body === "object" && typeof body.name === "string") {
|
|
36001
|
+
return body.name;
|
|
36002
|
+
}
|
|
36003
|
+
return null;
|
|
36004
|
+
}
|
|
36005
|
+
function aliasDeploymentId(body) {
|
|
36006
|
+
if (body && typeof body === "object" && typeof body.deploymentId === "string") {
|
|
36007
|
+
return body.deploymentId;
|
|
36008
|
+
}
|
|
36009
|
+
return null;
|
|
36010
|
+
}
|
|
36011
|
+
async function runRcDataSwitch(deps, opts) {
|
|
36012
|
+
if (opts.to !== "live" && opts.to !== "rc") {
|
|
36013
|
+
throw new Error(`--to must be live or rc (got ${String(opts.to ?? "nothing")}); nothing was written`);
|
|
36014
|
+
}
|
|
36015
|
+
if (opts.apply && !opts.ownerGo) {
|
|
36016
|
+
throw new Error("--apply requires --owner-go (it touches the live data key); nothing was written");
|
|
36017
|
+
}
|
|
36018
|
+
const secrets = [];
|
|
36019
|
+
const token = await deps.readToken();
|
|
36020
|
+
secrets.push(token);
|
|
36021
|
+
if (!token) throw new Error("no _org/vercel/API_TOKEN; nothing was written");
|
|
36022
|
+
const listed = await readJson(deps, "GET envs", envCollectionUrl2(), secrets, { method: "GET" });
|
|
36023
|
+
const rows = envsOf2(listed);
|
|
36024
|
+
if (!rows) throw new Error("env list returned an unexpected shape; nothing was written");
|
|
36025
|
+
const plan = RC_DATA_BOUND_KEYS.map((bound) => {
|
|
36026
|
+
const source = opts.to === "live" ? bound.liveVaultKey : bound.rcVaultKey;
|
|
36027
|
+
const existing = rows.find((row) => isRcBranchEnv(row, bound.envKey) && typeof row.id === "string" && row.id.length > 0);
|
|
36028
|
+
const entryId = existing && typeof existing.id === "string" ? existing.id : null;
|
|
36029
|
+
if (source === null) return { action: existing ? "delete" : "noop", key: bound.envKey, entryId, source };
|
|
36030
|
+
return { action: entryId ? "patch" : "create", key: bound.envKey, entryId, source };
|
|
36031
|
+
});
|
|
36032
|
+
if (!opts.apply) {
|
|
36033
|
+
return { command: "suite rc-data", direction: opts.to, dryRun: true, plan, changed: [] };
|
|
36034
|
+
}
|
|
36035
|
+
const values = /* @__PURE__ */ new Map();
|
|
36036
|
+
for (const step of plan) {
|
|
36037
|
+
if (step.action !== "patch" && step.action !== "create") continue;
|
|
36038
|
+
const value = await deps.readVault(step.source);
|
|
36039
|
+
secrets.push(value);
|
|
36040
|
+
if (!value) throw new Error(`vault ${step.source} is missing or unreadable; nothing was written`);
|
|
36041
|
+
values.set(step.key, value);
|
|
36042
|
+
}
|
|
36043
|
+
const changed = [];
|
|
36044
|
+
for (const step of plan) {
|
|
36045
|
+
try {
|
|
36046
|
+
if (step.action === "patch") {
|
|
36047
|
+
await readJson(deps, `PATCH ${step.key}`, envItemUrl2(step.entryId), secrets, {
|
|
36048
|
+
method: "PATCH",
|
|
36049
|
+
headers: { "Content-Type": "application/json" },
|
|
36050
|
+
body: JSON.stringify({ value: values.get(step.key), type: "sensitive" })
|
|
36051
|
+
});
|
|
36052
|
+
changed.push(step.key);
|
|
36053
|
+
} else if (step.action === "create") {
|
|
36054
|
+
await readJson(deps, `POST ${step.key}`, envCollectionUrl2(), secrets, {
|
|
36055
|
+
method: "POST",
|
|
36056
|
+
headers: { "Content-Type": "application/json" },
|
|
36057
|
+
body: JSON.stringify({ key: step.key, value: values.get(step.key), type: "sensitive", target: ["preview"], gitBranch: RC_DATA_GIT_BRANCH })
|
|
36058
|
+
});
|
|
36059
|
+
changed.push(step.key);
|
|
36060
|
+
} else if (step.action === "delete") {
|
|
36061
|
+
await readJson(deps, `DELETE ${step.key}`, envItemUrl2(step.entryId), secrets, { method: "DELETE" });
|
|
36062
|
+
changed.push(step.key);
|
|
36063
|
+
}
|
|
36064
|
+
} catch (e) {
|
|
36065
|
+
const changedLabel = changed.length ? changed.join(", ") : "none";
|
|
36066
|
+
throw new Error(`${e.message}; changed: ${changedLabel}; no redeploy`);
|
|
36067
|
+
}
|
|
36068
|
+
}
|
|
36069
|
+
const aliasBefore = aliasDeploymentId(await readJson(deps, "GET alias", aliasUrl(), secrets, { method: "GET" }));
|
|
36070
|
+
if (!aliasBefore) throw new Error(`alias ${RC_DATA_ALIAS} has no deployment; envs changed: ${changed.join(", ")}; no redeploy`);
|
|
36071
|
+
const name = deploymentName(await readJson(deps, "GET deployment", deploymentUrl(aliasBefore), secrets, { method: "GET" }));
|
|
36072
|
+
if (!name) throw new Error(`deployment ${aliasBefore} has no name; envs changed: ${changed.join(", ")}; no redeploy`);
|
|
36073
|
+
const created = await readJson(deps, "POST redeploy", redeployUrl(), secrets, {
|
|
36074
|
+
method: "POST",
|
|
36075
|
+
headers: { "Content-Type": "application/json" },
|
|
36076
|
+
body: JSON.stringify({ name, deploymentId: aliasBefore, meta: { redeployReason: `MMI-Hub#7588 rc-data switch to ${opts.to}` } })
|
|
36077
|
+
});
|
|
36078
|
+
const newId = aliasDeploymentId({ deploymentId: created.id });
|
|
36079
|
+
if (!newId) throw new Error(`redeploy answered without an id; envs changed: ${changed.join(", ")}`);
|
|
36080
|
+
let state = null;
|
|
36081
|
+
for (let attempt = 0; attempt < POLL_ATTEMPTS; attempt += 1) {
|
|
36082
|
+
state = deploymentReadyState(await readJson(deps, "GET deployment", deploymentUrl(newId), secrets, { method: "GET" }));
|
|
36083
|
+
if (state === "READY") break;
|
|
36084
|
+
if (state === "ERROR" || state === "CANCELED" || state === "FAILED") {
|
|
36085
|
+
throw new Error(`redeploy ${newId} ended ${state}; envs changed: ${changed.join(", ")}`);
|
|
36086
|
+
}
|
|
36087
|
+
await deps.sleep(POLL_INTERVAL_MS);
|
|
36088
|
+
}
|
|
36089
|
+
if (state !== "READY") throw new Error(`redeploy ${newId} never reached READY (last: ${state ?? "unknown"}); envs changed: ${changed.join(", ")}`);
|
|
36090
|
+
const aliasAfter = aliasDeploymentId(await readJson(deps, "GET alias", aliasUrl(), secrets, { method: "GET" }));
|
|
36091
|
+
if (aliasAfter !== newId) {
|
|
36092
|
+
throw new Error(`alias ${RC_DATA_ALIAS} still points at ${aliasAfter ?? "nothing"} (expected ${newId}); envs changed: ${changed.join(", ")}`);
|
|
36093
|
+
}
|
|
36094
|
+
return { command: "suite rc-data", direction: opts.to, dryRun: false, plan, changed, deploymentId: newId, alias: RC_DATA_ALIAS };
|
|
36095
|
+
}
|
|
36096
|
+
|
|
35814
36097
|
// src/suite-train-io.ts
|
|
35815
36098
|
var import_node_fs43 = require("node:fs");
|
|
35816
36099
|
var import_node_path43 = require("node:path");
|
|
@@ -39400,6 +39683,7 @@ function applyChecklistCheck(body, query, checked) {
|
|
|
39400
39683
|
|
|
39401
39684
|
// src/command-register-collaboration.ts
|
|
39402
39685
|
init_github_client();
|
|
39686
|
+
init_github_quota();
|
|
39403
39687
|
|
|
39404
39688
|
// src/issue-commands.ts
|
|
39405
39689
|
var import_node_fs34 = require("node:fs");
|
|
@@ -40340,6 +40624,7 @@ var import_node_path35 = require("node:path");
|
|
|
40340
40624
|
|
|
40341
40625
|
// src/cross-repo-filing-issue.ts
|
|
40342
40626
|
init_github_client();
|
|
40627
|
+
init_github_quota();
|
|
40343
40628
|
function crossRepoFilingRetryCommand(prRepo, prNumber) {
|
|
40344
40629
|
return `mmi-cli devops pr merge ${prNumber} --repo ${prRepo}`;
|
|
40345
40630
|
}
|
|
@@ -40442,6 +40727,7 @@ ${pr.body ?? ""}`;
|
|
|
40442
40727
|
}
|
|
40443
40728
|
|
|
40444
40729
|
// src/post-merge-recon.ts
|
|
40730
|
+
init_github_quota();
|
|
40445
40731
|
var POST_MERGE_RECON_SCHEMA_VERSION = 1;
|
|
40446
40732
|
function postMergeReconRetryCommand(repo, pr) {
|
|
40447
40733
|
return crossRepoFilingRetryCommand(repo, Number(pr));
|
|
@@ -41244,6 +41530,7 @@ async function checkDocsIndexAtHead(opts, deps) {
|
|
|
41244
41530
|
|
|
41245
41531
|
// src/pr-create-claim-guard.ts
|
|
41246
41532
|
init_github_client();
|
|
41533
|
+
init_github_quota();
|
|
41247
41534
|
var CLAIM_GUARD_RATE_LIMIT_WAIT_CAP_MS = 3e4;
|
|
41248
41535
|
function withRateLimitRetry(client, seams = {}) {
|
|
41249
41536
|
const throttledPaths = /* @__PURE__ */ new Set();
|
|
@@ -44768,6 +45055,16 @@ var GIT_COMMIT_TIMEOUT_MS = 6e4;
|
|
|
44768
45055
|
function trainGitArgs(args) {
|
|
44769
45056
|
return ["-c", "gc.auto=0", "-c", "maintenance.auto=false", ...args];
|
|
44770
45057
|
}
|
|
45058
|
+
async function announceSummaryLineCountRefusal(path2) {
|
|
45059
|
+
let content;
|
|
45060
|
+
try {
|
|
45061
|
+
content = await (0, import_promises9.readFile)(path2, "utf8");
|
|
45062
|
+
} catch (e) {
|
|
45063
|
+
return `release: could not read --announce-summary-file ${path2}: ${e.message} \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing`;
|
|
45064
|
+
}
|
|
45065
|
+
const lines2 = summaryFileLines(content);
|
|
45066
|
+
return lines2.length < 3 || lines2.length > 6 ? `release: --announce-summary-file must contain 3-6 non-empty LLM-curated lines (found ${lines2.length}) \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing` : null;
|
|
45067
|
+
}
|
|
44771
45068
|
async function announceSummaryRefusal(path2, isHub = false) {
|
|
44772
45069
|
let lines2;
|
|
44773
45070
|
try {
|
|
@@ -45221,6 +45518,10 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
45221
45518
|
const retryDeploy = new Option("--retry-deploy", "re-dispatch the FAILED rc deploy of the promoted candidate in the active rcand ledger \u2014 same tag and SHA, never re-cut or re-pushed (#7053)");
|
|
45222
45519
|
trainCmd.addOption(commandName === "rcand" ? retryDeploy : retryDeploy.hideHelp());
|
|
45223
45520
|
trainCmd.option("--repo <owner/repo>", "dry-run plan for a target repo without relying on the current checkout; --apply still uses the current checkout").action(async (o) => {
|
|
45521
|
+
if (commandName === "release" && o.announceSummaryFile) {
|
|
45522
|
+
const refusal = await announceSummaryLineCountRefusal(o.announceSummaryFile);
|
|
45523
|
+
if (refusal) return fail(refusal);
|
|
45524
|
+
}
|
|
45224
45525
|
try {
|
|
45225
45526
|
await requireFreshTrainCli(commandName);
|
|
45226
45527
|
} catch (e) {
|
|
@@ -45693,12 +45994,18 @@ async function withEnv(name, value, fn) {
|
|
|
45693
45994
|
}
|
|
45694
45995
|
}
|
|
45695
45996
|
async function prepareRcCheckout(git3, repo) {
|
|
45696
|
-
|
|
45997
|
+
await prepareLaneCheckout(git3, repo, "rc", "release");
|
|
45998
|
+
}
|
|
45999
|
+
async function prepareDevelopmentCheckout(git3, repo) {
|
|
46000
|
+
await prepareLaneCheckout(git3, repo, "development", "rcand");
|
|
46001
|
+
}
|
|
46002
|
+
async function prepareLaneCheckout(git3, repo, branch, lane) {
|
|
46003
|
+
if ((await git3(["status", "--porcelain"])).trim()) throw new Error(`${repo}: checkout has uncommitted changes; refuse to switch it to ${branch} for ${lane}`);
|
|
45697
46004
|
await git3(["fetch", "origin", "--tags"]);
|
|
45698
|
-
await git3(["switch",
|
|
45699
|
-
await git3(["merge", "--ff-only",
|
|
45700
|
-
const [head, origin] = (await git3(["rev-parse", "HEAD",
|
|
45701
|
-
if (!head || head !== origin) throw new Error(`${repo}: local
|
|
46005
|
+
await git3(["switch", branch]);
|
|
46006
|
+
await git3(["merge", "--ff-only", `origin/${branch}`]);
|
|
46007
|
+
const [head, origin] = (await git3(["rev-parse", "HEAD", `origin/${branch}`])).trim().split(/\s+/);
|
|
46008
|
+
if (!head || head !== origin) throw new Error(`${repo}: local ${branch} ${head?.slice(0, 12)} is not origin/${branch} ${origin?.slice(0, 12)}; stop`);
|
|
45702
46009
|
}
|
|
45703
46010
|
function rcRunCauseFromLog(log) {
|
|
45704
46011
|
const causes = [...new Set([...log.matchAll(/##\[error\](?!Process completed with exit code)([^\r\n]+)/g)].map((m) => m[1].trim()).filter(Boolean))];
|
|
@@ -45775,6 +46082,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}) {
|
|
|
45775
46082
|
}
|
|
45776
46083
|
};
|
|
45777
46084
|
const runRcand = async (repo, previousRc, rcWasAbsent) => inTrain(repo, async (deps) => {
|
|
46085
|
+
if (repo !== SUITE_REPO) await prepareDevelopmentCheckout((args) => deps.run("git", args), repo);
|
|
45778
46086
|
await deps.run("git", ["fetch", "origin", "--tags"]);
|
|
45779
46087
|
let rcNow;
|
|
45780
46088
|
try {
|
|
@@ -45924,6 +46232,12 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}) {
|
|
|
45924
46232
|
}
|
|
45925
46233
|
},
|
|
45926
46234
|
tagSha,
|
|
46235
|
+
rcTagsAt: async (repo, sha) => {
|
|
46236
|
+
if (!/^[0-9a-f]{40}$/i.test(sha)) return [];
|
|
46237
|
+
const target = await checkout(repo);
|
|
46238
|
+
await exec("git", ["fetch", "--quiet", "origin", "--tags"], target, WATCH_TIMEOUT);
|
|
46239
|
+
return (await exec("git", ["tag", "--points-at", sha, "--list", "v*-rc.*"], target)).split("\n").map((t) => t.trim()).filter(Boolean);
|
|
46240
|
+
},
|
|
45927
46241
|
contains: async (repo, ancestor, descendant) => {
|
|
45928
46242
|
const target = await checkout(repo);
|
|
45929
46243
|
await exec("git", ["fetch", "origin", "--tags"], target);
|
|
@@ -45987,6 +46301,17 @@ function parseMemberSummaryFiles(entries) {
|
|
|
45987
46301
|
}
|
|
45988
46302
|
return memberSummaryFiles;
|
|
45989
46303
|
}
|
|
46304
|
+
function suiteRcDataProdDeps() {
|
|
46305
|
+
return {
|
|
46306
|
+
fetch: async (url, init) => {
|
|
46307
|
+
const res = await fetch(url, { ...init, signal: AbortSignal.timeout(15e3) });
|
|
46308
|
+
return { ok: res.ok, status: res.status, text: () => res.text() };
|
|
46309
|
+
},
|
|
46310
|
+
readToken: readOrgVercelApiToken,
|
|
46311
|
+
readVault: async (vaultKey) => fetchSecretValue(makeSecretsDeps(await loadConfig()), vaultKey, { repo: RC_DATA_VAULT_REPO }),
|
|
46312
|
+
sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms))
|
|
46313
|
+
};
|
|
46314
|
+
}
|
|
45990
46315
|
function registerSuiteCommands(program3) {
|
|
45991
46316
|
const suite = program3.command("suite").description("MM-Work suite status, candidate cut and release (#7262/#7263)");
|
|
45992
46317
|
const leadRoot = async () => {
|
|
@@ -46059,6 +46384,31 @@ function registerSuiteCommands(program3) {
|
|
|
46059
46384
|
return fail(`suite release: ${e.message}`);
|
|
46060
46385
|
}
|
|
46061
46386
|
});
|
|
46387
|
+
suite.command("rc-data").description("switch the rc Preview envs between the live and rc data sets as one bound switch (#7588)").option("--to <direction>", "switch source: live or rc").option("--apply", "write the switch (default is a dry-run plan)").option("--owner-go", "explicit approval: touches the live data key").option("--json", "structured receipt").action(async (o) => {
|
|
46388
|
+
try {
|
|
46389
|
+
if (o.to !== "live" && o.to !== "rc") throw new Error(`--to must be live or rc (got ${o.to ?? "nothing"}); nothing was written`);
|
|
46390
|
+
const result = await runRcDataSwitch(suiteRcDataProdDeps(), { to: o.to, apply: o.apply === true, ownerGo: o.ownerGo === true });
|
|
46391
|
+
if (o.json) {
|
|
46392
|
+
console.log(JSON.stringify(result, null, 2));
|
|
46393
|
+
return;
|
|
46394
|
+
}
|
|
46395
|
+
console.log([
|
|
46396
|
+
`suite rc-data ${result.dryRun ? "dry-run" : "receipt"} (--to ${result.direction}):`,
|
|
46397
|
+
...result.plan.map((step) => {
|
|
46398
|
+
if (step.action === "patch") return ` - patch ${step.key} rc entry ${step.entryId} <- vault ${step.source}`;
|
|
46399
|
+
if (step.action === "create") return ` - create ${step.key} rc preview entry <- vault ${step.source}`;
|
|
46400
|
+
if (step.action === "delete") return ` - delete ${step.key} rc entry ${step.entryId} (generic Preview applies again)`;
|
|
46401
|
+
return ` - keep ${step.key} (no rc entry; generic Preview already applies)`;
|
|
46402
|
+
}),
|
|
46403
|
+
...result.dryRun ? [" rerun with --apply --owner-go to write, then redeploy rc.mutmut.work"] : [
|
|
46404
|
+
` - changed: ${result.changed.join(", ") || "none"}`,
|
|
46405
|
+
` - redeployed ${result.deploymentId}; alias ${result.alias} verified`
|
|
46406
|
+
]
|
|
46407
|
+
].join("\n"));
|
|
46408
|
+
} catch (e) {
|
|
46409
|
+
return fail(`suite rc-data: ${e.message}`);
|
|
46410
|
+
}
|
|
46411
|
+
});
|
|
46062
46412
|
suite.command("status").description("compare served versions with development, rc and main branch heads across MM-Work modules").option("--suite <name>", "suite name (mm-work)", "mm-work").option("--json", "structured output").action(async (o) => {
|
|
46063
46413
|
if (o.suite !== "mm-work") return fail("suite status: only mm-work is supported");
|
|
46064
46414
|
const github = defaultGitHubClient();
|
|
@@ -51534,9 +51884,13 @@ program2.addHelpText(
|
|
|
51534
51884
|
\`mmi-cli commands\` shows the agentic map; \`mmi-cli <house> --help\` lists one house.
|
|
51535
51885
|
`
|
|
51536
51886
|
);
|
|
51887
|
+
var appActorCommand = "poll";
|
|
51537
51888
|
function appActorDeps() {
|
|
51538
51889
|
return {
|
|
51539
|
-
fetchSecret: async (key) => fetchSecretValue(makeSecretsDeps(await loadConfig()), key, {
|
|
51890
|
+
fetchSecret: async (key) => fetchSecretValue(makeSecretsDeps(await loadConfig()), key, {
|
|
51891
|
+
repo: APP_VAULT_REPO,
|
|
51892
|
+
attribution: { command: appActorCommand, via: formatSessionIdentity(describeSessionIdentity()) }
|
|
51893
|
+
}),
|
|
51540
51894
|
fetch: (url, init) => fetch(url, init)
|
|
51541
51895
|
};
|
|
51542
51896
|
}
|
|
@@ -51544,6 +51898,7 @@ setPollTokenProvider(() => pollTokenOrUndefined(appActorDeps()));
|
|
|
51544
51898
|
var registerPreAction = Reflect.get(program2, ["h", "ook"].join(""));
|
|
51545
51899
|
registerPreAction.call(program2, "preAction", async (_thisCommand, actionCommand) => {
|
|
51546
51900
|
const commandPath3 = commandPathOf(actionCommand);
|
|
51901
|
+
appActorCommand = commandPath3;
|
|
51547
51902
|
emitCliCallTelemetry(commandPath3);
|
|
51548
51903
|
try {
|
|
51549
51904
|
await activateAppActor(commandPath3, process.env, () => mintInstallationToken(appActorDeps()));
|
package/package.json
CHANGED