@mutmutco/cli 4.1.16 → 4.1.18
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 +347 -71
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -13181,10 +13181,10 @@ var rollout_plan_default = {
|
|
|
13181
13181
|
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)."
|
|
13182
13182
|
},
|
|
13183
13183
|
baseline: {
|
|
13184
|
-
version: "4.1.
|
|
13185
|
-
tag: "v4.1.
|
|
13186
|
-
commit: "
|
|
13187
|
-
npm: "@mutmutco/cli@4.1.
|
|
13184
|
+
version: "4.1.18",
|
|
13185
|
+
tag: "v4.1.18",
|
|
13186
|
+
commit: "6a8f41433f0f",
|
|
13187
|
+
npm: "@mutmutco/cli@4.1.18"
|
|
13188
13188
|
},
|
|
13189
13189
|
exitCriterion: "fleet-n-of-n",
|
|
13190
13190
|
hubOnlyShortcut: "forbidden",
|
|
@@ -13201,14 +13201,14 @@ var rollout_plan_default = {
|
|
|
13201
13201
|
repo: "mutmutco/mmi-hub",
|
|
13202
13202
|
role: "canary",
|
|
13203
13203
|
schedule: "train",
|
|
13204
|
-
v3Target: "v4.1.
|
|
13204
|
+
v3Target: "v4.1.18"
|
|
13205
13205
|
}
|
|
13206
13206
|
],
|
|
13207
13207
|
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.",
|
|
13208
13208
|
rollback: {
|
|
13209
13209
|
independent: true,
|
|
13210
|
-
mechanism: "npm dist-tag latest -> 4.1.
|
|
13211
|
-
v3Target: "v4.1.
|
|
13210
|
+
mechanism: "npm dist-tag latest -> 4.1.18 and redeploy the Hub Lambda from tag v4.1.18 (6a8f41433f0f); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
13211
|
+
v3Target: "v4.1.18 (@mutmutco/cli@4.1.18, tag commit 6a8f41433f0f \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
13212
13212
|
}
|
|
13213
13213
|
},
|
|
13214
13214
|
{
|
|
@@ -14193,7 +14193,8 @@ async function runPrForIssue(deps, issueRef, opts) {
|
|
|
14193
14193
|
return extractPrForIssueResponse(resp);
|
|
14194
14194
|
}
|
|
14195
14195
|
async function runIssueViewContext(deps, opts) {
|
|
14196
|
-
const
|
|
14196
|
+
const baseFields = opts.context ? opts.jsonFields.split(",").map((field) => field.trim()).filter((field) => field !== "linkedPrs" && field !== "children").join(",") : opts.jsonFields;
|
|
14197
|
+
const fields = opts.comments || opts.context ? ensureField(baseFields, "comments") : baseFields;
|
|
14197
14198
|
const base = await deps.ghJson(["issue", "view", String(opts.number), "--repo", opts.repo, "--json", fields], GH_LIST_TIMEOUT_MS);
|
|
14198
14199
|
if (!opts.context) return base;
|
|
14199
14200
|
const result = { ...base };
|
|
@@ -25266,9 +25267,35 @@ function repoIndexPathTieBreak(path2) {
|
|
|
25266
25267
|
if (/\.(test|spec)\.[^.]+$/i.test(p)) return 1;
|
|
25267
25268
|
return 0;
|
|
25268
25269
|
}
|
|
25270
|
+
var LOCAL_REPO_INDEX_REFRESH_BUDGET_MS = 2e3;
|
|
25271
|
+
var LOCAL_REPO_INDEX_DELTA_FILE_LIMIT = 50;
|
|
25269
25272
|
function repoIndexStorePath(cwd) {
|
|
25270
25273
|
return repoRuntimeStatePath(cwd, "repo-index", "index.json");
|
|
25271
25274
|
}
|
|
25275
|
+
function localRepoIndexUsagePath(cwd) {
|
|
25276
|
+
return repoRuntimeStatePath(cwd, "repo-index", "local-usage.json");
|
|
25277
|
+
}
|
|
25278
|
+
function recordLocalRepoIndexQueryUsage(cwd, now = /* @__PURE__ */ new Date()) {
|
|
25279
|
+
const store = localRepoIndexUsagePath(cwd);
|
|
25280
|
+
let previous = 0;
|
|
25281
|
+
try {
|
|
25282
|
+
const raw = JSON.parse((0, import_node_fs24.readFileSync)(store, "utf8"));
|
|
25283
|
+
if (typeof raw.count === "number" && Number.isFinite(raw.count)) previous = raw.count;
|
|
25284
|
+
} catch {
|
|
25285
|
+
}
|
|
25286
|
+
(0, import_node_fs24.mkdirSync)((0, import_node_path21.dirname)(store), { recursive: true });
|
|
25287
|
+
(0, import_node_fs24.writeFileSync)(store, `${JSON.stringify({ count: previous + 1, updatedAt: now.toISOString() }, null, 2)}
|
|
25288
|
+
`, "utf8");
|
|
25289
|
+
}
|
|
25290
|
+
function readLocalRepoIndexQueryUsage(cwd) {
|
|
25291
|
+
try {
|
|
25292
|
+
const raw = JSON.parse((0, import_node_fs24.readFileSync)(localRepoIndexUsagePath(cwd), "utf8"));
|
|
25293
|
+
if (typeof raw.count !== "number" || !Number.isFinite(raw.count)) return null;
|
|
25294
|
+
return { count: raw.count, updatedAt: raw.updatedAt ?? "" };
|
|
25295
|
+
} catch {
|
|
25296
|
+
return null;
|
|
25297
|
+
}
|
|
25298
|
+
}
|
|
25272
25299
|
function isHardDeniedPath(relPosix) {
|
|
25273
25300
|
return isHardDeniedRepoIndexPath(relPosix);
|
|
25274
25301
|
}
|
|
@@ -25379,14 +25406,41 @@ function listCandidatePaths(cwd, exec = import_node_child_process12.execFileSync
|
|
|
25379
25406
|
return [];
|
|
25380
25407
|
}
|
|
25381
25408
|
}
|
|
25382
|
-
|
|
25383
|
-
|
|
25384
|
-
|
|
25385
|
-
|
|
25386
|
-
|
|
25409
|
+
var RepoIndexRefreshBudgetError = class extends Error {
|
|
25410
|
+
constructor() {
|
|
25411
|
+
super(`local index refresh exceeded ${LOCAL_REPO_INDEX_REFRESH_BUDGET_MS}ms`);
|
|
25412
|
+
}
|
|
25413
|
+
};
|
|
25414
|
+
function assertRefreshBudget(deadline) {
|
|
25415
|
+
if (deadline !== void 0 && Date.now() >= deadline) throw new RepoIndexRefreshBudgetError();
|
|
25416
|
+
}
|
|
25417
|
+
function deadlineExec(deadline) {
|
|
25418
|
+
return ((file, args, options) => {
|
|
25419
|
+
assertRefreshBudget(deadline);
|
|
25420
|
+
return (0, import_node_child_process12.execFileSync)(file, args, {
|
|
25421
|
+
...options,
|
|
25422
|
+
timeout: Math.max(1, deadline - Date.now()),
|
|
25423
|
+
windowsHide: true
|
|
25424
|
+
});
|
|
25425
|
+
});
|
|
25426
|
+
}
|
|
25427
|
+
function gitHead(cwd, exec = import_node_child_process12.execFileSync) {
|
|
25428
|
+
try {
|
|
25429
|
+
return String(exec("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", windowsHide: true })).trim() || void 0;
|
|
25430
|
+
} catch (error) {
|
|
25431
|
+
if (error instanceof RepoIndexRefreshBudgetError) throw error;
|
|
25432
|
+
return void 0;
|
|
25433
|
+
}
|
|
25434
|
+
}
|
|
25435
|
+
function sourceFilesForIndex(cwd, deadline) {
|
|
25436
|
+
const exec = deadline === void 0 ? import_node_child_process12.execFileSync : deadlineExec(deadline);
|
|
25437
|
+
const candidates = listCandidatePaths(cwd, exec).filter(isIndexablePath);
|
|
25438
|
+
assertRefreshBudget(deadline);
|
|
25439
|
+
const ignored = defaultIsIgnored(cwd, candidates, exec);
|
|
25440
|
+
const files = [];
|
|
25387
25441
|
for (const rel of candidates) {
|
|
25388
|
-
|
|
25389
|
-
if (isHardDeniedPath(rel)) continue;
|
|
25442
|
+
assertRefreshBudget(deadline);
|
|
25443
|
+
if (ignored.has(rel) || isHardDeniedPath(rel)) continue;
|
|
25390
25444
|
const abs = (0, import_node_path21.join)(cwd, ...rel.split("/"));
|
|
25391
25445
|
if (!(0, import_node_fs24.existsSync)(abs)) continue;
|
|
25392
25446
|
let text;
|
|
@@ -25396,31 +25450,118 @@ function rebuildRepoIndex(cwd, repoSlug3) {
|
|
|
25396
25450
|
continue;
|
|
25397
25451
|
}
|
|
25398
25452
|
if (text.length > 15e5) continue;
|
|
25399
|
-
|
|
25400
|
-
const symbols = extractSymbols(text);
|
|
25401
|
-
const docBlurb = extractModuleBlurb(text);
|
|
25402
|
-
const top = rel.includes("/") ? rel.split("/")[0] : "";
|
|
25403
|
-
const hint = docBlurb ?? (top ? readmeHints.get(top) : void 0) ?? readmeHints.get("");
|
|
25404
|
-
entries.push({
|
|
25453
|
+
files.push({
|
|
25405
25454
|
path: rel,
|
|
25406
|
-
hash,
|
|
25407
|
-
|
|
25408
|
-
...hint ? { blurb: hint.slice(0, BLURB_CAP) } : {}
|
|
25455
|
+
hash: (0, import_node_crypto6.createHash)("sha256").update(text).digest("hex").slice(0, 16),
|
|
25456
|
+
text
|
|
25409
25457
|
});
|
|
25410
25458
|
}
|
|
25459
|
+
return files;
|
|
25460
|
+
}
|
|
25461
|
+
function entryForSource(file, readmeHints) {
|
|
25462
|
+
const docBlurb = extractModuleBlurb(file.text);
|
|
25463
|
+
const top = file.path.includes("/") ? file.path.split("/")[0] : "";
|
|
25464
|
+
const hint = docBlurb ?? (top ? readmeHints.get(top) : void 0) ?? readmeHints.get("");
|
|
25465
|
+
return {
|
|
25466
|
+
path: file.path,
|
|
25467
|
+
hash: file.hash,
|
|
25468
|
+
symbols: extractSymbols(file.text),
|
|
25469
|
+
...hint ? { blurb: hint.slice(0, BLURB_CAP) } : {}
|
|
25470
|
+
};
|
|
25471
|
+
}
|
|
25472
|
+
function buildRepoIndexProjection(cwd, repoSlug3, deadline) {
|
|
25473
|
+
const files = sourceFilesForIndex(cwd, deadline);
|
|
25474
|
+
assertRefreshBudget(deadline);
|
|
25475
|
+
const readmeHints = loadReadmeHints(cwd, files.map((file) => file.path));
|
|
25476
|
+
const entries = files.map((file) => {
|
|
25477
|
+
assertRefreshBudget(deadline);
|
|
25478
|
+
return entryForSource(file, readmeHints);
|
|
25479
|
+
});
|
|
25411
25480
|
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
25412
|
-
const
|
|
25481
|
+
const exec = deadline === void 0 ? import_node_child_process12.execFileSync : deadlineExec(deadline);
|
|
25482
|
+
const indexHead = gitHead(cwd, exec);
|
|
25483
|
+
return {
|
|
25413
25484
|
schema: REPO_INDEX_SCHEMA,
|
|
25414
25485
|
repo: repoSlug3,
|
|
25415
25486
|
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25487
|
+
...indexHead ? { indexHead } : {},
|
|
25416
25488
|
entries
|
|
25417
25489
|
};
|
|
25490
|
+
}
|
|
25491
|
+
function writeRepoIndex(cwd, projection) {
|
|
25418
25492
|
const store = repoIndexStorePath(cwd);
|
|
25419
25493
|
(0, import_node_fs24.mkdirSync)((0, import_node_path21.dirname)(store), { recursive: true });
|
|
25420
25494
|
(0, import_node_fs24.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
|
|
25421
25495
|
`, "utf8");
|
|
25496
|
+
}
|
|
25497
|
+
function rebuildRepoIndex(cwd, repoSlug3) {
|
|
25498
|
+
const projection = buildRepoIndexProjection(cwd, repoSlug3);
|
|
25499
|
+
writeRepoIndex(cwd, projection);
|
|
25422
25500
|
return projection;
|
|
25423
25501
|
}
|
|
25502
|
+
function refreshRepoIndex(cwd, repoSlug3, options = {}) {
|
|
25503
|
+
const budgetMs = options.budgetMs ?? LOCAL_REPO_INDEX_REFRESH_BUDGET_MS;
|
|
25504
|
+
const deadline = Date.now() + Math.max(0, budgetMs);
|
|
25505
|
+
try {
|
|
25506
|
+
const exec = deadlineExec(deadline);
|
|
25507
|
+
const existing = loadRepoIndex(cwd);
|
|
25508
|
+
if (!existing) {
|
|
25509
|
+
const projection2 = buildRepoIndexProjection(cwd, repoSlug3, deadline);
|
|
25510
|
+
writeRepoIndex(cwd, projection2);
|
|
25511
|
+
return { status: "rebuilt", projection: projection2 };
|
|
25512
|
+
}
|
|
25513
|
+
const head = gitHead(cwd, exec);
|
|
25514
|
+
if (!head) return { status: "stale", reason: "checkout HEAD is unavailable" };
|
|
25515
|
+
const worktreeDirty = String(exec("git", ["status", "--porcelain", "--untracked-files=normal"], {
|
|
25516
|
+
cwd,
|
|
25517
|
+
encoding: "utf8",
|
|
25518
|
+
windowsHide: true
|
|
25519
|
+
})).length > 0;
|
|
25520
|
+
if (existing.indexHead === head && !worktreeDirty) return { status: "refreshed", projection: existing };
|
|
25521
|
+
if (!existing.indexHead) {
|
|
25522
|
+
const projection2 = buildRepoIndexProjection(cwd, repoSlug3, deadline);
|
|
25523
|
+
writeRepoIndex(cwd, projection2);
|
|
25524
|
+
return { status: "rebuilt", projection: projection2 };
|
|
25525
|
+
}
|
|
25526
|
+
const files = sourceFilesForIndex(cwd, deadline);
|
|
25527
|
+
const current = new Map(files.map((file) => [file.path, file]));
|
|
25528
|
+
const previous = new Map(existing.entries.map((entry) => [entry.path, entry]));
|
|
25529
|
+
const changed = /* @__PURE__ */ new Set();
|
|
25530
|
+
for (const [path2, file] of current) {
|
|
25531
|
+
if (previous.get(path2)?.hash !== file.hash) changed.add(path2);
|
|
25532
|
+
}
|
|
25533
|
+
for (const path2 of previous.keys()) {
|
|
25534
|
+
if (!current.has(path2)) changed.add(path2);
|
|
25535
|
+
}
|
|
25536
|
+
const readmeChanged = [...changed].some((path2) => path2 === "README.md" || /^[^/]+\/README\.md$/.test(path2));
|
|
25537
|
+
if (changed.size > LOCAL_REPO_INDEX_DELTA_FILE_LIMIT || readmeChanged) {
|
|
25538
|
+
const projection2 = buildRepoIndexProjection(cwd, repoSlug3, deadline);
|
|
25539
|
+
writeRepoIndex(cwd, projection2);
|
|
25540
|
+
return { status: "rebuilt", projection: projection2 };
|
|
25541
|
+
}
|
|
25542
|
+
const changedSources = [...changed].map((path2) => current.get(path2)).filter((file) => file !== void 0);
|
|
25543
|
+
const needsHint = changedSources.some((file) => !extractModuleBlurb(file.text));
|
|
25544
|
+
const readmeHints = needsHint ? loadReadmeHints(cwd, files.map((file) => file.path)) : /* @__PURE__ */ new Map();
|
|
25545
|
+
const entries = files.map((file) => {
|
|
25546
|
+
assertRefreshBudget(deadline);
|
|
25547
|
+
const old = previous.get(file.path);
|
|
25548
|
+
return old?.hash === file.hash ? old : entryForSource(file, readmeHints);
|
|
25549
|
+
});
|
|
25550
|
+
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
25551
|
+
const projection = {
|
|
25552
|
+
...existing,
|
|
25553
|
+
repo: repoSlug3,
|
|
25554
|
+
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25555
|
+
indexHead: head,
|
|
25556
|
+
entries
|
|
25557
|
+
};
|
|
25558
|
+
writeRepoIndex(cwd, projection);
|
|
25559
|
+
return { status: "refreshed", projection };
|
|
25560
|
+
} catch (error) {
|
|
25561
|
+
const reason = error instanceof RepoIndexRefreshBudgetError ? error.message : `local index refresh failed: ${error.message}`;
|
|
25562
|
+
return { status: "stale", reason };
|
|
25563
|
+
}
|
|
25564
|
+
}
|
|
25424
25565
|
function loadRepoIndex(cwd) {
|
|
25425
25566
|
const store = repoIndexStorePath(cwd);
|
|
25426
25567
|
if (!(0, import_node_fs24.existsSync)(store)) return null;
|
|
@@ -25437,6 +25578,7 @@ function repoIndexStatus(cwd) {
|
|
|
25437
25578
|
const idx = loadRepoIndex(cwd);
|
|
25438
25579
|
if (!idx) return { present: false, path: path2 };
|
|
25439
25580
|
const symbolCount = idx.entries.reduce((n, e) => n + e.symbols.length, 0);
|
|
25581
|
+
const localUsage = readLocalRepoIndexQueryUsage(cwd);
|
|
25440
25582
|
return {
|
|
25441
25583
|
present: true,
|
|
25442
25584
|
path: path2,
|
|
@@ -25444,7 +25586,8 @@ function repoIndexStatus(cwd) {
|
|
|
25444
25586
|
repo: idx.repo,
|
|
25445
25587
|
builtAt: idx.builtAt,
|
|
25446
25588
|
fileCount: idx.entries.length,
|
|
25447
|
-
symbolCount
|
|
25589
|
+
symbolCount,
|
|
25590
|
+
...localUsage ? { localQueryCount: localUsage.count } : {}
|
|
25448
25591
|
};
|
|
25449
25592
|
}
|
|
25450
25593
|
function searchRepoIndex(idx, query, limit = 20) {
|
|
@@ -26238,9 +26381,9 @@ async function probeRepoIndexV4ReadinessCloud(queries, deps) {
|
|
|
26238
26381
|
if (!token) return { ok: false, error: "no Hub session token (run `gh auth login`)" };
|
|
26239
26382
|
const baseUrl = deps.baseUrl.replace(/\/$/, "");
|
|
26240
26383
|
const headers = { ...clientVersionHeaders(), Authorization: ["Bearer", token].join(" "), "content-type": "application/json" };
|
|
26241
|
-
const shadowPass = async (readinessProbe) => {
|
|
26384
|
+
const shadowPass = async (readinessProbe, passQueries = queries) => {
|
|
26242
26385
|
const ms = {};
|
|
26243
|
-
for (const query of
|
|
26386
|
+
for (const query of passQueries) {
|
|
26244
26387
|
const res = await fetchWithRetry(deps.fetch ?? fetch, `${baseUrl}/repo-index/v4/shadow`, {
|
|
26245
26388
|
method: "POST",
|
|
26246
26389
|
headers,
|
|
@@ -26262,6 +26405,11 @@ async function probeRepoIndexV4ReadinessCloud(queries, deps) {
|
|
|
26262
26405
|
}
|
|
26263
26406
|
const verdict = await shadowPass(true);
|
|
26264
26407
|
if (!verdict.ok) return verdict;
|
|
26408
|
+
const latencyRetries = queries.filter((query) => query.latencyMs !== void 0 && verdict.ms[query.id] !== void 0 && verdict.ms[query.id] > query.latencyMs);
|
|
26409
|
+
if (latencyRetries.length) {
|
|
26410
|
+
const retry = await shadowPass(true, latencyRetries);
|
|
26411
|
+
if (!retry.ok) return retry;
|
|
26412
|
+
}
|
|
26265
26413
|
const res = await fetchWithRetry(deps.fetch ?? fetch, `${baseUrl}/repo-index/status`, {
|
|
26266
26414
|
method: "GET",
|
|
26267
26415
|
headers: { ...clientVersionHeaders(), Authorization: ["Bearer", token].join(" ") }
|
|
@@ -26791,9 +26939,9 @@ function buildGraphEdges(cwd, repo, commit, rosterRepos2) {
|
|
|
26791
26939
|
}
|
|
26792
26940
|
|
|
26793
26941
|
// src/repo-index-sync.ts
|
|
26794
|
-
function execFileUtf8(file, args) {
|
|
26942
|
+
function execFileUtf8(file, args, env, cwd) {
|
|
26795
26943
|
return new Promise((resolve6, reject) => {
|
|
26796
|
-
(0, import_node_child_process15.execFile)(file, args, { encoding: "utf8", windowsHide: true }, (error, stdout) => {
|
|
26944
|
+
(0, import_node_child_process15.execFile)(file, args, { encoding: "utf8", windowsHide: true, ...env ? { env } : {}, ...cwd ? { cwd } : {} }, (error, stdout) => {
|
|
26797
26945
|
if (error) reject(error);
|
|
26798
26946
|
else resolve6(String(stdout ?? ""));
|
|
26799
26947
|
});
|
|
@@ -26801,6 +26949,9 @@ function execFileUtf8(file, args) {
|
|
|
26801
26949
|
}
|
|
26802
26950
|
var ESTATE_CLASSIFY_CONCURRENCY = 8;
|
|
26803
26951
|
var ESTATE_HEALTHY_CLASSIFY_BUDGET_MS = 5 * 601e3;
|
|
26952
|
+
function formatEstateRunFailureSummary(failed) {
|
|
26953
|
+
return failed.map((f) => `${f.repo} (${f.probe?.failureClass ?? "failed"})`).join(", ");
|
|
26954
|
+
}
|
|
26804
26955
|
var COMMIT3 = /^[a-f0-9]{40}$/;
|
|
26805
26956
|
var SHA256 = /^[a-f0-9]{64}$/;
|
|
26806
26957
|
var UTC_MILLIS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
@@ -26836,14 +26987,75 @@ function checkoutExactCommit(repo, dest, token, commit) {
|
|
|
26836
26987
|
const head = git3(["rev-parse", "HEAD"]).trim().toLowerCase();
|
|
26837
26988
|
if (head !== commit) throw new Error(`checkout of ${repo} resolved ${head}, not the requested commit ${commit}`);
|
|
26838
26989
|
}
|
|
26990
|
+
var HeadProbeError = class extends Error {
|
|
26991
|
+
constructor(message, exitCode, failureClass) {
|
|
26992
|
+
super(message);
|
|
26993
|
+
this.exitCode = exitCode;
|
|
26994
|
+
this.failureClass = failureClass;
|
|
26995
|
+
}
|
|
26996
|
+
exitCode;
|
|
26997
|
+
failureClass;
|
|
26998
|
+
};
|
|
26999
|
+
function classifyProbeStderr(stderr) {
|
|
27000
|
+
const text = stderr.toLowerCase();
|
|
27001
|
+
if (/(could not read username|could not read password|authentication failed|invalid credentials|permission denied|terminal prompts disabled|returned error: 401|returned error: 403)/.test(text)) return "auth-denied";
|
|
27002
|
+
if (/(repository .* not found|couldn't find remote ref|not found)/.test(text)) return "not-found";
|
|
27003
|
+
if (/(timed out|timeout|operation timed out|connection timed out)/.test(text)) return "timeout";
|
|
27004
|
+
if (/(could not resolve host|network is unreachable|connection (refused|reset)|temporary failure|failed to connect|could not connect)/.test(text)) return "network";
|
|
27005
|
+
return "unreadable";
|
|
27006
|
+
}
|
|
27007
|
+
function headProbeEnv() {
|
|
27008
|
+
const env = { ...process.env };
|
|
27009
|
+
delete env.GIT_ASKPASS;
|
|
27010
|
+
delete env.SSH_ASKPASS;
|
|
27011
|
+
for (const key of Object.keys(env)) {
|
|
27012
|
+
if (key.startsWith("GIT_CONFIG_")) delete env[key];
|
|
27013
|
+
}
|
|
27014
|
+
env.GIT_TERMINAL_PROMPT = "0";
|
|
27015
|
+
return env;
|
|
27016
|
+
}
|
|
27017
|
+
function toHeadProbeError(error, repo) {
|
|
27018
|
+
const e = error;
|
|
27019
|
+
const exitCode = typeof e.code === "number" ? e.code : null;
|
|
27020
|
+
const stderr = typeof e.stderr === "string" ? e.stderr : "";
|
|
27021
|
+
return new HeadProbeError(
|
|
27022
|
+
`remote HEAD probe of ${repo} failed (exit ${exitCode ?? "unknown"}, ${classifyProbeStderr(stderr)})`,
|
|
27023
|
+
exitCode,
|
|
27024
|
+
classifyProbeStderr(stderr)
|
|
27025
|
+
);
|
|
27026
|
+
}
|
|
26839
27027
|
async function remoteHead(repo, token) {
|
|
26840
27028
|
const basic = Buffer.from(`x-access-token:${token}`, "utf8").toString("base64");
|
|
26841
|
-
|
|
26842
|
-
|
|
26843
|
-
|
|
26844
|
-
|
|
27029
|
+
let stdout;
|
|
27030
|
+
try {
|
|
27031
|
+
stdout = await execFileUtf8(
|
|
27032
|
+
"git",
|
|
27033
|
+
[
|
|
27034
|
+
"-c",
|
|
27035
|
+
"credential.helper=",
|
|
27036
|
+
// #5750: actions/checkout persists its token as a URL-scoped extraheader in the checked-out
|
|
27037
|
+
// repo's .git/config — repo-local config that env sanitization cannot reach. Run from a
|
|
27038
|
+
// neutral cwd so no repo config applies, and blank both extraheader scopes so the ONLY
|
|
27039
|
+
// credential is the app token below. Credential values are never copied or logged.
|
|
27040
|
+
"-c",
|
|
27041
|
+
"http.extraheader=",
|
|
27042
|
+
"-c",
|
|
27043
|
+
"http.https://github.com/.extraheader=",
|
|
27044
|
+
"-c",
|
|
27045
|
+
`http.extraHeader=Authorization: Basic ${basic}`,
|
|
27046
|
+
"ls-remote",
|
|
27047
|
+
"--exit-code",
|
|
27048
|
+
`https://github.com/${repo}.git`,
|
|
27049
|
+
"HEAD"
|
|
27050
|
+
],
|
|
27051
|
+
headProbeEnv(),
|
|
27052
|
+
(0, import_node_os14.tmpdir)()
|
|
27053
|
+
);
|
|
27054
|
+
} catch (error) {
|
|
27055
|
+
throw toHeadProbeError(error, repo);
|
|
27056
|
+
}
|
|
26845
27057
|
const match = stdout.match(/^([a-f0-9]{40})\s+HEAD$/m);
|
|
26846
|
-
if (!match) throw new
|
|
27058
|
+
if (!match) throw new HeadProbeError(`could not resolve remote HEAD for ${repo}`, null, "unreadable");
|
|
26847
27059
|
return match[1];
|
|
26848
27060
|
}
|
|
26849
27061
|
async function mapLimit(items, limit, fn) {
|
|
@@ -26912,6 +27124,7 @@ async function syncEstateRepoIndex(opts) {
|
|
|
26912
27124
|
const skipped = [];
|
|
26913
27125
|
const drift = [];
|
|
26914
27126
|
const needsFullRebuild = [];
|
|
27127
|
+
let rosterSize = 0;
|
|
26915
27128
|
const answer = (ok) => ({
|
|
26916
27129
|
ok: ok ?? failed.length === 0,
|
|
26917
27130
|
published,
|
|
@@ -26919,7 +27132,12 @@ async function syncEstateRepoIndex(opts) {
|
|
|
26919
27132
|
skipped,
|
|
26920
27133
|
provenanceToken: CURRENT_REPO_INDEX_PROVENANCE_TOKEN,
|
|
26921
27134
|
drift,
|
|
26922
|
-
needsFullRebuild
|
|
27135
|
+
needsFullRebuild,
|
|
27136
|
+
receipt: {
|
|
27137
|
+
changed: published.length,
|
|
27138
|
+
skipped: Math.max(0, rosterSize - published.length - failed.length),
|
|
27139
|
+
failed: failed.length
|
|
27140
|
+
}
|
|
26923
27141
|
});
|
|
26924
27142
|
const fullRebuildToken = (opts.fullRebuild ?? "").trim();
|
|
26925
27143
|
const forceFull = fullRebuildToken !== "";
|
|
@@ -26959,6 +27177,7 @@ async function syncEstateRepoIndex(opts) {
|
|
|
26959
27177
|
const busy = new Set(
|
|
26960
27178
|
(opts.skipRepos ?? []).map((repo) => normalizeRepoIndexRepo(repo)).filter((repo) => repo !== null)
|
|
26961
27179
|
);
|
|
27180
|
+
rosterSize = repos.length;
|
|
26962
27181
|
const emit = (progress) => {
|
|
26963
27182
|
try {
|
|
26964
27183
|
opts.onClassify?.(progress);
|
|
@@ -26991,12 +27210,12 @@ async function syncEstateRepoIndex(opts) {
|
|
|
26991
27210
|
}
|
|
26992
27211
|
let targetCommit = requestedCommit || void 0;
|
|
26993
27212
|
if (base && !forceFull) {
|
|
26994
|
-
let
|
|
27213
|
+
let headProbe = null;
|
|
26995
27214
|
if (!targetCommit) {
|
|
26996
27215
|
try {
|
|
26997
27216
|
targetCommit = await remoteHead(repo, opts.githubToken);
|
|
26998
|
-
} catch {
|
|
26999
|
-
|
|
27217
|
+
} catch (error) {
|
|
27218
|
+
headProbe = error instanceof HeadProbeError ? error : new HeadProbeError(error.message, null, "unreadable");
|
|
27000
27219
|
}
|
|
27001
27220
|
}
|
|
27002
27221
|
const provenance = await fetchRepoIndexV4ProvenanceCloud(repo, opts.deps).catch(
|
|
@@ -27020,11 +27239,10 @@ async function syncEstateRepoIndex(opts) {
|
|
|
27020
27239
|
emit({ row: row2, warning });
|
|
27021
27240
|
return { repo, row: row2, warning, base, expectedActiveDigest };
|
|
27022
27241
|
}
|
|
27023
|
-
if (
|
|
27024
|
-
const row2 = { repo, reason: "head-unreadable", action: "
|
|
27025
|
-
|
|
27026
|
-
|
|
27027
|
-
return { repo, row: row2, warning, base, expectedActiveDigest };
|
|
27242
|
+
if (headProbe) {
|
|
27243
|
+
const row2 = { repo, reason: "head-unreadable", action: "failed", activeCommit: base.commit };
|
|
27244
|
+
emit({ row: row2 });
|
|
27245
|
+
return { repo, row: row2, headProbe, base, expectedActiveDigest };
|
|
27028
27246
|
}
|
|
27029
27247
|
}
|
|
27030
27248
|
const row = {
|
|
@@ -27041,6 +27259,14 @@ async function syncEstateRepoIndex(opts) {
|
|
|
27041
27259
|
drift.push(entry.row);
|
|
27042
27260
|
if (entry.warning) skipped.push(entry.warning);
|
|
27043
27261
|
if (entry.row.action === "needs-full-rebuild") needsFullRebuild.push(entry.repo);
|
|
27262
|
+
if (entry.headProbe) {
|
|
27263
|
+
const exit = entry.headProbe.exitCode === null ? "unknown" : String(entry.headProbe.exitCode);
|
|
27264
|
+
failed.push({
|
|
27265
|
+
repo: entry.repo,
|
|
27266
|
+
error: `remote HEAD unreadable (exit ${exit}, ${entry.headProbe.failureClass}) \u2014 verified-ready authority at ${entry.row.activeCommit ?? "unknown"} was not re-probed; reconcile FAILED`,
|
|
27267
|
+
probe: { exitCode: entry.headProbe.exitCode, failureClass: entry.headProbe.failureClass }
|
|
27268
|
+
});
|
|
27269
|
+
}
|
|
27044
27270
|
}
|
|
27045
27271
|
if (opts.plan) return answer();
|
|
27046
27272
|
for (const entry of classified) {
|
|
@@ -33890,34 +34116,60 @@ async function removeWorktreeWithReconcile(wtPath, git3, listWorktrees, pathExis
|
|
|
33890
34116
|
return formatGitCommandError(e);
|
|
33891
34117
|
}
|
|
33892
34118
|
};
|
|
33893
|
-
const
|
|
33894
|
-
if (!firstError) {
|
|
33895
|
-
await git3(["worktree", "prune"]).catch(() => "");
|
|
33896
|
-
return { status: "removed" };
|
|
33897
|
-
}
|
|
33898
|
-
let worktrees = await listWorktrees().catch(() => []);
|
|
33899
|
-
if (!isWorktreePathRegistered(worktrees, wtPath)) {
|
|
34119
|
+
const pruneAndVerify = async (reason) => {
|
|
33900
34120
|
await git3(["worktree", "prune"]).catch(() => "");
|
|
33901
|
-
|
|
34121
|
+
let worktrees2;
|
|
34122
|
+
try {
|
|
34123
|
+
worktrees2 = await listWorktrees();
|
|
34124
|
+
} catch (e) {
|
|
34125
|
+
return {
|
|
34126
|
+
status: "failed",
|
|
34127
|
+
error: `could not verify worktree removal after prune: ${formatGitCommandError(e)}; path=${wtPath}`
|
|
34128
|
+
};
|
|
34129
|
+
}
|
|
34130
|
+
if (isWorktreePathRegistered(worktrees2, wtPath)) {
|
|
34131
|
+
return {
|
|
34132
|
+
status: "failed",
|
|
34133
|
+
error: `worktree remains registered after prune; path=${wtPath}`
|
|
34134
|
+
};
|
|
34135
|
+
}
|
|
34136
|
+
return { status: "removed", ...reason ? { reason } : {} };
|
|
34137
|
+
};
|
|
34138
|
+
const firstError = await attemptRemove();
|
|
34139
|
+
if (!firstError) return pruneAndVerify();
|
|
34140
|
+
let worktrees;
|
|
34141
|
+
try {
|
|
34142
|
+
worktrees = await listWorktrees();
|
|
34143
|
+
} catch (e) {
|
|
34144
|
+
return {
|
|
34145
|
+
status: "failed",
|
|
34146
|
+
error: `could not verify worktree registration after remove failure: ${formatGitCommandError(e)}; path=${wtPath}`
|
|
34147
|
+
};
|
|
33902
34148
|
}
|
|
34149
|
+
if (!isWorktreePathRegistered(worktrees, wtPath)) return pruneAndVerify("reconciled-unregistered");
|
|
33903
34150
|
const retryError = await attemptRemove();
|
|
33904
|
-
if (!retryError)
|
|
33905
|
-
await git3(["worktree", "prune"]).catch(() => "");
|
|
33906
|
-
return { status: "removed", reason: "reconciled-after-retry" };
|
|
33907
|
-
}
|
|
34151
|
+
if (!retryError) return pruneAndVerify("reconciled-after-retry");
|
|
33908
34152
|
if (isNotAWorkingTreeMessage(retryError) || isNotAWorkingTreeMessage(firstError)) {
|
|
33909
|
-
|
|
33910
|
-
|
|
33911
|
-
|
|
33912
|
-
return {
|
|
34153
|
+
try {
|
|
34154
|
+
worktrees = await listWorktrees();
|
|
34155
|
+
} catch (e) {
|
|
34156
|
+
return {
|
|
34157
|
+
status: "failed",
|
|
34158
|
+
error: `could not verify worktree registration after remove failure: ${formatGitCommandError(e)}; path=${wtPath}`
|
|
34159
|
+
};
|
|
33913
34160
|
}
|
|
34161
|
+
if (!isWorktreePathRegistered(worktrees, wtPath)) return pruneAndVerify("reconciled-not-a-working-tree");
|
|
33914
34162
|
}
|
|
33915
|
-
|
|
33916
|
-
|
|
33917
|
-
|
|
33918
|
-
|
|
33919
|
-
|
|
34163
|
+
try {
|
|
34164
|
+
worktrees = await listWorktrees();
|
|
34165
|
+
} catch (e) {
|
|
34166
|
+
return {
|
|
34167
|
+
status: "failed",
|
|
34168
|
+
error: `could not verify worktree registration after remove failure: ${formatGitCommandError(e)}; path=${wtPath}`
|
|
34169
|
+
};
|
|
33920
34170
|
}
|
|
34171
|
+
const registered = isWorktreePathRegistered(worktrees, wtPath);
|
|
34172
|
+
if (!registered) return pruneAndVerify("reconciled-unregistered");
|
|
33921
34173
|
return {
|
|
33922
34174
|
status: "failed",
|
|
33923
34175
|
error: formatWorktreeRemovalFailureDetail({
|
|
@@ -33928,6 +34180,9 @@ async function removeWorktreeWithReconcile(wtPath, git3, listWorktrees, pathExis
|
|
|
33928
34180
|
})
|
|
33929
34181
|
};
|
|
33930
34182
|
}
|
|
34183
|
+
function prMergeLocalCleanupExitCode(cleanup) {
|
|
34184
|
+
return cleanup?.worktree?.status === "failed" || cleanup?.localBranch?.status === "failed" ? 1 : void 0;
|
|
34185
|
+
}
|
|
33931
34186
|
async function cleanupPrMergeLocalBranch(branch, options) {
|
|
33932
34187
|
const report = { branch };
|
|
33933
34188
|
if (!branch) {
|
|
@@ -34034,7 +34289,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
34034
34289
|
const removal = await removeWorktreeWithReconcile(
|
|
34035
34290
|
wtPath,
|
|
34036
34291
|
git3,
|
|
34037
|
-
async () => parseGitWorktreePorcelain(await
|
|
34292
|
+
async () => parseGitWorktreePorcelain(await git3(["worktree", "list", "--porcelain"])),
|
|
34038
34293
|
pathExists
|
|
34039
34294
|
);
|
|
34040
34295
|
if (removal.status === "failed") {
|
|
@@ -41393,11 +41648,29 @@ async function runRepoIndexSearchCommand(query, o, defaultMode) {
|
|
|
41393
41648
|
const useLocal = o.local === true && o.cloud !== true;
|
|
41394
41649
|
if (useLocal) {
|
|
41395
41650
|
const root = await repoRoot();
|
|
41396
|
-
|
|
41397
|
-
if (!
|
|
41651
|
+
const refreshed = refreshRepoIndex(root, inferRepoSlug(root));
|
|
41652
|
+
if (!refreshed.projection) {
|
|
41653
|
+
const receipt = {
|
|
41654
|
+
ok: false,
|
|
41655
|
+
source: "local",
|
|
41656
|
+
repo: inferRepoSlug(root),
|
|
41657
|
+
query,
|
|
41658
|
+
hits: [],
|
|
41659
|
+
status: refreshed.status,
|
|
41660
|
+
reason: refreshed.reason ?? "local index refresh failed"
|
|
41661
|
+
};
|
|
41662
|
+
if (o.json) consoleIo.log(JSON.stringify(receipt, null, 2));
|
|
41663
|
+
else console.error(`repo-index: local index stale \u2014 ${receipt.reason}`);
|
|
41664
|
+
return await cleanExit(1);
|
|
41665
|
+
}
|
|
41666
|
+
const idx = refreshed.projection;
|
|
41398
41667
|
const hits = searchRepoIndex(idx, query, limit);
|
|
41668
|
+
try {
|
|
41669
|
+
recordLocalRepoIndexQueryUsage(root);
|
|
41670
|
+
} catch {
|
|
41671
|
+
}
|
|
41399
41672
|
if (o.json) {
|
|
41400
|
-
consoleIo.log(JSON.stringify({ ok: true, source: "local", repo: idx.repo, query, hits }, null, 2));
|
|
41673
|
+
consoleIo.log(JSON.stringify({ ok: true, source: "local", repo: idx.repo, query, hits, status: refreshed.status }, null, 2));
|
|
41401
41674
|
return;
|
|
41402
41675
|
}
|
|
41403
41676
|
if (hits.length === 0) {
|
|
@@ -41644,7 +41917,10 @@ repoIndex.command("sync-estate").description("Hub indexer: publish a pushed comm
|
|
|
41644
41917
|
for (const f of res.failed) {
|
|
41645
41918
|
console.error(`repo-index: FAILED ${f.repo}: ${f.error}`);
|
|
41646
41919
|
}
|
|
41647
|
-
|
|
41920
|
+
console.log(`repo-index: estate reconcile receipt \u2014 ${res.receipt.changed} changed, ${res.receipt.skipped} skipped, ${res.receipt.failed} failed`);
|
|
41921
|
+
if (!res.ok) {
|
|
41922
|
+
await failGraceful(`sync-estate incomplete (${res.failed.length} failed): ${formatEstateRunFailureSummary(res.failed)}`);
|
|
41923
|
+
}
|
|
41648
41924
|
} catch (e) {
|
|
41649
41925
|
await failGraceful(e.message);
|
|
41650
41926
|
}
|
|
@@ -43487,7 +43763,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
43487
43763
|
})) {
|
|
43488
43764
|
console.error(line);
|
|
43489
43765
|
}
|
|
43490
|
-
process.exitCode = postMergeReconExitCode({ boardAdvance, crossRepoFilingIssue }) ?? process.exitCode;
|
|
43766
|
+
process.exitCode = prMergeLocalCleanupExitCode(localCleanup) ?? postMergeReconExitCode({ boardAdvance, crossRepoFilingIssue }) ?? process.exitCode;
|
|
43491
43767
|
});
|
|
43492
43768
|
registerQueryCommands(program2);
|
|
43493
43769
|
registerIssueLifecycleCommands(program2, { attach: attachToProject });
|
package/package.json
CHANGED