@mutmutco/cli 4.0.18 → 4.1.0
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 +2654 -450
- package/dist/repo-index-v4.cjs +392 -59
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -5680,8 +5680,8 @@ var program = new Command();
|
|
|
5680
5680
|
|
|
5681
5681
|
// src/index.ts
|
|
5682
5682
|
var import_promises8 = require("node:fs/promises");
|
|
5683
|
-
var
|
|
5684
|
-
var
|
|
5683
|
+
var import_node_fs43 = require("node:fs");
|
|
5684
|
+
var import_node_child_process20 = require("node:child_process");
|
|
5685
5685
|
init_cli_shared();
|
|
5686
5686
|
|
|
5687
5687
|
// src/issue-surface.ts
|
|
@@ -5696,6 +5696,10 @@ function surfaceLabel(value) {
|
|
|
5696
5696
|
const v = value.trim();
|
|
5697
5697
|
return v.toLowerCase().startsWith(SURFACE_PREFIX) ? v : `${SURFACE_PREFIX}${v}`;
|
|
5698
5698
|
}
|
|
5699
|
+
function surfaceLabelsFromParsed(parsed) {
|
|
5700
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
5701
|
+
return parsed.map((entry) => typeof entry === "string" ? entry : entry.name).filter((n) => typeof n === "string" && n.toLowerCase().startsWith(SURFACE_PREFIX));
|
|
5702
|
+
}
|
|
5699
5703
|
async function readRepoSurfaceLabels(repo, deps = {}) {
|
|
5700
5704
|
const run = deps.run ?? execFileP2;
|
|
5701
5705
|
try {
|
|
@@ -5704,9 +5708,17 @@ async function readRepoSurfaceLabels(repo, deps = {}) {
|
|
|
5704
5708
|
["label", "list", "--repo", repo, "--limit", "1000", "--json", "name"],
|
|
5705
5709
|
{ timeout: SURFACE_READ_TIMEOUT_MS }
|
|
5706
5710
|
);
|
|
5707
|
-
const
|
|
5708
|
-
if (
|
|
5709
|
-
|
|
5711
|
+
const labels = surfaceLabelsFromParsed(JSON.parse(stdout));
|
|
5712
|
+
if (labels !== void 0) return labels;
|
|
5713
|
+
} catch {
|
|
5714
|
+
}
|
|
5715
|
+
try {
|
|
5716
|
+
const { stdout } = await run(
|
|
5717
|
+
"gh",
|
|
5718
|
+
["api", "--paginate", `repos/${repo}/labels`],
|
|
5719
|
+
{ timeout: SURFACE_READ_TIMEOUT_MS }
|
|
5720
|
+
);
|
|
5721
|
+
return surfaceLabelsFromParsed(JSON.parse(stdout));
|
|
5710
5722
|
} catch {
|
|
5711
5723
|
return void 0;
|
|
5712
5724
|
}
|
|
@@ -5740,7 +5752,7 @@ async function checkSurfaceRequirement(input, deps = {}) {
|
|
|
5740
5752
|
warn: `warning: could not read ${input.repo}'s labels, so the 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>\``
|
|
5741
5753
|
};
|
|
5742
5754
|
}
|
|
5743
|
-
if (known.length === 0) return { enforcing: false };
|
|
5755
|
+
if (known.length === 0) return { enforcing: false, taxonomyAbsent: true };
|
|
5744
5756
|
if (labelsCarrySurface(input.labels)) return { enforcing: true };
|
|
5745
5757
|
const command = input.command ?? "issue create";
|
|
5746
5758
|
const where = input.rowLabel ? `${input.rowLabel}: ` : "";
|
|
@@ -5774,7 +5786,11 @@ function conflictingSurfaceInputs(surfaceFlag, labels) {
|
|
|
5774
5786
|
}
|
|
5775
5787
|
async function surfaceLabelApplies(repo, deps = {}) {
|
|
5776
5788
|
const known = await (deps.read ?? readRepoSurfaceLabels)(repo, { run: deps.run });
|
|
5777
|
-
|
|
5789
|
+
if (known === void 0) return true;
|
|
5790
|
+
return known.length > 0;
|
|
5791
|
+
}
|
|
5792
|
+
function shouldWithdrawSurfaceFlag(result) {
|
|
5793
|
+
return result.taxonomyAbsent === true;
|
|
5778
5794
|
}
|
|
5779
5795
|
|
|
5780
5796
|
// src/index.ts
|
|
@@ -6677,7 +6693,8 @@ var COMMAND_LADDER_WRITES = [
|
|
|
6677
6693
|
{ gh: "gh pr merge", replacement: "mmi-cli devops pr merge (or mmi-cli devops pr land)" }
|
|
6678
6694
|
];
|
|
6679
6695
|
var COMMAND_LADDER_READS = [
|
|
6680
|
-
|
|
6696
|
+
// #5483: `show` aliases `view` for board-verb callers; advertise the canonical read plus the alias.
|
|
6697
|
+
{ gh: "gh issue view <N>", replacement: "mmi-cli oracle issue view <N> (alias: show)" },
|
|
6681
6698
|
{ gh: "gh pr view <N>", replacement: "mmi-cli devops pr view <N>" },
|
|
6682
6699
|
{ gh: "gh pr checks <N>", replacement: "mmi-cli devops pr checks-wait <N>" }
|
|
6683
6700
|
];
|
|
@@ -6705,7 +6722,7 @@ function commandLadderHint() {
|
|
|
6705
6722
|
}
|
|
6706
6723
|
|
|
6707
6724
|
// src/index.ts
|
|
6708
|
-
var
|
|
6725
|
+
var import_node_path40 = require("node:path");
|
|
6709
6726
|
|
|
6710
6727
|
// src/merge-ci-policy.ts
|
|
6711
6728
|
function resolveMergeCiPolicy(input) {
|
|
@@ -7609,7 +7626,7 @@ function extractForwardRefs(markdown) {
|
|
|
7609
7626
|
});
|
|
7610
7627
|
return refs;
|
|
7611
7628
|
}
|
|
7612
|
-
function checkPins(root,
|
|
7629
|
+
function checkPins(root, readFile7, docs2) {
|
|
7613
7630
|
const findings = [];
|
|
7614
7631
|
for (const [doc, markdown] of Object.entries(docs2)) {
|
|
7615
7632
|
for (const pin of extractPins(markdown)) {
|
|
@@ -7617,7 +7634,7 @@ function checkPins(root, readFile6, docs2) {
|
|
|
7617
7634
|
findings.push({ kind: "malformed-pin", doc, line: pin.line, detail: pin.text });
|
|
7618
7635
|
continue;
|
|
7619
7636
|
}
|
|
7620
|
-
const source =
|
|
7637
|
+
const source = readFile7((0, import_node_path11.join)(root, pin.file));
|
|
7621
7638
|
if (source == null) {
|
|
7622
7639
|
findings.push({ kind: "missing-test", doc, line: pin.line, detail: pin.file });
|
|
7623
7640
|
continue;
|
|
@@ -7831,7 +7848,7 @@ function defaultTrackedFirstSegments(root, firstSegments, exec = import_node_chi
|
|
|
7831
7848
|
}
|
|
7832
7849
|
}
|
|
7833
7850
|
function runDocRefs(root, deps = {}) {
|
|
7834
|
-
const
|
|
7851
|
+
const readFile7 = deps.readFile ?? readFileOrNull;
|
|
7835
7852
|
const exists = deps.exists ?? import_node_fs13.existsSync;
|
|
7836
7853
|
const listDocs = deps.listDocs ?? defaultListDocs;
|
|
7837
7854
|
const isIgnored = deps.isIgnored ?? ((paths) => defaultIsIgnored(root, paths));
|
|
@@ -7840,11 +7857,11 @@ function runDocRefs(root, deps = {}) {
|
|
|
7840
7857
|
const walked = listDocs(root);
|
|
7841
7858
|
const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
|
|
7842
7859
|
const docs2 = Object.fromEntries(
|
|
7843
|
-
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel,
|
|
7860
|
+
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile7((0, import_node_path11.join)(root, rel))]).filter(([, body]) => body != null)
|
|
7844
7861
|
);
|
|
7845
7862
|
const refResult = checkRefs(root, { exists, isIgnored, trackedFirstSegments }, docs2);
|
|
7846
7863
|
const findings = [
|
|
7847
|
-
...checkPins(root,
|
|
7864
|
+
...checkPins(root, readFile7, docs2).findings,
|
|
7848
7865
|
...refResult.findings
|
|
7849
7866
|
];
|
|
7850
7867
|
if (commandPaths == null) {
|
|
@@ -7867,11 +7884,11 @@ function runDocRefs(root, deps = {}) {
|
|
|
7867
7884
|
|
|
7868
7885
|
// src/docs-index-command.ts
|
|
7869
7886
|
var DOCS_INDEX_PATH = "docs/index.md";
|
|
7887
|
+
var GENERATED_HEADER = "<!-- Generated by `mmi-cli oracle docs index --write`. Do not edit by hand. -->";
|
|
7870
7888
|
function isRoutableDocsPath(relPath) {
|
|
7871
7889
|
const normalized = relPath.replace(/\\/g, "/");
|
|
7872
7890
|
return normalized !== "index.md" && normalized.split("/")[0]?.toLowerCase() !== "archive";
|
|
7873
7891
|
}
|
|
7874
|
-
var GENERATED_HEADER = "<!-- Generated by `mmi-cli oracle docs index --write`. Do not edit by hand. -->";
|
|
7875
7892
|
function plainInline(text) {
|
|
7876
7893
|
return text.replace(/\r?\n/g, " ").replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").replace(/`/g, "").replace(/\*+/g, "").replace(/~~/g, "").replace(/__([^_]+)__/g, "$1").replace(/(^|[\s(])_([^_]+)_($|[\s).,!?:;])/g, "$1$2$3").replace(/^>\s*/, "").replace(/\\([\\`*_\[\]{}()#+.!|>-])/g, "$1").replace(/\s+/g, " ").trim();
|
|
7877
7894
|
}
|
|
@@ -8580,22 +8597,22 @@ function labelsToPrune(orgLabelNames) {
|
|
|
8580
8597
|
const org = new Set(orgLabelNames);
|
|
8581
8598
|
return GITHUB_DEFAULT_LABELS.filter((name) => !org.has(name));
|
|
8582
8599
|
}
|
|
8583
|
-
function resolveSeedContent(seed, vars,
|
|
8584
|
-
if (seed.source === "self") return
|
|
8600
|
+
function resolveSeedContent(seed, vars, readFile7) {
|
|
8601
|
+
if (seed.source === "self") return readFile7(seed.target);
|
|
8585
8602
|
if (seed.source.startsWith("seed:")) {
|
|
8586
|
-
const tmpl =
|
|
8603
|
+
const tmpl = readFile7(`skills/bootstrap/seeds/${seed.source.slice("seed:".length)}`);
|
|
8587
8604
|
return tmpl == null ? null : renderSeed(tmpl, vars);
|
|
8588
8605
|
}
|
|
8589
8606
|
return null;
|
|
8590
8607
|
}
|
|
8591
|
-
function resolveSeedWriteContent(seed, vars,
|
|
8608
|
+
function resolveSeedWriteContent(seed, vars, readFile7, remoteContent) {
|
|
8592
8609
|
if (!seed.managedBlock) {
|
|
8593
|
-
return { ok: true, content: resolveSeedContent(seed, vars,
|
|
8610
|
+
return { ok: true, content: resolveSeedContent(seed, vars, readFile7), managed: seed.source === "managed-block" };
|
|
8594
8611
|
}
|
|
8595
|
-
const base = remoteContent ?? resolveSeedContent(seed, vars,
|
|
8612
|
+
const base = remoteContent ?? resolveSeedContent(seed, vars, readFile7);
|
|
8596
8613
|
if (base == null) return { ok: true, content: null, managed: true };
|
|
8597
8614
|
const blockSeed = { ...seed, source: seed.managedBlock.source, managedBlock: void 0 };
|
|
8598
|
-
const desired = resolveSeedContent(blockSeed, vars,
|
|
8615
|
+
const desired = resolveSeedContent(blockSeed, vars, readFile7);
|
|
8599
8616
|
if (desired == null) return { ok: true, content: null, managed: true };
|
|
8600
8617
|
const result = upsertManagedSeedBlock(base, desired, seed.managedBlock.begin, seed.managedBlock.end);
|
|
8601
8618
|
return result.ok ? { ok: true, content: result.content, managed: true } : { ok: false, reason: result.reason, managed: true };
|
|
@@ -9318,6 +9335,7 @@ init_github_client();
|
|
|
9318
9335
|
|
|
9319
9336
|
// ../infra/registry-endpoints.mjs
|
|
9320
9337
|
var PROJECTS_LIST_PATH = "/projects/list";
|
|
9338
|
+
var DEPLOY_PORT_COLLISIONS_PATH = "/projects/deploy-port-collisions";
|
|
9321
9339
|
var ORG_CONFIG_PATH = "/org/config";
|
|
9322
9340
|
var PROJECTS_ENVELOPE_KEY = "projects";
|
|
9323
9341
|
var REGISTRY_FETCH_TIMEOUT_MS = 8e3;
|
|
@@ -10318,6 +10336,188 @@ function isPriorityFieldConfigured(cfg) {
|
|
|
10318
10336
|
|
|
10319
10337
|
// src/gh-create.ts
|
|
10320
10338
|
init_cli_shared();
|
|
10339
|
+
init_github_client();
|
|
10340
|
+
|
|
10341
|
+
// src/github-quota.ts
|
|
10342
|
+
init_github_client();
|
|
10343
|
+
var RATE_LIMIT_WAIT_CAP_MS = 12e4;
|
|
10344
|
+
var RateLimitedError = class extends Error {
|
|
10345
|
+
receipt;
|
|
10346
|
+
constructor(receipt) {
|
|
10347
|
+
super(receipt.message);
|
|
10348
|
+
this.name = "RateLimitedError";
|
|
10349
|
+
this.receipt = receipt;
|
|
10350
|
+
}
|
|
10351
|
+
};
|
|
10352
|
+
function isRateLimitedError(e) {
|
|
10353
|
+
return e instanceof RateLimitedError;
|
|
10354
|
+
}
|
|
10355
|
+
function isRateLimitText(text) {
|
|
10356
|
+
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);
|
|
10357
|
+
}
|
|
10358
|
+
function isRateLimitRefusal(e) {
|
|
10359
|
+
if (e instanceof GitHubApiError && e.rateLimited) return true;
|
|
10360
|
+
if (e instanceof GitHubApiError && e.status === 403) {
|
|
10361
|
+
return /rate limit|abuse detection|secondary rate/i.test(e.message);
|
|
10362
|
+
}
|
|
10363
|
+
if (e instanceof RateLimitedError) return true;
|
|
10364
|
+
const text = e instanceof Error ? `${e.message}
|
|
10365
|
+
${e.stderr ?? ""}` : String(e ?? "");
|
|
10366
|
+
return isRateLimitText(text);
|
|
10367
|
+
}
|
|
10368
|
+
function resetEpochFromError(e) {
|
|
10369
|
+
if (e instanceof GitHubApiError && typeof e.rateLimitReset === "number") return e.rateLimitReset;
|
|
10370
|
+
if (e instanceof RateLimitedError && typeof e.receipt.resetEpochSeconds === "number") {
|
|
10371
|
+
return e.receipt.resetEpochSeconds;
|
|
10372
|
+
}
|
|
10373
|
+
return void 0;
|
|
10374
|
+
}
|
|
10375
|
+
function rateLimitWaitMs(resetEpochSeconds, nowMs, capMs = RATE_LIMIT_WAIT_CAP_MS) {
|
|
10376
|
+
if (typeof resetEpochSeconds !== "number" || !Number.isFinite(resetEpochSeconds)) return void 0;
|
|
10377
|
+
const wait = resetEpochSeconds * 1e3 - nowMs;
|
|
10378
|
+
if (wait <= 0) return 0;
|
|
10379
|
+
if (wait > capMs) return void 0;
|
|
10380
|
+
return wait;
|
|
10381
|
+
}
|
|
10382
|
+
function rateLimitedReceipt(opts) {
|
|
10383
|
+
const resetEpochSeconds = opts.resetEpochSeconds;
|
|
10384
|
+
const resetAt = typeof resetEpochSeconds === "number" ? new Date(resetEpochSeconds * 1e3).toISOString() : void 0;
|
|
10385
|
+
const note = rateLimitResetNote(resetEpochSeconds, opts.nowMs ?? Date.now());
|
|
10386
|
+
const context = opts.context ?? "GitHub API";
|
|
10387
|
+
const remaining = opts.remaining && (opts.remaining.graphql !== void 0 || opts.remaining.core !== void 0) ? opts.remaining : void 0;
|
|
10388
|
+
return {
|
|
10389
|
+
status: "rate_limited",
|
|
10390
|
+
...resetEpochSeconds !== void 0 ? { resetEpochSeconds } : {},
|
|
10391
|
+
...resetAt ? { resetAt } : {},
|
|
10392
|
+
message: opts.message ?? `${context}: ${note}`,
|
|
10393
|
+
retryCommand: opts.retryCommand,
|
|
10394
|
+
...remaining ? { remaining } : {}
|
|
10395
|
+
};
|
|
10396
|
+
}
|
|
10397
|
+
async function runWithRateLimitBackoff(operation, opts) {
|
|
10398
|
+
const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
|
|
10399
|
+
const now = opts.now ?? Date.now;
|
|
10400
|
+
const log = opts.log ?? ((message) => console.warn(message));
|
|
10401
|
+
const capMs = opts.capMs ?? RATE_LIMIT_WAIT_CAP_MS;
|
|
10402
|
+
const tryOnce = async () => {
|
|
10403
|
+
try {
|
|
10404
|
+
return await operation();
|
|
10405
|
+
} catch (e) {
|
|
10406
|
+
if (!isRateLimitRefusal(e) || !opts.fallback) throw e;
|
|
10407
|
+
try {
|
|
10408
|
+
return await opts.fallback();
|
|
10409
|
+
} catch (e2) {
|
|
10410
|
+
if (isRateLimitRefusal(e2)) throw e2;
|
|
10411
|
+
throw e2;
|
|
10412
|
+
}
|
|
10413
|
+
}
|
|
10414
|
+
};
|
|
10415
|
+
try {
|
|
10416
|
+
return await tryOnce();
|
|
10417
|
+
} catch (e) {
|
|
10418
|
+
if (!isRateLimitRefusal(e)) throw e;
|
|
10419
|
+
const reset = resetEpochFromError(e);
|
|
10420
|
+
const waitMs = rateLimitWaitMs(reset, now(), capMs);
|
|
10421
|
+
if (waitMs === void 0) {
|
|
10422
|
+
throw new RateLimitedError(rateLimitedReceipt({
|
|
10423
|
+
resetEpochSeconds: reset,
|
|
10424
|
+
retryCommand: opts.retryCommand,
|
|
10425
|
+
context: opts.context,
|
|
10426
|
+
nowMs: now()
|
|
10427
|
+
}));
|
|
10428
|
+
}
|
|
10429
|
+
if (waitMs > 0) {
|
|
10430
|
+
log(`${opts.context}: rate-limited \u2014 waiting ${Math.round(waitMs / 1e3)}s for pool reset`);
|
|
10431
|
+
await sleep2(waitMs);
|
|
10432
|
+
}
|
|
10433
|
+
try {
|
|
10434
|
+
return await tryOnce();
|
|
10435
|
+
} catch (e2) {
|
|
10436
|
+
if (!isRateLimitRefusal(e2)) throw e2;
|
|
10437
|
+
throw new RateLimitedError(rateLimitedReceipt({
|
|
10438
|
+
resetEpochSeconds: resetEpochFromError(e2) ?? reset,
|
|
10439
|
+
retryCommand: opts.retryCommand,
|
|
10440
|
+
context: opts.context,
|
|
10441
|
+
nowMs: now()
|
|
10442
|
+
}));
|
|
10443
|
+
}
|
|
10444
|
+
}
|
|
10445
|
+
}
|
|
10446
|
+
|
|
10447
|
+
// src/repo-resolve.ts
|
|
10448
|
+
init_cli_shared();
|
|
10449
|
+
|
|
10450
|
+
// src/registry-degrade.ts
|
|
10451
|
+
function registryDegradeError(error) {
|
|
10452
|
+
return new Error(
|
|
10453
|
+
`Hub registry read failed (${error}) \u2014 board coords could not be discovered. This is NOT necessarily the registry: a local DNS/network fault reaches this path as a timeout too. Check in order \u2014 (1) resolve the Hub API host (fails in seconds and rules out the local-network class), (2) if it resolves, retry: a Lambda cold start clears shortly, (3) if it still fails, check auth (a rejected credential is not a timeout).`
|
|
10454
|
+
);
|
|
10455
|
+
}
|
|
10456
|
+
|
|
10457
|
+
// src/config-discovery.ts
|
|
10458
|
+
function stripMutableBoardConfig(cfg) {
|
|
10459
|
+
const {
|
|
10460
|
+
projectOwner: _projectOwner,
|
|
10461
|
+
projectNumber: _projectNumber,
|
|
10462
|
+
projectId: _projectId,
|
|
10463
|
+
statusFieldId: _statusFieldId,
|
|
10464
|
+
statusOptions: _statusOptions,
|
|
10465
|
+
priorityFieldId: _priorityFieldId,
|
|
10466
|
+
priorityOptions: _priorityOptions,
|
|
10467
|
+
...rest
|
|
10468
|
+
} = cfg;
|
|
10469
|
+
return rest;
|
|
10470
|
+
}
|
|
10471
|
+
function boardConfigFromProject(meta, floor = {}) {
|
|
10472
|
+
return {
|
|
10473
|
+
...stripMutableBoardConfig(floor),
|
|
10474
|
+
projectOwner: meta.projectOwner,
|
|
10475
|
+
projectNumber: meta.projectNumber,
|
|
10476
|
+
projectId: meta.projectId,
|
|
10477
|
+
statusFieldId: meta.statusFieldId,
|
|
10478
|
+
statusOptions: meta.statusOptions,
|
|
10479
|
+
priorityFieldId: meta.priorityFieldId,
|
|
10480
|
+
priorityOptions: meta.priorityOptions
|
|
10481
|
+
};
|
|
10482
|
+
}
|
|
10483
|
+
|
|
10484
|
+
// src/repo-resolve.ts
|
|
10485
|
+
function slugOf(repoOrSlug) {
|
|
10486
|
+
return (repoOrSlug.includes("/") ? repoOrSlug.split("/").pop() : repoOrSlug).toLowerCase();
|
|
10487
|
+
}
|
|
10488
|
+
function repoFromRemoteUrl(remoteUrl) {
|
|
10489
|
+
const m = remoteUrl.trim().match(/^(?:[a-z][a-z0-9+.-]*:\/\/)?(?:[^@\s/]+@)?github\.com[:/]([^/\s:]+)\/([^/\s]+?)(?:\.git)?\/?$/i);
|
|
10490
|
+
return m ? `${m[1]}/${m[2]}` : void 0;
|
|
10491
|
+
}
|
|
10492
|
+
function repoFromSelector(selector) {
|
|
10493
|
+
const trimmed = selector.trim();
|
|
10494
|
+
const url = trimmed.match(/^https:\/\/github\.com\/([^/]+\/[^/]+)\/(?:issues|pull)\/\d+$/i);
|
|
10495
|
+
if (url) return url[1];
|
|
10496
|
+
const qualified = trimmed.match(/^([^/\s#]+\/[^/\s#]+)#\d+$/);
|
|
10497
|
+
return qualified?.[1];
|
|
10498
|
+
}
|
|
10499
|
+
async function resolveRepo(repo) {
|
|
10500
|
+
if (repo) return repo;
|
|
10501
|
+
const fromOrigin = repoFromRemoteUrl(await gitOut(["remote", "get-url", "origin"])) ?? repoFromRemoteUrl(originUrlFromGitConfig() ?? "");
|
|
10502
|
+
if (fromOrigin) return fromOrigin;
|
|
10503
|
+
try {
|
|
10504
|
+
const { stdout } = await execFileP2("gh", ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"], { timeout: 5e3 });
|
|
10505
|
+
return stdout.trim() || void 0;
|
|
10506
|
+
} catch {
|
|
10507
|
+
return void 0;
|
|
10508
|
+
}
|
|
10509
|
+
}
|
|
10510
|
+
async function loadConfigForBoardSelector(selector, repoOption) {
|
|
10511
|
+
const targetRepo3 = repoFromSelector(selector) ?? repoOption;
|
|
10512
|
+
const floor = await loadConfig();
|
|
10513
|
+
if (!floor.sagaApiUrl) return stripMutableBoardConfig(floor);
|
|
10514
|
+
const slug = targetRepo3 ? slugOf(targetRepo3) : await repoSlug();
|
|
10515
|
+
const read = await fetchProjectBySlugChecked(slug, registryClientDeps(floor));
|
|
10516
|
+
if (!read.ok) throw registryDegradeError(read.error);
|
|
10517
|
+
return read.project ? boardConfigFromProject(read.project, floor) : stripMutableBoardConfig(floor);
|
|
10518
|
+
}
|
|
10519
|
+
|
|
10520
|
+
// src/gh-create.ts
|
|
10321
10521
|
var ISSUE_TYPES = ["bug", "feature", "task"];
|
|
10322
10522
|
var GH_MUTATION_TIMEOUT_MS = 12e4;
|
|
10323
10523
|
function timeoutKillNote(err, timeoutMs) {
|
|
@@ -10440,6 +10640,101 @@ function parseExistingPr(stderr) {
|
|
|
10440
10640
|
}
|
|
10441
10641
|
var GH_CREATE_UPSTREAM_RETRIES = 3;
|
|
10442
10642
|
var GH_CREATE_RETRY_BACKOFF_MS = [2e3, 5e3];
|
|
10643
|
+
function isGhCreateRateLimited(result) {
|
|
10644
|
+
return result.status === "rate_limited";
|
|
10645
|
+
}
|
|
10646
|
+
var isGhRateLimitError = isRateLimitText;
|
|
10647
|
+
function flagValue(args, flag) {
|
|
10648
|
+
const i = args.indexOf(flag);
|
|
10649
|
+
if (i === -1 || i + 1 >= args.length) return void 0;
|
|
10650
|
+
return args[i + 1];
|
|
10651
|
+
}
|
|
10652
|
+
function buildPrCreateRetryCommand(args) {
|
|
10653
|
+
const parts = ["mmi-cli", "devops", "pr", "create"];
|
|
10654
|
+
for (const flag of ["--repo", "--base", "--head", "--title"]) {
|
|
10655
|
+
const v = flagValue(args, flag);
|
|
10656
|
+
if (v !== void 0) parts.push(flag, shellQuote(v));
|
|
10657
|
+
}
|
|
10658
|
+
const bodyFile = flagValue(args, "--body-file");
|
|
10659
|
+
if (bodyFile !== void 0) parts.push("--body-file", shellQuote(bodyFile));
|
|
10660
|
+
else {
|
|
10661
|
+
const body = flagValue(args, "--body");
|
|
10662
|
+
if (body !== void 0) parts.push("--body", shellQuote(body));
|
|
10663
|
+
}
|
|
10664
|
+
if (args.includes("--draft")) parts.push("--draft");
|
|
10665
|
+
return parts.join(" ");
|
|
10666
|
+
}
|
|
10667
|
+
function collectLabelFlags(args) {
|
|
10668
|
+
const labels = [];
|
|
10669
|
+
for (let i = 0; i < args.length; i++) {
|
|
10670
|
+
if (args[i] === "--label" && i + 1 < args.length) {
|
|
10671
|
+
labels.push(args[i + 1]);
|
|
10672
|
+
i++;
|
|
10673
|
+
}
|
|
10674
|
+
}
|
|
10675
|
+
return labels;
|
|
10676
|
+
}
|
|
10677
|
+
function buildIssueCreateRetryCommand(args) {
|
|
10678
|
+
const parts = ["mmi-cli", "oracle", "issue", "create"];
|
|
10679
|
+
const repo = flagValue(args, "--repo");
|
|
10680
|
+
if (repo !== void 0) parts.push("--repo", shellQuote(repo));
|
|
10681
|
+
const labels = collectLabelFlags(args);
|
|
10682
|
+
const type = labels.find((l) => ISSUE_TYPES.includes(l));
|
|
10683
|
+
if (type) parts.push("--type", type);
|
|
10684
|
+
const title = flagValue(args, "--title");
|
|
10685
|
+
if (title !== void 0) parts.push("--title", shellQuote(title));
|
|
10686
|
+
const bodyFile = flagValue(args, "--body-file");
|
|
10687
|
+
if (bodyFile !== void 0) parts.push("--body-file", shellQuote(bodyFile));
|
|
10688
|
+
else {
|
|
10689
|
+
const body = flagValue(args, "--body");
|
|
10690
|
+
if (body !== void 0) parts.push("--body", shellQuote(body));
|
|
10691
|
+
}
|
|
10692
|
+
for (const label of labels) {
|
|
10693
|
+
if (label === type) continue;
|
|
10694
|
+
parts.push("--label", shellQuote(label));
|
|
10695
|
+
}
|
|
10696
|
+
return parts.join(" ");
|
|
10697
|
+
}
|
|
10698
|
+
function shellQuote(value) {
|
|
10699
|
+
if (/^[A-Za-z0-9_./:@+=,-]+$/.test(value)) return value;
|
|
10700
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
10701
|
+
}
|
|
10702
|
+
function parseRateLimitPools(stdout) {
|
|
10703
|
+
try {
|
|
10704
|
+
const resources = JSON.parse(stdout).resources;
|
|
10705
|
+
const out = {};
|
|
10706
|
+
if (typeof resources?.graphql?.remaining === "number" && typeof resources.graphql.reset === "number") {
|
|
10707
|
+
out.graphql = { remaining: resources.graphql.remaining, reset: resources.graphql.reset };
|
|
10708
|
+
}
|
|
10709
|
+
if (typeof resources?.core?.remaining === "number" && typeof resources.core.reset === "number") {
|
|
10710
|
+
out.core = { remaining: resources.core.remaining, reset: resources.core.reset };
|
|
10711
|
+
}
|
|
10712
|
+
return out;
|
|
10713
|
+
} catch {
|
|
10714
|
+
return {};
|
|
10715
|
+
}
|
|
10716
|
+
}
|
|
10717
|
+
function rateLimitedResult(opts) {
|
|
10718
|
+
const remaining = opts.remaining ?? remainingFromPools(opts.pools);
|
|
10719
|
+
return rateLimitedReceipt({
|
|
10720
|
+
...opts,
|
|
10721
|
+
context: opts.context ?? "pr create",
|
|
10722
|
+
message: opts.message,
|
|
10723
|
+
remaining
|
|
10724
|
+
});
|
|
10725
|
+
}
|
|
10726
|
+
function remainingFromPools(pools) {
|
|
10727
|
+
if (!pools) return void 0;
|
|
10728
|
+
const remaining = {};
|
|
10729
|
+
if (typeof pools.graphql?.remaining === "number") remaining.graphql = pools.graphql.remaining;
|
|
10730
|
+
if (typeof pools.core?.remaining === "number") remaining.core = pools.core.remaining;
|
|
10731
|
+
return remaining.graphql !== void 0 || remaining.core !== void 0 ? remaining : void 0;
|
|
10732
|
+
}
|
|
10733
|
+
function execErrorText(err) {
|
|
10734
|
+
if (typeof err !== "object" || err === null) return String(err ?? "");
|
|
10735
|
+
const e = err;
|
|
10736
|
+
return [e.stderr, e.stdout, e.message].filter((s) => typeof s === "string" && s.trim()).join("\n");
|
|
10737
|
+
}
|
|
10443
10738
|
function httpStatusCodes(stderr) {
|
|
10444
10739
|
const out = [];
|
|
10445
10740
|
for (const m of stderr.matchAll(/\bHTTP\/?\d*(?:\.\d)?\s+(\d{3})\b/g)) out.push(Number(m[1]));
|
|
@@ -10460,19 +10755,362 @@ function upstreamFaultMessage(verb, stderr) {
|
|
|
10460
10755
|
const advice = mayRetryCreate(verb) ? "Retrying is the right action" : "The write may have completed server-side before the error \u2014 VERIFY before retrying, or a retry will duplicate it";
|
|
10461
10756
|
return `gh ${verb} create failed: GitHub's API is failing (server-side 5xx) \u2014 this is NOT a problem with your diff or arguments. GitHub request ID: ${requestId ?? "unavailable"}. ${advice}; note githubstatus.com may still read green while this is happening.`;
|
|
10462
10757
|
}
|
|
10758
|
+
async function resolveRepoForCreate(args, exec) {
|
|
10759
|
+
const fromFlag = flagValue(args, "--repo");
|
|
10760
|
+
if (fromFlag) return fromFlag;
|
|
10761
|
+
try {
|
|
10762
|
+
const { stdout } = await exec("git", ["remote", "get-url", "origin"], { timeout: 1e4 });
|
|
10763
|
+
const fromOrigin = repoFromRemoteUrl(stdout.trim());
|
|
10764
|
+
if (fromOrigin) return fromOrigin;
|
|
10765
|
+
} catch {
|
|
10766
|
+
}
|
|
10767
|
+
return repoFromRemoteUrl(originUrlFromGitConfig() ?? "") || void 0;
|
|
10768
|
+
}
|
|
10769
|
+
async function resolveHeadForCreate(args, exec) {
|
|
10770
|
+
const fromFlag = flagValue(args, "--head");
|
|
10771
|
+
if (fromFlag) return fromFlag;
|
|
10772
|
+
try {
|
|
10773
|
+
const { stdout } = await exec("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: 1e4 });
|
|
10774
|
+
const head = stdout.trim();
|
|
10775
|
+
return head && head !== "HEAD" ? head : void 0;
|
|
10776
|
+
} catch {
|
|
10777
|
+
return void 0;
|
|
10778
|
+
}
|
|
10779
|
+
}
|
|
10780
|
+
async function resolveBodyForCreate(args, read) {
|
|
10781
|
+
const bodyFile = flagValue(args, "--body-file");
|
|
10782
|
+
if (bodyFile !== void 0) return read(bodyFile, "utf8");
|
|
10783
|
+
return flagValue(args, "--body") ?? "";
|
|
10784
|
+
}
|
|
10785
|
+
async function defaultFindOpenPr(exec, input) {
|
|
10786
|
+
const owner = input.repo.split("/")[0];
|
|
10787
|
+
if (!owner) return void 0;
|
|
10788
|
+
const qs = new URLSearchParams({ state: "open", head: `${owner}:${input.head}` });
|
|
10789
|
+
if (input.base) qs.set("base", input.base);
|
|
10790
|
+
try {
|
|
10791
|
+
const { stdout } = await exec("gh", ["api", `repos/${input.repo}/pulls?${qs.toString()}`], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
10792
|
+
const rows = JSON.parse(stdout);
|
|
10793
|
+
const first = Array.isArray(rows) ? rows[0] : void 0;
|
|
10794
|
+
if (typeof first?.number === "number" && typeof first.html_url === "string") {
|
|
10795
|
+
return { number: first.number, url: first.html_url, existing: true };
|
|
10796
|
+
}
|
|
10797
|
+
} catch {
|
|
10798
|
+
}
|
|
10799
|
+
return void 0;
|
|
10800
|
+
}
|
|
10801
|
+
async function defaultRestCreatePr(exec, input, deps = {}) {
|
|
10802
|
+
const write = deps.write ?? import_promises.writeFile;
|
|
10803
|
+
const remove2 = deps.remove ?? import_promises.unlink;
|
|
10804
|
+
const ensureDir = deps.ensureDir ?? import_promises.mkdir;
|
|
10805
|
+
const dir = deps.dir ?? (0, import_node_os4.tmpdir)();
|
|
10806
|
+
const file = (0, import_node_path13.join)(dir, `mmi-rest-pr-${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}.json`);
|
|
10807
|
+
const payload = {
|
|
10808
|
+
title: input.title,
|
|
10809
|
+
head: input.head,
|
|
10810
|
+
body: input.body
|
|
10811
|
+
};
|
|
10812
|
+
if (input.base) payload.base = input.base;
|
|
10813
|
+
if (input.draft) payload.draft = true;
|
|
10814
|
+
await ensureDir((0, import_node_path13.dirname)(file), { recursive: true }).catch(() => {
|
|
10815
|
+
});
|
|
10816
|
+
await write(file, JSON.stringify(payload), "utf8");
|
|
10817
|
+
const args = [
|
|
10818
|
+
"api",
|
|
10819
|
+
"--method",
|
|
10820
|
+
"POST",
|
|
10821
|
+
`repos/${input.repo}/pulls`,
|
|
10822
|
+
"--input",
|
|
10823
|
+
file
|
|
10824
|
+
];
|
|
10825
|
+
try {
|
|
10826
|
+
const { stdout } = await exec("gh", args, { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
10827
|
+
const parsed = JSON.parse(stdout);
|
|
10828
|
+
if (typeof parsed.number === "number" && typeof parsed.html_url === "string") {
|
|
10829
|
+
return { number: parsed.number, url: parsed.html_url };
|
|
10830
|
+
}
|
|
10831
|
+
throw Object.assign(new Error(`REST pr create: unexpected payload: ${stdout.slice(0, 200)}`), { stderr: stdout });
|
|
10832
|
+
} catch (e) {
|
|
10833
|
+
const err = e;
|
|
10834
|
+
const text = execErrorText(err);
|
|
10835
|
+
const fromText = parseExistingPr(text);
|
|
10836
|
+
if (fromText) return { ...fromText, existing: true };
|
|
10837
|
+
if (/already exists/i.test(text)) {
|
|
10838
|
+
const found = await defaultFindOpenPr(exec, { repo: input.repo, head: input.head, base: input.base });
|
|
10839
|
+
if (found) return found;
|
|
10840
|
+
}
|
|
10841
|
+
throw e;
|
|
10842
|
+
} finally {
|
|
10843
|
+
try {
|
|
10844
|
+
await remove2(file);
|
|
10845
|
+
} catch {
|
|
10846
|
+
}
|
|
10847
|
+
}
|
|
10848
|
+
}
|
|
10849
|
+
async function defaultRestCreateIssue(exec, input) {
|
|
10850
|
+
const args = [
|
|
10851
|
+
"api",
|
|
10852
|
+
"--method",
|
|
10853
|
+
"POST",
|
|
10854
|
+
`repos/${input.repo}/issues`,
|
|
10855
|
+
"-f",
|
|
10856
|
+
`title=${input.title}`,
|
|
10857
|
+
"-f",
|
|
10858
|
+
`body=${input.body}`
|
|
10859
|
+
];
|
|
10860
|
+
for (const label of input.labels ?? []) {
|
|
10861
|
+
args.push("-f", `labels[]=${label}`);
|
|
10862
|
+
}
|
|
10863
|
+
try {
|
|
10864
|
+
const { stdout } = await exec("gh", args, { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
10865
|
+
const parsed = JSON.parse(stdout);
|
|
10866
|
+
if (typeof parsed.number === "number" && typeof parsed.html_url === "string") {
|
|
10867
|
+
return { number: parsed.number, url: parsed.html_url };
|
|
10868
|
+
}
|
|
10869
|
+
throw Object.assign(new Error(`REST issue create: unexpected payload: ${stdout.slice(0, 200)}`), { stderr: stdout });
|
|
10870
|
+
} catch (e) {
|
|
10871
|
+
const err = e;
|
|
10872
|
+
const text = `${err.stderr ?? ""}
|
|
10873
|
+
${err.message ?? ""}
|
|
10874
|
+
${err.stdout ?? ""}`;
|
|
10875
|
+
if (isRateLimitText(text)) {
|
|
10876
|
+
throw Object.assign(new Error(text.trim() || "REST issue create rate-limited"), {
|
|
10877
|
+
stderr: err.stderr ?? text
|
|
10878
|
+
});
|
|
10879
|
+
}
|
|
10880
|
+
throw e;
|
|
10881
|
+
}
|
|
10882
|
+
}
|
|
10883
|
+
async function defaultReadRateLimit(exec) {
|
|
10884
|
+
try {
|
|
10885
|
+
const { stdout } = await exec("gh", ["api", "rate_limit"], { timeout: 15e3 });
|
|
10886
|
+
return parseRateLimitPools(stdout);
|
|
10887
|
+
} catch {
|
|
10888
|
+
return {};
|
|
10889
|
+
}
|
|
10890
|
+
}
|
|
10891
|
+
async function createIssueViaRestFallback(args, swappedArgs, deps) {
|
|
10892
|
+
const retryCommand = buildIssueCreateRetryCommand(args);
|
|
10893
|
+
const repo = await resolveRepoForCreate(args, deps.exec);
|
|
10894
|
+
const title = flagValue(args, "--title");
|
|
10895
|
+
if (!repo || !title) {
|
|
10896
|
+
const pools2 = await deps.readRateLimit();
|
|
10897
|
+
const reset = pools2.graphql?.reset ?? pools2.core?.reset;
|
|
10898
|
+
return rateLimitedResult({
|
|
10899
|
+
resetEpochSeconds: reset,
|
|
10900
|
+
retryCommand,
|
|
10901
|
+
nowMs: deps.now(),
|
|
10902
|
+
pools: pools2,
|
|
10903
|
+
context: "issue create",
|
|
10904
|
+
message: `issue create: GraphQL rate-limited and cannot REST-fallback (need --repo/--title; have repo=${repo ?? "\u2205"} title=${title ? "set" : "\u2205"}) \u2014 ${rateLimitResetNote(reset, deps.now())}`
|
|
10905
|
+
});
|
|
10906
|
+
}
|
|
10907
|
+
const body = await resolveBodyForCreate(swappedArgs, deps.readFile);
|
|
10908
|
+
const labels = collectLabelFlags(args);
|
|
10909
|
+
const pools = await deps.readRateLimit();
|
|
10910
|
+
const coreRemaining = pools.core?.remaining;
|
|
10911
|
+
const coreReset = pools.core?.reset;
|
|
10912
|
+
const graphqlReset = pools.graphql?.reset;
|
|
10913
|
+
if (typeof coreRemaining === "number" && coreRemaining <= 0) {
|
|
10914
|
+
const reset = coreReset ?? graphqlReset;
|
|
10915
|
+
const waitMs = rateLimitWaitMs(reset, deps.now());
|
|
10916
|
+
if (waitMs === void 0) {
|
|
10917
|
+
return rateLimitedResult({
|
|
10918
|
+
resetEpochSeconds: reset,
|
|
10919
|
+
retryCommand,
|
|
10920
|
+
nowMs: deps.now(),
|
|
10921
|
+
pools,
|
|
10922
|
+
context: "issue create",
|
|
10923
|
+
message: `issue create: GraphQL and REST rate limits exhausted \u2014 ${rateLimitResetNote(reset, deps.now())}`
|
|
10924
|
+
});
|
|
10925
|
+
}
|
|
10926
|
+
}
|
|
10927
|
+
console.warn(`issue create: GraphQL rate-limited \u2014 creating via REST POST repos/${repo}/issues (#5489)`);
|
|
10928
|
+
try {
|
|
10929
|
+
return await runWithRateLimitBackoff(
|
|
10930
|
+
() => deps.restCreateIssue({ repo, title, body, labels }),
|
|
10931
|
+
{
|
|
10932
|
+
context: "issue create",
|
|
10933
|
+
retryCommand,
|
|
10934
|
+
sleep: deps.sleep,
|
|
10935
|
+
now: deps.now,
|
|
10936
|
+
log: (message) => console.warn(message)
|
|
10937
|
+
}
|
|
10938
|
+
);
|
|
10939
|
+
} catch (e) {
|
|
10940
|
+
if (isRateLimitedError(e)) {
|
|
10941
|
+
const after = await deps.readRateLimit();
|
|
10942
|
+
return {
|
|
10943
|
+
...e.receipt,
|
|
10944
|
+
remaining: e.receipt.remaining ?? remainingFromPools(after)
|
|
10945
|
+
};
|
|
10946
|
+
}
|
|
10947
|
+
throw e;
|
|
10948
|
+
}
|
|
10949
|
+
}
|
|
10950
|
+
async function createPrViaRestFallback(args, swappedArgs, deps, knownPools) {
|
|
10951
|
+
const retryCommand = buildPrCreateRetryCommand(args);
|
|
10952
|
+
const repo = await resolveRepoForCreate(args, deps.exec);
|
|
10953
|
+
const head = await resolveHeadForCreate(args, deps.exec);
|
|
10954
|
+
const base = flagValue(args, "--base");
|
|
10955
|
+
const title = flagValue(args, "--title");
|
|
10956
|
+
if (!repo || !head || !title) {
|
|
10957
|
+
const pools2 = knownPools ?? await deps.readRateLimit();
|
|
10958
|
+
const reset = pools2.graphql?.reset ?? pools2.core?.reset;
|
|
10959
|
+
return rateLimitedResult({
|
|
10960
|
+
resetEpochSeconds: reset,
|
|
10961
|
+
retryCommand,
|
|
10962
|
+
nowMs: deps.now(),
|
|
10963
|
+
pools: pools2,
|
|
10964
|
+
message: `pr create: GraphQL rate-limited and cannot REST-fallback (need --repo/--head/--title; have repo=${repo ?? "\u2205"} head=${head ?? "\u2205"} title=${title ? "set" : "\u2205"}) \u2014 ${rateLimitResetNote(reset, deps.now())}`
|
|
10965
|
+
});
|
|
10966
|
+
}
|
|
10967
|
+
const existing = await deps.findOpenPr({ repo, head, base });
|
|
10968
|
+
if (existing) {
|
|
10969
|
+
console.warn(`pr create: GraphQL rate-limited \u2014 reusing existing open PR #${existing.number} for ${head} (#5451/#5487)`);
|
|
10970
|
+
return existing;
|
|
10971
|
+
}
|
|
10972
|
+
const pools = knownPools ?? await deps.readRateLimit();
|
|
10973
|
+
const coreRemaining = pools.core?.remaining;
|
|
10974
|
+
const graphqlReset = pools.graphql?.reset;
|
|
10975
|
+
const coreReset = pools.core?.reset;
|
|
10976
|
+
if (typeof coreRemaining === "number" && coreRemaining <= 0) {
|
|
10977
|
+
const reset = coreReset ?? graphqlReset;
|
|
10978
|
+
const waitMs = rateLimitWaitMs(reset, deps.now());
|
|
10979
|
+
if (waitMs === void 0) {
|
|
10980
|
+
return rateLimitedResult({
|
|
10981
|
+
resetEpochSeconds: reset,
|
|
10982
|
+
retryCommand,
|
|
10983
|
+
nowMs: deps.now(),
|
|
10984
|
+
pools,
|
|
10985
|
+
message: `pr create: GraphQL and REST rate limits exhausted \u2014 ${rateLimitResetNote(reset, deps.now())}`
|
|
10986
|
+
});
|
|
10987
|
+
}
|
|
10988
|
+
if (waitMs > 0) {
|
|
10989
|
+
console.warn(`pr create: REST pool exhausted \u2014 waiting ${Math.round(waitMs / 1e3)}s for reset (#5451)`);
|
|
10990
|
+
await deps.sleep(waitMs);
|
|
10991
|
+
}
|
|
10992
|
+
} else if (typeof pools.graphql?.remaining === "number" && pools.graphql.remaining <= 0) {
|
|
10993
|
+
const waitMs = typeof coreRemaining === "number" && coreRemaining > 0 ? void 0 : rateLimitWaitMs(graphqlReset, deps.now());
|
|
10994
|
+
if (waitMs && waitMs > 0) {
|
|
10995
|
+
console.warn(`pr create: GraphQL exhausted \u2014 waiting ${Math.round(waitMs / 1e3)}s before REST fallback (#5451)`);
|
|
10996
|
+
await deps.sleep(waitMs);
|
|
10997
|
+
}
|
|
10998
|
+
}
|
|
10999
|
+
const body = await resolveBodyForCreate(swappedArgs, deps.readFile);
|
|
11000
|
+
console.warn(`pr create: GraphQL rate-limited \u2014 creating via REST POST repos/${repo}/pulls (#5451/#5487)`);
|
|
11001
|
+
try {
|
|
11002
|
+
return await deps.restCreatePr({
|
|
11003
|
+
repo,
|
|
11004
|
+
title,
|
|
11005
|
+
body,
|
|
11006
|
+
head,
|
|
11007
|
+
base,
|
|
11008
|
+
draft: args.includes("--draft")
|
|
11009
|
+
});
|
|
11010
|
+
} catch (e) {
|
|
11011
|
+
const err = e;
|
|
11012
|
+
const text = execErrorText(err);
|
|
11013
|
+
if (isGhRateLimitError(text) || /HTTP\/?\d*(?:\.\d)?\s+403\b.*rate limit/i.test(text)) {
|
|
11014
|
+
const after = await deps.readRateLimit();
|
|
11015
|
+
const reset = after.core?.reset ?? after.graphql?.reset ?? err.rateLimitReset ?? coreReset ?? graphqlReset;
|
|
11016
|
+
const waitMs = rateLimitWaitMs(reset, deps.now());
|
|
11017
|
+
if (waitMs !== void 0) {
|
|
11018
|
+
if (waitMs > 0) {
|
|
11019
|
+
console.warn(`pr create: REST also rate-limited \u2014 waiting ${Math.round(waitMs / 1e3)}s for reset (#5451)`);
|
|
11020
|
+
await deps.sleep(waitMs);
|
|
11021
|
+
}
|
|
11022
|
+
try {
|
|
11023
|
+
return await deps.restCreatePr({
|
|
11024
|
+
repo,
|
|
11025
|
+
title,
|
|
11026
|
+
body,
|
|
11027
|
+
head,
|
|
11028
|
+
base,
|
|
11029
|
+
draft: args.includes("--draft")
|
|
11030
|
+
});
|
|
11031
|
+
} catch (e2) {
|
|
11032
|
+
const text2 = execErrorText(e2);
|
|
11033
|
+
if (isGhRateLimitError(text2)) {
|
|
11034
|
+
const after2 = await deps.readRateLimit();
|
|
11035
|
+
return rateLimitedResult({
|
|
11036
|
+
resetEpochSeconds: after2.core?.reset ?? after2.graphql?.reset ?? reset,
|
|
11037
|
+
retryCommand,
|
|
11038
|
+
nowMs: deps.now(),
|
|
11039
|
+
pools: after2,
|
|
11040
|
+
message: `pr create: GraphQL and REST rate limits exhausted \u2014 ${rateLimitResetNote(after2.core?.reset ?? after2.graphql?.reset ?? reset, deps.now())}`
|
|
11041
|
+
});
|
|
11042
|
+
}
|
|
11043
|
+
throw e2;
|
|
11044
|
+
}
|
|
11045
|
+
}
|
|
11046
|
+
return rateLimitedResult({
|
|
11047
|
+
resetEpochSeconds: reset,
|
|
11048
|
+
retryCommand,
|
|
11049
|
+
nowMs: deps.now(),
|
|
11050
|
+
pools: after,
|
|
11051
|
+
message: `pr create: GraphQL and REST rate limits exhausted \u2014 ${rateLimitResetNote(reset, deps.now())}`
|
|
11052
|
+
});
|
|
11053
|
+
}
|
|
11054
|
+
throw e;
|
|
11055
|
+
}
|
|
11056
|
+
}
|
|
10463
11057
|
async function ghCreate(args, deps = {}) {
|
|
10464
11058
|
const exec = deps.exec ?? execFileP2;
|
|
10465
11059
|
const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
|
|
11060
|
+
const now = deps.now ?? Date.now;
|
|
11061
|
+
const read = deps.readFile ?? import_promises.readFile;
|
|
11062
|
+
const restCreatePr = deps.restCreatePr ?? ((input) => defaultRestCreatePr(exec, input));
|
|
11063
|
+
const restCreateIssue = deps.restCreateIssue ?? ((input) => defaultRestCreateIssue(exec, input));
|
|
11064
|
+
const findOpenPr = deps.findOpenPr ?? ((input) => defaultFindOpenPr(exec, input));
|
|
11065
|
+
const readRateLimit = deps.readRateLimit ?? (() => defaultReadRateLimit(exec));
|
|
10466
11066
|
const swapped = await bodyArgsViaFile(args);
|
|
11067
|
+
const restDeps = {
|
|
11068
|
+
exec,
|
|
11069
|
+
sleep: sleep2,
|
|
11070
|
+
now,
|
|
11071
|
+
readFile: read,
|
|
11072
|
+
restCreatePr,
|
|
11073
|
+
findOpenPr,
|
|
11074
|
+
readRateLimit
|
|
11075
|
+
};
|
|
11076
|
+
const viaRest = async (pools) => {
|
|
11077
|
+
try {
|
|
11078
|
+
const result = await createPrViaRestFallback(args, swapped.args, restDeps, pools);
|
|
11079
|
+
await swapped.cleanup();
|
|
11080
|
+
return result;
|
|
11081
|
+
} catch (restErr) {
|
|
11082
|
+
await swapped.cleanup();
|
|
11083
|
+
const restText = execErrorText(restErr);
|
|
11084
|
+
if (isGhRateLimitError(restText)) {
|
|
11085
|
+
const pools2 = await readRateLimit();
|
|
11086
|
+
return rateLimitedResult({
|
|
11087
|
+
resetEpochSeconds: pools2.core?.reset ?? pools2.graphql?.reset,
|
|
11088
|
+
retryCommand: buildPrCreateRetryCommand(args),
|
|
11089
|
+
nowMs: now(),
|
|
11090
|
+
pools: pools2,
|
|
11091
|
+
message: `pr create: GraphQL and REST rate limits exhausted \u2014 ${rateLimitResetNote(pools2.core?.reset ?? pools2.graphql?.reset, now())}`
|
|
11092
|
+
});
|
|
11093
|
+
}
|
|
11094
|
+
return fail(`gh pr create failed (GraphQL rate-limited; REST fallback also failed): ${restText.trim()}`);
|
|
11095
|
+
}
|
|
11096
|
+
};
|
|
10467
11097
|
try {
|
|
11098
|
+
if (args[0] === "pr") {
|
|
11099
|
+
const pools = await readRateLimit();
|
|
11100
|
+
if (typeof pools.graphql?.remaining === "number" && pools.graphql.remaining <= 0) {
|
|
11101
|
+
console.warn(
|
|
11102
|
+
typeof pools.core?.remaining === "number" && pools.core.remaining > 0 ? `pr create: GraphQL pool already exhausted (REST core remaining=${pools.core.remaining}) \u2014 skipping gh pr create, using REST (#5487)` : "pr create: GraphQL pool already exhausted \u2014 attempting REST fallback / typed receipt (#5487)"
|
|
11103
|
+
);
|
|
11104
|
+
return await viaRest(pools);
|
|
11105
|
+
}
|
|
11106
|
+
}
|
|
10468
11107
|
for (let attempt = 1; attempt <= GH_CREATE_UPSTREAM_RETRIES; attempt++) {
|
|
10469
11108
|
try {
|
|
10470
11109
|
const { stdout } = await exec("gh", swapped.args, { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
10471
11110
|
return parseCreatedUrl(stdout);
|
|
10472
11111
|
} catch (e) {
|
|
10473
11112
|
const err = e;
|
|
10474
|
-
const errText =
|
|
10475
|
-
${err.message ?? ""}`;
|
|
11113
|
+
const errText = execErrorText(err);
|
|
10476
11114
|
const faultText = (err.stderr ?? "").trim() ? err.stderr : err.message ?? "";
|
|
10477
11115
|
const existing = args[0] === "pr" ? parseExistingPr(errText) : void 0;
|
|
10478
11116
|
if (existing) {
|
|
@@ -10480,6 +11118,38 @@ ${err.message ?? ""}`;
|
|
|
10480
11118
|
console.warn(`${args[0]} create: a PR already exists for this branch \u2014 #${existing.number} (nothing to do)`);
|
|
10481
11119
|
return { ...existing, existing: true };
|
|
10482
11120
|
}
|
|
11121
|
+
if (args[0] === "pr" && isGhRateLimitError(errText)) {
|
|
11122
|
+
return await viaRest();
|
|
11123
|
+
}
|
|
11124
|
+
if (args[0] === "issue" && isGhRateLimitError(errText)) {
|
|
11125
|
+
try {
|
|
11126
|
+
const result = await createIssueViaRestFallback(args, swapped.args, {
|
|
11127
|
+
exec,
|
|
11128
|
+
sleep: sleep2,
|
|
11129
|
+
now,
|
|
11130
|
+
readFile: read,
|
|
11131
|
+
restCreateIssue,
|
|
11132
|
+
readRateLimit
|
|
11133
|
+
});
|
|
11134
|
+
await swapped.cleanup();
|
|
11135
|
+
return result;
|
|
11136
|
+
} catch (restErr) {
|
|
11137
|
+
await swapped.cleanup();
|
|
11138
|
+
const restText = execErrorText(restErr);
|
|
11139
|
+
if (isGhRateLimitError(restText)) {
|
|
11140
|
+
const pools = await readRateLimit();
|
|
11141
|
+
return rateLimitedResult({
|
|
11142
|
+
resetEpochSeconds: pools.core?.reset ?? pools.graphql?.reset,
|
|
11143
|
+
retryCommand: buildIssueCreateRetryCommand(args),
|
|
11144
|
+
nowMs: now(),
|
|
11145
|
+
pools,
|
|
11146
|
+
context: "issue create",
|
|
11147
|
+
message: `issue create: GraphQL and REST rate limits exhausted \u2014 ${rateLimitResetNote(pools.core?.reset ?? pools.graphql?.reset, now())}`
|
|
11148
|
+
});
|
|
11149
|
+
}
|
|
11150
|
+
return fail(`gh issue create failed (GraphQL rate-limited; REST fallback also failed): ${restText.trim()}`);
|
|
11151
|
+
}
|
|
11152
|
+
}
|
|
10483
11153
|
const note = timeoutKillNote(e, GH_MUTATION_TIMEOUT_MS);
|
|
10484
11154
|
if (isUpstreamGitHubFault(faultText) && mayRetryCreate(args[0]) && attempt < GH_CREATE_UPSTREAM_RETRIES) {
|
|
10485
11155
|
const waitMs = GH_CREATE_RETRY_BACKOFF_MS[attempt - 1];
|
|
@@ -10488,8 +11158,21 @@ ${err.message ?? ""}`;
|
|
|
10488
11158
|
continue;
|
|
10489
11159
|
}
|
|
10490
11160
|
await swapped.cleanup();
|
|
11161
|
+
if ((args[0] === "pr" || args[0] === "issue") && isGhRateLimitError(errText)) {
|
|
11162
|
+
const pools = await readRateLimit();
|
|
11163
|
+
const context = args[0] === "issue" ? "issue create" : "pr create";
|
|
11164
|
+
const retryCommand = args[0] === "issue" ? buildIssueCreateRetryCommand(args) : buildPrCreateRetryCommand(args);
|
|
11165
|
+
return rateLimitedResult({
|
|
11166
|
+
resetEpochSeconds: pools.graphql?.reset ?? pools.core?.reset,
|
|
11167
|
+
retryCommand,
|
|
11168
|
+
nowMs: now(),
|
|
11169
|
+
pools,
|
|
11170
|
+
context,
|
|
11171
|
+
message: `${context}: GraphQL rate-limited \u2014 ${rateLimitResetNote(pools.graphql?.reset ?? pools.core?.reset, now())}`
|
|
11172
|
+
});
|
|
11173
|
+
}
|
|
10491
11174
|
if (isUpstreamGitHubFault(faultText)) return fail(upstreamFaultMessage(args[0], faultText));
|
|
10492
|
-
return fail(`gh ${args[0]} create failed: ${(err.stderr || err.message || String(e)).trim()}${note ? ` (${note})` : ""}`);
|
|
11175
|
+
return fail(`gh ${args[0]} create failed: ${(err.stderr || err.stdout || err.message || String(e)).trim()}${note ? ` (${note})` : ""}`);
|
|
10493
11176
|
}
|
|
10494
11177
|
}
|
|
10495
11178
|
throw new Error("ghCreate: retry loop exhausted without a verdict");
|
|
@@ -10497,6 +11180,14 @@ ${err.message ?? ""}`;
|
|
|
10497
11180
|
await swapped.cleanup();
|
|
10498
11181
|
}
|
|
10499
11182
|
}
|
|
11183
|
+
function requireGhCreateOk(result, context = "pr create") {
|
|
11184
|
+
if (isGhCreateRateLimited(result)) {
|
|
11185
|
+
return fail(
|
|
11186
|
+
`${context}: rate_limited \u2014 ${result.message}. retry: ${result.retryCommand}` + (result.resetAt ? ` (resets at ${result.resetAt})` : "")
|
|
11187
|
+
);
|
|
11188
|
+
}
|
|
11189
|
+
return result;
|
|
11190
|
+
}
|
|
10500
11191
|
|
|
10501
11192
|
// src/secrets.ts
|
|
10502
11193
|
var OWNER2 = "mutmutco";
|
|
@@ -12176,10 +12867,10 @@ var rollout_plan_default = {
|
|
|
12176
12867
|
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)."
|
|
12177
12868
|
},
|
|
12178
12869
|
baseline: {
|
|
12179
|
-
version: "4.0
|
|
12180
|
-
tag: "v4.0
|
|
12181
|
-
commit: "
|
|
12182
|
-
npm: "@mutmutco/cli@4.0
|
|
12870
|
+
version: "4.1.0",
|
|
12871
|
+
tag: "v4.1.0",
|
|
12872
|
+
commit: "4080226297a9",
|
|
12873
|
+
npm: "@mutmutco/cli@4.1.0"
|
|
12183
12874
|
},
|
|
12184
12875
|
exitCriterion: "fleet-n-of-n",
|
|
12185
12876
|
hubOnlyShortcut: "forbidden",
|
|
@@ -12196,14 +12887,14 @@ var rollout_plan_default = {
|
|
|
12196
12887
|
repo: "mutmutco/mmi-hub",
|
|
12197
12888
|
role: "canary",
|
|
12198
12889
|
schedule: "train",
|
|
12199
|
-
v3Target: "v4.0
|
|
12890
|
+
v3Target: "v4.1.0"
|
|
12200
12891
|
}
|
|
12201
12892
|
],
|
|
12202
12893
|
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.",
|
|
12203
12894
|
rollback: {
|
|
12204
12895
|
independent: true,
|
|
12205
|
-
mechanism: "npm dist-tag latest -> 4.0
|
|
12206
|
-
v3Target: "v4.0
|
|
12896
|
+
mechanism: "npm dist-tag latest -> 4.1.0 and redeploy the Hub Lambda from tag v4.1.0 (4080226297a9); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
12897
|
+
v3Target: "v4.1.0 (@mutmutco/cli@4.1.0, tag commit 4080226297a9 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
12207
12898
|
}
|
|
12208
12899
|
},
|
|
12209
12900
|
{
|
|
@@ -12687,79 +13378,6 @@ function parentLinkFields(result, error) {
|
|
|
12687
13378
|
return {};
|
|
12688
13379
|
}
|
|
12689
13380
|
|
|
12690
|
-
// src/config-discovery.ts
|
|
12691
|
-
function stripMutableBoardConfig(cfg) {
|
|
12692
|
-
const {
|
|
12693
|
-
projectOwner: _projectOwner,
|
|
12694
|
-
projectNumber: _projectNumber,
|
|
12695
|
-
projectId: _projectId,
|
|
12696
|
-
statusFieldId: _statusFieldId,
|
|
12697
|
-
statusOptions: _statusOptions,
|
|
12698
|
-
priorityFieldId: _priorityFieldId,
|
|
12699
|
-
priorityOptions: _priorityOptions,
|
|
12700
|
-
...rest
|
|
12701
|
-
} = cfg;
|
|
12702
|
-
return rest;
|
|
12703
|
-
}
|
|
12704
|
-
function boardConfigFromProject(meta, floor = {}) {
|
|
12705
|
-
return {
|
|
12706
|
-
...stripMutableBoardConfig(floor),
|
|
12707
|
-
projectOwner: meta.projectOwner,
|
|
12708
|
-
projectNumber: meta.projectNumber,
|
|
12709
|
-
projectId: meta.projectId,
|
|
12710
|
-
statusFieldId: meta.statusFieldId,
|
|
12711
|
-
statusOptions: meta.statusOptions,
|
|
12712
|
-
priorityFieldId: meta.priorityFieldId,
|
|
12713
|
-
priorityOptions: meta.priorityOptions
|
|
12714
|
-
};
|
|
12715
|
-
}
|
|
12716
|
-
|
|
12717
|
-
// src/repo-resolve.ts
|
|
12718
|
-
init_cli_shared();
|
|
12719
|
-
|
|
12720
|
-
// src/registry-degrade.ts
|
|
12721
|
-
function registryDegradeError(error) {
|
|
12722
|
-
return new Error(
|
|
12723
|
-
`Hub registry read failed (${error}) \u2014 board coords could not be discovered. This is NOT necessarily the registry: a local DNS/network fault reaches this path as a timeout too. Check in order \u2014 (1) resolve the Hub API host (fails in seconds and rules out the local-network class), (2) if it resolves, retry: a Lambda cold start clears shortly, (3) if it still fails, check auth (a rejected credential is not a timeout).`
|
|
12724
|
-
);
|
|
12725
|
-
}
|
|
12726
|
-
|
|
12727
|
-
// src/repo-resolve.ts
|
|
12728
|
-
function slugOf(repoOrSlug) {
|
|
12729
|
-
return (repoOrSlug.includes("/") ? repoOrSlug.split("/").pop() : repoOrSlug).toLowerCase();
|
|
12730
|
-
}
|
|
12731
|
-
function repoFromRemoteUrl(remoteUrl) {
|
|
12732
|
-
const m = remoteUrl.trim().match(/^(?:[a-z][a-z0-9+.-]*:\/\/)?(?:[^@\s/]+@)?github\.com[:/]([^/\s:]+)\/([^/\s]+?)(?:\.git)?\/?$/i);
|
|
12733
|
-
return m ? `${m[1]}/${m[2]}` : void 0;
|
|
12734
|
-
}
|
|
12735
|
-
function repoFromSelector(selector) {
|
|
12736
|
-
const trimmed = selector.trim();
|
|
12737
|
-
const url = trimmed.match(/^https:\/\/github\.com\/([^/]+\/[^/]+)\/(?:issues|pull)\/\d+$/i);
|
|
12738
|
-
if (url) return url[1];
|
|
12739
|
-
const qualified = trimmed.match(/^([^/\s#]+\/[^/\s#]+)#\d+$/);
|
|
12740
|
-
return qualified?.[1];
|
|
12741
|
-
}
|
|
12742
|
-
async function resolveRepo(repo) {
|
|
12743
|
-
if (repo) return repo;
|
|
12744
|
-
const fromOrigin = repoFromRemoteUrl(await gitOut(["remote", "get-url", "origin"])) ?? repoFromRemoteUrl(originUrlFromGitConfig() ?? "");
|
|
12745
|
-
if (fromOrigin) return fromOrigin;
|
|
12746
|
-
try {
|
|
12747
|
-
const { stdout } = await execFileP2("gh", ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"], { timeout: 5e3 });
|
|
12748
|
-
return stdout.trim() || void 0;
|
|
12749
|
-
} catch {
|
|
12750
|
-
return void 0;
|
|
12751
|
-
}
|
|
12752
|
-
}
|
|
12753
|
-
async function loadConfigForBoardSelector(selector, repoOption) {
|
|
12754
|
-
const targetRepo3 = repoFromSelector(selector) ?? repoOption;
|
|
12755
|
-
const floor = await loadConfig();
|
|
12756
|
-
if (!floor.sagaApiUrl) return stripMutableBoardConfig(floor);
|
|
12757
|
-
const slug = targetRepo3 ? slugOf(targetRepo3) : await repoSlug();
|
|
12758
|
-
const read = await fetchProjectBySlugChecked(slug, registryClientDeps(floor));
|
|
12759
|
-
if (!read.ok) throw registryDegradeError(read.error);
|
|
12760
|
-
return read.project ? boardConfigFromProject(read.project, floor) : stripMutableBoardConfig(floor);
|
|
12761
|
-
}
|
|
12762
|
-
|
|
12763
13381
|
// src/issue-view-json.ts
|
|
12764
13382
|
var DEFAULT_ISSUE_VIEW_FIELDS = "number,title,state,url,labels,author,assignees,milestone,body";
|
|
12765
13383
|
function normalizeIssueViewJsonFields(tokens2) {
|
|
@@ -18764,10 +19382,69 @@ var import_node_path17 = require("node:path");
|
|
|
18764
19382
|
var import_node_util6 = require("node:util");
|
|
18765
19383
|
init_github_client();
|
|
18766
19384
|
|
|
19385
|
+
// src/board-snapshot.ts
|
|
19386
|
+
init_fetch_retry();
|
|
19387
|
+
init_client_version();
|
|
19388
|
+
var BOARD_SNAPSHOT_TIMEOUT_MS = 25e3;
|
|
19389
|
+
function isSnapshotShape(body) {
|
|
19390
|
+
const b = body;
|
|
19391
|
+
return Boolean(
|
|
19392
|
+
b && typeof b === "object" && typeof b.project?.id === "string" && typeof b.project?.title === "string" && typeof b.viewer === "string" && b.viewer.length > 0 && Array.isArray(b.nodes) && Array.isArray(b.writableRepos) && Array.isArray(b.unreadableRepos) && Array.isArray(b.pullRequests) && Array.isArray(b.warnings) && typeof b.partial === "boolean"
|
|
19393
|
+
);
|
|
19394
|
+
}
|
|
19395
|
+
async function fetchHubBoardSnapshot(request, deps) {
|
|
19396
|
+
if (!deps.baseUrl) return { state: "unavailable", reason: "no Hub API URL (set MMI_HUB_URL or use a current MMI CLI/plugin build)" };
|
|
19397
|
+
const token = await deps.token();
|
|
19398
|
+
if (!token) return { state: "unavailable", reason: "no Hub session token (run `gh auth login`)" };
|
|
19399
|
+
let res;
|
|
19400
|
+
try {
|
|
19401
|
+
res = await fetchWithRetry(
|
|
19402
|
+
deps.fetch ?? fetch,
|
|
19403
|
+
`${deps.baseUrl.replace(/\/$/, "")}/board/snapshot`,
|
|
19404
|
+
{
|
|
19405
|
+
method: "POST",
|
|
19406
|
+
headers: { ...clientVersionHeaders(), Authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
19407
|
+
body: JSON.stringify({
|
|
19408
|
+
owner: request.owner,
|
|
19409
|
+
number: request.number,
|
|
19410
|
+
...request.statusFieldId ? { statusFieldId: request.statusFieldId } : {},
|
|
19411
|
+
activeOnly: request.activeOnly !== false,
|
|
19412
|
+
allowPartial: request.allowPartial === true,
|
|
19413
|
+
includePullRequests: request.includePullRequests !== false
|
|
19414
|
+
})
|
|
19415
|
+
},
|
|
19416
|
+
{ attempts: 3, timeoutMs: deps.timeoutMs ?? BOARD_SNAPSHOT_TIMEOUT_MS, sleep: deps.retrySleep }
|
|
19417
|
+
);
|
|
19418
|
+
} catch (e) {
|
|
19419
|
+
return { state: "unavailable", reason: e.message };
|
|
19420
|
+
}
|
|
19421
|
+
if (res.status === 426) return { state: "refused", reason: upgradeRequiredError(res, await res.json().catch(() => null)) };
|
|
19422
|
+
if (res.status === 400 || res.status === 401 || res.status === 403) {
|
|
19423
|
+
const detail = (await res.json().catch(() => null))?.error;
|
|
19424
|
+
return { state: "refused", reason: detail ? `board snapshot HTTP ${res.status}: ${detail}` : `board snapshot HTTP ${res.status}` };
|
|
19425
|
+
}
|
|
19426
|
+
if (!res.ok) return { state: "unavailable", reason: `board snapshot HTTP ${res.status}` };
|
|
19427
|
+
const body = await res.json().catch(() => null);
|
|
19428
|
+
if (!isSnapshotShape(body)) return { state: "unavailable", reason: "malformed board snapshot response" };
|
|
19429
|
+
return { state: "ok", snapshot: body };
|
|
19430
|
+
}
|
|
19431
|
+
|
|
18767
19432
|
// src/board-dependency.ts
|
|
18768
19433
|
var DEPENDS_ON_LINE = /^\s*[-*]?\s*\*\*Depends on:\*\*\s*(.+)$/im;
|
|
18769
19434
|
var ISSUE_REF = /([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)#(\d+)/g;
|
|
18770
19435
|
var DEPENDENCY_FETCH_CONCURRENCY = 8;
|
|
19436
|
+
var ISSUE_BODY_GRAPHQL = `
|
|
19437
|
+
query($repoOwner: String!, $repoName: String!, $number: Int!) {
|
|
19438
|
+
repository(owner: $repoOwner, name: $repoName) {
|
|
19439
|
+
issue(number: $number) { body }
|
|
19440
|
+
}
|
|
19441
|
+
}`;
|
|
19442
|
+
var ISSUE_STATE_GRAPHQL = `
|
|
19443
|
+
query($repoOwner: String!, $repoName: String!, $number: Int!) {
|
|
19444
|
+
repository(owner: $repoOwner, name: $repoName) {
|
|
19445
|
+
issue(number: $number) { state }
|
|
19446
|
+
}
|
|
19447
|
+
}`;
|
|
18771
19448
|
function parseDependsOnRefs(body) {
|
|
18772
19449
|
const match = body.match(DEPENDS_ON_LINE);
|
|
18773
19450
|
if (!match) return [];
|
|
@@ -18777,27 +19454,66 @@ function parseDependsOnRefs(body) {
|
|
|
18777
19454
|
}
|
|
18778
19455
|
return refs;
|
|
18779
19456
|
}
|
|
18780
|
-
async function
|
|
18781
|
-
|
|
19457
|
+
async function readIssueBodyWithQuota(client, repo, number, opts) {
|
|
19458
|
+
const [owner, name] = repo.split("/");
|
|
19459
|
+
if (!owner || !name) throw new Error(`invalid repository ref for issue-body read: ${repo}`);
|
|
19460
|
+
const viaRest = async () => {
|
|
19461
|
+
const data = await client.rest("GET", `repos/${repo}/issues/${number}`);
|
|
19462
|
+
return data?.body ?? "";
|
|
19463
|
+
};
|
|
19464
|
+
const viaGraphql = async () => {
|
|
19465
|
+
const data = await client.graphql(ISSUE_BODY_GRAPHQL, { repoOwner: owner, repoName: name, number });
|
|
19466
|
+
return data.repository?.issue?.body ?? "";
|
|
19467
|
+
};
|
|
19468
|
+
return runWithRateLimitBackoff(viaRest, {
|
|
19469
|
+
context: opts.context ?? `issue-body ${repo}#${number}`,
|
|
19470
|
+
retryCommand: opts.retryCommand,
|
|
19471
|
+
fallback: viaGraphql,
|
|
19472
|
+
sleep: opts.sleep,
|
|
19473
|
+
now: opts.now,
|
|
19474
|
+
log: opts.log,
|
|
19475
|
+
capMs: opts.capMs
|
|
19476
|
+
});
|
|
19477
|
+
}
|
|
19478
|
+
async function issueState(client, repo, number, quota) {
|
|
19479
|
+
const [owner, name] = repo.split("/");
|
|
19480
|
+
const viaRest = async () => {
|
|
18782
19481
|
const data = await client.rest("GET", `repos/${repo}/issues/${number}`);
|
|
18783
19482
|
const state = data?.state?.toLowerCase();
|
|
18784
19483
|
if (state === "open") return "open";
|
|
18785
19484
|
if (state === "closed") return "closed";
|
|
18786
19485
|
return "unknown";
|
|
18787
|
-
}
|
|
19486
|
+
};
|
|
19487
|
+
const viaGraphql = async () => {
|
|
19488
|
+
if (!owner || !name) return "unknown";
|
|
19489
|
+
const data = await client.graphql(ISSUE_STATE_GRAPHQL, { repoOwner: owner, repoName: name, number });
|
|
19490
|
+
const state = data.repository?.issue?.state?.toLowerCase();
|
|
19491
|
+
if (state === "open") return "open";
|
|
19492
|
+
if (state === "closed") return "closed";
|
|
19493
|
+
return "unknown";
|
|
19494
|
+
};
|
|
19495
|
+
try {
|
|
19496
|
+
return await runWithRateLimitBackoff(viaRest, {
|
|
19497
|
+
context: `dependency state ${repo}#${number}`,
|
|
19498
|
+
retryCommand: quota.retryCommand,
|
|
19499
|
+
fallback: viaGraphql
|
|
19500
|
+
});
|
|
19501
|
+
} catch (e) {
|
|
19502
|
+
if (isRateLimitRefusal(e)) throw e;
|
|
18788
19503
|
return "unknown";
|
|
18789
19504
|
}
|
|
18790
19505
|
}
|
|
18791
|
-
async function dependencyBlocksClaim(client, body) {
|
|
19506
|
+
async function dependencyBlocksClaim(client, body, opts = {}) {
|
|
18792
19507
|
const refs = parseDependsOnRefs(body);
|
|
18793
19508
|
if (!refs.length) return { blocked: false, openDependencies: [] };
|
|
19509
|
+
const retryCommand = opts.retryCommand ?? "mmi-cli oracle board claim --check";
|
|
18794
19510
|
const states = new Array(refs.length);
|
|
18795
19511
|
let next = 0;
|
|
18796
19512
|
const worker = async () => {
|
|
18797
19513
|
while (next < refs.length) {
|
|
18798
19514
|
const index = next++;
|
|
18799
19515
|
const ref = refs[index];
|
|
18800
|
-
states[index] = await issueState(client, ref.repo, ref.number);
|
|
19516
|
+
states[index] = await issueState(client, ref.repo, ref.number, { retryCommand });
|
|
18801
19517
|
}
|
|
18802
19518
|
};
|
|
18803
19519
|
await Promise.all(Array.from({ length: Math.min(DEPENDENCY_FETCH_CONCURRENCY, refs.length) }, () => worker()));
|
|
@@ -18840,15 +19556,27 @@ async function filterDependencyBlockedClaimables(items, client, opts = {}) {
|
|
|
18840
19556
|
continue;
|
|
18841
19557
|
}
|
|
18842
19558
|
}
|
|
18843
|
-
|
|
18844
|
-
|
|
18845
|
-
|
|
18846
|
-
|
|
18847
|
-
|
|
18848
|
-
|
|
18849
|
-
|
|
18850
|
-
|
|
18851
|
-
|
|
19559
|
+
try {
|
|
19560
|
+
const gate = await dependencyBlocksClaim(client, body);
|
|
19561
|
+
if (gate.blocked) {
|
|
19562
|
+
results[index] = {
|
|
19563
|
+
bucket: "blocked",
|
|
19564
|
+
item,
|
|
19565
|
+
warnings: [`${item.ref} blocked on open ${gate.openDependencies.join(", ")}`]
|
|
19566
|
+
};
|
|
19567
|
+
} else {
|
|
19568
|
+
results[index] = { bucket: "claimable", item, warnings: [] };
|
|
19569
|
+
}
|
|
19570
|
+
} catch (e) {
|
|
19571
|
+
if (isRateLimitRefusal(e)) {
|
|
19572
|
+
results[index] = {
|
|
19573
|
+
bucket: "claimable",
|
|
19574
|
+
item,
|
|
19575
|
+
warnings: [`dependency check skipped for ${item.ref} (rate-limited): ${e.message}`]
|
|
19576
|
+
};
|
|
19577
|
+
continue;
|
|
19578
|
+
}
|
|
19579
|
+
throw e;
|
|
18852
19580
|
}
|
|
18853
19581
|
}
|
|
18854
19582
|
};
|
|
@@ -19553,10 +20281,64 @@ function renderBoardSource() {
|
|
|
19553
20281
|
async function readBoard(options, deps = {}) {
|
|
19554
20282
|
const cfg = resolveBoardConfig(options.config);
|
|
19555
20283
|
const client = deps.client ?? defaultGitHubClient();
|
|
19556
|
-
|
|
19557
|
-
|
|
19558
|
-
|
|
19559
|
-
|
|
20284
|
+
let collected;
|
|
20285
|
+
let writable;
|
|
20286
|
+
let pullRequests;
|
|
20287
|
+
let snapshotFallback;
|
|
20288
|
+
const attempt = deps.snapshot ? await fetchHubBoardSnapshot(
|
|
20289
|
+
{
|
|
20290
|
+
owner: cfg.projectOwner,
|
|
20291
|
+
number: cfg.projectNumber,
|
|
20292
|
+
statusFieldId: cfg.statusFieldId,
|
|
20293
|
+
activeOnly: true,
|
|
20294
|
+
allowPartial: options.allowPartial === true
|
|
20295
|
+
},
|
|
20296
|
+
deps.snapshot
|
|
20297
|
+
) : void 0;
|
|
20298
|
+
if (attempt?.state === "refused") {
|
|
20299
|
+
throw new Error(attempt.reason);
|
|
20300
|
+
}
|
|
20301
|
+
if (attempt?.state === "ok") {
|
|
20302
|
+
const snapshot = attempt.snapshot;
|
|
20303
|
+
const currentRepo = await resolveCurrentRepo(options, deps);
|
|
20304
|
+
const warnings = [...snapshot.warnings];
|
|
20305
|
+
let partial = snapshot.partial;
|
|
20306
|
+
const read = nodesToItems(snapshot.nodes, warnings, cfg);
|
|
20307
|
+
if (read.failures.length) {
|
|
20308
|
+
const message = `partial board read: ${read.failures.length} item(s) came back truncated and were not read \u2014 ${read.failures.join(" | ")}`;
|
|
20309
|
+
if (!options.allowPartial) throw new Error(message);
|
|
20310
|
+
warnings.push(message);
|
|
20311
|
+
partial = true;
|
|
20312
|
+
}
|
|
20313
|
+
for (const unreadable of snapshot.unreadableRepos) {
|
|
20314
|
+
warnings.push(`partial claimable access read: ${unreadable.repo}: viewer write access UNREAD (${unreadable.error})`);
|
|
20315
|
+
partial = true;
|
|
20316
|
+
}
|
|
20317
|
+
collected = {
|
|
20318
|
+
items: read.items,
|
|
20319
|
+
viewer: snapshot.viewer,
|
|
20320
|
+
repo: currentRepo,
|
|
20321
|
+
projectId: snapshot.project.id,
|
|
20322
|
+
projectTitle: snapshot.project.title,
|
|
20323
|
+
warnings,
|
|
20324
|
+
partial
|
|
20325
|
+
};
|
|
20326
|
+
writable = {
|
|
20327
|
+
repos: new Set(snapshot.writableRepos.map((repo) => repo.toLowerCase())),
|
|
20328
|
+
unknown: new Set(snapshot.unreadableRepos.map((entry) => entry.repo.toLowerCase()))
|
|
20329
|
+
};
|
|
20330
|
+
pullRequests = snapshot.pullRequests;
|
|
20331
|
+
} else {
|
|
20332
|
+
if (attempt?.state === "unavailable") snapshotFallback = attempt.reason;
|
|
20333
|
+
collected = await collectBoardItems(cfg, { repo: options.repo, allowPartial: options.allowPartial, activeOnly: true }, deps);
|
|
20334
|
+
if (snapshotFallback) {
|
|
20335
|
+
collected.warnings.push(`Hub board snapshot unavailable (${snapshotFallback}) \u2014 served by the direct user-auth read (emergency fallback)`);
|
|
20336
|
+
}
|
|
20337
|
+
const probed = await resolveWritableReposForClaimables(collected.items, client);
|
|
20338
|
+
collected.warnings.push(...probed.warnings);
|
|
20339
|
+
collected.partial = collected.partial || probed.partial;
|
|
20340
|
+
writable = { repos: probed.repos, unknown: probed.unknown };
|
|
20341
|
+
}
|
|
19560
20342
|
const groups = partitionBoardItems(collected.items, collected.viewer, collected.repo, writableOrUnknown(writable));
|
|
19561
20343
|
const report = {
|
|
19562
20344
|
project: { owner: cfg.projectOwner, number: cfg.projectNumber, id: collected.projectId, title: collected.projectTitle || String(cfg.projectNumber) },
|
|
@@ -19565,7 +20347,8 @@ async function readBoard(options, deps = {}) {
|
|
|
19565
20347
|
...groups,
|
|
19566
20348
|
warnings: collected.warnings,
|
|
19567
20349
|
partial: collected.partial,
|
|
19568
|
-
source: "live"
|
|
20350
|
+
source: "live",
|
|
20351
|
+
...pullRequests ? { pullRequests } : {}
|
|
19569
20352
|
};
|
|
19570
20353
|
if (options.includeBundleDetails || options.includeAllBodies) {
|
|
19571
20354
|
await attachBundleDetails(report, client, options.allowPartial ?? false, { all: options.includeAllBodies });
|
|
@@ -19752,15 +20535,21 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
19752
20535
|
let body = flatItem.details?.body;
|
|
19753
20536
|
if (body === void 0) {
|
|
19754
20537
|
try {
|
|
19755
|
-
const
|
|
19756
|
-
body =
|
|
20538
|
+
const retryCommand = `mmi-cli oracle board claim ${flatItem.number}${options.check ? " --check" : ""}`;
|
|
20539
|
+
body = await readIssueBodyWithQuota(client, flatItem.repository, flatItem.number, {
|
|
20540
|
+
retryCommand,
|
|
20541
|
+
context: `board claim ${flatItem.ref} issue-body`
|
|
20542
|
+
});
|
|
19757
20543
|
} catch (e) {
|
|
20544
|
+
if (isRateLimitRefusal(e)) throw e;
|
|
19758
20545
|
throw new Error(
|
|
19759
20546
|
`${flatItem.ref} claim refused: could not read the issue body to check dependencies (${ghError(e)}); retry once the GitHub API is reachable`
|
|
19760
20547
|
);
|
|
19761
20548
|
}
|
|
19762
20549
|
}
|
|
19763
|
-
const gate = await dependencyBlocksClaim(client, body
|
|
20550
|
+
const gate = await dependencyBlocksClaim(client, body, {
|
|
20551
|
+
retryCommand: `mmi-cli oracle board claim ${flatItem.number}${options.check ? " --check" : ""}`
|
|
20552
|
+
});
|
|
19764
20553
|
if (gate.blocked) {
|
|
19765
20554
|
throw new Error(`${flatItem.ref} is not claimable: blocked on open ${gate.openDependencies.join(", ")}`);
|
|
19766
20555
|
}
|
|
@@ -20065,11 +20854,7 @@ async function fetchIssueProjectItem(client, cfg, selector) {
|
|
|
20065
20854
|
return { viewer, ...read.state === "ok" ? { item: read.item } : {} };
|
|
20066
20855
|
}
|
|
20067
20856
|
function isGitHubRateLimitError(e) {
|
|
20068
|
-
|
|
20069
|
-
if (e instanceof GitHubApiError && e.status === 403) {
|
|
20070
|
-
return /rate limit|abuse detection|secondary rate/i.test(e.message);
|
|
20071
|
-
}
|
|
20072
|
-
return /rate limit|API rate limit|secondary rate|abuse detection/i.test(ghError(e));
|
|
20857
|
+
return isRateLimitRefusal(e);
|
|
20073
20858
|
}
|
|
20074
20859
|
var ISSUE_DETAILS_GRAPHQL_COMMENTS_PAGE = 100;
|
|
20075
20860
|
var ISSUE_DETAILS_GRAPHQL_QUERY = `
|
|
@@ -20745,6 +21530,13 @@ function ghError(e) {
|
|
|
20745
21530
|
}
|
|
20746
21531
|
|
|
20747
21532
|
// src/attach-to-project.ts
|
|
21533
|
+
function boardAttachRateLimitedReceipt(resetEpochSeconds) {
|
|
21534
|
+
return {
|
|
21535
|
+
onBoard: false,
|
|
21536
|
+
boardAttach: "rate_limited",
|
|
21537
|
+
...typeof resetEpochSeconds === "number" ? { resetEpochSeconds, resetAt: new Date(resetEpochSeconds * 1e3).toISOString() } : {}
|
|
21538
|
+
};
|
|
21539
|
+
}
|
|
20748
21540
|
async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn = (m) => process.stderr.write(m), retry = {}) {
|
|
20749
21541
|
let projectItemId;
|
|
20750
21542
|
try {
|
|
@@ -22335,7 +23127,7 @@ function checkHotfixCoverage(options = {}) {
|
|
|
22335
23127
|
const out = git3(["rev-list", "--no-merges", range]).trim();
|
|
22336
23128
|
return out ? out.split("\n") : [];
|
|
22337
23129
|
};
|
|
22338
|
-
const
|
|
23130
|
+
const isAncestor2 = (sha, ref) => {
|
|
22339
23131
|
try {
|
|
22340
23132
|
git3(["merge-base", "--is-ancestor", sha, ref]);
|
|
22341
23133
|
return true;
|
|
@@ -22349,7 +23141,7 @@ function checkHotfixCoverage(options = {}) {
|
|
|
22349
23141
|
const out = git3(["patch-id", "--stable"], { input: diff }).trim();
|
|
22350
23142
|
return out ? out.split(" ")[0] : null;
|
|
22351
23143
|
};
|
|
22352
|
-
const
|
|
23144
|
+
const changedPaths2 = (sha) => {
|
|
22353
23145
|
const out = git3(["show", "--no-color", "--name-only", "--pretty=format:", sha]).trim();
|
|
22354
23146
|
return out ? out.split("\n") : [];
|
|
22355
23147
|
};
|
|
@@ -22370,13 +23162,13 @@ function checkHotfixCoverage(options = {}) {
|
|
|
22370
23162
|
};
|
|
22371
23163
|
const commits = mainOnly.map((sha) => {
|
|
22372
23164
|
const subject = git3(["log", "-1", "--format=%s", sha]).trim();
|
|
22373
|
-
const paths =
|
|
23165
|
+
const paths = changedPaths2(sha);
|
|
22374
23166
|
if (paths.length > 0 && paths.every((p) => manifestPaths.includes(p))) {
|
|
22375
23167
|
return { sha, subject, coverage: "exempt-distribution" };
|
|
22376
23168
|
}
|
|
22377
23169
|
const sources = cherrySources(sha);
|
|
22378
23170
|
if (sources.length > 0) {
|
|
22379
|
-
if (sources.every((s) =>
|
|
23171
|
+
if (sources.every((s) => isAncestor2(s, rcRef))) {
|
|
22380
23172
|
return { sha, subject, coverage: "trailer", sources };
|
|
22381
23173
|
}
|
|
22382
23174
|
if (ack.some((a) => sha.startsWith(a))) return { sha, subject, coverage: "acked", sources };
|
|
@@ -22399,7 +23191,7 @@ function checkHotfixCoverage(options = {}) {
|
|
|
22399
23191
|
function checkHotfixCarries(options) {
|
|
22400
23192
|
const { cwd = process.cwd(), branch, baseRef, targets } = options;
|
|
22401
23193
|
const git3 = options.git ?? ((args, opts) => (0, import_node_child_process11.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
|
|
22402
|
-
const
|
|
23194
|
+
const isAncestor2 = (sha, ref) => {
|
|
22403
23195
|
try {
|
|
22404
23196
|
git3(["merge-base", "--is-ancestor", sha, ref]);
|
|
22405
23197
|
return true;
|
|
@@ -22421,7 +23213,7 @@ function checkHotfixCarries(options) {
|
|
|
22421
23213
|
}
|
|
22422
23214
|
const shaMatches = (a, b) => a.startsWith(b) || b.startsWith(a);
|
|
22423
23215
|
const evaluated = targets.map((t) => {
|
|
22424
|
-
if (
|
|
23216
|
+
if (isAncestor2(t.sha, branch)) return { ...t, carried: true, via: "ancestor" };
|
|
22425
23217
|
if ([...carried].some((s) => shaMatches(s, t.sha))) return { ...t, carried: true, via: "trailer" };
|
|
22426
23218
|
return { ...t, carried: false };
|
|
22427
23219
|
});
|
|
@@ -22533,7 +23325,7 @@ function pathIsTolerated(path2, tolerated) {
|
|
|
22533
23325
|
return path2.startsWith(root);
|
|
22534
23326
|
});
|
|
22535
23327
|
}
|
|
22536
|
-
var HOTFIX_SKILL_TOLERATED_ROOTS = ["skills"
|
|
23328
|
+
var HOTFIX_SKILL_TOLERATED_ROOTS = ["skills"];
|
|
22537
23329
|
async function cherryPickWithToleratedPaths(deps, sha, tolerated) {
|
|
22538
23330
|
try {
|
|
22539
23331
|
await deps.run("git", ["cherry-pick", "-x", sha]);
|
|
@@ -23425,6 +24217,80 @@ async function announceRelease(deps, args) {
|
|
|
23425
24217
|
init_github_client();
|
|
23426
24218
|
init_hub_auth();
|
|
23427
24219
|
|
|
24220
|
+
// src/deploy-port-doctor.ts
|
|
24221
|
+
init_fetch_retry();
|
|
24222
|
+
init_client_version();
|
|
24223
|
+
|
|
24224
|
+
// ../infra/src/deploy-port-collision.ts
|
|
24225
|
+
function formatDeployPortCollisionReportLine(collision) {
|
|
24226
|
+
const owners = collision.owners.map((o) => `${o.slug}/${o.stage}`).join(", ");
|
|
24227
|
+
return `${collision.sshHost}:${collision.port} owned by ${owners} \u2014 reassign one with mmi-cli oracle org project set-deploy mutmutco/<slug> --stage <stage> --port <free-port> (do not auto-reassign live ports from doctor)`;
|
|
24228
|
+
}
|
|
24229
|
+
|
|
24230
|
+
// src/deploy-port-doctor.ts
|
|
24231
|
+
async function fetchDeployPortCollisions(deps) {
|
|
24232
|
+
if (!deps.baseUrl) return { ok: false, collisions: [], error: "no Hub API base URL configured" };
|
|
24233
|
+
const token = await deps.token();
|
|
24234
|
+
if (!token) return { ok: false, collisions: [], error: "no Hub session token", status: 401 };
|
|
24235
|
+
try {
|
|
24236
|
+
const res = await fetchWithRetry(
|
|
24237
|
+
deps.fetch ?? fetch,
|
|
24238
|
+
`${deps.baseUrl.replace(/\/$/, "")}${DEPLOY_PORT_COLLISIONS_PATH}`,
|
|
24239
|
+
{
|
|
24240
|
+
method: "GET",
|
|
24241
|
+
headers: {
|
|
24242
|
+
Accept: "application/json",
|
|
24243
|
+
Authorization: `Bearer ${token}`,
|
|
24244
|
+
...clientVersionHeaders()
|
|
24245
|
+
}
|
|
24246
|
+
},
|
|
24247
|
+
{ attempts: 3, timeoutMs: deps.timeoutMs ?? REGISTRY_FETCH_TIMEOUT_MS, sleep: deps.retrySleep }
|
|
24248
|
+
);
|
|
24249
|
+
if (res.status === 426) {
|
|
24250
|
+
return { ok: false, collisions: [], error: upgradeRequiredError(res, await res.json().catch(() => null)), status: 426 };
|
|
24251
|
+
}
|
|
24252
|
+
if (res.status === 403) {
|
|
24253
|
+
return { ok: false, collisions: [], error: "master-admin only", status: 403 };
|
|
24254
|
+
}
|
|
24255
|
+
if (!res.ok) {
|
|
24256
|
+
const detail = (await res.json().catch(() => null))?.error;
|
|
24257
|
+
return { ok: false, collisions: [], error: detail ?? `HTTP ${res.status}`, status: res.status };
|
|
24258
|
+
}
|
|
24259
|
+
const body = await res.json();
|
|
24260
|
+
const collisions = [];
|
|
24261
|
+
for (const c of body.collisions ?? []) {
|
|
24262
|
+
if (typeof c.sshHost !== "string" || typeof c.port !== "number" || !Array.isArray(c.owners)) continue;
|
|
24263
|
+
const owners = c.owners.filter((o) => typeof o?.slug === "string" && typeof o?.stage === "string").map((o) => ({ slug: o.slug, stage: o.stage }));
|
|
24264
|
+
if (!owners.length) continue;
|
|
24265
|
+
const report = typeof c.report === "string" && c.report.trim() ? c.report : formatDeployPortCollisionReportLine({
|
|
24266
|
+
sshHost: c.sshHost,
|
|
24267
|
+
port: c.port,
|
|
24268
|
+
owners: owners.map((o) => ({ ...o, sshHost: c.sshHost, port: c.port }))
|
|
24269
|
+
});
|
|
24270
|
+
collisions.push({ sshHost: c.sshHost, port: c.port, owners, report });
|
|
24271
|
+
}
|
|
24272
|
+
return { ok: collisions.length === 0, collisions };
|
|
24273
|
+
} catch (e) {
|
|
24274
|
+
return { ok: false, collisions: [], error: e.message };
|
|
24275
|
+
}
|
|
24276
|
+
}
|
|
24277
|
+
function renderDeployPortDoctor(report) {
|
|
24278
|
+
if (report.error) {
|
|
24279
|
+
return [`org project deploy doctor: ${report.error}`];
|
|
24280
|
+
}
|
|
24281
|
+
if (report.ok) {
|
|
24282
|
+
return ["org project deploy doctor: OK \u2014 no duplicate (sshHost, port) DEPLOY# coordinates"];
|
|
24283
|
+
}
|
|
24284
|
+
const lines = [
|
|
24285
|
+
`org project deploy doctor: CHECK \u2014 ${report.collisions.length} colliding (sshHost, port) coordinate(s)`
|
|
24286
|
+
];
|
|
24287
|
+
for (const c of report.collisions) lines.push(` ${c.report}`);
|
|
24288
|
+
lines.push(
|
|
24289
|
+
"Recovery: reassign one owner with mmi-cli oracle org project set-deploy mutmutco/<slug> --stage <stage> --port <free-port> (never auto-reassign from doctor)"
|
|
24290
|
+
);
|
|
24291
|
+
return lines;
|
|
24292
|
+
}
|
|
24293
|
+
|
|
23428
24294
|
// src/repo-index.ts
|
|
23429
24295
|
var import_node_crypto6 = require("node:crypto");
|
|
23430
24296
|
var import_node_child_process12 = require("node:child_process");
|
|
@@ -23454,6 +24320,66 @@ function isSafeRepoIndexPath(value) {
|
|
|
23454
24320
|
if (segments.some((segment) => !segment || segment === "." || segment === "..")) return false;
|
|
23455
24321
|
return !isHardDeniedRepoIndexPath(value);
|
|
23456
24322
|
}
|
|
24323
|
+
var REPO_INDEX_ALGORITHM_VERSION = 1;
|
|
24324
|
+
var REPO_INDEX_FILTER_VERSION = 2;
|
|
24325
|
+
var REPO_INDEX_MATERIAL_LAYOUT_VERSION = 2;
|
|
24326
|
+
var LEGACY_REPO_INDEX_PROVENANCE = Object.freeze({
|
|
24327
|
+
algorithmVersion: 1,
|
|
24328
|
+
filterVersion: 1,
|
|
24329
|
+
materialLayoutVersion: 1
|
|
24330
|
+
});
|
|
24331
|
+
var CURRENT_REPO_INDEX_PROVENANCE = Object.freeze({
|
|
24332
|
+
algorithmVersion: REPO_INDEX_ALGORITHM_VERSION,
|
|
24333
|
+
filterVersion: REPO_INDEX_FILTER_VERSION,
|
|
24334
|
+
materialLayoutVersion: REPO_INDEX_MATERIAL_LAYOUT_VERSION
|
|
24335
|
+
});
|
|
24336
|
+
function repoIndexProvenanceToken(provenance = CURRENT_REPO_INDEX_PROVENANCE) {
|
|
24337
|
+
const resolved = provenance && typeof provenance === "object" ? provenance : {};
|
|
24338
|
+
return `a${resolved.algorithmVersion}-f${resolved.filterVersion}-l${resolved.materialLayoutVersion}`;
|
|
24339
|
+
}
|
|
24340
|
+
var CURRENT_REPO_INDEX_PROVENANCE_TOKEN = repoIndexProvenanceToken(CURRENT_REPO_INDEX_PROVENANCE);
|
|
24341
|
+
var GENERATED_PROJECTION = Object.freeze([
|
|
24342
|
+
// Host plugin packages: the assembled skills/hooks/scripts/bin payload and the copied host
|
|
24343
|
+
// manifest directory. `packages/<host>-plugin/<file>` at the root stays admitted.
|
|
24344
|
+
/^packages\/(?:claude|codex|kimi|cursor|hermes)-plugin\/(?:skills|hooks|scripts|bin)\//,
|
|
24345
|
+
/^packages\/(?:claude|codex|kimi|cursor|hermes)-plugin\/\.[A-Za-z0-9-]+-plugin\//,
|
|
24346
|
+
// In-repo plugin roots assembled by the same declaration.
|
|
24347
|
+
/^\.kilo-plugin\/(?:skills|scripts)\//,
|
|
24348
|
+
/^\.pi-plugin\/(?:skills|scripts|extensions)\//,
|
|
24349
|
+
// Release metadata regenerated from the tracked tree at every prepare.
|
|
24350
|
+
/^distribution-bom\.json$/,
|
|
24351
|
+
// Cross-repo generator outputs the estate audit named.
|
|
24352
|
+
/^product\/connector\/generated\//,
|
|
24353
|
+
/^packages\/jerv-pi\/generated\/roster\//,
|
|
24354
|
+
/^docs\/\.release-inbox\//
|
|
24355
|
+
]);
|
|
24356
|
+
function isGeneratedRepoIndexProjection(value) {
|
|
24357
|
+
return typeof value === "string" && GENERATED_PROJECTION.some((pattern) => pattern.test(value));
|
|
24358
|
+
}
|
|
24359
|
+
function isCanonicalRepoIndexPath(value) {
|
|
24360
|
+
return isSafeRepoIndexPath(value) && !isGeneratedRepoIndexProjection(value);
|
|
24361
|
+
}
|
|
24362
|
+
function readRepoIndexProvenance(manifest) {
|
|
24363
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) return null;
|
|
24364
|
+
const provenance = manifest.indexProvenance;
|
|
24365
|
+
if (provenance === void 0) return { ...LEGACY_REPO_INDEX_PROVENANCE };
|
|
24366
|
+
if (!provenance || typeof provenance !== "object" || Array.isArray(provenance)) return null;
|
|
24367
|
+
const keys = ["algorithmVersion", "filterVersion", "materialLayoutVersion"];
|
|
24368
|
+
if (Object.keys(provenance).length !== keys.length) return null;
|
|
24369
|
+
for (const key of keys) {
|
|
24370
|
+
const value = provenance[key];
|
|
24371
|
+
if (!Number.isInteger(value) || value < 1) return null;
|
|
24372
|
+
}
|
|
24373
|
+
return {
|
|
24374
|
+
algorithmVersion: provenance.algorithmVersion,
|
|
24375
|
+
filterVersion: provenance.filterVersion,
|
|
24376
|
+
materialLayoutVersion: provenance.materialLayoutVersion
|
|
24377
|
+
};
|
|
24378
|
+
}
|
|
24379
|
+
function isRepoIndexDeltaCompatible(manifest) {
|
|
24380
|
+
const provenance = readRepoIndexProvenance(manifest);
|
|
24381
|
+
return provenance !== null && provenance.algorithmVersion === REPO_INDEX_ALGORITHM_VERSION && provenance.filterVersion === REPO_INDEX_FILTER_VERSION && provenance.materialLayoutVersion === REPO_INDEX_MATERIAL_LAYOUT_VERSION;
|
|
24382
|
+
}
|
|
23457
24383
|
|
|
23458
24384
|
// src/repo-index.ts
|
|
23459
24385
|
var REPO_INDEX_SCHEMA = 1;
|
|
@@ -23721,20 +24647,115 @@ function inferRepoSlug(cwd, exec = import_node_child_process12.execFileSync) {
|
|
|
23721
24647
|
}
|
|
23722
24648
|
|
|
23723
24649
|
// src/repo-index-cloud-client.ts
|
|
23724
|
-
var
|
|
24650
|
+
var import_node_crypto10 = require("node:crypto");
|
|
23725
24651
|
init_fetch_retry();
|
|
23726
24652
|
init_client_version();
|
|
23727
24653
|
init_compat();
|
|
23728
24654
|
|
|
23729
24655
|
// src/repo-index-v4/builder.ts
|
|
23730
|
-
var
|
|
23731
|
-
var
|
|
24656
|
+
var import_node_crypto9 = require("node:crypto");
|
|
24657
|
+
var import_node_child_process14 = require("node:child_process");
|
|
23732
24658
|
var import_node_fs25 = require("node:fs");
|
|
23733
24659
|
var import_node_os12 = require("node:os");
|
|
23734
24660
|
var import_node_path23 = require("node:path");
|
|
23735
24661
|
|
|
23736
|
-
//
|
|
24662
|
+
// ../infra/repo-index-material-buckets.mjs
|
|
23737
24663
|
var import_node_crypto7 = require("node:crypto");
|
|
24664
|
+
var REPO_INDEX_MATERIAL_BUCKET_COUNT = 2;
|
|
24665
|
+
var REPO_INDEX_MATERIAL_KINDS = Object.freeze(["chunks", "embeddings"]);
|
|
24666
|
+
var REPO_INDEX_MATERIAL_PREFIX = "material-v2";
|
|
24667
|
+
function canonicalMaterialJson(value) {
|
|
24668
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
24669
|
+
if (Array.isArray(value)) return `[${value.map(canonicalMaterialJson).join(",")}]`;
|
|
24670
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalMaterialJson(value[key])}`).join(",")}}`;
|
|
24671
|
+
}
|
|
24672
|
+
function repoIndexMaterialDigest(value) {
|
|
24673
|
+
return (0, import_node_crypto7.createHash)("sha256").update(canonicalMaterialJson(value)).digest("hex");
|
|
24674
|
+
}
|
|
24675
|
+
function normalizeRepoIndexMaterialPath(path2) {
|
|
24676
|
+
return String(path2).replace(/^\.\//, "").normalize("NFC");
|
|
24677
|
+
}
|
|
24678
|
+
function repoIndexMaterialBucket(path2) {
|
|
24679
|
+
const hash = (0, import_node_crypto7.createHash)("sha256").update(normalizeRepoIndexMaterialPath(path2), "utf8").digest();
|
|
24680
|
+
return hash.readUInt32BE(0) % REPO_INDEX_MATERIAL_BUCKET_COUNT;
|
|
24681
|
+
}
|
|
24682
|
+
function compareRepoIndexChunks(a, b) {
|
|
24683
|
+
const aPath = String(a?.path ?? "");
|
|
24684
|
+
const bPath = String(b?.path ?? "");
|
|
24685
|
+
if (aPath !== bPath) return aPath < bPath ? -1 : 1;
|
|
24686
|
+
const aLine = Number(a?.citations?.[0]?.startLine ?? 0);
|
|
24687
|
+
const bLine = Number(b?.citations?.[0]?.startLine ?? 0);
|
|
24688
|
+
if (aLine !== bLine) return aLine - bLine;
|
|
24689
|
+
const aId = String(a?.id ?? "");
|
|
24690
|
+
const bId = String(b?.id ?? "");
|
|
24691
|
+
return aId === bId ? 0 : aId < bId ? -1 : 1;
|
|
24692
|
+
}
|
|
24693
|
+
function compareRepoIndexEmbeddings(a, b) {
|
|
24694
|
+
const aId = String(a?.chunkId ?? "");
|
|
24695
|
+
const bId = String(b?.chunkId ?? "");
|
|
24696
|
+
return aId === bId ? 0 : aId < bId ? -1 : 1;
|
|
24697
|
+
}
|
|
24698
|
+
function stripRepoIndexChunkCommit(chunk) {
|
|
24699
|
+
const citations = Array.isArray(chunk?.citations) ? chunk.citations : [];
|
|
24700
|
+
return {
|
|
24701
|
+
...chunk,
|
|
24702
|
+
citations: citations.map((citation2) => {
|
|
24703
|
+
const rest = { ...citation2 };
|
|
24704
|
+
delete rest.commit;
|
|
24705
|
+
return rest;
|
|
24706
|
+
})
|
|
24707
|
+
};
|
|
24708
|
+
}
|
|
24709
|
+
function repoIndexMaterialIndexKey(repoBase, digest) {
|
|
24710
|
+
return `${repoBase}/${REPO_INDEX_MATERIAL_PREFIX}/index/${digest}.json`;
|
|
24711
|
+
}
|
|
24712
|
+
function repoIndexMaterialIndexUri(repo, digest) {
|
|
24713
|
+
return `s3://${repoIndexMaterialIndexKey(`repo-index/v4/${repo}`, digest)}`;
|
|
24714
|
+
}
|
|
24715
|
+
function repoIndexMaterialBucketBody(kind, bucket, records) {
|
|
24716
|
+
return { schemaVersion: 4, materialLayoutVersion: 2, kind, bucket, bucketCount: REPO_INDEX_MATERIAL_BUCKET_COUNT, count: records.length, records };
|
|
24717
|
+
}
|
|
24718
|
+
var bucketBody = repoIndexMaterialBucketBody;
|
|
24719
|
+
function buildRepoIndexMaterialLayout(repo, chunks, embeddings) {
|
|
24720
|
+
const chunkList = Array.isArray(chunks) ? chunks : [];
|
|
24721
|
+
const embeddingList = Array.isArray(embeddings) ? embeddings : [];
|
|
24722
|
+
const pathByChunkId = new Map(chunkList.map((chunk) => [String(chunk?.id), String(chunk?.path)]));
|
|
24723
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
24724
|
+
for (const kind of REPO_INDEX_MATERIAL_KINDS) {
|
|
24725
|
+
for (let bucket = 0; bucket < REPO_INDEX_MATERIAL_BUCKET_COUNT; bucket++) grouped.set(`${kind}:${bucket}`, []);
|
|
24726
|
+
}
|
|
24727
|
+
for (const chunk of chunkList) {
|
|
24728
|
+
grouped.get(`chunks:${repoIndexMaterialBucket(chunk?.path)}`).push(stripRepoIndexChunkCommit(chunk));
|
|
24729
|
+
}
|
|
24730
|
+
for (const embedding of embeddingList) {
|
|
24731
|
+
const path2 = pathByChunkId.get(String(embedding?.chunkId));
|
|
24732
|
+
if (path2 === void 0) throw new Error("repo-index material embeds a chunk that is not in the corpus");
|
|
24733
|
+
grouped.get(`embeddings:${repoIndexMaterialBucket(path2)}`).push(embedding);
|
|
24734
|
+
}
|
|
24735
|
+
const buckets = [];
|
|
24736
|
+
for (const kind of REPO_INDEX_MATERIAL_KINDS) {
|
|
24737
|
+
for (let bucket = 0; bucket < REPO_INDEX_MATERIAL_BUCKET_COUNT; bucket++) {
|
|
24738
|
+
const records = grouped.get(`${kind}:${bucket}`);
|
|
24739
|
+
records.sort(kind === "chunks" ? compareRepoIndexChunks : compareRepoIndexEmbeddings);
|
|
24740
|
+
const body = bucketBody(kind, bucket, records);
|
|
24741
|
+
buckets.push({ kind, bucket, count: records.length, digest: repoIndexMaterialDigest(body), body });
|
|
24742
|
+
}
|
|
24743
|
+
}
|
|
24744
|
+
const index = {
|
|
24745
|
+
schemaVersion: 4,
|
|
24746
|
+
materialLayoutVersion: 2,
|
|
24747
|
+
repo,
|
|
24748
|
+
bucketCount: REPO_INDEX_MATERIAL_BUCKET_COUNT,
|
|
24749
|
+
chunkCount: chunkList.length,
|
|
24750
|
+
embeddingCount: embeddingList.length,
|
|
24751
|
+
// Ordered descriptors: chunks buckets ascending, then embeddings buckets ascending.
|
|
24752
|
+
buckets: buckets.map(({ kind, bucket, count, digest }) => ({ kind, bucket, count, digest }))
|
|
24753
|
+
};
|
|
24754
|
+
return { bucketCount: REPO_INDEX_MATERIAL_BUCKET_COUNT, buckets, index, indexDigest: repoIndexMaterialDigest(index) };
|
|
24755
|
+
}
|
|
24756
|
+
|
|
24757
|
+
// src/repo-index-v4/chunks.ts
|
|
24758
|
+
var import_node_crypto8 = require("node:crypto");
|
|
23738
24759
|
var import_node_fs24 = require("node:fs");
|
|
23739
24760
|
var import_node_path22 = require("node:path");
|
|
23740
24761
|
|
|
@@ -23801,7 +24822,7 @@ async function parserFor(language) {
|
|
|
23801
24822
|
return parser;
|
|
23802
24823
|
}
|
|
23803
24824
|
function sha256(value) {
|
|
23804
|
-
return (0,
|
|
24825
|
+
return (0, import_node_crypto8.createHash)("sha256").update(value).digest("hex");
|
|
23805
24826
|
}
|
|
23806
24827
|
function pointerId(path2, kind, contentHash, start, end, symbol) {
|
|
23807
24828
|
return sha256(["repo-index-v4", path2, kind, contentHash, String(start), String(end), symbol ?? ""].join("\0"));
|
|
@@ -23889,12 +24910,17 @@ async function structuralChunksForSource(repo, commit, path2, source) {
|
|
|
23889
24910
|
parser?.delete();
|
|
23890
24911
|
}
|
|
23891
24912
|
}
|
|
23892
|
-
|
|
24913
|
+
function canonicalRepoIndexPaths(cwd) {
|
|
23893
24914
|
const candidates = listCandidatePaths(cwd).filter(isIndexablePath).sort((a, b) => a.localeCompare(b));
|
|
23894
24915
|
const ignored = defaultIsIgnored(cwd, candidates);
|
|
24916
|
+
return candidates.filter((path2) => !ignored.has(path2) && !isHardDeniedPath(path2) && isCanonicalRepoIndexPath(path2));
|
|
24917
|
+
}
|
|
24918
|
+
function compareV4Chunks(a, b) {
|
|
24919
|
+
return compareRepoIndexChunks(a, b);
|
|
24920
|
+
}
|
|
24921
|
+
async function buildStructuralChunksForPaths(cwd, repo, commit, paths) {
|
|
23895
24922
|
const chunks = [];
|
|
23896
|
-
for (const path2 of
|
|
23897
|
-
if (ignored.has(path2) || isHardDeniedPath(path2)) continue;
|
|
24923
|
+
for (const path2 of paths) {
|
|
23898
24924
|
const absolute = (0, import_node_path22.join)(cwd, ...path2.split("/"));
|
|
23899
24925
|
if (!(0, import_node_fs24.existsSync)(absolute)) continue;
|
|
23900
24926
|
let source;
|
|
@@ -23905,7 +24931,84 @@ async function buildStructuralChunks(cwd, repo, commit) {
|
|
|
23905
24931
|
}
|
|
23906
24932
|
chunks.push(...await structuralChunksForSource(repo, commit, path2, source));
|
|
23907
24933
|
}
|
|
23908
|
-
return chunks.sort(
|
|
24934
|
+
return chunks.sort(compareV4Chunks);
|
|
24935
|
+
}
|
|
24936
|
+
async function buildStructuralChunks(cwd, repo, commit) {
|
|
24937
|
+
return buildStructuralChunksForPaths(cwd, repo, commit, canonicalRepoIndexPaths(cwd));
|
|
24938
|
+
}
|
|
24939
|
+
|
|
24940
|
+
// src/repo-index-v4/delta.ts
|
|
24941
|
+
var import_node_child_process13 = require("node:child_process");
|
|
24942
|
+
var COMMIT = /^[a-f0-9]{40}$/;
|
|
24943
|
+
var V4_DELTA_DEEPEN_STEPS = [50, 250, 1e3];
|
|
24944
|
+
function gitRunner(cwd) {
|
|
24945
|
+
return (args) => {
|
|
24946
|
+
const result = (0, import_node_child_process13.spawnSync)("git", [...args], { cwd, encoding: "utf8", windowsHide: true, maxBuffer: 64 * 1024 * 1024 });
|
|
24947
|
+
if (result.error) return { status: -1, stdout: "" };
|
|
24948
|
+
return { status: typeof result.status === "number" ? result.status : -1, stdout: String(result.stdout ?? "") };
|
|
24949
|
+
};
|
|
24950
|
+
}
|
|
24951
|
+
function parseNameStatusZ(output) {
|
|
24952
|
+
const fields = output.split("\0");
|
|
24953
|
+
if (fields.length && fields[fields.length - 1] === "") fields.pop();
|
|
24954
|
+
const changes = [];
|
|
24955
|
+
for (let i = 0; i < fields.length; ) {
|
|
24956
|
+
const raw = fields[i++] ?? "";
|
|
24957
|
+
const letter = raw[0];
|
|
24958
|
+
if (!letter || !/^[AMDTRC]\d*$/.test(raw)) return null;
|
|
24959
|
+
if (letter === "R" || letter === "C") {
|
|
24960
|
+
const oldPath = fields[i++];
|
|
24961
|
+
const path3 = fields[i++];
|
|
24962
|
+
if (!oldPath || !path3) return null;
|
|
24963
|
+
changes.push(letter === "C" ? { status: "A", path: path3 } : { status: "R", path: path3, oldPath });
|
|
24964
|
+
continue;
|
|
24965
|
+
}
|
|
24966
|
+
const path2 = fields[i++];
|
|
24967
|
+
if (!path2) return null;
|
|
24968
|
+
changes.push({ status: letter, path: path2 });
|
|
24969
|
+
}
|
|
24970
|
+
return changes;
|
|
24971
|
+
}
|
|
24972
|
+
function changedPaths(changes) {
|
|
24973
|
+
return [...new Set(changes.filter((change) => change.status !== "D").map((change) => change.path))].sort();
|
|
24974
|
+
}
|
|
24975
|
+
function removedPaths(changes) {
|
|
24976
|
+
const removed = changes.flatMap((change) => change.status === "D" ? [change.path] : change.status === "R" && change.oldPath ? [change.oldPath] : []);
|
|
24977
|
+
return [...new Set(removed)].sort();
|
|
24978
|
+
}
|
|
24979
|
+
function commitPresent(git3, commit) {
|
|
24980
|
+
return git3(["cat-file", "-e", `${commit}^{commit}`]).status === 0;
|
|
24981
|
+
}
|
|
24982
|
+
function isAncestor(git3, base, head) {
|
|
24983
|
+
return git3(["merge-base", "--is-ancestor", base, head]).status === 0;
|
|
24984
|
+
}
|
|
24985
|
+
function planRepoIndexV4Delta(opts) {
|
|
24986
|
+
const headCommit = opts.headCommit.toLowerCase();
|
|
24987
|
+
const baseCommit = opts.baseCommit ? opts.baseCommit.toLowerCase() : null;
|
|
24988
|
+
const full = (fallbackReason) => ({ mode: "full", headCommit, fallbackReason, ...baseCommit ? { baseCommit } : {} });
|
|
24989
|
+
if (opts.forceFull) return full("explicit-full-rebuild");
|
|
24990
|
+
if (!baseCommit || !COMMIT.test(baseCommit) || baseCommit === headCommit) return full("no-active-authority");
|
|
24991
|
+
if (opts.basePipelineCompatible === false) return full("incompatible-base-provenance");
|
|
24992
|
+
if (opts.hasPriorMaterial === false) return full("no-prior-material");
|
|
24993
|
+
const { git: git3 } = opts;
|
|
24994
|
+
let usable = commitPresent(git3, baseCommit) && isAncestor(git3, baseCommit, headCommit);
|
|
24995
|
+
for (const depth of V4_DELTA_DEEPEN_STEPS) {
|
|
24996
|
+
if (usable || !opts.deepen) break;
|
|
24997
|
+
try {
|
|
24998
|
+
opts.deepen(depth);
|
|
24999
|
+
} catch {
|
|
25000
|
+
break;
|
|
25001
|
+
}
|
|
25002
|
+
usable = commitPresent(git3, baseCommit) && isAncestor(git3, baseCommit, headCommit);
|
|
25003
|
+
}
|
|
25004
|
+
if (!usable) {
|
|
25005
|
+
return full(commitPresent(git3, baseCommit) ? "base-not-ancestor" : "base-commit-unavailable");
|
|
25006
|
+
}
|
|
25007
|
+
const diff = git3(["diff", "--name-status", "-z", "--find-renames", baseCommit, headCommit]);
|
|
25008
|
+
if (diff.status !== 0) return full("diff-unreadable");
|
|
25009
|
+
const changes = parseNameStatusZ(diff.stdout);
|
|
25010
|
+
if (!changes) return full("unsupported-change-status");
|
|
25011
|
+
return { mode: "delta", baseCommit, headCommit, changes };
|
|
23909
25012
|
}
|
|
23910
25013
|
|
|
23911
25014
|
// src/repo-index-v4/builder.ts
|
|
@@ -23913,7 +25016,7 @@ var V4_MAX_CHUNKS = 1e4;
|
|
|
23913
25016
|
var V4_MAX_ARTIFACT_BYTES = 64 * 1024 * 1024;
|
|
23914
25017
|
var V4_EMBED_BATCH = 32;
|
|
23915
25018
|
var V4_VECTOR_DECIMAL_PLACES = 6;
|
|
23916
|
-
var
|
|
25019
|
+
var COMMIT2 = /^[a-f0-9]{40}$/;
|
|
23917
25020
|
function canonicalJson(value) {
|
|
23918
25021
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
23919
25022
|
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
@@ -23921,7 +25024,7 @@ function canonicalJson(value) {
|
|
|
23921
25024
|
return `{${Object.keys(record).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson(record[k])}`).join(",")}}`;
|
|
23922
25025
|
}
|
|
23923
25026
|
function sha2562(value) {
|
|
23924
|
-
return (0,
|
|
25027
|
+
return (0, import_node_crypto9.createHash)("sha256").update(canonicalJson(value)).digest("hex");
|
|
23925
25028
|
}
|
|
23926
25029
|
function quantizeV4Vector(vector) {
|
|
23927
25030
|
return vector.map((value) => Number(value.toFixed(V4_VECTOR_DECIMAL_PLACES)));
|
|
@@ -23930,11 +25033,11 @@ function statePath(cwd) {
|
|
|
23930
25033
|
return repoRuntimeStatePath(cwd, "repo-index", "v4.json");
|
|
23931
25034
|
}
|
|
23932
25035
|
function git(cwd, args) {
|
|
23933
|
-
return String((0,
|
|
25036
|
+
return String((0, import_node_child_process14.execFileSync)("git", args, { cwd, encoding: "utf8", windowsHide: true, stdio: ["ignore", "pipe", "ignore"] })).trim();
|
|
23934
25037
|
}
|
|
23935
25038
|
function gitInfo(cwd) {
|
|
23936
25039
|
const commit = git(cwd, ["rev-parse", "HEAD"]).toLowerCase();
|
|
23937
|
-
if (!
|
|
25040
|
+
if (!COMMIT2.test(commit)) throw new Error("repo-index v4 requires an exact git HEAD commit");
|
|
23938
25041
|
let defaultBranch = "main";
|
|
23939
25042
|
try {
|
|
23940
25043
|
defaultBranch = git(cwd, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]).replace(/^origin\//, "") || defaultBranch;
|
|
@@ -23956,6 +25059,9 @@ function prior(cwd) {
|
|
|
23956
25059
|
return null;
|
|
23957
25060
|
}
|
|
23958
25061
|
}
|
|
25062
|
+
function repoIndexV4DeltaBase(envelope) {
|
|
25063
|
+
return envelope && isRepoIndexDeltaCompatible(envelope.manifest) ? envelope : null;
|
|
25064
|
+
}
|
|
23959
25065
|
function tombstone(repo, commit, path2, createdAt) {
|
|
23960
25066
|
const base = { repo, commit, path: path2, reason: "deleted", createdAt };
|
|
23961
25067
|
return { ...base, id: sha2562(base) };
|
|
@@ -23990,7 +25096,7 @@ function runEmbedderOnce(cwd, chunks, modelDirectory, createdAt) {
|
|
|
23990
25096
|
(0, import_node_fs25.writeFileSync)(requestFile, JSON.stringify(request));
|
|
23991
25097
|
let result;
|
|
23992
25098
|
try {
|
|
23993
|
-
result = (0,
|
|
25099
|
+
result = (0, import_node_child_process14.spawnSync)(process.execPath, [file, requestFile], { encoding: "utf8", windowsHide: true, timeout: V4_EMBED_TIMEOUT_MS, maxBuffer: V4_MAX_ARTIFACT_BYTES, env });
|
|
23994
25100
|
} finally {
|
|
23995
25101
|
(0, import_node_fs25.rmSync)(requestDir, { recursive: true, force: true });
|
|
23996
25102
|
}
|
|
@@ -24046,23 +25152,53 @@ function runEmbedder(cwd, chunks, modelDirectory, createdAt) {
|
|
|
24046
25152
|
const stderr = runner.stderrTail ? ` stderr=${runner.stderrTail}` : "";
|
|
24047
25153
|
throw new Error(`repo-index v4 embedding runner failed: ${runner.detail ?? "unknown"}${code}${stdout}${stderr}`);
|
|
24048
25154
|
}
|
|
25155
|
+
function restampCitations(chunk, repo, commit) {
|
|
25156
|
+
return { ...chunk, citations: chunk.citations.map((citation2) => ({ ...citation2, repo, commit })) };
|
|
25157
|
+
}
|
|
24049
25158
|
async function buildRepoIndexV4(cwd, repo, opts = {}) {
|
|
25159
|
+
return (await buildRepoIndexV4Detailed(cwd, repo, opts)).envelope;
|
|
25160
|
+
}
|
|
25161
|
+
async function buildRepoIndexV4Detailed(cwd, repo, opts = {}) {
|
|
24050
25162
|
const { commit, defaultBranch, createdAt } = gitInfo(cwd);
|
|
24051
|
-
const
|
|
24052
|
-
if (chunks.length > V4_MAX_CHUNKS) throw new Error(`repo-index v4 exceeds ${V4_MAX_CHUNKS} chunk ceiling`);
|
|
25163
|
+
const delta = opts.delta;
|
|
24053
25164
|
const old = prior(cwd);
|
|
25165
|
+
const deltaBase = repoIndexV4DeltaBase(old);
|
|
25166
|
+
let chunks;
|
|
25167
|
+
let carried = 0;
|
|
25168
|
+
let built = 0;
|
|
25169
|
+
let changedCount = 0;
|
|
25170
|
+
let removedCount = 0;
|
|
25171
|
+
if (delta) {
|
|
25172
|
+
const admitted = new Set(canonicalRepoIndexPaths(cwd));
|
|
25173
|
+
const changed = new Set(changedPaths(delta.changes));
|
|
25174
|
+
const removed = new Set(removedPaths(delta.changes));
|
|
25175
|
+
changedCount = changed.size;
|
|
25176
|
+
removedCount = removed.size;
|
|
25177
|
+
const rebuilt = await buildStructuralChunksForPaths(cwd, repo, commit, [...changed].filter((path3) => admitted.has(path3)).sort());
|
|
25178
|
+
const kept = delta.prior.chunks.filter((chunk) => !changed.has(chunk.path) && !removed.has(chunk.path) && admitted.has(chunk.path)).map((chunk) => restampCitations(chunk, repo, commit));
|
|
25179
|
+
carried = kept.length;
|
|
25180
|
+
built = rebuilt.length;
|
|
25181
|
+
chunks = [...kept, ...rebuilt].sort(compareV4Chunks);
|
|
25182
|
+
} else {
|
|
25183
|
+
chunks = await buildStructuralChunks(cwd, repo, commit);
|
|
25184
|
+
built = chunks.length;
|
|
25185
|
+
}
|
|
25186
|
+
if (chunks.length > V4_MAX_CHUNKS) throw new Error(`repo-index v4 exceeds ${V4_MAX_CHUNKS} chunk ceiling`);
|
|
24054
25187
|
const currentPaths = new Set(chunks.map((c) => c.path));
|
|
25188
|
+
const priorChunks = delta ? delta.prior.chunks : old?.manifest.chunks ?? [];
|
|
25189
|
+
const priorTombstones = delta ? delta.prior.tombstones ?? [] : old?.manifest.tombstones ?? [];
|
|
24055
25190
|
const tombstonePaths = [
|
|
24056
|
-
...
|
|
24057
|
-
...
|
|
25191
|
+
...priorTombstones.map((t) => t.path),
|
|
25192
|
+
...priorChunks.map((c) => c.path).filter((path3) => !currentPaths.has(path3))
|
|
24058
25193
|
];
|
|
24059
25194
|
const tombstones = [...new Set(tombstonePaths)].sort().map((path3) => tombstone(repo, commit, path3, createdAt));
|
|
24060
|
-
const
|
|
25195
|
+
const embeddingSource = delta ? delta.prior : { chunks: deltaBase?.manifest.chunks ?? [], embeddings: deltaBase?.manifest.embeddings ?? [] };
|
|
25196
|
+
const oldChunkHashById = new Map(embeddingSource.chunks.map((c) => [c.id, c.contentHash]));
|
|
24061
25197
|
const reusableEmbedding = (embedding) => {
|
|
24062
25198
|
const norm = Math.hypot(...embedding.vector);
|
|
24063
25199
|
return embedding.vector.length === 384 && embedding.vector.every(Number.isFinite) && norm >= 0.98 && norm <= 1.02 && embedding.provenance.provider === "local" && embedding.provenance.modelDigest === "828e1496d7fabb79cfa4dcd84fa38625c0d3d21da474a00f08db0f559940cf35" && embedding.provenance.dimensions === 384 && embedding.provenance.input === "pointer-safe-structural-chunk";
|
|
24064
25200
|
};
|
|
24065
|
-
const oldEmbeddingByHash = new Map(
|
|
25201
|
+
const oldEmbeddingByHash = new Map(embeddingSource.embeddings.filter(reusableEmbedding).map((e) => [oldChunkHashById.get(e.chunkId), e]));
|
|
24066
25202
|
const reusable = chunks.flatMap((chunk) => {
|
|
24067
25203
|
const priorEmbedding = oldEmbeddingByHash.get(chunk.contentHash);
|
|
24068
25204
|
return priorEmbedding ? [{ ...priorEmbedding, chunkId: chunk.id }] : [];
|
|
@@ -24070,21 +25206,21 @@ async function buildRepoIndexV4(cwd, repo, opts = {}) {
|
|
|
24070
25206
|
const reusableIds = new Set(reusable.map((e) => e.chunkId));
|
|
24071
25207
|
const missing = chunks.filter((chunk) => !reusableIds.has(chunk.id));
|
|
24072
25208
|
const generated = missing.length === 0 ? { embeddings: [] } : opts.embed === false ? { embeddings: [], reason: "embeddings-unavailable" } : runEmbedder(cwd, missing, opts.modelDirectory, createdAt);
|
|
24073
|
-
const embeddings = [...reusable, ...generated.embeddings].map((embedding) => ({ ...embedding, vector: quantizeV4Vector(embedding.vector) })).sort(
|
|
24074
|
-
const
|
|
24075
|
-
const
|
|
24076
|
-
|
|
24077
|
-
const identity2 = { repo, commit, immutable: true, kind, uri: `s3://repo-index/v4/${repo}/${commit}/${digest}.${kind}.json`, sha256: digest, createdAt };
|
|
25209
|
+
const embeddings = [...reusable, ...generated.embeddings].map((embedding) => ({ ...embedding, vector: quantizeV4Vector(embedding.vector) })).sort(compareRepoIndexEmbeddings);
|
|
25210
|
+
const materialIndexDigest = buildRepoIndexMaterialLayout(repo, chunks, embeddings).indexDigest;
|
|
25211
|
+
const artifact = (kind, digest, uri) => {
|
|
25212
|
+
const identity2 = { repo, commit, immutable: true, kind, uri, sha256: digest, createdAt };
|
|
24078
25213
|
return { ...identity2, id: sha2562(identity2) };
|
|
24079
25214
|
};
|
|
24080
|
-
const artifacts = [artifact("
|
|
25215
|
+
const artifacts = [artifact("material-index", materialIndexDigest, repoIndexMaterialIndexUri(repo, materialIndexDigest))];
|
|
24081
25216
|
const grammarProvenance = {
|
|
24082
25217
|
runtime: { name: "web-tree-sitter", version: "0.26.12", license: "MIT" },
|
|
24083
25218
|
grammarPack: { name: "tree-sitter-wasm", version: "1.1.4", license: "MIT", digest: "f3089ddf2c9615a423783b645c4dfb23ccda30807bbd059746f583952357c489" },
|
|
24084
25219
|
languages: ["go", "java", "javascript", "jsx", "kotlin", "python", "rust", "tsx", "typescript"]
|
|
24085
25220
|
};
|
|
24086
25221
|
const rrf = { algorithm: "reciprocal-rank-fusion", k: 60, lexicalWeight: 1, semanticWeight: 1 };
|
|
24087
|
-
const
|
|
25222
|
+
const indexProvenance = { ...CURRENT_REPO_INDEX_PROVENANCE };
|
|
25223
|
+
const identity = { repo, commit, defaultBranch, immutable: true, ...delta ? { parentCommit: delta.baseCommit } : {}, createdAt, grammarProvenance, indexProvenance, chunks, embeddings, artifacts, tombstones, rrf };
|
|
24088
25224
|
const manifest = { ...identity, id: sha2562(identity) };
|
|
24089
25225
|
const complete = embeddings.length === chunks.length;
|
|
24090
25226
|
const envelope = {
|
|
@@ -24098,7 +25234,23 @@ async function buildRepoIndexV4(cwd, repo, opts = {}) {
|
|
|
24098
25234
|
(0, import_node_fs25.mkdirSync)((0, import_node_path23.dirname)(path2), { recursive: true });
|
|
24099
25235
|
(0, import_node_fs25.writeFileSync)(path2, `${JSON.stringify(envelope, null, 2)}
|
|
24100
25236
|
`, "utf8");
|
|
24101
|
-
|
|
25237
|
+
const metrics = {
|
|
25238
|
+
mode: delta ? "delta" : "full",
|
|
25239
|
+
...delta ? { baseCommit: delta.baseCommit } : {},
|
|
25240
|
+
changedPaths: changedCount,
|
|
25241
|
+
removedPaths: removedCount,
|
|
25242
|
+
chunksTotal: chunks.length,
|
|
25243
|
+
chunksCarried: carried,
|
|
25244
|
+
chunksBuilt: built,
|
|
25245
|
+
embeddingsReused: reusable.length,
|
|
25246
|
+
embeddingsEmbedded: generated.embeddings.length,
|
|
25247
|
+
tombstones: tombstones.length
|
|
25248
|
+
};
|
|
25249
|
+
return { envelope, metrics };
|
|
25250
|
+
}
|
|
25251
|
+
function formatV4BuildMetrics(repo, metrics) {
|
|
25252
|
+
const base = metrics.baseCommit ? ` base=${metrics.baseCommit.slice(0, 12)}` : "";
|
|
25253
|
+
return `${repo}: ${metrics.mode}${base} changed=${metrics.changedPaths} removed=${metrics.removedPaths} chunks=${metrics.chunksTotal} (carried=${metrics.chunksCarried} built=${metrics.chunksBuilt}) embeddings(reused=${metrics.embeddingsReused} new=${metrics.embeddingsEmbedded}) tombstones=${metrics.tombstones}`;
|
|
24102
25254
|
}
|
|
24103
25255
|
function repoIndexV4StorePath(cwd) {
|
|
24104
25256
|
return statePath(cwd);
|
|
@@ -24111,12 +25263,12 @@ var V4_STAGE_MAX_SHARDS = 128;
|
|
|
24111
25263
|
function encodedBytes(value) {
|
|
24112
25264
|
return Buffer.byteLength(JSON.stringify(value));
|
|
24113
25265
|
}
|
|
24114
|
-
function split(kind, records, manifest) {
|
|
25266
|
+
function split(kind, records, manifest, bucket) {
|
|
24115
25267
|
const groups = [];
|
|
24116
25268
|
let group = [];
|
|
24117
25269
|
for (const record of records) {
|
|
24118
25270
|
const next = [...group, record];
|
|
24119
|
-
const probe = { repo: manifest.repo, commit: manifest.commit, manifestId: manifest.id, kind, index: 127, total: 128, digest: "0".repeat(64), records: next };
|
|
25271
|
+
const probe = { repo: manifest.repo, commit: manifest.commit, manifestId: manifest.id, kind, ...bucket === void 0 ? {} : { bucket }, index: 127, total: 128, digest: "0".repeat(64), records: next };
|
|
24120
25272
|
if (group.length && (group.length >= V4_STAGE_MAX_RECORDS || encodedBytes(probe) > V4_STAGE_MAX_BODY_BYTES)) {
|
|
24121
25273
|
groups.push(group);
|
|
24122
25274
|
group = [record];
|
|
@@ -24132,6 +25284,7 @@ function split(kind, records, manifest) {
|
|
|
24132
25284
|
commit: manifest.commit,
|
|
24133
25285
|
manifestId: manifest.id,
|
|
24134
25286
|
kind,
|
|
25287
|
+
...bucket === void 0 ? {} : { bucket },
|
|
24135
25288
|
index,
|
|
24136
25289
|
total: groups.length,
|
|
24137
25290
|
digest: sha2562(items),
|
|
@@ -24141,11 +25294,30 @@ function split(kind, records, manifest) {
|
|
|
24141
25294
|
return request;
|
|
24142
25295
|
});
|
|
24143
25296
|
}
|
|
24144
|
-
function shardRepoIndexV4(envelope) {
|
|
25297
|
+
function shardRepoIndexV4(envelope, opts = {}) {
|
|
24145
25298
|
const { chunks, embeddings, ...header } = envelope.manifest;
|
|
24146
|
-
const
|
|
24147
|
-
|
|
24148
|
-
|
|
25299
|
+
const identity = { repo: header.repo, commit: header.commit, manifestId: header.id, header, status: envelope.status };
|
|
25300
|
+
if ((header.indexProvenance?.materialLayoutVersion ?? 1) < 2) {
|
|
25301
|
+
const stages2 = [...split("chunks", chunks, envelope.manifest), ...split("embeddings", embeddings ?? [], envelope.manifest)];
|
|
25302
|
+
const shards2 = stages2.map(({ kind, index, total, digest, records }) => ({ kind, index, total, digest, count: records.length }));
|
|
25303
|
+
return { stages: stages2, finalize: { ...identity, shards: shards2 } };
|
|
25304
|
+
}
|
|
25305
|
+
const reusable = new Set(opts.priorBucketDigests ?? []);
|
|
25306
|
+
const layout = buildRepoIndexMaterialLayout(header.repo, chunks, embeddings ?? []);
|
|
25307
|
+
const stages = [];
|
|
25308
|
+
const shards = [];
|
|
25309
|
+
const buckets = layout.buckets.map((bucket) => {
|
|
25310
|
+
if (reusable.has(bucket.digest)) return { kind: bucket.kind, bucket: bucket.bucket, count: bucket.count, digest: bucket.digest, shards: 0 };
|
|
25311
|
+
const fragments = split(bucket.kind, bucket.body.records, envelope.manifest, bucket.bucket);
|
|
25312
|
+
stages.push(...fragments);
|
|
25313
|
+
shards.push(...fragments.map(({ kind, index, total, digest, records }) => ({ kind, bucket: bucket.bucket, index, total, digest, count: records.length })));
|
|
25314
|
+
return { kind: bucket.kind, bucket: bucket.bucket, count: bucket.count, digest: bucket.digest, shards: fragments.length };
|
|
25315
|
+
});
|
|
25316
|
+
if (buckets.length !== 2 * REPO_INDEX_MATERIAL_BUCKET_COUNT) throw new Error("repo-index v4 material layout is incomplete");
|
|
25317
|
+
return { stages, finalize: { ...identity, shards, materialLayout: 2, buckets } };
|
|
25318
|
+
}
|
|
25319
|
+
function repoIndexV4BucketDigests(repo, chunks, embeddings) {
|
|
25320
|
+
return buildRepoIndexMaterialLayout(repo, chunks, embeddings).buckets.map((bucket) => bucket.digest);
|
|
24149
25321
|
}
|
|
24150
25322
|
|
|
24151
25323
|
// src/repo-index-cloud-client.ts
|
|
@@ -24154,6 +25326,55 @@ async function repoIndexSourceHostHeaders() {
|
|
|
24154
25326
|
const { detectSurface: detectSurface2 } = await Promise.resolve().then(() => (init_plugin_guard_io(), plugin_guard_io_exports));
|
|
24155
25327
|
return { [SOURCE_HOST_HEADER]: detectSurface2(process.env) };
|
|
24156
25328
|
}
|
|
25329
|
+
var REPO_INDEX_SEARCH_LEGACY_V4_UNAVAILABLE = "repo-index search gap \u2014 Hub returned untyped v4 unavailability (not a CLI version gate); run `mmi-cli oracle repo-index status --cloud --json`, then `mmi-cli harbour org schedules run MMI-Hub/repo-index-reconcile` if authorities are incomplete";
|
|
25330
|
+
function isBareRepoIndexV4Unavailable(msg) {
|
|
25331
|
+
return /^v4\s+unavailable$/i.test(msg.replace(/\s+/g, " ").trim());
|
|
25332
|
+
}
|
|
25333
|
+
function refuseBareRepoIndexV4Unavailable(msg) {
|
|
25334
|
+
return isBareRepoIndexV4Unavailable(msg) ? REPO_INDEX_SEARCH_LEGACY_V4_UNAVAILABLE : msg;
|
|
25335
|
+
}
|
|
25336
|
+
function explainRepoIndexSearchError(body, status) {
|
|
25337
|
+
const errorClass = typeof body.errorClass === "string" ? body.errorClass.trim().toLowerCase() : "";
|
|
25338
|
+
const failures = Array.isArray(body.v4Authority?.authorityFailures) ? body.v4Authority.authorityFailures : [];
|
|
25339
|
+
const failureSummary = failures.length ? ` \u2014 ${failures.length} authority failure(s): ${failures.slice(0, 8).map((f) => `${f.repo}:${f.reason}`).join(", ")}${failures.length > 8 ? "\u2026" : ""}` : "";
|
|
25340
|
+
const next = typeof body.next === "string" && body.next.trim() ? ` \u2014 ${body.next.trim()}` : "";
|
|
25341
|
+
switch (errorClass) {
|
|
25342
|
+
case "incomplete-authority-cohort":
|
|
25343
|
+
return "v4 authority cohort incomplete \u2014 missing or unreadable active v4 authority for one or more roster repos" + failureSummary + (next || " \u2014 run `mmi-cli harbour org schedules run MMI-Hub/repo-index-reconcile`");
|
|
25344
|
+
case "query-embedding-unavailable":
|
|
25345
|
+
return "v4 query embedding unavailable \u2014 Hub could not embed the query (pinned BGE path)";
|
|
25346
|
+
case "estate-exceeds-v4-reader-bound":
|
|
25347
|
+
return "v4 search refused \u2014 estate exceeds bounded v4 reader";
|
|
25348
|
+
case "v4-read-failure":
|
|
25349
|
+
return "v4 authority read failed \u2014 Hub could not load v4 index material";
|
|
25350
|
+
default:
|
|
25351
|
+
break;
|
|
25352
|
+
}
|
|
25353
|
+
const raw = typeof body.error === "string" ? body.error.replace(/\s+/g, " ").trim() : "";
|
|
25354
|
+
if (isBareRepoIndexV4Unavailable(raw)) return REPO_INDEX_SEARCH_LEGACY_V4_UNAVAILABLE;
|
|
25355
|
+
return refuseBareRepoIndexV4Unavailable(raw || `search HTTP ${status ?? "error"}`);
|
|
25356
|
+
}
|
|
25357
|
+
var REPO_INDEX_SEARCH_NOT_READY_NEXT = "run `mmi-cli harbour org schedules run MMI-Hub/repo-index-reconcile` then re-check `mmi-cli oracle repo-index status --cloud --json`";
|
|
25358
|
+
function asCloudSearchNotReadyReceipt(failure) {
|
|
25359
|
+
return {
|
|
25360
|
+
ok: false,
|
|
25361
|
+
error: refuseBareRepoIndexV4Unavailable(failure.error),
|
|
25362
|
+
...failure.status !== void 0 ? { status: failure.status } : {},
|
|
25363
|
+
...typeof failure.errorClass === "string" ? { errorClass: failure.errorClass } : {},
|
|
25364
|
+
retrieval: failure.retrieval?.served === "unavailable" ? failure.retrieval : { requested: "v4", served: "unavailable" },
|
|
25365
|
+
...failure.v4Authority ? { v4Authority: failure.v4Authority } : {},
|
|
25366
|
+
...Array.isArray(failure.omittedRepos) ? { omittedRepos: failure.omittedRepos } : {},
|
|
25367
|
+
next: typeof failure.next === "string" && failure.next.trim() ? failure.next : REPO_INDEX_SEARCH_NOT_READY_NEXT
|
|
25368
|
+
};
|
|
25369
|
+
}
|
|
25370
|
+
function shouldRetryRepoIndexSearchLexical(failure, attemptedMode) {
|
|
25371
|
+
if (attemptedMode === "lexical") return false;
|
|
25372
|
+
const errorClass = typeof failure.errorClass === "string" ? failure.errorClass.trim().toLowerCase() : "";
|
|
25373
|
+
if (errorClass === "query-embedding-unavailable") return true;
|
|
25374
|
+
const error = typeof failure.error === "string" ? failure.error : "";
|
|
25375
|
+
if (isBareRepoIndexV4Unavailable(error)) return true;
|
|
25376
|
+
return /not a CLI version gate/i.test(error);
|
|
25377
|
+
}
|
|
24157
25378
|
var WARMUP_MAX_PASSES = 3;
|
|
24158
25379
|
async function probeRepoIndexV4ReadinessCloud(queries, deps) {
|
|
24159
25380
|
if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
|
|
@@ -24202,28 +25423,128 @@ async function probeRepoIndexV4ReadinessCloud(queries, deps) {
|
|
|
24202
25423
|
return { ok: false, error: error.message };
|
|
24203
25424
|
}
|
|
24204
25425
|
}
|
|
24205
|
-
|
|
25426
|
+
function priorV4BucketDigests(repo, prior2) {
|
|
25427
|
+
return prior2.deltaCompatible ? repoIndexV4BucketDigests(repo, prior2.chunks, prior2.embeddings) : [];
|
|
25428
|
+
}
|
|
25429
|
+
var REPO_INDEX_V4_MATERIAL_ROUTE = "/repo-index/v4/material";
|
|
25430
|
+
var V4_MATERIAL_MAX_PAGES = 256;
|
|
25431
|
+
async function fetchV4MaterialKind(repo, kind, deps, request, pin) {
|
|
25432
|
+
const records = [];
|
|
25433
|
+
let pageToken;
|
|
25434
|
+
let commit = pin?.commit;
|
|
25435
|
+
let digest = pin?.digest;
|
|
25436
|
+
let arrayDigest;
|
|
25437
|
+
let total;
|
|
25438
|
+
let deltaCompatible;
|
|
25439
|
+
for (let page = 0; page < V4_MATERIAL_MAX_PAGES; page++) {
|
|
25440
|
+
const params = new URLSearchParams({ repo });
|
|
25441
|
+
if (pageToken) params.set("pageToken", pageToken);
|
|
25442
|
+
else {
|
|
25443
|
+
params.set("kind", kind);
|
|
25444
|
+
if (digest) params.set("digest", digest);
|
|
25445
|
+
}
|
|
25446
|
+
const result = await request(`${REPO_INDEX_V4_MATERIAL_ROUTE}?${params}`);
|
|
25447
|
+
if (!result.ok) return result;
|
|
25448
|
+
const body = result.body;
|
|
25449
|
+
if (!Array.isArray(body.records) || typeof body.total !== "number" || typeof body.offset !== "number") return { ok: false, error: "v4 material page is malformed" };
|
|
25450
|
+
if (body.repo !== repo || body.kind !== kind) return { ok: false, error: "v4 material page identity does not match the request" };
|
|
25451
|
+
if (commit === void 0) commit = body.commit;
|
|
25452
|
+
else if (body.commit !== commit) return { ok: false, error: "v4 material page changed commit mid-walk" };
|
|
25453
|
+
if (digest === void 0) digest = body.digest;
|
|
25454
|
+
else if (body.digest !== digest) return { ok: false, error: "v4 material page changed digest mid-walk" };
|
|
25455
|
+
if (arrayDigest === void 0) arrayDigest = body.arrayDigest;
|
|
25456
|
+
else if (body.arrayDigest !== arrayDigest) return { ok: false, error: "v4 material page changed content digest mid-walk" };
|
|
25457
|
+
if (total === void 0) total = body.total;
|
|
25458
|
+
else if (body.total !== total) return { ok: false, error: "v4 material page changed record count mid-walk" };
|
|
25459
|
+
if (body.offset !== records.length) return { ok: false, error: "v4 material page arrived out of order" };
|
|
25460
|
+
if (body.deltaCompatible !== void 0) deltaCompatible = body.deltaCompatible;
|
|
25461
|
+
records.push(...body.records);
|
|
25462
|
+
pageToken = body.nextPageToken;
|
|
25463
|
+
if (!pageToken) break;
|
|
25464
|
+
}
|
|
25465
|
+
if (pageToken) return { ok: false, error: `v4 material walk exceeded ${V4_MATERIAL_MAX_PAGES} pages` };
|
|
25466
|
+
if (typeof commit !== "string" || typeof digest !== "string" || typeof arrayDigest !== "string") return { ok: false, error: "v4 material response omitted its authority identity" };
|
|
25467
|
+
if (records.length !== total) return { ok: false, error: `v4 material walk reassembled ${records.length} ${kind} records, expected ${total}` };
|
|
25468
|
+
if (sha2562(records) !== arrayDigest) return { ok: false, error: `v4 ${kind} material failed its declared content digest` };
|
|
25469
|
+
return { ok: true, records, commit, digest, ...deltaCompatible === void 0 ? {} : { deltaCompatible } };
|
|
25470
|
+
}
|
|
25471
|
+
async function fetchRepoIndexV4MaterialCloud(repo, deps) {
|
|
24206
25472
|
if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
|
|
24207
25473
|
const token = await deps.token();
|
|
24208
25474
|
if (!token) return { ok: false, error: "no Hub session token (run `gh auth login`)" };
|
|
25475
|
+
const baseUrl = deps.baseUrl.replace(/\/$/, "");
|
|
25476
|
+
const request = async (path2) => {
|
|
25477
|
+
const res = await fetchWithRetry(deps.fetch ?? fetch, `${baseUrl}${path2}`, {
|
|
25478
|
+
method: "GET",
|
|
25479
|
+
headers: { ...clientVersionHeaders(), Authorization: `Bearer ${token}` }
|
|
25480
|
+
}, { attempts: RETRY_ATTEMPTS2, timeoutMs: 12e4, sleep: deps.retrySleep });
|
|
25481
|
+
const body = await res.json().catch(() => ({}));
|
|
25482
|
+
if (!res.ok) return { ok: false, error: typeof body.error === "string" ? body.error : `v4 material HTTP ${res.status}`, status: res.status };
|
|
25483
|
+
return { ok: true, body };
|
|
25484
|
+
};
|
|
24209
25485
|
try {
|
|
24210
|
-
const
|
|
24211
|
-
|
|
24212
|
-
|
|
24213
|
-
|
|
25486
|
+
const chunks = await fetchV4MaterialKind(repo, "chunks", deps, request);
|
|
25487
|
+
if (!chunks.ok) return chunks;
|
|
25488
|
+
const pin = { commit: chunks.commit, digest: chunks.digest };
|
|
25489
|
+
const embeddings = await fetchV4MaterialKind(repo, "embeddings", deps, request, pin);
|
|
25490
|
+
if (!embeddings.ok) return embeddings;
|
|
25491
|
+
const tombstones = await fetchV4MaterialKind(repo, "tombstones", deps, request, pin);
|
|
25492
|
+
if (!tombstones.ok) return tombstones;
|
|
25493
|
+
const chunkRecords = chunks.records;
|
|
25494
|
+
const chunkIds = new Set(chunkRecords.map((chunk) => chunk?.id));
|
|
25495
|
+
if (chunkRecords.some((chunk) => !chunk?.id || !chunk.path || !Array.isArray(chunk.citations) || chunk.citations.some((citation2) => citation2.repo !== repo || citation2.commit !== pin.commit || citation2.path !== chunk.path))) {
|
|
25496
|
+
return { ok: false, error: "v4 prior material contains a foreign or malformed citation" };
|
|
25497
|
+
}
|
|
25498
|
+
const embeddingRecords = embeddings.records;
|
|
25499
|
+
if (embeddingRecords.some((embedding) => !chunkIds.has(embedding?.chunkId))) return { ok: false, error: "v4 prior material embeds a chunk that is not in the corpus" };
|
|
25500
|
+
return {
|
|
25501
|
+
ok: true,
|
|
25502
|
+
material: {
|
|
25503
|
+
commit: pin.commit,
|
|
25504
|
+
digest: pin.digest,
|
|
25505
|
+
chunks: chunkRecords,
|
|
25506
|
+
embeddings: embeddingRecords,
|
|
25507
|
+
tombstones: tombstones.records,
|
|
25508
|
+
// A Hub that did not answer the question has not proved the base is same-pipeline, and an
|
|
25509
|
+
// unproved base is a full rebuild — never an assumed-compatible one.
|
|
25510
|
+
deltaCompatible: tombstones.deltaCompatible === true
|
|
25511
|
+
}
|
|
25512
|
+
};
|
|
25513
|
+
} catch (error) {
|
|
25514
|
+
return { ok: false, error: error.message };
|
|
25515
|
+
}
|
|
25516
|
+
}
|
|
25517
|
+
async function fetchRepoIndexV4ProvenanceCloud(repo, deps) {
|
|
25518
|
+
if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
|
|
25519
|
+
const token = await deps.token();
|
|
25520
|
+
if (!token) return { ok: false, error: "no Hub session token (run `gh auth login`)" };
|
|
25521
|
+
const params = new URLSearchParams({ repo, kind: "tombstones", limit: "1" });
|
|
25522
|
+
try {
|
|
25523
|
+
const res = await fetchWithRetry(deps.fetch ?? fetch, `${deps.baseUrl.replace(/\/$/, "")}${REPO_INDEX_V4_MATERIAL_ROUTE}?${params}`, {
|
|
25524
|
+
method: "GET",
|
|
25525
|
+
headers: { ...clientVersionHeaders(), Authorization: `Bearer ${token}` }
|
|
24214
25526
|
}, { attempts: RETRY_ATTEMPTS2, timeoutMs: deps.timeoutMs ?? REGISTRY_FETCH_TIMEOUT_MS, sleep: deps.retrySleep });
|
|
24215
25527
|
const body = await res.json().catch(() => ({}));
|
|
24216
|
-
if (!res.ok) return { ok: false, error: typeof body.error === "string" ? body.error : `
|
|
24217
|
-
|
|
24218
|
-
|
|
24219
|
-
|
|
25528
|
+
if (!res.ok) return { ok: false, error: typeof body.error === "string" ? body.error : `v4 material HTTP ${res.status}`, status: res.status };
|
|
25529
|
+
if (body.repo !== repo || typeof body.commit !== "string" || typeof body.digest !== "string") {
|
|
25530
|
+
return { ok: false, error: "v4 provenance probe answered for a different authority" };
|
|
25531
|
+
}
|
|
25532
|
+
return {
|
|
25533
|
+
ok: true,
|
|
25534
|
+
commit: body.commit,
|
|
25535
|
+
digest: body.digest,
|
|
25536
|
+
deltaCompatible: body.deltaCompatible === true,
|
|
25537
|
+
...body.indexProvenance ? { indexProvenance: body.indexProvenance } : {}
|
|
25538
|
+
};
|
|
25539
|
+
} catch (error) {
|
|
25540
|
+
return { ok: false, error: error.message };
|
|
24220
25541
|
}
|
|
24221
25542
|
}
|
|
24222
|
-
async function publishRepoIndexV4Cloud(envelope, deps) {
|
|
25543
|
+
async function publishRepoIndexV4Cloud(envelope, deps, opts = {}) {
|
|
24223
25544
|
if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
|
|
24224
25545
|
const token = await deps.token();
|
|
24225
25546
|
if (!token) return { ok: false, error: "no Hub session token (run `gh auth login`)" };
|
|
24226
|
-
const upload = shardRepoIndexV4(envelope);
|
|
25547
|
+
const upload = shardRepoIndexV4(envelope, { ...opts.priorBucketDigests ? { priorBucketDigests: opts.priorBucketDigests } : {} });
|
|
24227
25548
|
const post = async (path2, payload) => {
|
|
24228
25549
|
const res = await fetchWithRetry(deps.fetch ?? fetch, `${deps.baseUrl.replace(/\/$/, "")}${path2}`, {
|
|
24229
25550
|
method: "POST",
|
|
@@ -24239,7 +25560,10 @@ async function publishRepoIndexV4Cloud(envelope, deps) {
|
|
|
24239
25560
|
const result = await post("/repo-index/v4/stage", stage);
|
|
24240
25561
|
if (!result.ok) return result;
|
|
24241
25562
|
}
|
|
24242
|
-
const finalized = await post("/repo-index/v4/finalize",
|
|
25563
|
+
const finalized = await post("/repo-index/v4/finalize", {
|
|
25564
|
+
...upload.finalize,
|
|
25565
|
+
...opts.expectedActiveDigest ? { expectedActiveDigest: opts.expectedActiveDigest } : {}
|
|
25566
|
+
});
|
|
24243
25567
|
if (!finalized.ok) return finalized;
|
|
24244
25568
|
return { ok: true, active: finalized.body.active ?? {} };
|
|
24245
25569
|
} catch (error) {
|
|
@@ -24279,9 +25603,13 @@ async function getRepoIndexV4GraphCloud(opts, deps) {
|
|
|
24279
25603
|
}
|
|
24280
25604
|
}
|
|
24281
25605
|
async function searchRepoIndexCloud(query, opts, deps) {
|
|
24282
|
-
if (!deps.baseUrl)
|
|
25606
|
+
if (!deps.baseUrl) {
|
|
25607
|
+
return asCloudSearchNotReadyReceipt({ ok: false, error: "Hub API URL not configured" });
|
|
25608
|
+
}
|
|
24283
25609
|
const token = await deps.token();
|
|
24284
|
-
if (!token)
|
|
25610
|
+
if (!token) {
|
|
25611
|
+
return asCloudSearchNotReadyReceipt({ ok: false, error: "no Hub session token (run `gh auth login`)" });
|
|
25612
|
+
}
|
|
24285
25613
|
const params = new URLSearchParams({
|
|
24286
25614
|
q: query,
|
|
24287
25615
|
mode: opts.mode ?? "hybrid",
|
|
@@ -24298,13 +25626,36 @@ async function searchRepoIndexCloud(query, opts, deps) {
|
|
|
24298
25626
|
deps.fetch ?? fetch,
|
|
24299
25627
|
`${deps.baseUrl.replace(/\/$/, "")}/repo-index/search?${params}`,
|
|
24300
25628
|
{ method: "GET", headers },
|
|
24301
|
-
{
|
|
25629
|
+
{
|
|
25630
|
+
attempts: RETRY_ATTEMPTS2,
|
|
25631
|
+
timeoutMs: opts.mode === "semantic" || opts.mode === "hybrid" ? 6e4 : deps.timeoutMs ?? REGISTRY_FETCH_TIMEOUT_MS,
|
|
25632
|
+
sleep: deps.retrySleep,
|
|
25633
|
+
retryOn: (response) => response.status >= 500 && response.status !== 503
|
|
25634
|
+
}
|
|
24302
25635
|
);
|
|
24303
|
-
if (res.status === 426)
|
|
25636
|
+
if (res.status === 426) {
|
|
25637
|
+
return asCloudSearchNotReadyReceipt({
|
|
25638
|
+
ok: false,
|
|
25639
|
+
error: upgradeRequiredError(res, await res.json().catch(() => null)),
|
|
25640
|
+
status: 426
|
|
25641
|
+
});
|
|
25642
|
+
}
|
|
24304
25643
|
const body = await res.json().catch(() => ({}));
|
|
24305
|
-
if (!res.ok)
|
|
25644
|
+
if (!res.ok) {
|
|
25645
|
+
const error = refuseBareRepoIndexV4Unavailable(explainRepoIndexSearchError(body, res.status));
|
|
25646
|
+
return asCloudSearchNotReadyReceipt({
|
|
25647
|
+
ok: false,
|
|
25648
|
+
error,
|
|
25649
|
+
status: res.status,
|
|
25650
|
+
...typeof body.errorClass === "string" ? { errorClass: body.errorClass } : {},
|
|
25651
|
+
...body.retrieval && body.retrieval.served === "unavailable" ? { retrieval: body.retrieval } : {},
|
|
25652
|
+
...body.v4Authority ? { v4Authority: body.v4Authority } : {},
|
|
25653
|
+
...Array.isArray(body.omittedRepos) ? { omittedRepos: body.omittedRepos } : {},
|
|
25654
|
+
...typeof body.next === "string" ? { next: body.next } : {}
|
|
25655
|
+
});
|
|
25656
|
+
}
|
|
24306
25657
|
const shadowRate = Math.max(0, Math.min(1, Number(process.env.MMI_REPO_INDEX_V4_SHADOW_RATE ?? 0.1) || 0));
|
|
24307
|
-
if ((0,
|
|
25658
|
+
if ((0, import_node_crypto10.createHash)("sha256").update(query).digest()[0] / 256 < shadowRate) {
|
|
24308
25659
|
void fetchWithRetry(
|
|
24309
25660
|
deps.fetch ?? fetch,
|
|
24310
25661
|
`${deps.baseUrl.replace(/\/$/, "")}/repo-index/v4/shadow`,
|
|
@@ -24319,10 +25670,15 @@ async function searchRepoIndexCloud(query, opts, deps) {
|
|
|
24319
25670
|
mode: body.mode ?? opts.mode ?? "hybrid",
|
|
24320
25671
|
hits: Array.isArray(body.hits) ? body.hits : [],
|
|
24321
25672
|
note: body.note,
|
|
24322
|
-
...body.
|
|
25673
|
+
...Array.isArray(body.degradedReasons) ? { degradedReasons: body.degradedReasons } : {},
|
|
25674
|
+
...Array.isArray(body.omittedRepos) ? { omittedRepos: body.omittedRepos } : {},
|
|
25675
|
+
...body.retrieval && body.retrieval.served === "v4" ? { retrieval: body.retrieval } : {}
|
|
24323
25676
|
};
|
|
24324
25677
|
} catch (e) {
|
|
24325
|
-
return {
|
|
25678
|
+
return asCloudSearchNotReadyReceipt({
|
|
25679
|
+
ok: false,
|
|
25680
|
+
error: refuseBareRepoIndexV4Unavailable(e.message)
|
|
25681
|
+
});
|
|
24326
25682
|
}
|
|
24327
25683
|
}
|
|
24328
25684
|
function isRepoIndexStatusError(st) {
|
|
@@ -24398,7 +25754,7 @@ async function gcRepoIndexCloud(deps) {
|
|
|
24398
25754
|
var import_node_fs27 = require("node:fs");
|
|
24399
25755
|
var import_node_os13 = require("node:os");
|
|
24400
25756
|
var import_node_path25 = require("node:path");
|
|
24401
|
-
var
|
|
25757
|
+
var import_node_child_process15 = require("node:child_process");
|
|
24402
25758
|
|
|
24403
25759
|
// ../infra/src/repo-index-roster.ts
|
|
24404
25760
|
var ORG2 = "mutmutco";
|
|
@@ -24537,7 +25893,7 @@ function buildGraphEdges(cwd, repo, commit, rosterRepos2) {
|
|
|
24537
25893
|
}
|
|
24538
25894
|
|
|
24539
25895
|
// src/repo-index-sync.ts
|
|
24540
|
-
var
|
|
25896
|
+
var COMMIT3 = /^[a-f0-9]{40}$/;
|
|
24541
25897
|
var SHA256 = /^[a-f0-9]{64}$/;
|
|
24542
25898
|
var UTC_MILLIS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
24543
25899
|
function normalizeRepo(raw) {
|
|
@@ -24550,15 +25906,31 @@ function rosterRepos(projects) {
|
|
|
24550
25906
|
}
|
|
24551
25907
|
function shallowClone(repo, dest, token) {
|
|
24552
25908
|
const basic = Buffer.from(`x-access-token:${token}`, "utf8").toString("base64");
|
|
24553
|
-
(0,
|
|
25909
|
+
(0, import_node_child_process15.execFileSync)(
|
|
24554
25910
|
"git",
|
|
24555
25911
|
["-c", `http.extraHeader=Authorization: Basic ${basic}`, "clone", "--depth", "1", "--single-branch", `https://github.com/${repo}.git`, dest],
|
|
24556
25912
|
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
|
|
24557
25913
|
);
|
|
24558
25914
|
}
|
|
25915
|
+
function checkoutExactCommit(repo, dest, token, commit) {
|
|
25916
|
+
const git3 = (args, authenticated = false) => {
|
|
25917
|
+
const basic = Buffer.from(`x-access-token:${token}`, "utf8").toString("base64");
|
|
25918
|
+
return String((0, import_node_child_process15.execFileSync)(
|
|
25919
|
+
"git",
|
|
25920
|
+
[...authenticated ? ["-c", `http.extraHeader=Authorization: Basic ${basic}`] : [], "-C", dest, ...args],
|
|
25921
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
|
|
25922
|
+
));
|
|
25923
|
+
};
|
|
25924
|
+
if (git3(["rev-parse", "HEAD"]).trim().toLowerCase() !== commit) {
|
|
25925
|
+
git3(["fetch", "--no-tags", "--depth", "1", "origin", commit], true);
|
|
25926
|
+
git3(["checkout", "--quiet", "--detach", commit]);
|
|
25927
|
+
}
|
|
25928
|
+
const head = git3(["rev-parse", "HEAD"]).trim().toLowerCase();
|
|
25929
|
+
if (head !== commit) throw new Error(`checkout of ${repo} resolved ${head}, not the requested commit ${commit}`);
|
|
25930
|
+
}
|
|
24559
25931
|
function remoteHead(repo, token) {
|
|
24560
25932
|
const basic = Buffer.from(`x-access-token:${token}`, "utf8").toString("base64");
|
|
24561
|
-
const output = (0,
|
|
25933
|
+
const output = (0, import_node_child_process15.execFileSync)(
|
|
24562
25934
|
"git",
|
|
24563
25935
|
["-c", `http.extraHeader=Authorization: Basic ${basic}`, "ls-remote", "--exit-code", `https://github.com/${repo}.git`, "HEAD"],
|
|
24564
25936
|
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
|
|
@@ -24567,7 +25939,7 @@ function remoteHead(repo, token) {
|
|
|
24567
25939
|
if (!match) throw new Error(`could not resolve remote HEAD for ${repo}`);
|
|
24568
25940
|
return match[1];
|
|
24569
25941
|
}
|
|
24570
|
-
function
|
|
25942
|
+
function verifiedReadyBase(statusValue, repo) {
|
|
24571
25943
|
if (!statusValue || typeof statusValue !== "object" || Array.isArray(statusValue)) return null;
|
|
24572
25944
|
const status = statusValue;
|
|
24573
25945
|
const v4 = status.v4;
|
|
@@ -24575,13 +25947,84 @@ function verifiedReadyCommit(statusValue, repo) {
|
|
|
24575
25947
|
const pointer = v4;
|
|
24576
25948
|
const artifactDigests = pointer.artifactDigests;
|
|
24577
25949
|
const counts = [pointer.chunkCount, pointer.embeddingCount, pointer.tombstoneCount];
|
|
24578
|
-
const structurallyVerified = status.repo === repo && pointer.repo === repo && pointer.state === "ready" && pointer.integrity === "verified" && typeof pointer.commit === "string" &&
|
|
24579
|
-
return structurallyVerified && typeof pointer.commit === "string" ? pointer.commit : null;
|
|
25950
|
+
const structurallyVerified = status.repo === repo && pointer.repo === repo && pointer.state === "ready" && pointer.integrity === "verified" && typeof pointer.commit === "string" && COMMIT3.test(pointer.commit) && typeof pointer.digest === "string" && SHA256.test(pointer.digest) && Array.isArray(artifactDigests) && artifactDigests.length === 2 && new Set(artifactDigests).size === artifactDigests.length && artifactDigests.every((digest) => typeof digest === "string" && SHA256.test(digest)) && counts.every((count) => Number.isInteger(count) && Number(count) >= 0) && pointer.embeddingCount === pointer.chunkCount && typeof pointer.activatedAt === "string" && UTC_MILLIS.test(pointer.activatedAt) && Number.isFinite(Date.parse(pointer.activatedAt)) && new Date(pointer.activatedAt).toISOString() === pointer.activatedAt;
|
|
25951
|
+
return structurallyVerified && typeof pointer.commit === "string" && typeof pointer.digest === "string" ? { commit: pointer.commit, digest: pointer.digest } : null;
|
|
25952
|
+
}
|
|
25953
|
+
function activePointerDigest(statusValue) {
|
|
25954
|
+
if (!statusValue || typeof statusValue !== "object" || Array.isArray(statusValue)) return null;
|
|
25955
|
+
const v4 = statusValue.v4;
|
|
25956
|
+
if (!v4 || typeof v4 !== "object" || Array.isArray(v4)) return null;
|
|
25957
|
+
const digest = v4.digest;
|
|
25958
|
+
return typeof digest === "string" && SHA256.test(digest) ? digest : null;
|
|
25959
|
+
}
|
|
25960
|
+
function deepenClone(repo, dest, token, depth) {
|
|
25961
|
+
const basic = Buffer.from(`x-access-token:${token}`, "utf8").toString("base64");
|
|
25962
|
+
(0, import_node_child_process15.execFileSync)(
|
|
25963
|
+
"git",
|
|
25964
|
+
["-c", `http.extraHeader=Authorization: Basic ${basic}`, "-C", dest, "fetch", "--no-tags", "--depth", String(depth), "origin"],
|
|
25965
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
|
|
25966
|
+
);
|
|
25967
|
+
}
|
|
25968
|
+
async function planDeltaBuild(repo, dir, base, deps, githubToken2, forceFull = false) {
|
|
25969
|
+
const git3 = gitRunner(dir);
|
|
25970
|
+
const headCommit = git3(["rev-parse", "HEAD"]).stdout.trim().toLowerCase();
|
|
25971
|
+
if (forceFull) return { plan: planRepoIndexV4Delta({ headCommit, baseCommit: base?.commit ?? null, forceFull: true, git: git3 }) };
|
|
25972
|
+
if (!base) return { plan: planRepoIndexV4Delta({ headCommit, baseCommit: null, git: git3 }) };
|
|
25973
|
+
const material = await fetchRepoIndexV4MaterialCloud(repo, deps);
|
|
25974
|
+
if (!material.ok) {
|
|
25975
|
+
return { plan: planRepoIndexV4Delta({ headCommit, baseCommit: base.commit, hasPriorMaterial: false, git: git3 }) };
|
|
25976
|
+
}
|
|
25977
|
+
const prior2 = material.material;
|
|
25978
|
+
const plan = planRepoIndexV4Delta({
|
|
25979
|
+
headCommit,
|
|
25980
|
+
// The material walk pins its own commit; trust that over the status read that raced it.
|
|
25981
|
+
baseCommit: prior2.commit,
|
|
25982
|
+
basePipelineCompatible: prior2.deltaCompatible,
|
|
25983
|
+
hasPriorMaterial: true,
|
|
25984
|
+
git: git3,
|
|
25985
|
+
deepen: (depth) => deepenClone(repo, dir, githubToken2, depth)
|
|
25986
|
+
});
|
|
25987
|
+
return { plan, prior: prior2 };
|
|
24580
25988
|
}
|
|
24581
25989
|
async function syncEstateRepoIndex(opts) {
|
|
25990
|
+
const published = [];
|
|
25991
|
+
const failed = [];
|
|
25992
|
+
const skipped = [];
|
|
25993
|
+
const drift = [];
|
|
25994
|
+
const needsFullRebuild = [];
|
|
25995
|
+
const answer = (ok) => ({
|
|
25996
|
+
ok: ok ?? failed.length === 0,
|
|
25997
|
+
published,
|
|
25998
|
+
failed,
|
|
25999
|
+
skipped,
|
|
26000
|
+
provenanceToken: CURRENT_REPO_INDEX_PROVENANCE_TOKEN,
|
|
26001
|
+
drift,
|
|
26002
|
+
needsFullRebuild
|
|
26003
|
+
});
|
|
26004
|
+
const fullRebuildToken = (opts.fullRebuild ?? "").trim();
|
|
26005
|
+
const forceFull = fullRebuildToken !== "";
|
|
26006
|
+
if (forceFull && fullRebuildToken !== CURRENT_REPO_INDEX_PROVENANCE_TOKEN) {
|
|
26007
|
+
failed.push({
|
|
26008
|
+
repo: opts.repo ? normalizeRepo(opts.repo) : "*",
|
|
26009
|
+
error: `full-rebuild token ${JSON.stringify(fullRebuildToken)} does not match this pipeline's provenance token ${CURRENT_REPO_INDEX_PROVENANCE_TOKEN} \u2014 refusing before any clone, embedding or upload`
|
|
26010
|
+
});
|
|
26011
|
+
return answer(false);
|
|
26012
|
+
}
|
|
26013
|
+
const requestedCommit = (opts.commit ?? "").trim().toLowerCase();
|
|
26014
|
+
if (requestedCommit) {
|
|
26015
|
+
if (!opts.repo) {
|
|
26016
|
+
failed.push({ repo: "*", error: "--commit names one repository tip and requires --repo" });
|
|
26017
|
+
return answer(false);
|
|
26018
|
+
}
|
|
26019
|
+
if (!COMMIT3.test(requestedCommit)) {
|
|
26020
|
+
failed.push({ repo: normalizeRepo(opts.repo), error: `--commit must be a 40-hex commit sha, got ${JSON.stringify(requestedCommit)}` });
|
|
26021
|
+
return answer(false);
|
|
26022
|
+
}
|
|
26023
|
+
}
|
|
24582
26024
|
const projects = await fetchProjectsList(opts.deps);
|
|
24583
26025
|
if (!projects) {
|
|
24584
|
-
|
|
26026
|
+
failed.push({ repo: "*", error: "could not read /projects/list" });
|
|
26027
|
+
return answer(false);
|
|
24585
26028
|
}
|
|
24586
26029
|
const allRosterRepos = rosterRepos(projects);
|
|
24587
26030
|
let repos = allRosterRepos;
|
|
@@ -24589,37 +26032,85 @@ async function syncEstateRepoIndex(opts) {
|
|
|
24589
26032
|
const want = normalizeRepo(opts.repo);
|
|
24590
26033
|
repos = repos.filter((r) => r.toLowerCase() === want.toLowerCase());
|
|
24591
26034
|
if (repos.length === 0) {
|
|
24592
|
-
|
|
26035
|
+
failed.push({ repo: want, error: "not on registry code roster" });
|
|
26036
|
+
return answer(false);
|
|
24593
26037
|
}
|
|
24594
26038
|
}
|
|
24595
|
-
const
|
|
24596
|
-
|
|
24597
|
-
|
|
26039
|
+
const busy = new Set(
|
|
26040
|
+
(opts.skipRepos ?? []).map((repo) => normalizeRepoIndexRepo(repo)).filter((repo) => repo !== null)
|
|
26041
|
+
);
|
|
24598
26042
|
for (const repo of repos) {
|
|
26043
|
+
if (busy.has(repo)) {
|
|
26044
|
+
drift.push({ repo, reason: "busy-elsewhere", action: "skip" });
|
|
26045
|
+
skipped.push(`${repo}: a per-repo reconcile run is already publishing it`);
|
|
26046
|
+
continue;
|
|
26047
|
+
}
|
|
26048
|
+
let base = null;
|
|
26049
|
+
let expectedActiveDigest;
|
|
26050
|
+
let reason;
|
|
24599
26051
|
try {
|
|
24600
26052
|
const status = await statusRepoIndexCloud(repo, opts.deps);
|
|
24601
|
-
|
|
24602
|
-
|
|
24603
|
-
|
|
24604
|
-
|
|
24605
|
-
|
|
24606
|
-
|
|
24607
|
-
}
|
|
26053
|
+
if (isRepoIndexStatusError(status)) {
|
|
26054
|
+
reason = "status-unreadable";
|
|
26055
|
+
} else {
|
|
26056
|
+
expectedActiveDigest = activePointerDigest(status) ?? "none";
|
|
26057
|
+
base = verifiedReadyBase(status, repo);
|
|
26058
|
+
reason = base ? "changed" : expectedActiveDigest === "none" ? "missing-authority" : "unverified-authority";
|
|
24608
26059
|
}
|
|
24609
26060
|
} catch {
|
|
26061
|
+
base = null;
|
|
26062
|
+
expectedActiveDigest = void 0;
|
|
26063
|
+
reason = "status-unreadable";
|
|
24610
26064
|
}
|
|
24611
|
-
|
|
24612
|
-
|
|
24613
|
-
|
|
24614
|
-
|
|
24615
|
-
|
|
26065
|
+
let targetCommit = requestedCommit || void 0;
|
|
26066
|
+
if (base && !forceFull) {
|
|
26067
|
+
if (!targetCommit) {
|
|
26068
|
+
try {
|
|
26069
|
+
targetCommit = remoteHead(repo, opts.githubToken);
|
|
26070
|
+
} catch {
|
|
26071
|
+
targetCommit = void 0;
|
|
26072
|
+
}
|
|
26073
|
+
}
|
|
26074
|
+
const provenance = await fetchRepoIndexV4ProvenanceCloud(repo, opts.deps).catch(
|
|
26075
|
+
(error) => ({ ok: false, error: error.message })
|
|
26076
|
+
);
|
|
26077
|
+
if (provenance.ok && !provenance.deltaCompatible) {
|
|
26078
|
+
drift.push({ repo, reason: "incompatible-provenance", action: "needs-full-rebuild", activeCommit: base.commit, ...targetCommit ? { targetCommit } : {} });
|
|
26079
|
+
needsFullRebuild.push(repo);
|
|
26080
|
+
skipped.push(`${repo}: DRIFT incompatible index provenance \u2014 the active authority at ${base.commit} was not built by ${CURRENT_REPO_INDEX_PROVENANCE_TOKEN}; migrate it explicitly with \`oracle repo-index sync-estate --repo ${repo} --full-rebuild ${CURRENT_REPO_INDEX_PROVENANCE_TOKEN}\``);
|
|
26081
|
+
continue;
|
|
26082
|
+
}
|
|
26083
|
+
if (targetCommit && targetCommit === base.commit) {
|
|
26084
|
+
drift.push({ repo, reason: "healthy", action: "skip", activeCommit: base.commit, targetCommit });
|
|
26085
|
+
skipped.push(`${repo}: unchanged verified-ready authority at ${targetCommit}`);
|
|
24616
26086
|
continue;
|
|
24617
26087
|
}
|
|
26088
|
+
}
|
|
26089
|
+
drift.push({
|
|
26090
|
+
repo,
|
|
26091
|
+
reason,
|
|
26092
|
+
action: "build",
|
|
26093
|
+
...base ? { activeCommit: base.commit } : {},
|
|
26094
|
+
...targetCommit ? { targetCommit } : {}
|
|
26095
|
+
});
|
|
26096
|
+
if (opts.plan) continue;
|
|
26097
|
+
const dir = (0, import_node_fs27.mkdtempSync)((0, import_node_path25.join)((0, import_node_os13.tmpdir)(), "mmi-repo-index-"));
|
|
26098
|
+
try {
|
|
24618
26099
|
shallowClone(repo, dir, opts.githubToken);
|
|
26100
|
+
if (requestedCommit) checkoutExactCommit(repo, dir, opts.githubToken, requestedCommit);
|
|
24619
26101
|
let v4;
|
|
24620
26102
|
try {
|
|
24621
|
-
|
|
24622
|
-
|
|
26103
|
+
const plan = await planDeltaBuild(repo, dir, base, opts.deps, opts.githubToken, forceFull);
|
|
26104
|
+
if (base && plan.plan.mode === "full") skipped.push(`${repo}: full rebuild (${plan.plan.fallbackReason})`);
|
|
26105
|
+
v4 = await buildRepoIndexV4Detailed(dir, repo, {
|
|
26106
|
+
modelDirectory: process.env.MMI_REPO_INDEXER_MODEL_DIR,
|
|
26107
|
+
...plan.plan.mode === "delta" && plan.prior ? { delta: { baseCommit: plan.plan.baseCommit, prior: plan.prior, changes: plan.plan.changes } } : {}
|
|
26108
|
+
});
|
|
26109
|
+
const priorBucketDigests = plan.plan.mode === "delta" && plan.prior ? priorV4BucketDigests(repo, plan.prior) : [];
|
|
26110
|
+
const v4Published = await publishRepoIndexV4Cloud(v4.envelope, opts.deps, {
|
|
26111
|
+
...expectedActiveDigest ? { expectedActiveDigest } : {},
|
|
26112
|
+
...priorBucketDigests.length ? { priorBucketDigests } : {}
|
|
26113
|
+
});
|
|
24623
26114
|
if (!v4Published.ok) {
|
|
24624
26115
|
failed.push({ repo, error: `v4 failed: ${v4Published.error}` });
|
|
24625
26116
|
continue;
|
|
@@ -24628,7 +26119,7 @@ async function syncEstateRepoIndex(opts) {
|
|
|
24628
26119
|
failed.push({ repo, error: `v4 failed: ${error.message}` });
|
|
24629
26120
|
continue;
|
|
24630
26121
|
}
|
|
24631
|
-
const v4Commit = v4.manifest.commit;
|
|
26122
|
+
const v4Commit = v4.envelope.manifest.commit;
|
|
24632
26123
|
let graphEdges;
|
|
24633
26124
|
try {
|
|
24634
26125
|
const edges = buildGraphEdges(dir, repo, v4Commit, allRosterRepos);
|
|
@@ -24638,15 +26129,16 @@ async function syncEstateRepoIndex(opts) {
|
|
|
24638
26129
|
} catch (error) {
|
|
24639
26130
|
skipped.push(`${repo}: optional v4 graph unavailable: ${error.message}`);
|
|
24640
26131
|
}
|
|
24641
|
-
const fileCount = v4.manifest.chunks.length;
|
|
24642
|
-
const embCount = v4.manifest.embeddings.length;
|
|
26132
|
+
const fileCount = v4.envelope.manifest.chunks.length;
|
|
26133
|
+
const embCount = v4.envelope.manifest.embeddings.length;
|
|
24643
26134
|
const embGap = Math.max(0, fileCount - embCount);
|
|
24644
26135
|
published.push({
|
|
24645
26136
|
repo,
|
|
24646
26137
|
fileCount,
|
|
24647
26138
|
embCount,
|
|
24648
26139
|
embGap,
|
|
24649
|
-
graphEdges
|
|
26140
|
+
graphEdges,
|
|
26141
|
+
metrics: v4.metrics
|
|
24650
26142
|
});
|
|
24651
26143
|
} catch (e) {
|
|
24652
26144
|
failed.push({ repo, error: e.message });
|
|
@@ -24657,7 +26149,7 @@ async function syncEstateRepoIndex(opts) {
|
|
|
24657
26149
|
}
|
|
24658
26150
|
}
|
|
24659
26151
|
}
|
|
24660
|
-
return
|
|
26152
|
+
return answer();
|
|
24661
26153
|
}
|
|
24662
26154
|
|
|
24663
26155
|
// src/repo-index-health.ts
|
|
@@ -24934,7 +26426,7 @@ async function runRepoIndexHealth(opts) {
|
|
|
24934
26426
|
}
|
|
24935
26427
|
|
|
24936
26428
|
// src/spawn-policy-core.ts
|
|
24937
|
-
var
|
|
26429
|
+
var import_node_child_process16 = require("node:child_process");
|
|
24938
26430
|
var import_node_fs29 = require("node:fs");
|
|
24939
26431
|
var import_node_path26 = require("node:path");
|
|
24940
26432
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
@@ -25005,7 +26497,7 @@ function findViolationsInSource(raw) {
|
|
|
25005
26497
|
return found;
|
|
25006
26498
|
}
|
|
25007
26499
|
function policedFiles(root) {
|
|
25008
|
-
const r = (0,
|
|
26500
|
+
const r = (0, import_node_child_process16.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
25009
26501
|
cwd: root,
|
|
25010
26502
|
encoding: "utf8",
|
|
25011
26503
|
windowsHide: true,
|
|
@@ -25039,7 +26531,7 @@ function runSpawnPolicy(root) {
|
|
|
25039
26531
|
}
|
|
25040
26532
|
|
|
25041
26533
|
// src/test-policy-core.ts
|
|
25042
|
-
var
|
|
26534
|
+
var import_node_child_process17 = require("node:child_process");
|
|
25043
26535
|
var import_node_fs30 = require("node:fs");
|
|
25044
26536
|
var import_node_path27 = require("node:path");
|
|
25045
26537
|
var POLICY_FILE = "test-policy.json";
|
|
@@ -25093,8 +26585,252 @@ function globToRegExp(glob) {
|
|
|
25093
26585
|
function isTestPath(path2) {
|
|
25094
26586
|
return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
|
|
25095
26587
|
}
|
|
25096
|
-
|
|
25097
|
-
|
|
26588
|
+
var SUPPORTED_SOURCE = /\.[cm]?[jt]s$/;
|
|
26589
|
+
var MAX_ANALYZED_BYTES = 512 * 1024;
|
|
26590
|
+
var WORD_CHAR = /[\w$\u0080-\uffff]/;
|
|
26591
|
+
var LINE_TERMINATOR = /[\n\r\u2028\u2029]/;
|
|
26592
|
+
var PLAIN_IDENTIFIER = /^[A-Za-z_$\u0080-\uffff][\w$\u0080-\uffff]*$/;
|
|
26593
|
+
var REGEX_POSITION_WORDS = /* @__PURE__ */ new Set([
|
|
26594
|
+
"return",
|
|
26595
|
+
"typeof",
|
|
26596
|
+
"instanceof",
|
|
26597
|
+
"in",
|
|
26598
|
+
"of",
|
|
26599
|
+
"new",
|
|
26600
|
+
"delete",
|
|
26601
|
+
"void",
|
|
26602
|
+
"throw",
|
|
26603
|
+
"yield",
|
|
26604
|
+
"await",
|
|
26605
|
+
"case",
|
|
26606
|
+
"do",
|
|
26607
|
+
"else",
|
|
26608
|
+
"if",
|
|
26609
|
+
"while",
|
|
26610
|
+
"for",
|
|
26611
|
+
"switch",
|
|
26612
|
+
"catch",
|
|
26613
|
+
"default",
|
|
26614
|
+
"extends"
|
|
26615
|
+
]);
|
|
26616
|
+
var VALUE_END = /^[\w$\u0080-\uffff"'`]/;
|
|
26617
|
+
var AMBIGUOUS_BEFORE_SLASH = /* @__PURE__ */ new Set([")", "]", "}"]);
|
|
26618
|
+
var PUNCTUATORS = [
|
|
26619
|
+
">>>=",
|
|
26620
|
+
"...",
|
|
26621
|
+
"===",
|
|
26622
|
+
"!==",
|
|
26623
|
+
"**=",
|
|
26624
|
+
"<<=",
|
|
26625
|
+
">>=",
|
|
26626
|
+
">>>",
|
|
26627
|
+
"&&=",
|
|
26628
|
+
"||=",
|
|
26629
|
+
"??=",
|
|
26630
|
+
"=>",
|
|
26631
|
+
"==",
|
|
26632
|
+
"!=",
|
|
26633
|
+
"<=",
|
|
26634
|
+
">=",
|
|
26635
|
+
"&&",
|
|
26636
|
+
"||",
|
|
26637
|
+
"??",
|
|
26638
|
+
"?.",
|
|
26639
|
+
"++",
|
|
26640
|
+
"--",
|
|
26641
|
+
"+=",
|
|
26642
|
+
"-=",
|
|
26643
|
+
"*=",
|
|
26644
|
+
"%=",
|
|
26645
|
+
"&=",
|
|
26646
|
+
"|=",
|
|
26647
|
+
"^=",
|
|
26648
|
+
"**",
|
|
26649
|
+
"<<",
|
|
26650
|
+
">>"
|
|
26651
|
+
];
|
|
26652
|
+
function scanTemplateChunk(src, start) {
|
|
26653
|
+
let i = start;
|
|
26654
|
+
while (i < src.length) {
|
|
26655
|
+
const c = src[i];
|
|
26656
|
+
if (c === "\\") {
|
|
26657
|
+
i += 2;
|
|
26658
|
+
continue;
|
|
26659
|
+
}
|
|
26660
|
+
if (c === "`") return { text: src.slice(start, i + 1), end: i + 1, expr: false };
|
|
26661
|
+
if (c === "$" && src[i + 1] === "{") return { text: src.slice(start, i + 2), end: i + 2, expr: true };
|
|
26662
|
+
i++;
|
|
26663
|
+
}
|
|
26664
|
+
return null;
|
|
26665
|
+
}
|
|
26666
|
+
function scanRegex(src, start) {
|
|
26667
|
+
let i = start + 1;
|
|
26668
|
+
let inClass = false;
|
|
26669
|
+
while (i < src.length) {
|
|
26670
|
+
const c = src[i];
|
|
26671
|
+
if (c === "\\") {
|
|
26672
|
+
i += 2;
|
|
26673
|
+
continue;
|
|
26674
|
+
}
|
|
26675
|
+
if (LINE_TERMINATOR.test(c)) return null;
|
|
26676
|
+
if (inClass) {
|
|
26677
|
+
if (c === "]") inClass = false;
|
|
26678
|
+
} else if (c === "[") {
|
|
26679
|
+
inClass = true;
|
|
26680
|
+
} else if (c === "/") {
|
|
26681
|
+
i++;
|
|
26682
|
+
while (i < src.length && WORD_CHAR.test(src[i])) i++;
|
|
26683
|
+
return { text: src.slice(start, i), end: i };
|
|
26684
|
+
}
|
|
26685
|
+
i++;
|
|
26686
|
+
}
|
|
26687
|
+
return null;
|
|
26688
|
+
}
|
|
26689
|
+
function lexSource(src) {
|
|
26690
|
+
const out = [];
|
|
26691
|
+
const templates = [];
|
|
26692
|
+
let nl = true;
|
|
26693
|
+
let i = 0;
|
|
26694
|
+
const push = (text) => {
|
|
26695
|
+
out.push({ text, nl });
|
|
26696
|
+
nl = false;
|
|
26697
|
+
};
|
|
26698
|
+
if (src.startsWith("#!")) {
|
|
26699
|
+
const end = src.search(LINE_TERMINATOR);
|
|
26700
|
+
push(src.slice(0, end === -1 ? src.length : end));
|
|
26701
|
+
i = end === -1 ? src.length : end;
|
|
26702
|
+
}
|
|
26703
|
+
while (i < src.length) {
|
|
26704
|
+
const c = src[i];
|
|
26705
|
+
if (LINE_TERMINATOR.test(c)) {
|
|
26706
|
+
nl = true;
|
|
26707
|
+
i++;
|
|
26708
|
+
continue;
|
|
26709
|
+
}
|
|
26710
|
+
if (c === " " || c === " " || c === "\f" || c === "\v" || c === "\xA0" || c === "\uFEFF") {
|
|
26711
|
+
i++;
|
|
26712
|
+
continue;
|
|
26713
|
+
}
|
|
26714
|
+
if (c === "/" && src[i + 1] === "/") {
|
|
26715
|
+
let j = i + 2;
|
|
26716
|
+
while (j < src.length && !LINE_TERMINATOR.test(src[j])) j++;
|
|
26717
|
+
i = j;
|
|
26718
|
+
continue;
|
|
26719
|
+
}
|
|
26720
|
+
if (c === "/" && src[i + 1] === "*") {
|
|
26721
|
+
const end = src.indexOf("*/", i + 2);
|
|
26722
|
+
if (end === -1) return null;
|
|
26723
|
+
if (LINE_TERMINATOR.test(src.slice(i, end + 2))) nl = true;
|
|
26724
|
+
i = end + 2;
|
|
26725
|
+
continue;
|
|
26726
|
+
}
|
|
26727
|
+
if (c === "/") {
|
|
26728
|
+
const prev = out[out.length - 1]?.text;
|
|
26729
|
+
if (prev && AMBIGUOUS_BEFORE_SLASH.has(prev)) return null;
|
|
26730
|
+
if (prev && VALUE_END.test(prev) && !(PLAIN_IDENTIFIER.test(prev) && REGEX_POSITION_WORDS.has(prev))) {
|
|
26731
|
+
push("/");
|
|
26732
|
+
i++;
|
|
26733
|
+
continue;
|
|
26734
|
+
}
|
|
26735
|
+
const re = scanRegex(src, i);
|
|
26736
|
+
if (!re) return null;
|
|
26737
|
+
push(re.text);
|
|
26738
|
+
i = re.end;
|
|
26739
|
+
continue;
|
|
26740
|
+
}
|
|
26741
|
+
if (c === '"' || c === "'") {
|
|
26742
|
+
let j = i + 1;
|
|
26743
|
+
for (; ; ) {
|
|
26744
|
+
if (j >= src.length) return null;
|
|
26745
|
+
const d = src[j];
|
|
26746
|
+
if (d === "\\") {
|
|
26747
|
+
j += 2;
|
|
26748
|
+
continue;
|
|
26749
|
+
}
|
|
26750
|
+
if (d === c) {
|
|
26751
|
+
j++;
|
|
26752
|
+
break;
|
|
26753
|
+
}
|
|
26754
|
+
if (d === "\n" || d === "\r") return null;
|
|
26755
|
+
j++;
|
|
26756
|
+
}
|
|
26757
|
+
push(src.slice(i, j));
|
|
26758
|
+
i = j;
|
|
26759
|
+
continue;
|
|
26760
|
+
}
|
|
26761
|
+
if (c === "`") {
|
|
26762
|
+
const chunk = scanTemplateChunk(src, i + 1);
|
|
26763
|
+
if (!chunk) return null;
|
|
26764
|
+
push(`\`${chunk.text}`);
|
|
26765
|
+
i = chunk.end;
|
|
26766
|
+
if (chunk.expr) templates.push(0);
|
|
26767
|
+
continue;
|
|
26768
|
+
}
|
|
26769
|
+
if (WORD_CHAR.test(c)) {
|
|
26770
|
+
let j = i;
|
|
26771
|
+
while (j < src.length && WORD_CHAR.test(src[j])) j++;
|
|
26772
|
+
push(src.slice(i, j));
|
|
26773
|
+
i = j;
|
|
26774
|
+
continue;
|
|
26775
|
+
}
|
|
26776
|
+
if (c === "{") {
|
|
26777
|
+
if (templates.length) templates[templates.length - 1]++;
|
|
26778
|
+
push("{");
|
|
26779
|
+
i++;
|
|
26780
|
+
continue;
|
|
26781
|
+
}
|
|
26782
|
+
if (c === "}") {
|
|
26783
|
+
if (templates.length && templates[templates.length - 1] === 0) {
|
|
26784
|
+
templates.pop();
|
|
26785
|
+
push("}");
|
|
26786
|
+
const chunk = scanTemplateChunk(src, i + 1);
|
|
26787
|
+
if (!chunk) return null;
|
|
26788
|
+
push(chunk.text);
|
|
26789
|
+
i = chunk.end;
|
|
26790
|
+
if (chunk.expr) templates.push(0);
|
|
26791
|
+
continue;
|
|
26792
|
+
}
|
|
26793
|
+
if (templates.length) templates[templates.length - 1]--;
|
|
26794
|
+
push("}");
|
|
26795
|
+
i++;
|
|
26796
|
+
continue;
|
|
26797
|
+
}
|
|
26798
|
+
const punct = PUNCTUATORS.find((p) => src.startsWith(p, i));
|
|
26799
|
+
push(punct ?? c);
|
|
26800
|
+
i += punct ? punct.length : 1;
|
|
26801
|
+
}
|
|
26802
|
+
return templates.length ? null : out;
|
|
26803
|
+
}
|
|
26804
|
+
function isMeaningfulChange(path2, before, after) {
|
|
26805
|
+
if (before == null || after == null) return true;
|
|
26806
|
+
if (before === after) return true;
|
|
26807
|
+
if (!SUPPORTED_SOURCE.test(path2)) return true;
|
|
26808
|
+
if (before.length > MAX_ANALYZED_BYTES || after.length > MAX_ANALYZED_BYTES) return true;
|
|
26809
|
+
if (before.includes("\0") || after.includes("\0")) return true;
|
|
26810
|
+
const a = lexSource(before);
|
|
26811
|
+
const b = lexSource(after);
|
|
26812
|
+
if (!a || !b || a.length !== b.length) return true;
|
|
26813
|
+
return !a.every((t, k) => t.text === b[k].text && t.nl === b[k].nl);
|
|
26814
|
+
}
|
|
26815
|
+
function annotateChangeMeaning(changed, policy, read) {
|
|
26816
|
+
const matchers = (policy.mandatory ?? []).map((m) => globToRegExp(m.glob));
|
|
26817
|
+
return changed.map((file) => {
|
|
26818
|
+
if (file.status !== "M" || !SUPPORTED_SOURCE.test(file.path)) return file;
|
|
26819
|
+
if (!matchers.some((re) => re.test(file.path)) && !isTestPath(file.path)) return file;
|
|
26820
|
+
let pair;
|
|
26821
|
+
try {
|
|
26822
|
+
pair = read(file.path);
|
|
26823
|
+
} catch {
|
|
26824
|
+
return file;
|
|
26825
|
+
}
|
|
26826
|
+
return isMeaningfulChange(file.path, pair.before, pair.after) ? file : { ...file, meaningful: false };
|
|
26827
|
+
});
|
|
26828
|
+
}
|
|
26829
|
+
function isMeaningfulRow(file) {
|
|
26830
|
+
return file.meaningful !== false;
|
|
26831
|
+
}
|
|
26832
|
+
function loadPolicy(root, readFile7 = readFileOrNull2) {
|
|
26833
|
+
const raw = readFile7((0, import_node_path27.join)(root, POLICY_FILE));
|
|
25098
26834
|
if (raw == null) return { mandatory: [], declared: false };
|
|
25099
26835
|
try {
|
|
25100
26836
|
return { ...JSON.parse(raw), declared: true };
|
|
@@ -25109,7 +26845,7 @@ function readFileOrNull2(path2) {
|
|
|
25109
26845
|
return null;
|
|
25110
26846
|
}
|
|
25111
26847
|
}
|
|
25112
|
-
function
|
|
26848
|
+
function removedPaths2(changed) {
|
|
25113
26849
|
return new Set(
|
|
25114
26850
|
changed.map((f) => f.status === "D" ? f.path : f.status === "R" || f.status === "C" ? f.from : void 0).filter((p) => typeof p === "string")
|
|
25115
26851
|
);
|
|
@@ -25119,9 +26855,10 @@ function classify(changed, policy, present = () => false) {
|
|
|
25119
26855
|
const mandatoryHits = changed.filter((f) => matchers.some((m) => m.re.test(f.path)));
|
|
25120
26856
|
const testChanges = changed.filter((f) => isTestPath(f.path));
|
|
25121
26857
|
const addedTests = testChanges.filter((f) => f.status === "A");
|
|
25122
|
-
const removed =
|
|
26858
|
+
const removed = removedPaths2(changed);
|
|
25123
26859
|
const discharged = (m) => (m.satisfiedBy?.length ?? 0) > 0 && m.satisfiedBy.every((p) => present(p) && !removed.has(p));
|
|
25124
|
-
const untestedHits = changed.filter((f) => matchers.some((m) => m.re.test(f.path) && !discharged(m)));
|
|
26860
|
+
const untestedHits = changed.filter((f) => isMeaningfulRow(f) && matchers.some((m) => m.re.test(f.path) && !discharged(m)));
|
|
26861
|
+
const meaningfulTestChanges = testChanges.filter(isMeaningfulRow);
|
|
25125
26862
|
const protectedBy = new Map((policy.protected ?? []).map((p) => [p.path, p.why ?? ""]));
|
|
25126
26863
|
for (const m of policy.mandatory ?? []) {
|
|
25127
26864
|
for (const p of m.satisfiedBy ?? []) {
|
|
@@ -25129,7 +26866,7 @@ function classify(changed, policy, present = () => false) {
|
|
|
25129
26866
|
}
|
|
25130
26867
|
}
|
|
25131
26868
|
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
25132
|
-
return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
|
|
26869
|
+
return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
|
|
25133
26870
|
}
|
|
25134
26871
|
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs30.existsSync)(path2)) {
|
|
25135
26872
|
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path27.join)(root, p)));
|
|
@@ -25139,7 +26876,7 @@ function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_
|
|
|
25139
26876
|
return [...new Set(declared)].filter((p) => !exists((0, import_node_path27.join)(root, p)));
|
|
25140
26877
|
}
|
|
25141
26878
|
function evaluate(changed, policy, present = () => false) {
|
|
25142
|
-
const { mandatoryHits, untestedHits,
|
|
26879
|
+
const { mandatoryHits, untestedHits, meaningfulTestChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
25143
26880
|
const findings = [];
|
|
25144
26881
|
if (removedProtected.length > 0) {
|
|
25145
26882
|
findings.push({
|
|
@@ -25150,7 +26887,7 @@ function evaluate(changed, policy, present = () => false) {
|
|
|
25150
26887
|
${f.why}`).join("\n") + "\n If you are removing one because it looked unrequired, read the reason above first \u2014 it is\n the answer to exactly that question. If this removal was asked for, add a commit trailer:\n Test-Policy-Override: <reason>"
|
|
25151
26888
|
});
|
|
25152
26889
|
}
|
|
25153
|
-
if (untestedHits.length > 0 &&
|
|
26890
|
+
if (untestedHits.length > 0 && meaningfulTestChanges.length === 0) {
|
|
25154
26891
|
findings.push({
|
|
25155
26892
|
kind: "mandatory-zone-untested",
|
|
25156
26893
|
paths: untestedHits.map((f) => f.path),
|
|
@@ -25169,14 +26906,14 @@ function evaluate(changed, policy, present = () => false) {
|
|
|
25169
26906
|
return findings;
|
|
25170
26907
|
}
|
|
25171
26908
|
function git2(args, cwd) {
|
|
25172
|
-
return (0,
|
|
26909
|
+
return (0, import_node_child_process17.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
25173
26910
|
}
|
|
25174
26911
|
var COAUTHOR_KEY = "Co-authored-by";
|
|
25175
26912
|
var GH_MESSAGE_SEPARATOR = /^-{5,}$/;
|
|
25176
26913
|
var LIFTED_KEYS = new RegExp(`^(?:${TRAILER_KEY}|${COAUTHOR_KEY}):`, "i");
|
|
25177
26914
|
function parseTrailers(message, cwd) {
|
|
25178
26915
|
try {
|
|
25179
|
-
return (0,
|
|
26916
|
+
return (0, import_node_child_process17.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
|
|
25180
26917
|
windowsHide: true,
|
|
25181
26918
|
cwd,
|
|
25182
26919
|
input: message,
|
|
@@ -25316,16 +27053,27 @@ function changedFilesSince(base, cwd) {
|
|
|
25316
27053
|
const untracked = git2(["ls-files", "--others", "--exclude-standard", "-z"], cwd).split("\0").filter(Boolean).map((path2) => ({ status: "A", path: path2.replace(/\\/g, "/") }));
|
|
25317
27054
|
return [...changed, ...untracked];
|
|
25318
27055
|
}
|
|
27056
|
+
function blobAt(base, path2, cwd) {
|
|
27057
|
+
try {
|
|
27058
|
+
return git2(["show", `${base}:${path2}`], cwd);
|
|
27059
|
+
} catch {
|
|
27060
|
+
return null;
|
|
27061
|
+
}
|
|
27062
|
+
}
|
|
25319
27063
|
function runTestPolicy(root, deps = {}) {
|
|
25320
27064
|
const policy = deps.policy ?? loadPolicy(root);
|
|
25321
27065
|
const exists = deps.exists ?? ((path2) => (0, import_node_fs30.existsSync)(path2));
|
|
25322
27066
|
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
25323
27067
|
const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
|
|
25324
27068
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
25325
|
-
const
|
|
27069
|
+
const raw = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
|
|
27070
|
+
const changed = deps.changed ? raw : annotateChangeMeaning(raw, policy, (path2) => ({
|
|
27071
|
+
before: blobAt(base, path2, root),
|
|
27072
|
+
after: readFileOrNull2((0, import_node_path27.join)(root, path2))
|
|
27073
|
+
}));
|
|
25326
27074
|
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
|
|
25327
27075
|
const present = (path2) => exists((0, import_node_path27.join)(root, path2));
|
|
25328
|
-
const removedByThisDiff =
|
|
27076
|
+
const removedByThisDiff = removedPaths2(changed);
|
|
25329
27077
|
const staleFindings = [];
|
|
25330
27078
|
const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
|
|
25331
27079
|
if (unresolved.length > 0) {
|
|
@@ -26665,7 +28413,7 @@ function registerSecretsCommands(program3) {
|
|
|
26665
28413
|
}
|
|
26666
28414
|
|
|
26667
28415
|
// src/app-actor.ts
|
|
26668
|
-
var
|
|
28416
|
+
var import_node_crypto11 = require("node:crypto");
|
|
26669
28417
|
var APP_ACTOR_ENV = "MMI_ACTOR";
|
|
26670
28418
|
var APP_VAULT_REPO = "mutmutco/MMI-Hub";
|
|
26671
28419
|
var APP_VAULT_KEYS = ["GITHUB_APP_ID", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_PRIVATE_KEY"];
|
|
@@ -26709,7 +28457,7 @@ function mintAppJwt(appId, privateKeyPem, nowSec) {
|
|
|
26709
28457
|
exp: now + APP_JWT_TTL_S,
|
|
26710
28458
|
iss: appId
|
|
26711
28459
|
}));
|
|
26712
|
-
const signer = (0,
|
|
28460
|
+
const signer = (0, import_node_crypto11.createSign)("RSA-SHA256");
|
|
26713
28461
|
signer.update(`${header}.${payload}`);
|
|
26714
28462
|
return `${header}.${payload}.${signer.sign(privateKeyPem, "base64url")}`;
|
|
26715
28463
|
}
|
|
@@ -27046,12 +28794,12 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
27046
28794
|
|
|
27047
28795
|
// src/schedules-commands.ts
|
|
27048
28796
|
var import_promises4 = require("node:fs/promises");
|
|
27049
|
-
var
|
|
28797
|
+
var import_node_child_process18 = require("node:child_process");
|
|
27050
28798
|
var import_node_util7 = require("node:util");
|
|
27051
28799
|
init_clean_exit();
|
|
27052
28800
|
init_github_client();
|
|
27053
28801
|
init_cli_shared();
|
|
27054
|
-
var execFileP5 = (0, import_node_util7.promisify)(
|
|
28802
|
+
var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process18.execFile);
|
|
27055
28803
|
var AWS_REGION = "eu-central-1";
|
|
27056
28804
|
var AWS_TIMEOUT_MS = 3e4;
|
|
27057
28805
|
var AWS_RETRY_DELAY_MS = 1500;
|
|
@@ -27651,7 +29399,7 @@ init_clean_exit();
|
|
|
27651
29399
|
init_github_client();
|
|
27652
29400
|
|
|
27653
29401
|
// src/bootstrap-drift.ts
|
|
27654
|
-
var
|
|
29402
|
+
var import_node_crypto12 = require("node:crypto");
|
|
27655
29403
|
function byteComparableSeeds(manifest, cls) {
|
|
27656
29404
|
return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
|
|
27657
29405
|
}
|
|
@@ -27671,7 +29419,7 @@ function compareSeedBytes(hubContent, repoContent) {
|
|
|
27671
29419
|
return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
|
|
27672
29420
|
}
|
|
27673
29421
|
function seedContentHash(content) {
|
|
27674
|
-
return (0,
|
|
29422
|
+
return (0, import_node_crypto12.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
|
|
27675
29423
|
}
|
|
27676
29424
|
function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
|
|
27677
29425
|
const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
|
|
@@ -27747,10 +29495,8 @@ function statusFor(read, currentSha) {
|
|
|
27747
29495
|
if (read.drift === "match") return { status: "match", record: { mergeSha: read.pr?.mergeSha } };
|
|
27748
29496
|
const pr2 = read.pr;
|
|
27749
29497
|
if (!pr2) return { status: "pending", record: {} };
|
|
29498
|
+
if (pr2.sourceSha && pr2.sourceSha !== currentSha) return { status: "pending", record: {} };
|
|
27750
29499
|
if (pr2.state === "closed") return { status: "closed-unmerged", record: { prNumber: pr2.number, prUrl: pr2.url } };
|
|
27751
|
-
if (pr2.state === "merged" && pr2.sourceSha && pr2.sourceSha !== currentSha) {
|
|
27752
|
-
return { status: "pending", record: {} };
|
|
27753
|
-
}
|
|
27754
29500
|
if (pr2.checks === "red") return { status: "red", record: { prNumber: pr2.number, prUrl: pr2.url } };
|
|
27755
29501
|
return { status: "open-pending", record: { prNumber: pr2.number, prUrl: pr2.url, mergeSha: pr2.mergeSha } };
|
|
27756
29502
|
}
|
|
@@ -27834,7 +29580,7 @@ function renderPropagationReport(plan) {
|
|
|
27834
29580
|
}
|
|
27835
29581
|
|
|
27836
29582
|
// src/bootstrap-propagation-identity.ts
|
|
27837
|
-
var
|
|
29583
|
+
var import_node_crypto13 = require("node:crypto");
|
|
27838
29584
|
var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
|
|
27839
29585
|
var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
|
|
27840
29586
|
function safeBranchPart(value, maxLength, fallback) {
|
|
@@ -27847,7 +29593,7 @@ function repoSlug2(repo) {
|
|
|
27847
29593
|
function propagationBranch(repo, target) {
|
|
27848
29594
|
const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
|
|
27849
29595
|
const targetPart = safeBranchPart(target, 48, "target");
|
|
27850
|
-
const hash = (0,
|
|
29596
|
+
const hash = (0, import_node_crypto13.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
|
|
27851
29597
|
return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
|
|
27852
29598
|
}
|
|
27853
29599
|
function legacyPropagationBranch(repo) {
|
|
@@ -28848,7 +30594,7 @@ function registerBootstrapCommands(program3) {
|
|
|
28848
30594
|
}
|
|
28849
30595
|
const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
|
|
28850
30596
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
28851
|
-
const
|
|
30597
|
+
const readFile7 = (p) => (0, import_node_fs34.existsSync)(p) ? (0, import_node_fs34.readFileSync)(p, "utf8") : null;
|
|
28852
30598
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
28853
30599
|
const putSeed = async (target, content, ref, sha) => {
|
|
28854
30600
|
const tmp = (0, import_node_path32.join)((0, import_node_os15.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
@@ -28963,7 +30709,7 @@ function registerBootstrapCommands(program3) {
|
|
|
28963
30709
|
if (isLegacyBlock) {
|
|
28964
30710
|
content = upsertManagedGitignoreBlock(remoteContent).content;
|
|
28965
30711
|
} else {
|
|
28966
|
-
const writeContent = resolveSeedWriteContent(resolved, vars,
|
|
30712
|
+
const writeContent = resolveSeedWriteContent(resolved, vars, readFile7, remoteContent);
|
|
28967
30713
|
if (!writeContent.ok) {
|
|
28968
30714
|
return fail(`bootstrap apply: ${resolved.target}: ${writeContent.reason} \u2014 refusing to overwrite repo-owned content`);
|
|
28969
30715
|
}
|
|
@@ -29014,7 +30760,7 @@ function registerBootstrapCommands(program3) {
|
|
|
29014
30760
|
if (prDecision.action === "reuse") {
|
|
29015
30761
|
seedPrUrl = prDecision.url;
|
|
29016
30762
|
} else {
|
|
29017
|
-
const created = await ghCreate([
|
|
30763
|
+
const created = requireGhCreateOk(await ghCreate([
|
|
29018
30764
|
"pr",
|
|
29019
30765
|
"create",
|
|
29020
30766
|
"--repo",
|
|
@@ -29031,7 +30777,7 @@ function registerBootstrapCommands(program3) {
|
|
|
29031
30777
|
${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owned \`${onlyTarget}\`` : `The org-owned \`${onlyTarget}\` seed`} is declared by MMI-Hub's \`skills/bootstrap/seeds/manifest.json\` at MMI-Hub@${seedSource.sha}; this PR reconciles that declared scope and preserves everything outside it. It carries that file and nothing else \u2014 no labels, ruleset, merge settings or registry META were touched.
|
|
29032
30778
|
|
|
29033
30779
|
\`${baseBranch}\` is protected (${seedPlan.reason}), so delivery goes via this branch + PR \u2014 a direct contents PUT 409s on a protected base.` : `Auto-opened by \`mmi-cli devops bootstrap apply --execute ${repo}\` (#2286): \`${baseBranch}\` is protected (${seedPlan.reason}), so the org-managed seed files are delivered via this branch + PR \u2014 a direct contents PUT 409s on a protected base ("N of N required status checks are expected"). Seeds are from MMI-Hub@${seedSource.sha}.`
|
|
29034
|
-
]);
|
|
30780
|
+
]), "bootstrap apply pr create");
|
|
29035
30781
|
seedPrUrl = created.url;
|
|
29036
30782
|
}
|
|
29037
30783
|
let autoMergeEnabled = true;
|
|
@@ -29063,7 +30809,7 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
|
|
|
29063
30809
|
}
|
|
29064
30810
|
const rulesetSeed = manifest.seeds.find((s) => s.target === ".github/rulesets/mmi-product-required-checks.json");
|
|
29065
30811
|
if (rulesetSeed) {
|
|
29066
|
-
const rulesetContent = resolveSeedContent({ ...rulesetSeed, target: rulesetSeed.target.replace("{{REPO_SLUG}}", slug) }, vars,
|
|
30812
|
+
const rulesetContent = resolveSeedContent({ ...rulesetSeed, target: rulesetSeed.target.replace("{{REPO_SLUG}}", slug) }, vars, readFile7);
|
|
29067
30813
|
if (rulesetContent) {
|
|
29068
30814
|
try {
|
|
29069
30815
|
const client = defaultGitHubClient();
|
|
@@ -29342,11 +31088,18 @@ LIVE apply to ${repo}:
|
|
|
29342
31088
|
[]
|
|
29343
31089
|
);
|
|
29344
31090
|
const prDecision = decideSeedPrAction(identifiedOpenPrs);
|
|
31091
|
+
const prBody = `Auto-opened by \`mmi-cli devops bootstrap propagate --target ${seed.target} --execute\` (#4238), wave ${rec.wave}.
|
|
31092
|
+
|
|
31093
|
+
Propagates MMI-Hub@${headSha}'s ${seed.managedBlock ? "declared marker-bounded block inside repo-owned" : "org-owned copy of"} \`${seed.target}\` \u2014 the file this PR carries and nothing else; content outside a managed block remains untouched.
|
|
31094
|
+
${renderPropagationTargetMarker(seed.target)}
|
|
31095
|
+
|
|
31096
|
+
Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target} --execute\` (#4240) reverts this PR's merge commit; never re-run propagate with old bytes.`;
|
|
29345
31097
|
let prUrl;
|
|
29346
31098
|
if (prDecision.action === "reuse") {
|
|
29347
31099
|
prUrl = prDecision.url;
|
|
31100
|
+
await gh(["pr", "edit", prUrl, "--repo", rec.repo, "--body", prBody]);
|
|
29348
31101
|
} else {
|
|
29349
|
-
const created = await ghCreate([
|
|
31102
|
+
const created = requireGhCreateOk(await ghCreate([
|
|
29350
31103
|
"pr",
|
|
29351
31104
|
"create",
|
|
29352
31105
|
"--repo",
|
|
@@ -29358,13 +31111,8 @@ LIVE apply to ${repo}:
|
|
|
29358
31111
|
"--title",
|
|
29359
31112
|
`chore: propagate ${seed.managedBlock ? "Hub-managed block in" : "org-owned"} ${seed.target} from MMI-Hub (wave ${rec.wave})`,
|
|
29360
31113
|
"--body",
|
|
29361
|
-
|
|
29362
|
-
|
|
29363
|
-
Propagates MMI-Hub@${headSha}'s ${seed.managedBlock ? "declared marker-bounded block inside repo-owned" : "org-owned copy of"} \`${seed.target}\` \u2014 the file this PR carries and nothing else; content outside a managed block remains untouched.
|
|
29364
|
-
${renderPropagationTargetMarker(seed.target)}
|
|
29365
|
-
|
|
29366
|
-
Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target} --execute\` (#4240) reverts this PR's merge commit; never re-run propagate with old bytes.`
|
|
29367
|
-
]);
|
|
31114
|
+
prBody
|
|
31115
|
+
]), "bootstrap propagate pr create");
|
|
29368
31116
|
prUrl = created.url;
|
|
29369
31117
|
}
|
|
29370
31118
|
if (rec.wave !== 0) {
|
|
@@ -29511,7 +31259,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
29511
31259
|
if (prDecision.action === "reuse") {
|
|
29512
31260
|
prUrl = prDecision.url;
|
|
29513
31261
|
} else {
|
|
29514
|
-
const created =
|
|
31262
|
+
const created = requireGhCreateOk(
|
|
31263
|
+
await ghCreate(["pr", "create", "--repo", repo, "--base", baseBranch, "--head", plan.branch, "--title", plan.title, "--body", plan.body]),
|
|
31264
|
+
"bootstrap rollback pr create"
|
|
31265
|
+
);
|
|
29515
31266
|
prUrl = created.url;
|
|
29516
31267
|
}
|
|
29517
31268
|
plan.prUrl = prUrl;
|
|
@@ -29711,6 +31462,15 @@ async function detectPublicIpFrom(url, fetchImpl) {
|
|
|
29711
31462
|
if (!validStageLiveIp(ip)) throw new Error(`public IP detection returned a non-IP body from ${url}: "${ip.slice(0, 80)}"`);
|
|
29712
31463
|
return ip;
|
|
29713
31464
|
}
|
|
31465
|
+
function isStageLiveDeployModel(deployModel, projectType) {
|
|
31466
|
+
if (deployModel) return deployModel === "tenant-container";
|
|
31467
|
+
return projectType === "web-app";
|
|
31468
|
+
}
|
|
31469
|
+
function stageLiveUnsupportedReason(deployModel, projectType) {
|
|
31470
|
+
if (isStageLiveDeployModel(deployModel, projectType)) return null;
|
|
31471
|
+
const got = deployModel ?? (projectType ? `projectType=${projectType}` : "unset");
|
|
31472
|
+
return `stage --live applies to tenant-container repos only (registry deployModel = ${got}; registry-publish and other models have no personal cloud dev stage)`;
|
|
31473
|
+
}
|
|
29714
31474
|
function stageLiveUpSteps(t) {
|
|
29715
31475
|
return [
|
|
29716
31476
|
{ label: `detect your public IPv4 and IPv6 (${IP_ECHO_URL} + ${IP6_ECHO_URL}, bounded, in parallel)` },
|
|
@@ -29725,6 +31485,16 @@ function stageLiveDownSteps(t) {
|
|
|
29725
31485
|
{ label: `remove the Cloudflare edge gate for ${t.host} via the Hub backend (tenant-control cf-gate-clear)` }
|
|
29726
31486
|
];
|
|
29727
31487
|
}
|
|
31488
|
+
function stageLiveDownNoop(t, reason) {
|
|
31489
|
+
return {
|
|
31490
|
+
command: "stage --live",
|
|
31491
|
+
mode: "down",
|
|
31492
|
+
slug: t.slug,
|
|
31493
|
+
repo: t.repo,
|
|
31494
|
+
dispatched: [],
|
|
31495
|
+
message: `no live stage to tear down \u2014 ${reason}`
|
|
31496
|
+
};
|
|
31497
|
+
}
|
|
29728
31498
|
async function runStageLiveUp(deps, t) {
|
|
29729
31499
|
if (!t.ref?.trim()) throw new Error("stage --live: cannot resolve the current branch to deploy");
|
|
29730
31500
|
const detected = await deps.detectIp();
|
|
@@ -29969,7 +31739,14 @@ function registerStageCommands(program3) {
|
|
|
29969
31739
|
const meta = await fetchProjectBySlug(slug, registryClientDeps(await loadConfig())).catch(() => null);
|
|
29970
31740
|
const host = requireEdgeDomain(meta, "dev", slug);
|
|
29971
31741
|
if (!host.trim()) throw new Error(`stage --live: no dev edge host for ${slug} (registry edgeDomains.dev)`);
|
|
29972
|
-
return {
|
|
31742
|
+
return {
|
|
31743
|
+
slug,
|
|
31744
|
+
repo,
|
|
31745
|
+
host,
|
|
31746
|
+
ref,
|
|
31747
|
+
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
31748
|
+
projectType: typeof meta?.projectType === "string" ? meta.projectType : void 0
|
|
31749
|
+
};
|
|
29973
31750
|
}
|
|
29974
31751
|
async function runStageLiveCommand(o) {
|
|
29975
31752
|
let target;
|
|
@@ -29978,6 +31755,19 @@ function registerStageCommands(program3) {
|
|
|
29978
31755
|
} catch (e) {
|
|
29979
31756
|
return failGraceful(e.message);
|
|
29980
31757
|
}
|
|
31758
|
+
const unsupported = stageLiveUnsupportedReason(target.deployModel, target.projectType);
|
|
31759
|
+
if (unsupported) {
|
|
31760
|
+
if (o.down) {
|
|
31761
|
+
const result = stageLiveDownNoop(target, unsupported);
|
|
31762
|
+
if (!o.apply) {
|
|
31763
|
+
const steps = [{ label: result.message }];
|
|
31764
|
+
if (o.json) return console.log(JSON.stringify({ command: "stage --live", mode: "down", slug: target.slug, repo: target.repo, noop: true, steps }, null, 2));
|
|
31765
|
+
return console.log(renderSteps("mmi-cli stage --live --down: dry-run plan", steps));
|
|
31766
|
+
}
|
|
31767
|
+
return printLine(o.json ? JSON.stringify(result) : `mmi-cli stage --live: ${result.message}`);
|
|
31768
|
+
}
|
|
31769
|
+
return failGraceful(unsupported);
|
|
31770
|
+
}
|
|
29981
31771
|
const mode = o.down ? "down" : "up";
|
|
29982
31772
|
if (!o.apply) {
|
|
29983
31773
|
const steps = o.down ? stageLiveDownSteps(target) : stageLiveUpSteps(target);
|
|
@@ -30510,6 +32300,13 @@ async function remoteBranchExists2(branch, options = {}) {
|
|
|
30510
32300
|
init_cli_shared();
|
|
30511
32301
|
init_clean_exit();
|
|
30512
32302
|
init_error_codes();
|
|
32303
|
+
function refuseRateLimited(e, json) {
|
|
32304
|
+
if (!isRateLimitedError(e)) return false;
|
|
32305
|
+
if (json) console.log(JSON.stringify(e.receipt));
|
|
32306
|
+
else console.error(`mmi-cli board claim failed: ${e.receipt.message} \u2014 retry: ${e.receipt.retryCommand}`);
|
|
32307
|
+
process.exitCode = 1;
|
|
32308
|
+
return true;
|
|
32309
|
+
}
|
|
30513
32310
|
function registerBoardCommands(program3) {
|
|
30514
32311
|
function withDiscoverMissDetail(message) {
|
|
30515
32312
|
const miss = lastBoardDiscoverMiss();
|
|
@@ -30521,13 +32318,14 @@ function registerBoardCommands(program3) {
|
|
|
30521
32318
|
}
|
|
30522
32319
|
async function runBoardRead(o) {
|
|
30523
32320
|
try {
|
|
32321
|
+
const config = await loadConfigForRepo(o.repo);
|
|
30524
32322
|
const report = await readBoard({
|
|
30525
|
-
config
|
|
32323
|
+
config,
|
|
30526
32324
|
repo: o.repo,
|
|
30527
32325
|
includeBundleDetails: o.bundleDetails,
|
|
30528
32326
|
includeAllBodies: o.bodies,
|
|
30529
32327
|
allowPartial: o.allowPartial
|
|
30530
|
-
});
|
|
32328
|
+
}, { snapshot: registryClientDeps(config) });
|
|
30531
32329
|
console.log(o.json ? JSON.stringify(report) : renderBoardReport(report));
|
|
30532
32330
|
} catch (e) {
|
|
30533
32331
|
return failGraceful(`board read failed: ${withDiscoverMissDetail(e.message)}`);
|
|
@@ -30558,6 +32356,7 @@ function registerBoardCommands(program3) {
|
|
|
30558
32356
|
if (o.json) return console.log(JSON.stringify(result));
|
|
30559
32357
|
console.log(result.checked ? checkVerdict(result.item.ref, result.alreadyClaimed) : result.partial ? `Partially claimed ${result.item.ref}: ${result.warning}` : result.alreadyClaimed ? `Already claimed ${result.item.ref} - In Progress (no change)` : `Claimed ${result.item.ref} - In Progress`);
|
|
30560
32358
|
} catch (e) {
|
|
32359
|
+
if (refuseRateLimited(e, o.json)) return;
|
|
30561
32360
|
return failGraceful(`board claim failed: ${e.message}`);
|
|
30562
32361
|
}
|
|
30563
32362
|
return;
|
|
@@ -30582,6 +32381,7 @@ function registerBoardCommands(program3) {
|
|
|
30582
32381
|
}
|
|
30583
32382
|
if (bulk.failed > 0) process.exitCode = 1;
|
|
30584
32383
|
} catch (e) {
|
|
32384
|
+
if (refuseRateLimited(e, o.json)) return;
|
|
30585
32385
|
return failGraceful(`board claim failed: ${e.message}`);
|
|
30586
32386
|
}
|
|
30587
32387
|
}), [
|
|
@@ -31183,7 +32983,7 @@ async function checkDocsIndexAtHead(opts, deps) {
|
|
|
31183
32983
|
|
|
31184
32984
|
// src/issue-commands.ts
|
|
31185
32985
|
var import_node_fs38 = require("node:fs");
|
|
31186
|
-
var
|
|
32986
|
+
var import_node_crypto14 = require("node:crypto");
|
|
31187
32987
|
init_cli_shared();
|
|
31188
32988
|
init_clean_exit();
|
|
31189
32989
|
init_error_codes();
|
|
@@ -31656,7 +33456,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
31656
33456
|
const identity = `${spec.type}
|
|
31657
33457
|
${spec.title.trim()}
|
|
31658
33458
|
${spec.body ?? ""}`;
|
|
31659
|
-
const hash = (0,
|
|
33459
|
+
const hash = (0, import_node_crypto14.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
31660
33460
|
return `${batchKey}:${hash}`;
|
|
31661
33461
|
}
|
|
31662
33462
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -31803,6 +33603,11 @@ ${lines}`);
|
|
|
31803
33603
|
const attached = await deps.attach(row.number, rowRepo(spec), rowPriority);
|
|
31804
33604
|
row.onBoard = attached.onBoard;
|
|
31805
33605
|
if (attached.projectItemId) row.projectItemId = attached.projectItemId;
|
|
33606
|
+
if (attached.boardAttach) {
|
|
33607
|
+
row.boardAttach = attached.boardAttach;
|
|
33608
|
+
if (attached.resetAt) row.resetAt = attached.resetAt;
|
|
33609
|
+
if (attached.resetEpochSeconds !== void 0) row.resetEpochSeconds = attached.resetEpochSeconds;
|
|
33610
|
+
}
|
|
31806
33611
|
}
|
|
31807
33612
|
const parentRef = spec.parent ?? options.parent;
|
|
31808
33613
|
if (parentRef) {
|
|
@@ -33098,6 +34903,214 @@ function registerPrLifecycleCommands(program3) {
|
|
|
33098
34903
|
});
|
|
33099
34904
|
}
|
|
33100
34905
|
|
|
34906
|
+
// src/post-merge-recon.ts
|
|
34907
|
+
var import_node_fs41 = require("node:fs");
|
|
34908
|
+
var import_node_path38 = require("node:path");
|
|
34909
|
+
|
|
34910
|
+
// src/cross-repo-filing-issue.ts
|
|
34911
|
+
init_github_client();
|
|
34912
|
+
function crossRepoFilingRetryCommand(prRepo, prNumber) {
|
|
34913
|
+
return `mmi-cli devops pr merge ${prNumber} --repo ${prRepo}`;
|
|
34914
|
+
}
|
|
34915
|
+
function crossRepoFilingRateLimitedResult(e, prRepo, prNumber) {
|
|
34916
|
+
return {
|
|
34917
|
+
status: "rate-limited",
|
|
34918
|
+
error: e instanceof Error ? e.message : String(e ?? "rate-limited"),
|
|
34919
|
+
rateLimited: rateLimitedReceipt({
|
|
34920
|
+
resetEpochSeconds: resetEpochFromError(e),
|
|
34921
|
+
retryCommand: crossRepoFilingRetryCommand(prRepo, prNumber),
|
|
34922
|
+
context: "cross-repo filing-issue reconciliation"
|
|
34923
|
+
})
|
|
34924
|
+
};
|
|
34925
|
+
}
|
|
34926
|
+
async function discoverSameOrgIssueNumber(client, currentRepo, number) {
|
|
34927
|
+
const [owner] = currentRepo.split("/");
|
|
34928
|
+
if (!owner) throw new Error(`cannot derive an organization from ${currentRepo}`);
|
|
34929
|
+
const repositories = await client.restPaginate(`orgs/${owner}/repos?type=all`);
|
|
34930
|
+
const hits = [];
|
|
34931
|
+
let next = 0;
|
|
34932
|
+
const worker = async () => {
|
|
34933
|
+
while (next < repositories.length) {
|
|
34934
|
+
const repo = repositories[next++]?.full_name;
|
|
34935
|
+
if (!repo || repo.toLowerCase() === currentRepo.toLowerCase()) continue;
|
|
34936
|
+
const issue2 = await readExactIssue(client, repo, number);
|
|
34937
|
+
if (issue2) hits.push(issue2);
|
|
34938
|
+
}
|
|
34939
|
+
};
|
|
34940
|
+
await Promise.all(Array.from({ length: Math.min(8, repositories.length) }, () => worker()));
|
|
34941
|
+
return hits.sort((left, right) => left.repo.localeCompare(right.repo));
|
|
34942
|
+
}
|
|
34943
|
+
async function readExactIssue(client, repo, number) {
|
|
34944
|
+
try {
|
|
34945
|
+
const issue2 = await client.rest("GET", `repos/${repo}/issues/${number}`);
|
|
34946
|
+
if (issue2.pull_request || issue2.number !== number || !issue2.title || !issue2.html_url) return void 0;
|
|
34947
|
+
const state = issue2.state?.toUpperCase();
|
|
34948
|
+
if (state !== "OPEN" && state !== "CLOSED") return void 0;
|
|
34949
|
+
return { repo, number, title: issue2.title, url: issue2.html_url, state };
|
|
34950
|
+
} catch (e) {
|
|
34951
|
+
if (e instanceof GitHubApiError && e.status === 404) return void 0;
|
|
34952
|
+
throw e;
|
|
34953
|
+
}
|
|
34954
|
+
}
|
|
34955
|
+
function explicitSameOrgIssueUrls(text, prRepo, closingNumbers) {
|
|
34956
|
+
const [owner] = prRepo.split("/");
|
|
34957
|
+
const found = /* @__PURE__ */ new Map();
|
|
34958
|
+
for (const match of text.matchAll(/https:\/\/github\.com\/([\w.-]+\/[\w.-]+)\/issues\/(\d+)\b/gi)) {
|
|
34959
|
+
const repo = match[1];
|
|
34960
|
+
const number = Number(match[2]);
|
|
34961
|
+
if (!repo || !Number.isInteger(number) || !closingNumbers.has(number)) continue;
|
|
34962
|
+
if (repo.split("/")[0]?.toLowerCase() !== owner.toLowerCase() || repo.toLowerCase() === prRepo.toLowerCase()) continue;
|
|
34963
|
+
found.set(`${repo.toLowerCase()}#${number}`, { repo, number });
|
|
34964
|
+
}
|
|
34965
|
+
return [...found.values()];
|
|
34966
|
+
}
|
|
34967
|
+
async function reconcileCrossRepoFilingIssue(client, prRepo, prNumber) {
|
|
34968
|
+
try {
|
|
34969
|
+
const pr2 = await client.rest("GET", `repos/${prRepo}/pulls/${prNumber}`);
|
|
34970
|
+
if (!pr2.merged_at) return { status: "not-applicable" };
|
|
34971
|
+
const text = `${pr2.title ?? ""}
|
|
34972
|
+
${pr2.body ?? ""}`;
|
|
34973
|
+
const closingNumbers = new Set(findClosingMentions(text).filter((mention) => !mention.negated).map((mention) => mention.issue));
|
|
34974
|
+
if (closingNumbers.size === 0) return { status: "not-applicable" };
|
|
34975
|
+
const explicit = explicitSameOrgIssueUrls(text, prRepo, closingNumbers);
|
|
34976
|
+
let target;
|
|
34977
|
+
if (explicit.length > 1) {
|
|
34978
|
+
return { status: "failed", error: `multiple same-org issue URLs match bare closing references (${explicit.map((x) => `${x.repo}#${x.number}`).join(", ")})` };
|
|
34979
|
+
}
|
|
34980
|
+
if (explicit.length === 1) {
|
|
34981
|
+
target = await readExactIssue(client, explicit[0].repo, explicit[0].number);
|
|
34982
|
+
if (!target) return { status: "failed", issue: `${explicit[0].repo}#${explicit[0].number}`, error: "the explicit foreign issue URL did not resolve to an issue" };
|
|
34983
|
+
} else {
|
|
34984
|
+
const branchNumber = Number(/^#?(\d+)(?:-|$)/.exec(pr2.head?.ref ?? "")?.[1]);
|
|
34985
|
+
if (!Number.isInteger(branchNumber) || !closingNumbers.has(branchNumber)) return { status: "not-applicable" };
|
|
34986
|
+
if (await readExactIssue(client, prRepo, branchNumber)) return { status: "not-applicable" };
|
|
34987
|
+
const matches = await discoverSameOrgIssueNumber(client, prRepo, branchNumber);
|
|
34988
|
+
if (matches.length !== 1) {
|
|
34989
|
+
return {
|
|
34990
|
+
status: "failed",
|
|
34991
|
+
error: matches.length === 0 ? `branch #${branchNumber} matches a bare closing reference, but no same-org filing issue was found` : `branch #${branchNumber} is ambiguous across ${matches.map((match) => match.repo).join(", ")}`
|
|
34992
|
+
};
|
|
34993
|
+
}
|
|
34994
|
+
target = matches[0];
|
|
34995
|
+
}
|
|
34996
|
+
const issue2 = `${target.repo}#${target.number}`;
|
|
34997
|
+
if (target.state === "CLOSED") return { status: "already-closed", issue: issue2, url: target.url };
|
|
34998
|
+
const prUrl = pr2.html_url ?? `https://github.com/${prRepo}/pull/${prNumber}`;
|
|
34999
|
+
const comment = await client.rest("POST", `repos/${target.repo}/issues/${target.number}/comments`, {
|
|
35000
|
+
body: { body: `Resolved by merged PR ${prUrl}.
|
|
35001
|
+
|
|
35002
|
+
<!-- mmi-cross-repo-close:${prRepo}#${prNumber} -->` }
|
|
35003
|
+
});
|
|
35004
|
+
if (!comment.html_url) throw new Error(`GitHub created no evidence URL on ${issue2}`);
|
|
35005
|
+
await closeIssue(client, { ref: issue2, reason: "completed", evidence: comment.html_url });
|
|
35006
|
+
return { status: "closed", issue: issue2, url: target.url, evidence: comment.html_url };
|
|
35007
|
+
} catch (e) {
|
|
35008
|
+
if (isRateLimitRefusal(e)) return crossRepoFilingRateLimitedResult(e, prRepo, prNumber);
|
|
35009
|
+
return { status: "failed", error: e.message };
|
|
35010
|
+
}
|
|
35011
|
+
}
|
|
35012
|
+
|
|
35013
|
+
// src/post-merge-recon.ts
|
|
35014
|
+
var POST_MERGE_RECON_SCHEMA_VERSION = 1;
|
|
35015
|
+
function postMergeReconRetryCommand(repo, pr2) {
|
|
35016
|
+
return crossRepoFilingRetryCommand(repo, Number(pr2));
|
|
35017
|
+
}
|
|
35018
|
+
function postMergeReconStatePath(cwd, repo, pr2) {
|
|
35019
|
+
const safe = repo.replace(/[^A-Za-z0-9._-]+/g, "_");
|
|
35020
|
+
return repoRuntimeStatePath(cwd, "post-merge-recon", `${safe}-${pr2}.json`);
|
|
35021
|
+
}
|
|
35022
|
+
function isBoardAdvanceRateLimitedOnly(result) {
|
|
35023
|
+
if (!result) return false;
|
|
35024
|
+
if (result.status === "fetch-failed") {
|
|
35025
|
+
return isRateLimitText(result.error ?? "");
|
|
35026
|
+
}
|
|
35027
|
+
const failures = boardAdvanceFailures(result);
|
|
35028
|
+
if (!failures.length) return false;
|
|
35029
|
+
return failures.every((f) => typeof f.error === "string" && isRateLimitText(f.error));
|
|
35030
|
+
}
|
|
35031
|
+
function postMergeReconExitCode(input) {
|
|
35032
|
+
if (input.crossRepoFilingIssue.status === "failed") return 1;
|
|
35033
|
+
if (isBoardAdvanceRateLimitedOnly(input.boardAdvance)) {
|
|
35034
|
+
} else {
|
|
35035
|
+
const boardCode = boardAdvanceExitCode(input.boardAdvance);
|
|
35036
|
+
if (boardCode !== void 0) return boardCode;
|
|
35037
|
+
}
|
|
35038
|
+
return void 0;
|
|
35039
|
+
}
|
|
35040
|
+
function buildPostMergeReconRecovery(input) {
|
|
35041
|
+
const pending = [];
|
|
35042
|
+
if (isBoardAdvanceRateLimitedOnly(input.boardAdvance)) pending.push("board-advance");
|
|
35043
|
+
if (input.crossRepoFilingIssue.status === "rate-limited") pending.push("cross-repo-filing");
|
|
35044
|
+
if (!pending.length) return void 0;
|
|
35045
|
+
const pr2 = Number(input.pr);
|
|
35046
|
+
const retryCommand = postMergeReconRetryCommand(input.repo, pr2);
|
|
35047
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
35048
|
+
let rateLimited;
|
|
35049
|
+
if (input.crossRepoFilingIssue.status === "rate-limited") {
|
|
35050
|
+
rateLimited = input.crossRepoFilingIssue.rateLimited;
|
|
35051
|
+
} else if (isBoardAdvanceRateLimitedOnly(input.boardAdvance)) {
|
|
35052
|
+
rateLimited = rateLimitedReceipt({
|
|
35053
|
+
resetEpochSeconds: void 0,
|
|
35054
|
+
retryCommand,
|
|
35055
|
+
context: "post-merge board advance",
|
|
35056
|
+
nowMs,
|
|
35057
|
+
message: input.boardAdvance.status === "fetch-failed" ? `post-merge board advance: ${input.boardAdvance.error ?? "rate-limited"}` : "post-merge board advance: rate-limited while moving closed issues to Done"
|
|
35058
|
+
});
|
|
35059
|
+
}
|
|
35060
|
+
return {
|
|
35061
|
+
schemaVersion: POST_MERGE_RECON_SCHEMA_VERSION,
|
|
35062
|
+
mergeStatus: "merged",
|
|
35063
|
+
repo: input.repo,
|
|
35064
|
+
pr: pr2,
|
|
35065
|
+
pending,
|
|
35066
|
+
retryCommand,
|
|
35067
|
+
createdAt: new Date(nowMs).toISOString(),
|
|
35068
|
+
...rateLimited ? { rateLimited } : {},
|
|
35069
|
+
boardAdvanceStatus: input.boardAdvance.status,
|
|
35070
|
+
crossRepoFilingStatus: input.crossRepoFilingIssue.status
|
|
35071
|
+
};
|
|
35072
|
+
}
|
|
35073
|
+
function writePostMergeReconRecovery(cwd, recovery) {
|
|
35074
|
+
const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
|
|
35075
|
+
(0, import_node_fs41.mkdirSync)((0, import_node_path38.dirname)(path2), { recursive: true });
|
|
35076
|
+
(0, import_node_fs41.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
|
|
35077
|
+
`, "utf8");
|
|
35078
|
+
return path2;
|
|
35079
|
+
}
|
|
35080
|
+
function clearPostMergeReconRecovery(cwd, repo, pr2) {
|
|
35081
|
+
const path2 = postMergeReconStatePath(cwd, repo, pr2);
|
|
35082
|
+
if (!(0, import_node_fs41.existsSync)(path2)) return;
|
|
35083
|
+
try {
|
|
35084
|
+
(0, import_node_fs41.unlinkSync)(path2);
|
|
35085
|
+
} catch {
|
|
35086
|
+
}
|
|
35087
|
+
}
|
|
35088
|
+
function postMergeReconWarnings(input) {
|
|
35089
|
+
const lines = [];
|
|
35090
|
+
const boardMsg = boardAdvanceFailureMessage(input.boardAdvance);
|
|
35091
|
+
if (boardMsg) {
|
|
35092
|
+
if (isBoardAdvanceRateLimitedOnly(input.boardAdvance)) {
|
|
35093
|
+
const reset = input.recovery?.rateLimited?.resetAt ? ` (reset ${input.recovery.rateLimited.resetAt})` : "";
|
|
35094
|
+
lines.push(
|
|
35095
|
+
`${input.context}: board advance deferred \u2014 GitHub rate-limited${reset}; the PR MERGED. Retry board/filing later: ${input.recovery?.retryCommand ?? postMergeReconRetryCommand("(repo)", "(pr)")}`
|
|
35096
|
+
);
|
|
35097
|
+
} else {
|
|
35098
|
+
lines.push(boardMsg);
|
|
35099
|
+
}
|
|
35100
|
+
}
|
|
35101
|
+
if (input.crossRepoFilingIssue.status === "rate-limited") {
|
|
35102
|
+
const r = input.crossRepoFilingIssue.rateLimited;
|
|
35103
|
+
lines.push(
|
|
35104
|
+
`${input.context}: cross-repo filing-issue reconciliation deferred \u2014 ${r.message}. The PR MERGED. Retry: ${r.retryCommand}` + (input.recoveryPath ? ` (recovery state: ${input.recoveryPath})` : "")
|
|
35105
|
+
);
|
|
35106
|
+
} else if (input.crossRepoFilingIssue.status === "failed") {
|
|
35107
|
+
lines.push(
|
|
35108
|
+
`${input.context}: cross-repo filing-issue reconciliation failed (${input.crossRepoFilingIssue.error}) \u2014 the PR MERGED, but a foreign filing issue may remain open.`
|
|
35109
|
+
);
|
|
35110
|
+
}
|
|
35111
|
+
return lines;
|
|
35112
|
+
}
|
|
35113
|
+
|
|
33101
35114
|
// src/session-report.ts
|
|
33102
35115
|
init_cli_shared();
|
|
33103
35116
|
var GC_GH_TIMEOUT_MS4 = 2e4;
|
|
@@ -35366,6 +37379,13 @@ function checkDocsIndex(probe) {
|
|
|
35366
37379
|
verbose: evidence
|
|
35367
37380
|
};
|
|
35368
37381
|
}
|
|
37382
|
+
var REPO_INDEX_RECONCILE_SCHEDULE_ID = "MMI-Hub/repo-index-reconcile";
|
|
37383
|
+
function isHubOwnedRepoIndexGap(probe) {
|
|
37384
|
+
if (probe.kind === "missing-authority") return true;
|
|
37385
|
+
if (probe.kind !== "healthy") return false;
|
|
37386
|
+
const readiness = probe.v4Readiness;
|
|
37387
|
+
return probe.cloudV4State === "invalid" || probe.cloudV4State === "tombstoned" || probe.cloudV4State === "degraded" && (probe.localV4EmbeddingCoverage ?? 1) < 0.95 || !readiness || readiness.verdict === "not-ready";
|
|
37388
|
+
}
|
|
35369
37389
|
function checkRepoIndexCloud(probe) {
|
|
35370
37390
|
if (!probe) return null;
|
|
35371
37391
|
const percent = (value) => value == null ? "n/a" : `${Math.round(value * 100)}%`;
|
|
@@ -35389,14 +37409,14 @@ function checkRepoIndexCloud(probe) {
|
|
|
35389
37409
|
];
|
|
35390
37410
|
switch (probe.kind) {
|
|
35391
37411
|
case "healthy": {
|
|
35392
|
-
const v4Unready =
|
|
37412
|
+
const v4Unready = isHubOwnedRepoIndexGap(probe);
|
|
35393
37413
|
const detail = probe.detail ?? `cloud v4 ${probe.cloudV4State ?? "absent"}, readiness ${readiness?.verdict ?? "unknown"}`;
|
|
35394
37414
|
return {
|
|
35395
37415
|
ok: !v4Unready,
|
|
35396
37416
|
id: "repo-index",
|
|
35397
37417
|
label: "repo-index",
|
|
35398
37418
|
detail,
|
|
35399
|
-
...v4Unready ? { fix:
|
|
37419
|
+
...v4Unready ? { fix: `Hub-owned: run \`mmi-cli harbour org schedules run ${REPO_INDEX_RECONCILE_SCHEDULE_ID}\` (or \`mmi-hub update\` then that schedule); require v4 authority and readiness to become ready` } : {},
|
|
35400
37420
|
verbose: evidence
|
|
35401
37421
|
};
|
|
35402
37422
|
}
|
|
@@ -35425,7 +37445,7 @@ function checkRepoIndexCloud(probe) {
|
|
|
35425
37445
|
id: "repo-index",
|
|
35426
37446
|
label: "repo-index",
|
|
35427
37447
|
detail: probe.detail ?? "no active v4 authority for this repo",
|
|
35428
|
-
fix:
|
|
37448
|
+
fix: `Hub-owned: dispatch \`mmi-cli harbour org schedules run ${REPO_INDEX_RECONCILE_SCHEDULE_ID}\` or run the authenticated per-repo sync`,
|
|
35429
37449
|
verbose: evidence
|
|
35430
37450
|
};
|
|
35431
37451
|
case "auth":
|
|
@@ -35801,7 +37821,30 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
35801
37821
|
return;
|
|
35802
37822
|
}
|
|
35803
37823
|
const row = checkRepoIndexCloud(probe);
|
|
35804
|
-
if (row)
|
|
37824
|
+
if (!row) return;
|
|
37825
|
+
if (opts.self && probe && !row.ok && isHubOwnedRepoIndexGap(probe)) {
|
|
37826
|
+
row.reportOnly = true;
|
|
37827
|
+
const evidence = [...row.verbose ?? [], `owner: Hub schedule ${REPO_INDEX_RECONCILE_SCHEDULE_ID}`];
|
|
37828
|
+
if (deps.dispatchRepoIndexReconcile) {
|
|
37829
|
+
healIntent(`repo-index \u2014 dispatching ${REPO_INDEX_RECONCILE_SCHEDULE_ID}`);
|
|
37830
|
+
try {
|
|
37831
|
+
const dispatched = await deps.dispatchRepoIndexReconcile();
|
|
37832
|
+
evidence.push(`self-route: ${dispatched.detail}`);
|
|
37833
|
+
row.fix = dispatched.ok ? `dispatched Hub-owned ${REPO_INDEX_RECONCILE_SCHEDULE_ID} \u2014 orientation may continue; re-run doctor after reconcile settles` : `Hub-owned ${REPO_INDEX_RECONCILE_SCHEDULE_ID} \u2014 dispatch failed (${dispatched.detail}); run \`mmi-cli harbour org schedules run ${REPO_INDEX_RECONCILE_SCHEDULE_ID}\``;
|
|
37834
|
+
if (dispatched.ok) {
|
|
37835
|
+
row.detail = `${row.detail} \u2014 reconcile dispatched`;
|
|
37836
|
+
}
|
|
37837
|
+
} catch (e) {
|
|
37838
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
37839
|
+
evidence.push(`self-route threw: ${message}`);
|
|
37840
|
+
row.fix = `Hub-owned ${REPO_INDEX_RECONCILE_SCHEDULE_ID} \u2014 dispatch threw (${message}); run \`mmi-cli harbour org schedules run ${REPO_INDEX_RECONCILE_SCHEDULE_ID}\``;
|
|
37841
|
+
}
|
|
37842
|
+
} else {
|
|
37843
|
+
row.fix = `Hub-owned ${REPO_INDEX_RECONCILE_SCHEDULE_ID} \u2014 run \`mmi-cli harbour org schedules run ${REPO_INDEX_RECONCILE_SCHEDULE_ID}\`; orientation may continue`;
|
|
37844
|
+
}
|
|
37845
|
+
row.verbose = evidence;
|
|
37846
|
+
}
|
|
37847
|
+
emitNow(row);
|
|
35805
37848
|
}
|
|
35806
37849
|
async function runBoardDoctorRow() {
|
|
35807
37850
|
if (!deps.boardDoctorFix) return;
|
|
@@ -35966,6 +38009,79 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
35966
38009
|
});
|
|
35967
38010
|
}
|
|
35968
38011
|
}
|
|
38012
|
+
async function runDeployPortCollisionsRow() {
|
|
38013
|
+
if (!deps.deployPortCollisionsState) return;
|
|
38014
|
+
try {
|
|
38015
|
+
const state = await deps.deployPortCollisionsState();
|
|
38016
|
+
if (!state) {
|
|
38017
|
+
emitNow({
|
|
38018
|
+
ok: true,
|
|
38019
|
+
warn: true,
|
|
38020
|
+
reportOnly: true,
|
|
38021
|
+
id: "deploy-port-collisions",
|
|
38022
|
+
label: "deploy port collisions",
|
|
38023
|
+
detail: "skipped",
|
|
38024
|
+
verbose: ["deployPortCollisionsState returned undefined"]
|
|
38025
|
+
});
|
|
38026
|
+
return;
|
|
38027
|
+
}
|
|
38028
|
+
if (state.forbidden) {
|
|
38029
|
+
emitNow({
|
|
38030
|
+
ok: true,
|
|
38031
|
+
reportOnly: true,
|
|
38032
|
+
id: "deploy-port-collisions",
|
|
38033
|
+
label: "deploy port collisions",
|
|
38034
|
+
detail: "skipped \u2014 master-admin only",
|
|
38035
|
+
verbose: ["deployPortCollisionsState returned 403"]
|
|
38036
|
+
});
|
|
38037
|
+
return;
|
|
38038
|
+
}
|
|
38039
|
+
if (state.readFailed) {
|
|
38040
|
+
emitNow({
|
|
38041
|
+
ok: false,
|
|
38042
|
+
warn: true,
|
|
38043
|
+
reportOnly: true,
|
|
38044
|
+
id: "deploy-port-collisions",
|
|
38045
|
+
label: "deploy port collisions",
|
|
38046
|
+
detail: `could not be read \u2014 ${state.error ?? "unknown error"}`,
|
|
38047
|
+
fix: "run `mmi-cli oracle org project deploy doctor`",
|
|
38048
|
+
verbose: [`deployPortCollisionsState failed: ${state.error ?? "unknown error"}`]
|
|
38049
|
+
});
|
|
38050
|
+
return;
|
|
38051
|
+
}
|
|
38052
|
+
if (state.ok) {
|
|
38053
|
+
emitNow({
|
|
38054
|
+
ok: true,
|
|
38055
|
+
reportOnly: true,
|
|
38056
|
+
id: "deploy-port-collisions",
|
|
38057
|
+
label: "deploy port collisions",
|
|
38058
|
+
detail: "no duplicate (sshHost, port)",
|
|
38059
|
+
verbose: ["no colliding DEPLOY# coordinates"]
|
|
38060
|
+
});
|
|
38061
|
+
return;
|
|
38062
|
+
}
|
|
38063
|
+
emitNow({
|
|
38064
|
+
ok: false,
|
|
38065
|
+
reportOnly: true,
|
|
38066
|
+
id: "deploy-port-collisions",
|
|
38067
|
+
label: "deploy port collisions",
|
|
38068
|
+
detail: `${state.lines.length} colliding coordinate(s)`,
|
|
38069
|
+
fix: "run `mmi-cli oracle org project deploy doctor` then set-deploy one owner to a free port",
|
|
38070
|
+
verbose: state.lines
|
|
38071
|
+
});
|
|
38072
|
+
} catch (e) {
|
|
38073
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
38074
|
+
emitNow({
|
|
38075
|
+
ok: true,
|
|
38076
|
+
warn: true,
|
|
38077
|
+
reportOnly: true,
|
|
38078
|
+
id: "deploy-port-collisions",
|
|
38079
|
+
label: "deploy port collisions",
|
|
38080
|
+
detail: `skipped \u2014 ${message}`,
|
|
38081
|
+
verbose: [`deployPortCollisionsState threw: ${message}`]
|
|
38082
|
+
});
|
|
38083
|
+
}
|
|
38084
|
+
}
|
|
35969
38085
|
async function runHousekeeperRows() {
|
|
35970
38086
|
const repoRoot2 = await deps.repoRoot();
|
|
35971
38087
|
const scratch = deps.executeScratchGc(repoRoot2, { apply: false });
|
|
@@ -36010,7 +38126,8 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
36010
38126
|
// Hub cloud probe (short timeout). Full lane + `--self`; `--preflight` may emit command-absent only (#4156).
|
|
36011
38127
|
{ id: "repo-index", when: isOrgRepo && (lane.full || Boolean(opts.self) || lane.preflight), run: runRepoIndexRow },
|
|
36012
38128
|
{ id: "board-doctor", when: isOrgRepo && lane.full, run: runBoardDoctorRow },
|
|
36013
|
-
{ id: "schedules-drift", when: isOrgRepo && lane.full, run: runSchedulesDriftRow }
|
|
38129
|
+
{ id: "schedules-drift", when: isOrgRepo && lane.full, run: runSchedulesDriftRow },
|
|
38130
|
+
{ id: "deploy-port-collisions", when: isOrgRepo && lane.full, run: runDeployPortCollisionsRow }
|
|
36014
38131
|
];
|
|
36015
38132
|
await Promise.all(parallelRows.filter((entry) => entry.when).map((entry) => entry.run()));
|
|
36016
38133
|
const suffix = [
|
|
@@ -36142,19 +38259,19 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
36142
38259
|
}
|
|
36143
38260
|
|
|
36144
38261
|
// src/doctor-io.ts
|
|
36145
|
-
var
|
|
38262
|
+
var import_node_fs42 = require("node:fs");
|
|
36146
38263
|
var import_node_os18 = require("node:os");
|
|
36147
|
-
var
|
|
36148
|
-
var
|
|
38264
|
+
var import_node_path39 = require("node:path");
|
|
38265
|
+
var import_node_child_process19 = require("node:child_process");
|
|
36149
38266
|
var import_node_util8 = require("node:util");
|
|
36150
38267
|
init_version_lag();
|
|
36151
38268
|
init_plugin_guard_io();
|
|
36152
|
-
var execFileP6 = (0, import_node_util8.promisify)(
|
|
38269
|
+
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process19.execFile);
|
|
36153
38270
|
var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
36154
38271
|
function installedClaudePluginVersion() {
|
|
36155
38272
|
try {
|
|
36156
38273
|
const file = JSON.parse(
|
|
36157
|
-
(0,
|
|
38274
|
+
(0, import_node_fs42.readFileSync)((0, import_node_path39.join)((0, import_node_os18.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
36158
38275
|
);
|
|
36159
38276
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
36160
38277
|
if (versions.length === 0) return void 0;
|
|
@@ -36165,7 +38282,7 @@ function installedClaudePluginVersion() {
|
|
|
36165
38282
|
}
|
|
36166
38283
|
function manifestVersion(path2) {
|
|
36167
38284
|
try {
|
|
36168
|
-
const manifest = JSON.parse((0,
|
|
38285
|
+
const manifest = JSON.parse((0, import_node_fs42.readFileSync)(path2, "utf8"));
|
|
36169
38286
|
return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
|
|
36170
38287
|
} catch {
|
|
36171
38288
|
return void 0;
|
|
@@ -36174,12 +38291,12 @@ function manifestVersion(path2) {
|
|
|
36174
38291
|
function readHermesPluginEvidence(env = process.env) {
|
|
36175
38292
|
const host = hermesConfigRoot(env);
|
|
36176
38293
|
const root = hermesPluginRoot(env);
|
|
36177
|
-
const installRecordPresent = (0,
|
|
36178
|
-
const manifestPath = (0,
|
|
38294
|
+
const installRecordPresent = (0, import_node_fs42.existsSync)(root);
|
|
38295
|
+
const manifestPath = (0, import_node_path39.join)(root, "plugin.yaml");
|
|
36179
38296
|
let installedVersion;
|
|
36180
38297
|
let manifest = "missing";
|
|
36181
38298
|
try {
|
|
36182
|
-
const text = (0,
|
|
38299
|
+
const text = (0, import_node_fs42.readFileSync)(manifestPath, "utf8");
|
|
36183
38300
|
let version;
|
|
36184
38301
|
try {
|
|
36185
38302
|
const parsed = JSON.parse(text).version;
|
|
@@ -36193,20 +38310,20 @@ function readHermesPluginEvidence(env = process.env) {
|
|
|
36193
38310
|
if (version) {
|
|
36194
38311
|
installedVersion = version;
|
|
36195
38312
|
manifest = "valid";
|
|
36196
|
-
} else if ((0,
|
|
38313
|
+
} else if ((0, import_node_fs42.existsSync)(manifestPath)) manifest = "invalid";
|
|
36197
38314
|
} catch {
|
|
36198
|
-
if ((0,
|
|
38315
|
+
if ((0, import_node_fs42.existsSync)(manifestPath)) manifest = "invalid";
|
|
36199
38316
|
}
|
|
36200
38317
|
let skills = false;
|
|
36201
38318
|
try {
|
|
36202
|
-
skills = (0,
|
|
38319
|
+
skills = (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, "skills")) && (0, import_node_fs42.statSync)((0, import_node_path39.join)(root, "skills")).isDirectory();
|
|
36203
38320
|
} catch {
|
|
36204
38321
|
}
|
|
36205
38322
|
return {
|
|
36206
|
-
hostPresent: (0,
|
|
38323
|
+
hostPresent: (0, import_node_fs42.existsSync)(host),
|
|
36207
38324
|
installRecordPresent,
|
|
36208
38325
|
manifest,
|
|
36209
|
-
payloadPresent: (0,
|
|
38326
|
+
payloadPresent: (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, "__init__.py")) && skills && manifest === "valid",
|
|
36210
38327
|
...installedVersion ? { installedVersion } : {}
|
|
36211
38328
|
};
|
|
36212
38329
|
}
|
|
@@ -36214,7 +38331,7 @@ function installedSurfacePluginVersion(surface) {
|
|
|
36214
38331
|
const token = surfaceToken(surface);
|
|
36215
38332
|
if (token === "kilo") {
|
|
36216
38333
|
try {
|
|
36217
|
-
const stamp = (0,
|
|
38334
|
+
const stamp = (0, import_node_fs42.readFileSync)((0, import_node_path39.join)((0, import_node_os18.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
|
|
36218
38335
|
return stamp || void 0;
|
|
36219
38336
|
} catch {
|
|
36220
38337
|
return void 0;
|
|
@@ -36222,23 +38339,23 @@ function installedSurfacePluginVersion(surface) {
|
|
|
36222
38339
|
}
|
|
36223
38340
|
if (token === "hermes") return readHermesPluginEvidence().installedVersion;
|
|
36224
38341
|
if (token === "cursor") {
|
|
36225
|
-
return manifestVersion((0,
|
|
38342
|
+
return manifestVersion((0, import_node_path39.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
|
|
36226
38343
|
}
|
|
36227
38344
|
if (token === "jervcode") {
|
|
36228
38345
|
return installedJervCodePackageVersion();
|
|
36229
38346
|
}
|
|
36230
38347
|
if (token === "kimi") {
|
|
36231
|
-
return manifestVersion((0,
|
|
38348
|
+
return manifestVersion((0, import_node_path39.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
|
|
36232
38349
|
}
|
|
36233
38350
|
if (token === "claude") return installedClaudePluginVersion();
|
|
36234
38351
|
if (token !== "codex") return void 0;
|
|
36235
38352
|
try {
|
|
36236
|
-
const raw = process.platform === "win32" ? (0,
|
|
38353
|
+
const raw = process.platform === "win32" ? (0, import_node_child_process19.execFileSync)("cmd.exe", ["/c", "codex", "plugin", "list", "--json"], {
|
|
36237
38354
|
encoding: "utf8",
|
|
36238
38355
|
stdio: ["ignore", "pipe", "ignore"],
|
|
36239
38356
|
timeout: 15e3,
|
|
36240
38357
|
windowsHide: true
|
|
36241
|
-
}) : (0,
|
|
38358
|
+
}) : (0, import_node_child_process19.execFileSync)("codex", ["plugin", "list", "--json"], {
|
|
36242
38359
|
encoding: "utf8",
|
|
36243
38360
|
stdio: ["ignore", "pipe", "ignore"],
|
|
36244
38361
|
timeout: 15e3,
|
|
@@ -36256,7 +38373,7 @@ function installedActivePluginVersion(surface = detectSurface(process.env)) {
|
|
|
36256
38373
|
}
|
|
36257
38374
|
function worktreeRootSync() {
|
|
36258
38375
|
try {
|
|
36259
|
-
const out = (0,
|
|
38376
|
+
const out = (0, import_node_child_process19.execFileSync)("git", ["rev-parse", "--show-toplevel"], { windowsHide: true, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
36260
38377
|
let root = out.endsWith("\n") ? out.slice(0, -1) : out;
|
|
36261
38378
|
if (process.platform === "win32" && root.endsWith("\r")) root = root.slice(0, -1);
|
|
36262
38379
|
return root || null;
|
|
@@ -36266,13 +38383,13 @@ function worktreeRootSync() {
|
|
|
36266
38383
|
}
|
|
36267
38384
|
var gitignorePath = () => {
|
|
36268
38385
|
const root = worktreeRootSync();
|
|
36269
|
-
return root === null ? null : (0,
|
|
38386
|
+
return root === null ? null : (0, import_node_path39.join)(root, ".gitignore");
|
|
36270
38387
|
};
|
|
36271
38388
|
function readGitignore() {
|
|
36272
38389
|
const path2 = gitignorePath();
|
|
36273
38390
|
if (path2 === null) return null;
|
|
36274
38391
|
try {
|
|
36275
|
-
return (0,
|
|
38392
|
+
return (0, import_node_fs42.readFileSync)(path2, "utf8");
|
|
36276
38393
|
} catch {
|
|
36277
38394
|
return null;
|
|
36278
38395
|
}
|
|
@@ -36281,16 +38398,16 @@ function writeGitignore(content) {
|
|
|
36281
38398
|
const path2 = gitignorePath();
|
|
36282
38399
|
if (path2 === null) return false;
|
|
36283
38400
|
try {
|
|
36284
|
-
(0,
|
|
38401
|
+
(0, import_node_fs42.writeFileSync)(path2, content, "utf8");
|
|
36285
38402
|
return true;
|
|
36286
38403
|
} catch {
|
|
36287
38404
|
return false;
|
|
36288
38405
|
}
|
|
36289
38406
|
}
|
|
36290
38407
|
function lineEndingState(root) {
|
|
36291
|
-
const attributesPresent = (0,
|
|
38408
|
+
const attributesPresent = (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, ".gitattributes"));
|
|
36292
38409
|
try {
|
|
36293
|
-
const output = (0,
|
|
38410
|
+
const output = (0, import_node_child_process19.execFileSync)("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
|
|
36294
38411
|
windowsHide: true,
|
|
36295
38412
|
encoding: "utf8",
|
|
36296
38413
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -36313,94 +38430,6 @@ async function repoRoot() {
|
|
|
36313
38430
|
return worktreeRootSync() ?? process.cwd();
|
|
36314
38431
|
}
|
|
36315
38432
|
|
|
36316
|
-
// src/cross-repo-filing-issue.ts
|
|
36317
|
-
init_github_client();
|
|
36318
|
-
async function discoverSameOrgIssueNumber(client, currentRepo, number) {
|
|
36319
|
-
const [owner] = currentRepo.split("/");
|
|
36320
|
-
if (!owner) throw new Error(`cannot derive an organization from ${currentRepo}`);
|
|
36321
|
-
const repositories = await client.restPaginate(`orgs/${owner}/repos?type=all`);
|
|
36322
|
-
const hits = [];
|
|
36323
|
-
let next = 0;
|
|
36324
|
-
const worker = async () => {
|
|
36325
|
-
while (next < repositories.length) {
|
|
36326
|
-
const repo = repositories[next++]?.full_name;
|
|
36327
|
-
if (!repo || repo.toLowerCase() === currentRepo.toLowerCase()) continue;
|
|
36328
|
-
const issue2 = await readExactIssue(client, repo, number);
|
|
36329
|
-
if (issue2) hits.push(issue2);
|
|
36330
|
-
}
|
|
36331
|
-
};
|
|
36332
|
-
await Promise.all(Array.from({ length: Math.min(8, repositories.length) }, () => worker()));
|
|
36333
|
-
return hits.sort((left, right) => left.repo.localeCompare(right.repo));
|
|
36334
|
-
}
|
|
36335
|
-
async function readExactIssue(client, repo, number) {
|
|
36336
|
-
try {
|
|
36337
|
-
const issue2 = await client.rest("GET", `repos/${repo}/issues/${number}`);
|
|
36338
|
-
if (issue2.pull_request || issue2.number !== number || !issue2.title || !issue2.html_url) return void 0;
|
|
36339
|
-
const state = issue2.state?.toUpperCase();
|
|
36340
|
-
if (state !== "OPEN" && state !== "CLOSED") return void 0;
|
|
36341
|
-
return { repo, number, title: issue2.title, url: issue2.html_url, state };
|
|
36342
|
-
} catch (e) {
|
|
36343
|
-
if (e instanceof GitHubApiError && e.status === 404) return void 0;
|
|
36344
|
-
throw e;
|
|
36345
|
-
}
|
|
36346
|
-
}
|
|
36347
|
-
function explicitSameOrgIssueUrls(text, prRepo, closingNumbers) {
|
|
36348
|
-
const [owner] = prRepo.split("/");
|
|
36349
|
-
const found = /* @__PURE__ */ new Map();
|
|
36350
|
-
for (const match of text.matchAll(/https:\/\/github\.com\/([\w.-]+\/[\w.-]+)\/issues\/(\d+)\b/gi)) {
|
|
36351
|
-
const repo = match[1];
|
|
36352
|
-
const number = Number(match[2]);
|
|
36353
|
-
if (!repo || !Number.isInteger(number) || !closingNumbers.has(number)) continue;
|
|
36354
|
-
if (repo.split("/")[0]?.toLowerCase() !== owner.toLowerCase() || repo.toLowerCase() === prRepo.toLowerCase()) continue;
|
|
36355
|
-
found.set(`${repo.toLowerCase()}#${number}`, { repo, number });
|
|
36356
|
-
}
|
|
36357
|
-
return [...found.values()];
|
|
36358
|
-
}
|
|
36359
|
-
async function reconcileCrossRepoFilingIssue(client, prRepo, prNumber) {
|
|
36360
|
-
try {
|
|
36361
|
-
const pr2 = await client.rest("GET", `repos/${prRepo}/pulls/${prNumber}`);
|
|
36362
|
-
if (!pr2.merged_at) return { status: "not-applicable" };
|
|
36363
|
-
const text = `${pr2.title ?? ""}
|
|
36364
|
-
${pr2.body ?? ""}`;
|
|
36365
|
-
const closingNumbers = new Set(findClosingMentions(text).filter((mention) => !mention.negated).map((mention) => mention.issue));
|
|
36366
|
-
if (closingNumbers.size === 0) return { status: "not-applicable" };
|
|
36367
|
-
const explicit = explicitSameOrgIssueUrls(text, prRepo, closingNumbers);
|
|
36368
|
-
let target;
|
|
36369
|
-
if (explicit.length > 1) {
|
|
36370
|
-
return { status: "failed", error: `multiple same-org issue URLs match bare closing references (${explicit.map((x) => `${x.repo}#${x.number}`).join(", ")})` };
|
|
36371
|
-
}
|
|
36372
|
-
if (explicit.length === 1) {
|
|
36373
|
-
target = await readExactIssue(client, explicit[0].repo, explicit[0].number);
|
|
36374
|
-
if (!target) return { status: "failed", issue: `${explicit[0].repo}#${explicit[0].number}`, error: "the explicit foreign issue URL did not resolve to an issue" };
|
|
36375
|
-
} else {
|
|
36376
|
-
const branchNumber = Number(/^#?(\d+)(?:-|$)/.exec(pr2.head?.ref ?? "")?.[1]);
|
|
36377
|
-
if (!Number.isInteger(branchNumber) || !closingNumbers.has(branchNumber)) return { status: "not-applicable" };
|
|
36378
|
-
if (await readExactIssue(client, prRepo, branchNumber)) return { status: "not-applicable" };
|
|
36379
|
-
const matches = await discoverSameOrgIssueNumber(client, prRepo, branchNumber);
|
|
36380
|
-
if (matches.length !== 1) {
|
|
36381
|
-
return {
|
|
36382
|
-
status: "failed",
|
|
36383
|
-
error: matches.length === 0 ? `branch #${branchNumber} matches a bare closing reference, but no same-org filing issue was found` : `branch #${branchNumber} is ambiguous across ${matches.map((match) => match.repo).join(", ")}`
|
|
36384
|
-
};
|
|
36385
|
-
}
|
|
36386
|
-
target = matches[0];
|
|
36387
|
-
}
|
|
36388
|
-
const issue2 = `${target.repo}#${target.number}`;
|
|
36389
|
-
if (target.state === "CLOSED") return { status: "already-closed", issue: issue2, url: target.url };
|
|
36390
|
-
const prUrl = pr2.html_url ?? `https://github.com/${prRepo}/pull/${prNumber}`;
|
|
36391
|
-
const comment = await client.rest("POST", `repos/${target.repo}/issues/${target.number}/comments`, {
|
|
36392
|
-
body: { body: `Resolved by merged PR ${prUrl}.
|
|
36393
|
-
|
|
36394
|
-
<!-- mmi-cross-repo-close:${prRepo}#${prNumber} -->` }
|
|
36395
|
-
});
|
|
36396
|
-
if (!comment.html_url) throw new Error(`GitHub created no evidence URL on ${issue2}`);
|
|
36397
|
-
await closeIssue(client, { ref: issue2, reason: "completed", evidence: comment.html_url });
|
|
36398
|
-
return { status: "closed", issue: issue2, url: target.url, evidence: comment.html_url };
|
|
36399
|
-
} catch (e) {
|
|
36400
|
-
return { status: "failed", error: e.message };
|
|
36401
|
-
}
|
|
36402
|
-
}
|
|
36403
|
-
|
|
36404
38433
|
// src/pr-deploy-dev.ts
|
|
36405
38434
|
function slugOfRepo(repo) {
|
|
36406
38435
|
return repo.split("/")[1]?.toLowerCase() ?? "";
|
|
@@ -36445,8 +38474,8 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
36445
38474
|
function ghMultiAccountCaveat(announcedLogin) {
|
|
36446
38475
|
try {
|
|
36447
38476
|
const hostsPath = ghHostsConfigPath(process.env, process.platform);
|
|
36448
|
-
if (!hostsPath || !(0,
|
|
36449
|
-
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0,
|
|
38477
|
+
if (!hostsPath || !(0, import_node_fs43.existsSync)(hostsPath)) return void 0;
|
|
38478
|
+
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs43.readFileSync)(hostsPath, "utf8")));
|
|
36450
38479
|
} catch {
|
|
36451
38480
|
return void 0;
|
|
36452
38481
|
}
|
|
@@ -36454,7 +38483,7 @@ function ghMultiAccountCaveat(announcedLogin) {
|
|
|
36454
38483
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
36455
38484
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
36456
38485
|
function envHealLockPath(home) {
|
|
36457
|
-
return (0,
|
|
38486
|
+
return (0, import_node_path40.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
36458
38487
|
}
|
|
36459
38488
|
async function withEnvHealLock(what, run) {
|
|
36460
38489
|
try {
|
|
@@ -36589,7 +38618,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
36589
38618
|
);
|
|
36590
38619
|
const result = applyPluginCachePlan(
|
|
36591
38620
|
plan,
|
|
36592
|
-
(p) => (0,
|
|
38621
|
+
(p) => (0, import_node_fs43.rmSync)(p, { recursive: true }),
|
|
36593
38622
|
stagingApplyFsGuard(configRoot)
|
|
36594
38623
|
);
|
|
36595
38624
|
return {
|
|
@@ -36627,7 +38656,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
36627
38656
|
// adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
|
|
36628
38657
|
// get a permanent — demanding an artifact it never asked for.
|
|
36629
38658
|
docsIndexState: (root) => {
|
|
36630
|
-
if (!(0,
|
|
38659
|
+
if (!(0, import_node_fs43.existsSync)((0, import_node_path40.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
36631
38660
|
const real = createDocsIndexDeps(root);
|
|
36632
38661
|
let docs2;
|
|
36633
38662
|
const listDocs = () => docs2 ??= real.listDocs();
|
|
@@ -36636,7 +38665,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
36636
38665
|
},
|
|
36637
38666
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
36638
38667
|
healDocsIndex: (root) => {
|
|
36639
|
-
if (!(0,
|
|
38668
|
+
if (!(0, import_node_fs43.existsSync)((0, import_node_path40.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
36640
38669
|
const real = createDocsIndexDeps(root);
|
|
36641
38670
|
let docs2;
|
|
36642
38671
|
const listDocs = () => docs2 ??= real.listDocs();
|
|
@@ -36703,6 +38732,21 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
36703
38732
|
clearTimeout(ceiling);
|
|
36704
38733
|
}
|
|
36705
38734
|
},
|
|
38735
|
+
// #5431: estate DEPLOY# (sshHost, port) uniqueness — master-only Hub read; report-only.
|
|
38736
|
+
deployPortCollisionsState: async () => {
|
|
38737
|
+
try {
|
|
38738
|
+
const cfg = await loadConfig();
|
|
38739
|
+
const report = await fetchDeployPortCollisions(registryClientDeps(cfg));
|
|
38740
|
+
if (report.status === 403) return { ok: true, lines: [], forbidden: true };
|
|
38741
|
+
if (report.error) return { ok: false, lines: [], readFailed: true, error: report.error };
|
|
38742
|
+
return {
|
|
38743
|
+
ok: report.ok,
|
|
38744
|
+
lines: report.collisions.map((c) => c.report)
|
|
38745
|
+
};
|
|
38746
|
+
} catch (e) {
|
|
38747
|
+
return { ok: false, lines: [], readFailed: true, error: e instanceof Error ? e.message : String(e) };
|
|
38748
|
+
}
|
|
38749
|
+
},
|
|
36706
38750
|
// #4173: estate commands absent from the running Commander program (stale global CLI).
|
|
36707
38751
|
missingCliCommands: () => {
|
|
36708
38752
|
const required = ["repo-index"];
|
|
@@ -36713,7 +38757,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
36713
38757
|
repoIndexCloudState: async (root) => {
|
|
36714
38758
|
let localV4 = { state: "absent" };
|
|
36715
38759
|
try {
|
|
36716
|
-
const parsed = JSON.parse((0,
|
|
38760
|
+
const parsed = JSON.parse((0, import_node_fs43.readFileSync)(repoIndexV4StorePath(root), "utf8"));
|
|
36717
38761
|
const state = parsed.status?.state;
|
|
36718
38762
|
if (parsed.schemaVersion === 4 && (state === "ready" || state === "degraded")) {
|
|
36719
38763
|
const chunks = parsed.manifest?.chunks?.length ?? 0;
|
|
@@ -36765,6 +38809,21 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
36765
38809
|
...localFields
|
|
36766
38810
|
};
|
|
36767
38811
|
}
|
|
38812
|
+
},
|
|
38813
|
+
// #5437: `--self` self-routes Hub-owned v4 readiness via the harbour reconcile schedule.
|
|
38814
|
+
dispatchRepoIndexReconcile: async () => {
|
|
38815
|
+
try {
|
|
38816
|
+
const res = await postSchedulesRun(REPO_INDEX_RECONCILE_SCHEDULE_ID, registryClientDeps(await loadConfig()));
|
|
38817
|
+
if (!res.ok) {
|
|
38818
|
+
return {
|
|
38819
|
+
ok: false,
|
|
38820
|
+
detail: res.error ?? `HTTP ${res.status}${res.body ? ` \u2014 ${JSON.stringify(res.body)}` : ""}`
|
|
38821
|
+
};
|
|
38822
|
+
}
|
|
38823
|
+
return { ok: true, detail: `accepted ${REPO_INDEX_RECONCILE_SCHEDULE_ID}` };
|
|
38824
|
+
} catch (e) {
|
|
38825
|
+
return { ok: false, detail: e instanceof Error ? e.message : String(e) };
|
|
38826
|
+
}
|
|
36768
38827
|
}
|
|
36769
38828
|
};
|
|
36770
38829
|
}
|
|
@@ -36976,19 +39035,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
36976
39035
|
});
|
|
36977
39036
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
36978
39037
|
rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").option("--json", "machine-readable output").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
|
|
36979
|
-
const path2 = (0,
|
|
36980
|
-
const current = (0,
|
|
39038
|
+
const path2 = (0, import_node_path40.join)(process.cwd(), ".gitignore");
|
|
39039
|
+
const current = (0, import_node_fs43.existsSync)(path2) ? (0, import_node_fs43.readFileSync)(path2, "utf8") : null;
|
|
36981
39040
|
const plan = planManagedGitignore(current);
|
|
36982
39041
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
36983
39042
|
if (opts.json) {
|
|
36984
|
-
if (opts.write && plan.changed) (0,
|
|
39043
|
+
if (opts.write && plan.changed) (0, import_node_fs43.writeFileSync)(path2, plan.content, "utf8");
|
|
36985
39044
|
console.log(JSON.stringify(plan, null, 2));
|
|
36986
39045
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
36987
39046
|
return;
|
|
36988
39047
|
}
|
|
36989
39048
|
if (opts.write) {
|
|
36990
39049
|
if (plan.changed) {
|
|
36991
|
-
(0,
|
|
39050
|
+
(0, import_node_fs43.writeFileSync)(path2, plan.content, "utf8");
|
|
36992
39051
|
console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
|
|
36993
39052
|
} else {
|
|
36994
39053
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -37126,8 +39185,14 @@ async function attachToProject(issueNumber, repo, priority) {
|
|
|
37126
39185
|
await setBoardItemPriority(defaultGitHubClient(), cfg, projectItemId, priority);
|
|
37127
39186
|
} catch (e) {
|
|
37128
39187
|
const err = e;
|
|
37129
|
-
|
|
39188
|
+
const detail = (err.stderr || err.message || String(e)).trim();
|
|
39189
|
+
if (isRateLimitText(detail)) {
|
|
39190
|
+
process.stderr.write(`warning: issue #${issueNumber} on board but Priority not set (rate-limited): ${detail}
|
|
39191
|
+
`);
|
|
39192
|
+
} else {
|
|
39193
|
+
process.stderr.write(`warning: issue #${issueNumber} board Priority not set: ${detail}
|
|
37130
39194
|
`);
|
|
39195
|
+
}
|
|
37131
39196
|
}
|
|
37132
39197
|
}
|
|
37133
39198
|
return { projectItemId, onBoard: true };
|
|
@@ -37146,6 +39211,18 @@ async function attachToProject(issueNumber, repo, priority) {
|
|
|
37146
39211
|
}
|
|
37147
39212
|
return { onBoard: true };
|
|
37148
39213
|
}
|
|
39214
|
+
if (isRateLimitText(detail)) {
|
|
39215
|
+
process.stderr.write(`warning: issue #${issueNumber} created but board attach rate-limited: ${detail}
|
|
39216
|
+
`);
|
|
39217
|
+
let resetEpochSeconds;
|
|
39218
|
+
try {
|
|
39219
|
+
const { stdout } = await execFileP2("gh", ["api", "rate_limit"], { timeout: 15e3 });
|
|
39220
|
+
const pools = parseRateLimitPools(stdout);
|
|
39221
|
+
resetEpochSeconds = pools.graphql?.reset ?? pools.core?.reset;
|
|
39222
|
+
} catch {
|
|
39223
|
+
}
|
|
39224
|
+
return boardAttachRateLimitedReceipt(resetEpochSeconds);
|
|
39225
|
+
}
|
|
37149
39226
|
process.stderr.write(`warning: issue #${issueNumber} created but NOT added to the project board: ${detail}
|
|
37150
39227
|
`);
|
|
37151
39228
|
return { onBoard: false };
|
|
@@ -37156,7 +39233,7 @@ function scheduleRelatedDiscovery(o) {
|
|
|
37156
39233
|
try {
|
|
37157
39234
|
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
37158
39235
|
if (o.repo) args.push("--repo", o.repo);
|
|
37159
|
-
spawnDetachedSelf(args, { spawn:
|
|
39236
|
+
spawnDetachedSelf(args, { spawn: import_node_child_process20.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
37160
39237
|
} catch {
|
|
37161
39238
|
}
|
|
37162
39239
|
}
|
|
@@ -37231,12 +39308,57 @@ async function runRepoIndexSearchCommand(query, o, defaultMode) {
|
|
|
37231
39308
|
}
|
|
37232
39309
|
const mode = o.semantic ? "semantic" : o.lexical ? "lexical" : defaultMode;
|
|
37233
39310
|
const cfg = await loadConfig();
|
|
37234
|
-
const
|
|
37235
|
-
|
|
39311
|
+
const deps = registryClientDeps(cfg);
|
|
39312
|
+
let res = await searchRepoIndexCloud(query, { mode, limit, repo: o.repo }, deps);
|
|
39313
|
+
if (!res.ok && shouldRetryRepoIndexSearchLexical(res, mode)) {
|
|
39314
|
+
const lexical = await searchRepoIndexCloud(query, { mode: "lexical", limit, repo: o.repo }, deps);
|
|
39315
|
+
if (lexical.ok) {
|
|
39316
|
+
res = {
|
|
39317
|
+
...lexical,
|
|
39318
|
+
degradedReasons: [...lexical.degradedReasons ?? [], "query-embedding-unavailable"],
|
|
39319
|
+
note: [
|
|
39320
|
+
lexical.note,
|
|
39321
|
+
"query embedding unavailable \u2014 served lexical citations (v4 semantic not claimed ready)"
|
|
39322
|
+
].filter(Boolean).join("; ")
|
|
39323
|
+
};
|
|
39324
|
+
}
|
|
39325
|
+
}
|
|
39326
|
+
if (!res.ok) {
|
|
39327
|
+
const receipt = asCloudSearchNotReadyReceipt(res);
|
|
39328
|
+
const typed = refuseBareRepoIndexV4Unavailable(receipt.error);
|
|
39329
|
+
if (o.json) {
|
|
39330
|
+
consoleIo.log(JSON.stringify({ source: "cloud", ...receipt, error: typed }, null, 2));
|
|
39331
|
+
} else {
|
|
39332
|
+
console.error(`mmi-cli ${typed}`);
|
|
39333
|
+
const authority = receipt.v4Authority;
|
|
39334
|
+
if (authority) {
|
|
39335
|
+
const ready = authority.readyAuthorities ?? 0;
|
|
39336
|
+
const roster = authority.rosterCount ?? 0;
|
|
39337
|
+
console.error(`repo-index: v4 status not-ready \u2014 readyAuthorities=${ready}/${roster} nOfN=${authority.nOfN === true}`);
|
|
39338
|
+
} else {
|
|
39339
|
+
console.error("repo-index: v4 status not-ready \u2014 Hub search unavailable (see cause above)");
|
|
39340
|
+
}
|
|
39341
|
+
if (authority?.authorityFailures?.length) {
|
|
39342
|
+
for (const failure of authority.authorityFailures.slice(0, 12)) {
|
|
39343
|
+
console.error(`repo-index: authority ${failure.repo} ${failure.reason}`);
|
|
39344
|
+
}
|
|
39345
|
+
if (authority.authorityFailures.length > 12) {
|
|
39346
|
+
console.error(`repo-index: \u2026 ${authority.authorityFailures.length - 12} more authority failure(s)`);
|
|
39347
|
+
}
|
|
39348
|
+
}
|
|
39349
|
+
if (receipt.omittedRepos?.length) {
|
|
39350
|
+
console.error(`repo-index: omittedRepos ${receipt.omittedRepos.slice(0, 8).join(", ")}${receipt.omittedRepos.length > 8 ? "\u2026" : ""}`);
|
|
39351
|
+
}
|
|
39352
|
+
if (receipt.next) console.error(`repo-index: next \u2014 ${receipt.next}`);
|
|
39353
|
+
console.error("repo-index: status remains not-ready until reconcile repairs authorities (search did not claim v4 ready)");
|
|
39354
|
+
}
|
|
39355
|
+
return await cleanExit(1);
|
|
39356
|
+
}
|
|
37236
39357
|
if (o.json) {
|
|
37237
39358
|
consoleIo.log(JSON.stringify({ source: "cloud", ...res }, null, 2));
|
|
37238
39359
|
return;
|
|
37239
39360
|
}
|
|
39361
|
+
if (res.note) console.error(`repo-index: ${res.note}`);
|
|
37240
39362
|
if (res.hits.length === 0) {
|
|
37241
39363
|
console.log(`repo-index: no cloud hits for ${JSON.stringify(query)} (${res.mode})`);
|
|
37242
39364
|
return;
|
|
@@ -37246,7 +39368,7 @@ async function runRepoIndexSearchCommand(query, o, defaultMode) {
|
|
|
37246
39368
|
console.log(`${h.score.toFixed(2)} ${h.kind.padEnd(8)} ${h.repo} ${h.path}${sym} (${h.why})`);
|
|
37247
39369
|
}
|
|
37248
39370
|
} catch (e) {
|
|
37249
|
-
return await failGraceful(e.message);
|
|
39371
|
+
return await failGraceful(refuseBareRepoIndexV4Unavailable(e.message));
|
|
37250
39372
|
}
|
|
37251
39373
|
}
|
|
37252
39374
|
var REPO_INDEX_SEARCH_WHEN = 'beats grep for cross-repo questions, "where does X happen", and unknown filenames; grep wins inside a known checkout';
|
|
@@ -37357,7 +39479,7 @@ repoIndex.command("gc").description("remove v4 cloud authority material for repo
|
|
|
37357
39479
|
return await failGraceful(e.message);
|
|
37358
39480
|
}
|
|
37359
39481
|
});
|
|
37360
|
-
repoIndex.command("sync-estate").description("Hub indexer:
|
|
39482
|
+
repoIndex.command("sync-estate").description("Hub indexer: publish a pushed commit as a delta, or run the estate drift/repair lane (CI / operator)").option("--repo <owner/name>", "limit to one registered repo").option("--commit <sha>", "index this exact 40-hex commit (requires --repo); the checkout is proved against it").option("--full-rebuild <token>", `explicit full rebuild \u2014 must quote this pipeline's provenance token (${CURRENT_REPO_INDEX_PROVENANCE_TOKEN})`).option("--skip-repo <owner/name>", "estate lane: a repo a per-repo run is already publishing (repeatable)", (value, all) => [...all, value], []).option("--plan", "read-only: classify every repo and print the plan without cloning, embedding or publishing").option("--json", "machine-readable result").action(async (o) => {
|
|
37361
39483
|
try {
|
|
37362
39484
|
const cfg = await loadConfig();
|
|
37363
39485
|
const gh = process.env.GH_TOKEN || process.env.GITHUB_TOKEN || "";
|
|
@@ -37365,6 +39487,10 @@ repoIndex.command("sync-estate").description("Hub indexer: shallow-clone registr
|
|
|
37365
39487
|
const res = await syncEstateRepoIndex({
|
|
37366
39488
|
deps: registryClientDeps(cfg),
|
|
37367
39489
|
repo: o.repo,
|
|
39490
|
+
commit: o.commit,
|
|
39491
|
+
fullRebuild: o.fullRebuild,
|
|
39492
|
+
skipRepos: o.skipRepo,
|
|
39493
|
+
plan: Boolean(o.plan),
|
|
37368
39494
|
githubToken: gh
|
|
37369
39495
|
});
|
|
37370
39496
|
if (o.json) {
|
|
@@ -37372,11 +39498,22 @@ repoIndex.command("sync-estate").description("Hub indexer: shallow-clone registr
|
|
|
37372
39498
|
if (!res.ok) process.exitCode = 1;
|
|
37373
39499
|
return;
|
|
37374
39500
|
}
|
|
39501
|
+
console.log(`repo-index: pipeline provenance ${res.provenanceToken} \u2014 delta is the normal path${o.plan ? " (plan only: nothing was cloned, embedded or published)" : ""}`);
|
|
39502
|
+
for (const row of res.drift) {
|
|
39503
|
+
if (row.reason === "healthy") continue;
|
|
39504
|
+
const at = row.targetCommit ? ` target=${row.targetCommit.slice(0, 12)}` : "";
|
|
39505
|
+
const active = row.activeCommit ? ` active=${row.activeCommit.slice(0, 12)}` : "";
|
|
39506
|
+
console.log(`repo-index: ${row.repo} ${row.reason} \u2192 ${row.action}${active}${at}`);
|
|
39507
|
+
}
|
|
37375
39508
|
for (const p of res.published) {
|
|
37376
39509
|
const gap = p.embGap ?? Math.max(0, p.fileCount - (p.embCount ?? 0));
|
|
37377
39510
|
console.log(`repo-index: published v4 ${p.repo} \u2014 ${p.fileCount} chunks emb=${p.embCount ?? 0}/${p.fileCount} gap=${gap} graph=${p.graphEdges ?? "unavailable"}`);
|
|
39511
|
+
if (p.metrics) console.log(`repo-index: ${formatV4BuildMetrics(p.repo, p.metrics)}`);
|
|
37378
39512
|
}
|
|
37379
39513
|
for (const warning of res.skipped) console.error(`repo-index: WARN ${warning}`);
|
|
39514
|
+
if (res.needsFullRebuild.length) {
|
|
39515
|
+
console.error(`repo-index: ${res.needsFullRebuild.length} repo(s) need an explicit tokened migration: ${res.needsFullRebuild.join(", ")}`);
|
|
39516
|
+
}
|
|
37380
39517
|
for (const f of res.failed) {
|
|
37381
39518
|
console.error(`repo-index: FAILED ${f.repo}: ${f.error}`);
|
|
37382
39519
|
}
|
|
@@ -37724,6 +39861,17 @@ projectDeploy.command("get [owner/repo]").description("read nonsecret DEPLOY# fa
|
|
|
37724
39861
|
const payload = stage ? { slug: out.slug, stage, deploy: out.stages[stage] ?? null } : out;
|
|
37725
39862
|
console.log(JSON.stringify(payload));
|
|
37726
39863
|
});
|
|
39864
|
+
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) => {
|
|
39865
|
+
const cfg = await loadConfig();
|
|
39866
|
+
const report = await fetchDeployPortCollisions(registryClientDeps(cfg));
|
|
39867
|
+
if (o.json) {
|
|
39868
|
+
printLine(JSON.stringify(report, null, 2));
|
|
39869
|
+
} else {
|
|
39870
|
+
for (const line of renderDeployPortDoctor(report)) printLine(line);
|
|
39871
|
+
}
|
|
39872
|
+
if (report.error && report.status !== 403) return failGraceful(`org project deploy doctor: ${report.error}`);
|
|
39873
|
+
if (!report.ok && !report.error) process.exitCode = 1;
|
|
39874
|
+
});
|
|
37727
39875
|
project.command("set [owner/repo]").description("upsert project META (idempotent merge; master-gated)").addOption(new Option("--class <class>", "deployable | content").choices(["deployable", "content"])).addOption(new Option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (capability shape)`).choices([...PROJECT_TYPES])).addOption(new Option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path; none means no Hub deploy registration)`).choices([...DEPLOY_MODELS])).addOption(new Option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).choices([...RELEASE_TRACKS])).option("--var <KEY=VALUE...>", settableVarHelp()).option("--set <KEY=VALUE...>", "alias of --var (one KEY=VALUE per flag; repeat the flag to set several)").option("--secrets-file <path>", 'read the #2244 secrets catalog map (JSON) from a file and merge it per entry into the existing catalog (clear all with --unset secrets). SHAPE: a JSON object keyed by env name, each entry {"key":"UPPER_SNAKE" (repeat the env name), "purpose":"<non-empty>", "group":"<e.g. auth|database>", "owner":"<github login>", "stages":[] (empty = the one stageless shared value; else any of dev/rc/main), "consumers":["runtime"|"box"|?], "provider":"<optional>"}').option("--unset <KEY...>", "META field to remove (repeatable): oauth, requiredRuntimeSecrets, requiredBuildSecrets, secrets, edgeDomains, requiredGcpApis, publishRequired").option("--clear-web-profile", "remove web-only registry fields (oauth, edgeDomains) for non-web/content projects").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
37728
39876
|
const cfg = await loadConfig();
|
|
37729
39877
|
let target;
|
|
@@ -37740,7 +39888,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
37740
39888
|
if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
|
|
37741
39889
|
if (o.secretsFile) {
|
|
37742
39890
|
try {
|
|
37743
|
-
vars.push(`secrets=${(0,
|
|
39891
|
+
vars.push(`secrets=${(0, import_node_fs43.readFileSync)(o.secretsFile, "utf8")}`);
|
|
37744
39892
|
} catch (e) {
|
|
37745
39893
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
37746
39894
|
}
|
|
@@ -38090,7 +40238,7 @@ function resolveCreateSurface(opts) {
|
|
|
38090
40238
|
function surfaceWaived() {
|
|
38091
40239
|
return rawFlag("--no-surface");
|
|
38092
40240
|
}
|
|
38093
|
-
var issue = program2.command("issue").description("issues \u2014
|
|
40241
|
+
var issue = program2.command("issue").description("issues \u2014 create and view with structured JSON (view; show is an alias for board-verb callers)");
|
|
38094
40242
|
withExamples(mutating(
|
|
38095
40243
|
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). Required when the target repo runs the one-surface-label board rule; any value satisfies it, so this is not a closed enum").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)").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"),
|
|
38096
40244
|
// --dry-run/--validate-only plan: resolve the same title source and validate the same type, priority,
|
|
@@ -38174,10 +40322,11 @@ withExamples(mutating(
|
|
|
38174
40322
|
return fail(`issue create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
|
|
38175
40323
|
}
|
|
38176
40324
|
{
|
|
38177
|
-
const
|
|
40325
|
+
const surfaceCheck = await checkSurfaceRequirement({ repo: targetRepo3, labels: extraLabels });
|
|
40326
|
+
const { refusal, warn } = surfaceCheck;
|
|
38178
40327
|
if (warn) process.stderr.write(`${warn}
|
|
38179
40328
|
`);
|
|
38180
|
-
if (
|
|
40329
|
+
if (shouldWithdrawSurfaceFlag(surfaceCheck) && surfaceFlagLabel) {
|
|
38181
40330
|
extraLabels = extraLabels.filter((l) => l !== surfaceFlagLabel);
|
|
38182
40331
|
args = buildIssueArgs({
|
|
38183
40332
|
type: issueType,
|
|
@@ -38196,7 +40345,13 @@ withExamples(mutating(
|
|
|
38196
40345
|
}
|
|
38197
40346
|
await ensureLabelsExist(extraLabels, targetRepo3);
|
|
38198
40347
|
const created = await ghCreate(args);
|
|
38199
|
-
|
|
40348
|
+
if (isGhCreateRateLimited(created)) {
|
|
40349
|
+
console.log(JSON.stringify(created));
|
|
40350
|
+
process.exitCode = 1;
|
|
40351
|
+
return;
|
|
40352
|
+
}
|
|
40353
|
+
const attached = await attachToProject(created.number, targetRepo3, priority);
|
|
40354
|
+
const { projectItemId, onBoard } = attached;
|
|
38200
40355
|
let parent;
|
|
38201
40356
|
let parentLinkError;
|
|
38202
40357
|
if (o.parent !== void 0) {
|
|
@@ -38217,6 +40372,12 @@ withExamples(mutating(
|
|
|
38217
40372
|
priority,
|
|
38218
40373
|
projectItemId,
|
|
38219
40374
|
onBoard,
|
|
40375
|
+
// #5489: partial receipt when the GitHub issue landed but Project v2 attach hit GraphQL quota.
|
|
40376
|
+
...attached.boardAttach ? {
|
|
40377
|
+
boardAttach: attached.boardAttach,
|
|
40378
|
+
...attached.resetAt ? { resetAt: attached.resetAt } : {},
|
|
40379
|
+
...attached.resetEpochSeconds !== void 0 ? { resetEpochSeconds: attached.resetEpochSeconds } : {}
|
|
40380
|
+
} : {},
|
|
38220
40381
|
...parentLinkFields(parent, parentLinkError)
|
|
38221
40382
|
}));
|
|
38222
40383
|
}), [
|
|
@@ -38238,7 +40399,7 @@ async function readParentField(number, repo) {
|
|
|
38238
40399
|
}
|
|
38239
40400
|
return resolveParentField(payload);
|
|
38240
40401
|
}
|
|
38241
|
-
issue.command("view <number>").description('read an issue as structured JSON \u2014 the mmi-cli path for non-board issue reads (#2347). --comments folds in every comment; --context also adds linkedPrs + children (the one-shot "load the whole item" read, #2894)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "number,title,body,url"').option("--comments", 'include every comment (body + comments in one call) \u2014 the agentic "read the whole item before working it" read (#2894)').option("--context", "full working context in one call: implies --comments and also adds linkedPrs and, for an epic, a children summary (#2894)").action(async (number, o) => {
|
|
40402
|
+
issue.command("view <number>").alias("show").description('read an issue as structured JSON \u2014 the mmi-cli path for non-board issue reads (#2347). --comments folds in every comment; --context also adds linkedPrs + children (the one-shot "load the whole item" read, #2894). `show` is an alias (#5483)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "number,title,body,url"').option("--comments", 'include every comment (body + comments in one call) \u2014 the agentic "read the whole item before working it" read (#2894)').option("--context", "full working context in one call: implies --comments and also adds linkedPrs and, for an epic, a children summary (#2894)").action(async (number, o) => {
|
|
38242
40403
|
const n = Number(number);
|
|
38243
40404
|
if (!Number.isInteger(n) || n <= 0) return fail("issue view: <number> must be a positive integer");
|
|
38244
40405
|
const repo = await resolveRepo(o.repo);
|
|
@@ -38495,9 +40656,8 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
|
|
|
38495
40656
|
} catch {
|
|
38496
40657
|
}
|
|
38497
40658
|
}
|
|
38498
|
-
const created = await ghCreate(args);
|
|
40659
|
+
const created = requireGhCreateOk(await ghCreate(args), "skill-lesson");
|
|
38499
40660
|
const { projectItemId, onBoard } = await attachToProject(created.number, targetRepo3, priority);
|
|
38500
|
-
console.log(JSON.stringify({ ...created, deduped: false, label: SKILL_LESSON_LABEL, skill, priority, projectItemId, onBoard }));
|
|
38501
40661
|
});
|
|
38502
40662
|
var pr = program2.command("pr").description("pull requests \u2014 reliable create with structured output");
|
|
38503
40663
|
withExamples(pr.command("create").description("create a PR and print {number,url} JSON").option("--title <title>", "PR title").option("--title-file <path|->", "read the PR title from a UTF-8 file, or from stdin with -").option("--body <body>", "PR body (markdown)").option("--body-file <path|->", "read PR body from a UTF-8 file, or from stdin with -").option("--base <branch>", "base branch (defaults to the repo default)").option("--head <branch>", "head branch (defaults to the current branch)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--draft", "open the PR in draft state (#2667)").option("--json", "machine-readable output (default; accepted for parity)").action(async (o) => {
|
|
@@ -38516,6 +40676,11 @@ withExamples(pr.command("create").description("create a PR and print {number,url
|
|
|
38516
40676
|
return fail(`pr create: ${docsCheck.detail} \u2014 ${docsCheck.fix}`);
|
|
38517
40677
|
}
|
|
38518
40678
|
const created = await ghCreate(buildPrArgs({ title, body, base: o.base, head: o.head, repo: o.repo, draft: o.draft }));
|
|
40679
|
+
if (isGhCreateRateLimited(created)) {
|
|
40680
|
+
console.log(JSON.stringify(created));
|
|
40681
|
+
process.exitCode = 1;
|
|
40682
|
+
return;
|
|
40683
|
+
}
|
|
38519
40684
|
invalidateStatuslineBoardCache();
|
|
38520
40685
|
console.log(JSON.stringify(created));
|
|
38521
40686
|
}), [
|
|
@@ -38547,11 +40712,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
38547
40712
|
}
|
|
38548
40713
|
});
|
|
38549
40714
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
38550
|
-
const wfDir = (0,
|
|
38551
|
-
if (!(0,
|
|
38552
|
-
return (0,
|
|
40715
|
+
const wfDir = (0, import_node_path40.join)(cwd, ".github", "workflows");
|
|
40716
|
+
if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
|
|
40717
|
+
return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
38553
40718
|
try {
|
|
38554
|
-
return workflowReportsPrChecks((0,
|
|
40719
|
+
return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path40.join)(wfDir, name), "utf8"));
|
|
38555
40720
|
} catch {
|
|
38556
40721
|
return true;
|
|
38557
40722
|
}
|
|
@@ -38603,16 +40768,16 @@ function ciAuditDeps() {
|
|
|
38603
40768
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
38604
40769
|
readSeedFile: (path2) => {
|
|
38605
40770
|
if (!root) return null;
|
|
38606
|
-
const fullPath = (0,
|
|
38607
|
-
return (0,
|
|
40771
|
+
const fullPath = (0, import_node_path40.join)(root, path2);
|
|
40772
|
+
return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
|
|
38608
40773
|
}
|
|
38609
40774
|
};
|
|
38610
40775
|
}
|
|
38611
40776
|
function hubRoot() {
|
|
38612
|
-
const fromPkg = (0,
|
|
40777
|
+
const fromPkg = (0, import_node_path40.join)(__dirname, "..", "..");
|
|
38613
40778
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
38614
|
-
if ((0,
|
|
38615
|
-
if ((0,
|
|
40779
|
+
if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(fromPkg, marker))) return fromPkg;
|
|
40780
|
+
if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(process.cwd(), marker))) return process.cwd();
|
|
38616
40781
|
return null;
|
|
38617
40782
|
}
|
|
38618
40783
|
async function waitLoopCorePool(label) {
|
|
@@ -38834,16 +40999,36 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
38834
40999
|
if (result.status !== "failed") {
|
|
38835
41000
|
const boardAdvance = await advanceClosedIssuesToDone2(number, result.repo);
|
|
38836
41001
|
const crossRepoFilingIssue = result.repo ? await reconcileCrossRepoFilingIssue(defaultGitHubClient(), result.repo, Number(number)) : { status: "failed", error: "could not resolve the PR repo for cross-repo filing-issue reconciliation" };
|
|
41002
|
+
const recovery = result.repo ? buildPostMergeReconRecovery({
|
|
41003
|
+
repo: result.repo,
|
|
41004
|
+
pr: number,
|
|
41005
|
+
boardAdvance,
|
|
41006
|
+
crossRepoFilingIssue
|
|
41007
|
+
}) : void 0;
|
|
41008
|
+
let recoveryPath;
|
|
41009
|
+
if (recovery) {
|
|
41010
|
+
try {
|
|
41011
|
+
recoveryPath = writePostMergeReconRecovery(process.cwd(), recovery);
|
|
41012
|
+
} catch {
|
|
41013
|
+
}
|
|
41014
|
+
} else if (result.repo) {
|
|
41015
|
+
clearPostMergeReconRecovery(process.cwd(), result.repo, number);
|
|
41016
|
+
}
|
|
38837
41017
|
Object.assign(result, {
|
|
38838
41018
|
...boardAdvance.entries.length ? { boardAdvance: boardAdvance.entries } : {},
|
|
38839
41019
|
boardAdvanceStatus: boardAdvance.status,
|
|
38840
|
-
...crossRepoFilingIssue.status !== "not-applicable" ? { crossRepoFilingIssue } : {}
|
|
41020
|
+
...crossRepoFilingIssue.status !== "not-applicable" ? { crossRepoFilingIssue } : {},
|
|
41021
|
+
...recovery ? { postMergeReconRecovery: recovery } : {}
|
|
38841
41022
|
});
|
|
38842
41023
|
invalidateStatuslineBoardCache();
|
|
38843
|
-
const
|
|
38844
|
-
|
|
38845
|
-
|
|
38846
|
-
|
|
41024
|
+
for (const line of postMergeReconWarnings({
|
|
41025
|
+
context: "pr land",
|
|
41026
|
+
boardAdvance,
|
|
41027
|
+
crossRepoFilingIssue,
|
|
41028
|
+
recovery,
|
|
41029
|
+
recoveryPath
|
|
41030
|
+
})) {
|
|
41031
|
+
console.error(line);
|
|
38847
41032
|
}
|
|
38848
41033
|
}
|
|
38849
41034
|
if (o.json) printLine(JSON.stringify(result));
|
|
@@ -38936,7 +41121,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
38936
41121
|
}
|
|
38937
41122
|
if (!repoForPostCleanup) throw e;
|
|
38938
41123
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
|
|
38939
|
-
const commitMessage = bodyFile ? (0,
|
|
41124
|
+
const commitMessage = bodyFile ? (0, import_node_fs43.readFileSync)(bodyFile, "utf8") : void 0;
|
|
38940
41125
|
await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
|
|
38941
41126
|
body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
|
|
38942
41127
|
timeoutMs: GH_MUTATION_TIMEOUT_MS
|
|
@@ -39016,6 +41201,21 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
39016
41201
|
const remoteBranch = { branch: headRef, existedBefore: remoteBefore, attempted: false, reason: remoteNotAttemptedReason };
|
|
39017
41202
|
const boardAdvance = await advanceClosedIssuesToDone2(number, repoForPostCleanup);
|
|
39018
41203
|
const crossRepoFilingIssue = repoForPostCleanup ? await reconcileCrossRepoFilingIssue(defaultGitHubClient(), repoForPostCleanup, Number(number)) : { status: "failed", error: "could not resolve the PR repo for cross-repo filing-issue reconciliation" };
|
|
41204
|
+
const recovery = repoForPostCleanup ? buildPostMergeReconRecovery({
|
|
41205
|
+
repo: repoForPostCleanup,
|
|
41206
|
+
pr: number,
|
|
41207
|
+
boardAdvance,
|
|
41208
|
+
crossRepoFilingIssue
|
|
41209
|
+
}) : void 0;
|
|
41210
|
+
let recoveryPath;
|
|
41211
|
+
if (recovery) {
|
|
41212
|
+
try {
|
|
41213
|
+
recoveryPath = writePostMergeReconRecovery(process.cwd(), recovery);
|
|
41214
|
+
} catch {
|
|
41215
|
+
}
|
|
41216
|
+
} else if (repoForPostCleanup) {
|
|
41217
|
+
clearPostMergeReconRecovery(process.cwd(), repoForPostCleanup, number);
|
|
41218
|
+
}
|
|
39019
41219
|
invalidateStatuslineBoardCache();
|
|
39020
41220
|
const devDeploy = devDeployPlan.applicable && devDeployDeps && remoteNotAttemptedReason !== "pr-already-merged" ? await dispatchDevDeploy(repoForPostCleanup, devDeployDeps) : void 0;
|
|
39021
41221
|
const devDeployManual = devDeployPlan.applicable ? devDeployPlan.manualPointer : void 0;
|
|
@@ -39047,18 +41247,22 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
39047
41247
|
...boardAdvance.entries.length ? { boardAdvance: boardAdvance.entries } : {},
|
|
39048
41248
|
boardAdvanceStatus: boardAdvance.status,
|
|
39049
41249
|
...crossRepoFilingIssue.status !== "not-applicable" ? { crossRepoFilingIssue } : {},
|
|
41250
|
+
...recovery ? { postMergeReconRecovery: recovery } : {},
|
|
39050
41251
|
...devDeploy ? { devDeploy: devDeploy.ok ? { dispatched: true } : { dispatched: false, manual: devDeployManual } } : {}
|
|
39051
41252
|
}));
|
|
39052
|
-
const boardAdvanceMessage = boardAdvanceFailureMessage(boardAdvance);
|
|
39053
|
-
if (boardAdvanceMessage) console.error(boardAdvanceMessage);
|
|
39054
41253
|
if (devDeploy && !devDeploy.ok) {
|
|
39055
41254
|
console.error(`pr merge: ${devDeployManual} (${devDeploy.detail})`);
|
|
39056
41255
|
}
|
|
39057
|
-
|
|
39058
|
-
|
|
41256
|
+
for (const line of postMergeReconWarnings({
|
|
41257
|
+
context: "pr merge",
|
|
41258
|
+
boardAdvance,
|
|
41259
|
+
crossRepoFilingIssue,
|
|
41260
|
+
recovery,
|
|
41261
|
+
recoveryPath
|
|
41262
|
+
})) {
|
|
41263
|
+
console.error(line);
|
|
39059
41264
|
}
|
|
39060
|
-
process.exitCode =
|
|
39061
|
-
if (crossRepoFilingIssue.status === "failed") process.exitCode = 1;
|
|
41265
|
+
process.exitCode = postMergeReconExitCode({ boardAdvance, crossRepoFilingIssue }) ?? process.exitCode;
|
|
39062
41266
|
});
|
|
39063
41267
|
registerQueryCommands(program2);
|
|
39064
41268
|
registerIssueLifecycleCommands(program2, { attach: attachToProject });
|
|
@@ -39613,12 +41817,12 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
39613
41817
|
targets = resolution.targets;
|
|
39614
41818
|
}
|
|
39615
41819
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
39616
|
-
const fileMatrix = (0,
|
|
41820
|
+
const fileMatrix = (0, import_node_fs43.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs43.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
39617
41821
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
39618
41822
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
39619
|
-
const fileContracts = (0,
|
|
41823
|
+
const fileContracts = (0, import_node_fs43.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs43.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
39620
41824
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
39621
|
-
const sanctioned = (0,
|
|
41825
|
+
const sanctioned = (0, import_node_fs43.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs43.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
39622
41826
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
39623
41827
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
39624
41828
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -39650,16 +41854,16 @@ function directoryBytes(path2) {
|
|
|
39650
41854
|
let total = 0;
|
|
39651
41855
|
let entries;
|
|
39652
41856
|
try {
|
|
39653
|
-
entries = (0,
|
|
41857
|
+
entries = (0, import_node_fs43.readdirSync)(path2, { withFileTypes: true });
|
|
39654
41858
|
} catch {
|
|
39655
41859
|
return 0;
|
|
39656
41860
|
}
|
|
39657
41861
|
for (const entry of entries) {
|
|
39658
|
-
const child2 = (0,
|
|
41862
|
+
const child2 = (0, import_node_path40.join)(path2, entry.name);
|
|
39659
41863
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
39660
41864
|
else {
|
|
39661
41865
|
try {
|
|
39662
|
-
total += (0,
|
|
41866
|
+
total += (0, import_node_fs43.statSync)(child2).size;
|
|
39663
41867
|
} catch {
|
|
39664
41868
|
}
|
|
39665
41869
|
}
|
|
@@ -39667,25 +41871,25 @@ function directoryBytes(path2) {
|
|
|
39667
41871
|
return total;
|
|
39668
41872
|
}
|
|
39669
41873
|
function listDirEntries(dir) {
|
|
39670
|
-
return (0,
|
|
41874
|
+
return (0, import_node_fs43.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
39671
41875
|
}
|
|
39672
41876
|
function readInstalledPluginRefs(configRoot) {
|
|
39673
41877
|
const p = installedPluginsPathForConfig(configRoot);
|
|
39674
|
-
if (!(0,
|
|
41878
|
+
if (!(0, import_node_fs43.existsSync)(p)) return [];
|
|
39675
41879
|
try {
|
|
39676
|
-
return installedPluginPaths((0,
|
|
41880
|
+
return installedPluginPaths((0, import_node_fs43.readFileSync)(p, "utf8"));
|
|
39677
41881
|
} catch {
|
|
39678
41882
|
return null;
|
|
39679
41883
|
}
|
|
39680
41884
|
}
|
|
39681
41885
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
39682
41886
|
return {
|
|
39683
|
-
exists: (p) => (0,
|
|
39684
|
-
listVersionDirs: (root) => (0,
|
|
41887
|
+
exists: (p) => (0, import_node_fs43.existsSync)(p),
|
|
41888
|
+
listVersionDirs: (root) => (0, import_node_fs43.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
39685
41889
|
dirBytes,
|
|
39686
|
-
listStagingDirs: (root) => (0,
|
|
41890
|
+
listStagingDirs: (root) => (0, import_node_fs43.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
39687
41891
|
try {
|
|
39688
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
41892
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path40.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs43.statSync)(p).mtimeMs) };
|
|
39689
41893
|
} catch {
|
|
39690
41894
|
return { name: d.name, mtimeMs: Date.now() };
|
|
39691
41895
|
}
|
|
@@ -39699,10 +41903,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
39699
41903
|
return {
|
|
39700
41904
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
39701
41905
|
mtimeMs: (name) => {
|
|
39702
|
-
const p = (0,
|
|
39703
|
-
if (!(0,
|
|
41906
|
+
const p = (0, import_node_path40.join)(stagingRoot, name);
|
|
41907
|
+
if (!(0, import_node_fs43.existsSync)(p)) return null;
|
|
39704
41908
|
try {
|
|
39705
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
41909
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs43.statSync)(q).mtimeMs);
|
|
39706
41910
|
} catch {
|
|
39707
41911
|
return null;
|
|
39708
41912
|
}
|
|
@@ -39728,7 +41932,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
39728
41932
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
39729
41933
|
);
|
|
39730
41934
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
39731
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
41935
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs43.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
39732
41936
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
39733
41937
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
39734
41938
|
else console.log(renderPluginCachePlan(plan, result));
|