@mutmutco/cli 4.0.10 → 4.0.12
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 +196 -30
- package/dist/repo-index-v4.cjs +31 -8
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -10827,10 +10827,10 @@ var rollout_plan_default = {
|
|
|
10827
10827
|
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)."
|
|
10828
10828
|
},
|
|
10829
10829
|
baseline: {
|
|
10830
|
-
version: "4.0.
|
|
10831
|
-
tag: "v4.0.
|
|
10832
|
-
commit: "
|
|
10833
|
-
npm: "@mutmutco/cli@4.0.
|
|
10830
|
+
version: "4.0.12",
|
|
10831
|
+
tag: "v4.0.12",
|
|
10832
|
+
commit: "0ead67177a73",
|
|
10833
|
+
npm: "@mutmutco/cli@4.0.12"
|
|
10834
10834
|
},
|
|
10835
10835
|
exitCriterion: "fleet-n-of-n",
|
|
10836
10836
|
hubOnlyShortcut: "forbidden",
|
|
@@ -10847,14 +10847,14 @@ var rollout_plan_default = {
|
|
|
10847
10847
|
repo: "mutmutco/mmi-hub",
|
|
10848
10848
|
role: "canary",
|
|
10849
10849
|
schedule: "train",
|
|
10850
|
-
v3Target: "v4.0.
|
|
10850
|
+
v3Target: "v4.0.12"
|
|
10851
10851
|
}
|
|
10852
10852
|
],
|
|
10853
10853
|
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.",
|
|
10854
10854
|
rollback: {
|
|
10855
10855
|
independent: true,
|
|
10856
|
-
mechanism: "npm dist-tag latest -> 4.0.
|
|
10857
|
-
v3Target: "v4.0.
|
|
10856
|
+
mechanism: "npm dist-tag latest -> 4.0.12 and redeploy the Hub Lambda from tag v4.0.12 (0ead67177a73); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
10857
|
+
v3Target: "v4.0.12 (@mutmutco/cli@4.0.12, tag commit 0ead67177a73 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
10858
10858
|
}
|
|
10859
10859
|
},
|
|
10860
10860
|
{
|
|
@@ -17552,6 +17552,12 @@ function evaluateClosingGuard(input, opts) {
|
|
|
17552
17552
|
};
|
|
17553
17553
|
}
|
|
17554
17554
|
const closedSkipMessage = skippedClosed.length ? alreadyClosedCommitClosingMessage(skippedClosed, opts.context) : void 0;
|
|
17555
|
+
const unregistered = findClaimedButUnregisteredClosings(input.text, input.closing, input.alreadyClosed);
|
|
17556
|
+
if (unregistered.length) {
|
|
17557
|
+
const base = claimedClosingWarning(unregistered, opts.context);
|
|
17558
|
+
return closedSkipMessage ? { blocked: false, message: `${base}
|
|
17559
|
+
${closedSkipMessage}` } : { blocked: false, message: base };
|
|
17560
|
+
}
|
|
17555
17561
|
if (input.closing.length === 0) {
|
|
17556
17562
|
return closedSkipMessage ? { blocked: false, message: closedSkipMessage } : { blocked: false };
|
|
17557
17563
|
}
|
|
@@ -17575,9 +17581,25 @@ function evaluateClosingGuard(input, opts) {
|
|
|
17575
17581
|
}
|
|
17576
17582
|
return closedSkipMessage ? { blocked: false, message: closedSkipMessage } : { blocked: false };
|
|
17577
17583
|
}
|
|
17584
|
+
function findClaimedButUnregisteredClosings(text, closing, alreadyClosed = []) {
|
|
17585
|
+
const registered = new Set(closing);
|
|
17586
|
+
const closed = new Set(alreadyClosed);
|
|
17587
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
17588
|
+
for (const m of findClosingMentions(text)) {
|
|
17589
|
+
if (m.negated) continue;
|
|
17590
|
+
claimed.add(m.issue);
|
|
17591
|
+
}
|
|
17592
|
+
return [...claimed].filter((n) => !registered.has(n) && !closed.has(n));
|
|
17593
|
+
}
|
|
17594
|
+
function claimedClosingWarning(unregistered, context = "pr merge") {
|
|
17595
|
+
const named = unregistered.map((n) => `#${n}`).join(", ");
|
|
17596
|
+
const first = `#${unregistered[0] ?? "N"}`;
|
|
17597
|
+
return `${context}: WARNING \u2014 the PR text claims it will close ${named}, but GitHub has NOT registered ${named} as closing references, so the merge will NOT close ${first}. Link the issue in the PR's Development sidebar or keep the closing keyword where GitHub parses it (an already-closed issue is omitted silently \u2014 that case is fine). Otherwise ${first} stays open after the merge.`;
|
|
17598
|
+
}
|
|
17578
17599
|
async function withAlreadyClosedCommitTargets(input, fetchIssueState) {
|
|
17579
17600
|
if (!input) return void 0;
|
|
17580
|
-
const
|
|
17601
|
+
const unregistered = findClaimedButUnregisteredClosings(input.text, input.closing);
|
|
17602
|
+
const candidates = [.../* @__PURE__ */ new Set([...(input.commitClosing ?? []).filter((n) => !input.closing.includes(n)), ...unregistered])];
|
|
17581
17603
|
if (!candidates.length) return input;
|
|
17582
17604
|
const alreadyClosed = [];
|
|
17583
17605
|
await Promise.all(
|
|
@@ -17589,7 +17611,7 @@ async function withAlreadyClosedCommitTargets(input, fetchIssueState) {
|
|
|
17589
17611
|
}
|
|
17590
17612
|
})
|
|
17591
17613
|
);
|
|
17592
|
-
return alreadyClosed.length ? { ...input, alreadyClosed } : input;
|
|
17614
|
+
return alreadyClosed.length ? { ...input, alreadyClosed: [.../* @__PURE__ */ new Set([...input.alreadyClosed ?? [], ...alreadyClosed])] } : input;
|
|
17593
17615
|
}
|
|
17594
17616
|
|
|
17595
17617
|
// src/issue-comment.ts
|
|
@@ -23444,22 +23466,32 @@ ${chunk.blurb ?? ""}`;
|
|
|
23444
23466
|
}
|
|
23445
23467
|
}
|
|
23446
23468
|
var V4_EMBED_TIMEOUT_MS = 15 * 6e4;
|
|
23447
|
-
|
|
23448
|
-
|
|
23469
|
+
var V4_EMBED_RETRY = 1;
|
|
23470
|
+
function runEmbedderOnce(cwd, chunks, modelDirectory, createdAt) {
|
|
23471
|
+
if (!chunks.length) return { ok: true, embeddings: [] };
|
|
23449
23472
|
const orchestratorRunner = (0, import_node_path23.join)(process.cwd(), "repo-indexer", "src", "batch.mjs");
|
|
23450
23473
|
const targetRunner = (0, import_node_path23.join)(cwd, "repo-indexer", "src", "batch.mjs");
|
|
23451
23474
|
const file = (0, import_node_fs25.existsSync)(orchestratorRunner) ? orchestratorRunner : targetRunner;
|
|
23452
|
-
if (!(0, import_node_fs25.existsSync)(file)) return {
|
|
23475
|
+
if (!(0, import_node_fs25.existsSync)(file)) return { ok: false, reason: "embeddings-unavailable" };
|
|
23453
23476
|
const request = { texts: chunks.map((chunk) => ({ id: chunk.id, text: embeddingInput(cwd, chunk) })), maxBatch: V4_EMBED_BATCH };
|
|
23454
23477
|
const env = { ...process.env, ...modelDirectory ? { MMI_REPO_INDEXER_MODEL_DIR: modelDirectory } : {} };
|
|
23455
23478
|
const result = (0, import_node_child_process13.spawnSync)(process.execPath, [file], { input: JSON.stringify(request), encoding: "utf8", windowsHide: true, timeout: V4_EMBED_TIMEOUT_MS, maxBuffer: V4_MAX_ARTIFACT_BYTES, env });
|
|
23456
23479
|
if (result.error || result.status !== 0) {
|
|
23457
|
-
const
|
|
23458
|
-
|
|
23480
|
+
const cleanExit2 = result.error === void 0 && result.signal === void 0 && typeof result.status === "number" && result.status !== 0;
|
|
23481
|
+
let code;
|
|
23482
|
+
let stderrTail;
|
|
23483
|
+
if (cleanExit2) {
|
|
23484
|
+
try {
|
|
23485
|
+
code = JSON.parse(String(result.stdout))?.code;
|
|
23486
|
+
} catch {
|
|
23487
|
+
}
|
|
23488
|
+
stderrTail = String(result.stderr).trim().slice(-400) || void 0;
|
|
23489
|
+
}
|
|
23490
|
+
return { ok: false, code, stderrTail, cleanExit: cleanExit2, detail: result.error?.message ?? result.signal ?? `exit status ${result.status ?? "unknown"}` };
|
|
23459
23491
|
}
|
|
23460
23492
|
try {
|
|
23461
23493
|
const response = JSON.parse(result.stdout);
|
|
23462
|
-
if (!response.ok || !response.provenance || !Array.isArray(response.embeddings)) return {
|
|
23494
|
+
if (!response.ok || !response.provenance || !Array.isArray(response.embeddings)) return { ok: false, reason: "embeddings-unavailable" };
|
|
23463
23495
|
const provenance = {
|
|
23464
23496
|
provider: response.provenance.provider,
|
|
23465
23497
|
model: response.provenance.model,
|
|
@@ -23470,10 +23502,23 @@ function runEmbedder(cwd, chunks, modelDirectory, createdAt) {
|
|
|
23470
23502
|
};
|
|
23471
23503
|
const byId = new Map(response.embeddings.map((e) => [e.id, e.vector]));
|
|
23472
23504
|
const embeddings = chunks.map((chunk) => ({ chunkId: chunk.id, vector: byId.get(chunk.id), provenance })).filter((e) => Array.isArray(e.vector) && e.vector.length === provenance.dimensions && e.vector.every(Number.isFinite));
|
|
23473
|
-
return embeddings.length === chunks.length ? { embeddings } : {
|
|
23505
|
+
return embeddings.length === chunks.length ? { ok: true, embeddings } : { ok: false, reason: "partial-coverage" };
|
|
23474
23506
|
} catch {
|
|
23475
|
-
return {
|
|
23507
|
+
return { ok: false, reason: "embeddings-unavailable" };
|
|
23508
|
+
}
|
|
23509
|
+
}
|
|
23510
|
+
function runEmbedder(cwd, chunks, modelDirectory, createdAt) {
|
|
23511
|
+
if (!chunks.length) return { embeddings: [] };
|
|
23512
|
+
let last;
|
|
23513
|
+
for (let attempt = 1; attempt <= V4_EMBED_RETRY + 1; attempt++) {
|
|
23514
|
+
last = runEmbedderOnce(cwd, chunks, modelDirectory, createdAt);
|
|
23515
|
+
if (last.ok) return { embeddings: last.embeddings, reason: last.reason };
|
|
23516
|
+
if (attempt > V4_EMBED_RETRY || last.cleanExit !== true) break;
|
|
23476
23517
|
}
|
|
23518
|
+
const runner = last;
|
|
23519
|
+
const code = runner.code ? ` code=${runner.code}` : "";
|
|
23520
|
+
const stderr = runner.stderrTail ? ` stderr=${runner.stderrTail}` : "";
|
|
23521
|
+
throw new Error(`repo-index v4 embedding runner failed: ${runner.detail ?? "unknown"}${code}${stderr}`);
|
|
23477
23522
|
}
|
|
23478
23523
|
async function buildRepoIndexV4(cwd, repo, opts = {}) {
|
|
23479
23524
|
const { commit, defaultBranch, createdAt } = gitInfo(cwd);
|
|
@@ -23579,6 +23624,7 @@ function shardRepoIndexV4(envelope) {
|
|
|
23579
23624
|
|
|
23580
23625
|
// src/repo-index-cloud-client.ts
|
|
23581
23626
|
var RETRY_ATTEMPTS2 = 3;
|
|
23627
|
+
var WARMUP_MAX_PASSES = 3;
|
|
23582
23628
|
async function probeRepoIndexV4ReadinessCloud(queries, deps) {
|
|
23583
23629
|
if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
|
|
23584
23630
|
const token = await deps.token();
|
|
@@ -23586,6 +23632,7 @@ async function probeRepoIndexV4ReadinessCloud(queries, deps) {
|
|
|
23586
23632
|
const baseUrl = deps.baseUrl.replace(/\/$/, "");
|
|
23587
23633
|
const headers = { ...clientVersionHeaders(), Authorization: ["Bearer", token].join(" "), "content-type": "application/json" };
|
|
23588
23634
|
const shadowPass = async (readinessProbe) => {
|
|
23635
|
+
const ms = {};
|
|
23589
23636
|
for (const query of queries) {
|
|
23590
23637
|
const res = await fetchWithRetry(deps.fetch ?? fetch, `${baseUrl}/repo-index/v4/shadow`, {
|
|
23591
23638
|
method: "POST",
|
|
@@ -23594,12 +23641,18 @@ async function probeRepoIndexV4ReadinessCloud(queries, deps) {
|
|
|
23594
23641
|
}, { attempts: RETRY_ATTEMPTS2, timeoutMs: 12e4, sleep: deps.retrySleep });
|
|
23595
23642
|
const body = await res.json().catch(() => ({}));
|
|
23596
23643
|
if (!res.ok) return { ok: false, error: `${query.id}: ${body.error ?? `v4 readiness probe HTTP ${res.status}`}${body.errorClass ? ` (${body.errorClass})` : ""}`, status: res.status };
|
|
23644
|
+
if (typeof body.v4?.ms === "number") ms[query.id] = body.v4.ms;
|
|
23597
23645
|
}
|
|
23598
|
-
return { ok: true };
|
|
23646
|
+
return { ok: true, ms };
|
|
23599
23647
|
};
|
|
23648
|
+
const steady = (ms) => queries.every((query) => query.latencyMs === void 0 || ms[query.id] === void 0 || ms[query.id] <= query.latencyMs);
|
|
23600
23649
|
try {
|
|
23601
|
-
|
|
23650
|
+
let warmup = await shadowPass(false);
|
|
23602
23651
|
if (!warmup.ok) return warmup;
|
|
23652
|
+
for (let pass = 2; pass <= WARMUP_MAX_PASSES && !steady(warmup.ms); pass++) {
|
|
23653
|
+
warmup = await shadowPass(false);
|
|
23654
|
+
if (!warmup.ok) return warmup;
|
|
23655
|
+
}
|
|
23603
23656
|
const verdict = await shadowPass(true);
|
|
23604
23657
|
if (!verdict.ok) return verdict;
|
|
23605
23658
|
const res = await fetchWithRetry(deps.fetch ?? fetch, `${baseUrl}/repo-index/status`, {
|
|
@@ -24211,15 +24264,21 @@ function formatRepoIndexCloudStatus(status) {
|
|
|
24211
24264
|
return `repo-index: cloud ${repo} v4 ${state}${commit}; ${readiness}`;
|
|
24212
24265
|
}
|
|
24213
24266
|
function defaultV4ReadinessSuite() {
|
|
24214
|
-
const
|
|
24215
|
-
const
|
|
24216
|
-
|
|
24267
|
+
const raw = repo_index_golden_queries_v4_default;
|
|
24268
|
+
const queries = (raw.queries ?? []).map(({ id, query, mode, latency }) => ({
|
|
24269
|
+
id,
|
|
24270
|
+
query,
|
|
24271
|
+
mode,
|
|
24272
|
+
...typeof latency?.p95Ms === "number" ? { latencyMs: latency.p95Ms } : {}
|
|
24273
|
+
}));
|
|
24274
|
+
const modes = new Set(queries.map((query) => query.mode));
|
|
24275
|
+
if (raw.schemaVersion !== 4 || !Array.isArray(raw.queries) || !["lexical", "semantic", "hybrid"].every((mode) => modes.has(mode))) {
|
|
24217
24276
|
throw new Error("invalid bundled repo-index-golden-queries-v4.json");
|
|
24218
24277
|
}
|
|
24219
|
-
return
|
|
24278
|
+
return { schemaVersion: 4, queries };
|
|
24220
24279
|
}
|
|
24221
24280
|
async function runRepoIndexV4ReadinessGate(opts) {
|
|
24222
|
-
const queries = opts.suite.queries.map(({ id, query, mode }) => ({ id, query, mode }));
|
|
24281
|
+
const queries = opts.suite.queries.map(({ id, query, mode, latencyMs }) => ({ id, query, mode, latencyMs }));
|
|
24223
24282
|
const probed = await opts.probe(queries);
|
|
24224
24283
|
if (!probed.ok) return { ok: false, findings: [{ ok: false, code: "v4-probe-error", detail: probed.error }] };
|
|
24225
24284
|
const ok = probed.readiness.verdict === "ready";
|
|
@@ -29480,9 +29539,25 @@ async function advanceClosedIssuesToDone(deps) {
|
|
|
29480
29539
|
const issue2 = `#${ref.number}`;
|
|
29481
29540
|
try {
|
|
29482
29541
|
const outcome = await deps.moveIssueToDone(issue2);
|
|
29483
|
-
|
|
29484
|
-
|
|
29485
|
-
|
|
29542
|
+
if (!outcome.moved) {
|
|
29543
|
+
entries.push({ issue: issue2, moved: false, status: "failed", error: outcome.error });
|
|
29544
|
+
continue;
|
|
29545
|
+
}
|
|
29546
|
+
if (deps.readIssueStatus) {
|
|
29547
|
+
const verified = await deps.readIssueStatus(issue2);
|
|
29548
|
+
if ("error" in verified) {
|
|
29549
|
+
entries.push({ issue: issue2, moved: true, status: "moved", error: `advanced but could not verify on the board (${verified.error})` });
|
|
29550
|
+
} else if (verified.status !== "Done") {
|
|
29551
|
+
entries.push({
|
|
29552
|
+
issue: issue2,
|
|
29553
|
+
moved: false,
|
|
29554
|
+
status: "failed",
|
|
29555
|
+
error: `advanced to Done but the board reports ${verified.status} \u2014 left behind; move it to Done by hand`
|
|
29556
|
+
});
|
|
29557
|
+
continue;
|
|
29558
|
+
}
|
|
29559
|
+
}
|
|
29560
|
+
entries.push({ issue: issue2, moved: true, status: "moved" });
|
|
29486
29561
|
} catch (e) {
|
|
29487
29562
|
entries.push({ issue: issue2, moved: false, status: "failed", error: e.message });
|
|
29488
29563
|
}
|
|
@@ -29559,6 +29634,20 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
29559
29634
|
moveIssueToDone: async (ref) => {
|
|
29560
29635
|
const moved = await moveBoardItem({ config: await loadConfigForBoardSelector2(ref, repoOption), selector: ref, status: "Done", repo: repoOption, allowPartial: true });
|
|
29561
29636
|
return moved.partial ? { moved: false, error: moved.warning } : { moved: true };
|
|
29637
|
+
},
|
|
29638
|
+
// #5317: after a successful move, re-read the board to verify Done actually holds — a move the API
|
|
29639
|
+
// reported but the board did not reflect is named as left behind, never silently dropped.
|
|
29640
|
+
readIssueStatus: async (ref) => {
|
|
29641
|
+
const number = Number(ref.replace(/^#/, ""));
|
|
29642
|
+
if (!Number.isInteger(number) || number <= 0 || !repo) return { error: `unusable ref ${ref}` };
|
|
29643
|
+
try {
|
|
29644
|
+
const board = await readBoard({ config: await loadConfigForBoardSelector2(ref, repoOption), repo: repoOption });
|
|
29645
|
+
const buckets = [...board.primary.userOwned, ...board.primary.claimable, ...board.primary.taken, ...board.primary.unownedInFlight, ...board.secondary.userOwned, ...board.secondary.claimable, ...board.secondary.taken, ...board.secondary.unownedInFlight];
|
|
29646
|
+
const item = buckets.find((candidate2) => candidate2.repository.toLowerCase() === repo.toLowerCase() && candidate2.number === number);
|
|
29647
|
+
return item ? { status: item.status } : { error: `issue ${ref} not found on the board after advance` };
|
|
29648
|
+
} catch (e) {
|
|
29649
|
+
return { error: e.message };
|
|
29650
|
+
}
|
|
29562
29651
|
}
|
|
29563
29652
|
});
|
|
29564
29653
|
}
|
|
@@ -35638,6 +35727,34 @@ ${pr2.body ?? ""}`;
|
|
|
35638
35727
|
}
|
|
35639
35728
|
}
|
|
35640
35729
|
|
|
35730
|
+
// src/pr-deploy-dev.ts
|
|
35731
|
+
function slugOfRepo(repo) {
|
|
35732
|
+
return repo.split("/")[1]?.toLowerCase() ?? "";
|
|
35733
|
+
}
|
|
35734
|
+
async function planDevDeployOnDevelopmentMerge(repo, baseRef, deps) {
|
|
35735
|
+
if (baseRef !== "development") return { applicable: false, reason: "not-development-base" };
|
|
35736
|
+
const slug = slugOfRepo(repo);
|
|
35737
|
+
if (!slug) return { applicable: false, reason: "unreadable" };
|
|
35738
|
+
let project2;
|
|
35739
|
+
try {
|
|
35740
|
+
project2 = await fetchProjectBySlug(slug, deps);
|
|
35741
|
+
} catch {
|
|
35742
|
+
return { applicable: false, reason: "unreadable" };
|
|
35743
|
+
}
|
|
35744
|
+
const model = project2?.deployModel;
|
|
35745
|
+
if (!model || !isCentralDispatchModel(model)) return { applicable: false, reason: "not-tenant" };
|
|
35746
|
+
return {
|
|
35747
|
+
applicable: true,
|
|
35748
|
+
// The issue's fallback ask, verbatim-shaped: the gap must be visible and actionable at the moment it opens.
|
|
35749
|
+
manualPointer: `dev not redeployed \u2014 run \`mmi-cli devops runtime tenant redeploy ${repo} dev\``
|
|
35750
|
+
};
|
|
35751
|
+
}
|
|
35752
|
+
async function dispatchDevDeploy(repo, deps) {
|
|
35753
|
+
const res = await tenantDeploy({ repo, stage: "dev" }, deps);
|
|
35754
|
+
if (res.ok) return { ok: true };
|
|
35755
|
+
return { ok: false, detail: res.body?.error ?? res.error ?? `HTTP ${res.status}` };
|
|
35756
|
+
}
|
|
35757
|
+
|
|
35641
35758
|
// src/index.ts
|
|
35642
35759
|
var execFileGitRun = async (file, args) => (await execFileP2(file, args, { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
35643
35760
|
async function githubRepoReachProbe() {
|
|
@@ -37107,6 +37224,43 @@ ${filelessTransitionGuide(target, o.stage)}`
|
|
|
37107
37224
|
const res = await setDeployCoords(slug, body, registryClientDeps(cfg));
|
|
37108
37225
|
return reportWrite("org project set-deploy", res);
|
|
37109
37226
|
});
|
|
37227
|
+
var projectTenantTasks = project.command("tenant-tasks").description("project-admin self-declare of tenant tasks for your own repo's dev/rc stages (#5316, #5308) \u2014 new-task-only, dev/rc-only; editing an existing task or declaring a main-stage task stays master-only (`org project set --var tenantTasks=...`)");
|
|
37228
|
+
projectTenantTasks.command("declare <name>").description('register a NEW tenant task \u2014 becomes runnable via `runtime tenant control \u2026 run-task` on dev/rc. Body shape {service,command[],stages[],timeoutSeconds?,artifact?} \u2014 stages limited to dev/rc here; artifact "required" tasks must carry {artifact} in the command argv.').option("--service <name>", "service in the tenant runtime the task runs in (required unless --var carries it)").option("--command <argv...>", "task command as argv (repeatable; required unless --var carries it)").option("--stages <list>", "comma-separated dev,rc stages this task may run on (required unless --var carries it)").option("--timeout-seconds <n>", "optional wall-clock ceiling 1..3600").option("--artifact <none|required>", "artifact contract (default none; required tasks must contain {artifact} in argv)").option("--var <json>", "full task object {service,command[],stages[],timeoutSeconds?,artifact?} \u2014 use instead of the individual flags").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action(async (name, o) => {
|
|
37229
|
+
const cfg = await loadConfig();
|
|
37230
|
+
let target;
|
|
37231
|
+
try {
|
|
37232
|
+
target = await projectTarget("org project tenant-tasks declare", o.repo);
|
|
37233
|
+
} catch (e) {
|
|
37234
|
+
return fail(e.message);
|
|
37235
|
+
}
|
|
37236
|
+
const slug = slugOf(target);
|
|
37237
|
+
let task;
|
|
37238
|
+
if (o.var) {
|
|
37239
|
+
try {
|
|
37240
|
+
task = JSON.parse(o.var);
|
|
37241
|
+
} catch {
|
|
37242
|
+
return fail("org project tenant-tasks declare: --var must be valid JSON");
|
|
37243
|
+
}
|
|
37244
|
+
} else {
|
|
37245
|
+
if (!o.service || !o.command?.length || !o.stages) {
|
|
37246
|
+
return fail("org project tenant-tasks declare: pass --var <json> or all of --service, --command, --stages");
|
|
37247
|
+
}
|
|
37248
|
+
task = {
|
|
37249
|
+
service: o.service,
|
|
37250
|
+
command: o.command,
|
|
37251
|
+
stages: o.stages.split(",").map((s) => s.trim()).filter(Boolean),
|
|
37252
|
+
...o.timeoutSeconds !== void 0 ? { timeoutSeconds: Number(o.timeoutSeconds) } : {},
|
|
37253
|
+
...o.artifact !== void 0 ? { artifact: o.artifact } : {}
|
|
37254
|
+
};
|
|
37255
|
+
}
|
|
37256
|
+
const res = await postJson(`/projects/${encodeURIComponent(slug)}/tenant-tasks`, { repo: target, tenantTasks: { [name]: task } }, registryClientDeps(cfg), "POST", { noRetry: true });
|
|
37257
|
+
if (o.json) {
|
|
37258
|
+
console.log(JSON.stringify(res, null, 2));
|
|
37259
|
+
} else {
|
|
37260
|
+
reportWrite("org project tenant-tasks declare", res);
|
|
37261
|
+
}
|
|
37262
|
+
if (!res.ok) process.exitCode = 1;
|
|
37263
|
+
});
|
|
37110
37264
|
var registry = program2.command("registry").description("the DDB org registry \u2014 org-level constants");
|
|
37111
37265
|
registry.command("org").description("the org config (account id, region, orgProjectId, sagaApiUrl)").option("--json", "machine-readable output").action(async (_o) => {
|
|
37112
37266
|
const cfg = await loadConfig();
|
|
@@ -38000,11 +38154,16 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
38000
38154
|
const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
|
|
38001
38155
|
const repoArgs = o.repo ? ["--repo", o.repo] : [];
|
|
38002
38156
|
const repoForPostCleanup = await resolveRepo(o.repo) ?? o.repo;
|
|
38003
|
-
const
|
|
38157
|
+
const prMeta = await execFileP2("gh", ["pr", "view", number, ...repoArgs, "--json", "headRefName,baseRefName", "--jq", "{head: .headRefName, base: .baseRefName}"], { timeout: GC_GH_TIMEOUT_MS }).then((r) => JSON.parse(r.stdout)).catch(async (e) => {
|
|
38004
38158
|
if (!isGitHubRateLimitError(e) || !repoForPostCleanup) throw e;
|
|
38005
38159
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 reading PR #${number} via REST instead (#4588).`);
|
|
38006
|
-
|
|
38160
|
+
const snapshot = await fetchRestPrSnapshot(number, repoForPostCleanup);
|
|
38161
|
+
return { head: snapshot.headRef, base: snapshot.baseRef };
|
|
38007
38162
|
});
|
|
38163
|
+
const headRef = prMeta.head;
|
|
38164
|
+
const baseRef = prMeta.base;
|
|
38165
|
+
const devDeployDeps = repoForPostCleanup ? registryClientDeps(await loadConfig()) : void 0;
|
|
38166
|
+
const devDeployPlan = repoForPostCleanup && devDeployDeps ? await planDevDeployOnDevelopmentMerge(repoForPostCleanup, baseRef, devDeployDeps).catch(() => ({ applicable: false, reason: "unreadable" })) : { applicable: false, reason: "unreadable" };
|
|
38008
38167
|
const closingGuardInput = await withAlreadyClosedCommitTargets(
|
|
38009
38168
|
await readClosingGuardInput(number, repoArgs, repoForPostCleanup, "pr merge"),
|
|
38010
38169
|
async (n) => {
|
|
@@ -38133,6 +38292,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
38133
38292
|
const enqueued = describePrMergeEnqueuedReason(await pollGhPrChecks(number, repoArgs).catch(() => void 0));
|
|
38134
38293
|
console.log(JSON.stringify({ mergeStatus: "auto-merge-enqueued", enqueuedReason: enqueued.reason, pr: number, branch: headRef, state: state || "unknown", upgradedToAuto: upgradedToAuto || void 0 }));
|
|
38135
38294
|
console.warn(`pr merge: PR #${number} is ENQUEUED, not merged \u2014 ${enqueued.message}.`);
|
|
38295
|
+
if (devDeployPlan.applicable) console.warn(`pr merge: ${devDeployPlan.manualPointer} once the merge lands (auto-merge does not redeploy the dev stage).`);
|
|
38136
38296
|
if (upgradedToAuto && !o.auto) {
|
|
38137
38297
|
console.warn(`pr merge: exiting ${PR_MERGE_ENQUEUED_EXIT_CODE} so a chained command does not treat this as a completed merge.`);
|
|
38138
38298
|
process.exitCode = PR_MERGE_ENQUEUED_EXIT_CODE;
|
|
@@ -38156,6 +38316,8 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
38156
38316
|
const boardAdvance = await advanceClosedIssuesToDone2(number, repoForPostCleanup);
|
|
38157
38317
|
const crossRepoFilingIssue = repoForPostCleanup ? await reconcileCrossRepoFilingIssue(defaultGitHubClient(), repoForPostCleanup, Number(number)) : { status: "failed", error: "could not resolve the PR repo for cross-repo filing-issue reconciliation" };
|
|
38158
38318
|
invalidateStatuslineBoardCache();
|
|
38319
|
+
const devDeploy = devDeployPlan.applicable && devDeployDeps && remoteNotAttemptedReason !== "pr-already-merged" ? await dispatchDevDeploy(repoForPostCleanup, devDeployDeps) : void 0;
|
|
38320
|
+
const devDeployManual = devDeployPlan.applicable ? devDeployPlan.manualPointer : void 0;
|
|
38159
38321
|
console.log(JSON.stringify({
|
|
38160
38322
|
mergeStatus: "merged",
|
|
38161
38323
|
merged: number,
|
|
@@ -38167,10 +38329,14 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
38167
38329
|
// tell "merged clean" from "merged, but the board did not follow" without guessing from the exit code.
|
|
38168
38330
|
...boardAdvance.entries.length ? { boardAdvance: boardAdvance.entries } : {},
|
|
38169
38331
|
boardAdvanceStatus: boardAdvance.status,
|
|
38170
|
-
...crossRepoFilingIssue.status !== "not-applicable" ? { crossRepoFilingIssue } : {}
|
|
38332
|
+
...crossRepoFilingIssue.status !== "not-applicable" ? { crossRepoFilingIssue } : {},
|
|
38333
|
+
...devDeploy ? { devDeploy: devDeploy.ok ? { dispatched: true } : { dispatched: false, manual: devDeployManual } } : {}
|
|
38171
38334
|
}));
|
|
38172
38335
|
const boardAdvanceMessage = boardAdvanceFailureMessage(boardAdvance);
|
|
38173
38336
|
if (boardAdvanceMessage) console.error(boardAdvanceMessage);
|
|
38337
|
+
if (devDeploy && !devDeploy.ok) {
|
|
38338
|
+
console.error(`pr merge: ${devDeployManual} (${devDeploy.detail})`);
|
|
38339
|
+
}
|
|
38174
38340
|
if (crossRepoFilingIssue.status === "failed") {
|
|
38175
38341
|
console.error(`pr merge: cross-repo filing-issue reconciliation failed (${crossRepoFilingIssue.error}) \u2014 the PR MERGED, but a foreign filing issue may remain open.`);
|
|
38176
38342
|
}
|
package/dist/repo-index-v4.cjs
CHANGED
|
@@ -439,22 +439,32 @@ ${chunk.blurb ?? ""}`;
|
|
|
439
439
|
}
|
|
440
440
|
}
|
|
441
441
|
var V4_EMBED_TIMEOUT_MS = 15 * 6e4;
|
|
442
|
-
|
|
443
|
-
|
|
442
|
+
var V4_EMBED_RETRY = 1;
|
|
443
|
+
function runEmbedderOnce(cwd, chunks, modelDirectory, createdAt) {
|
|
444
|
+
if (!chunks.length) return { ok: true, embeddings: [] };
|
|
444
445
|
const orchestratorRunner = (0, import_node_path4.join)(process.cwd(), "repo-indexer", "src", "batch.mjs");
|
|
445
446
|
const targetRunner = (0, import_node_path4.join)(cwd, "repo-indexer", "src", "batch.mjs");
|
|
446
447
|
const file = (0, import_node_fs3.existsSync)(orchestratorRunner) ? orchestratorRunner : targetRunner;
|
|
447
|
-
if (!(0, import_node_fs3.existsSync)(file)) return {
|
|
448
|
+
if (!(0, import_node_fs3.existsSync)(file)) return { ok: false, reason: "embeddings-unavailable" };
|
|
448
449
|
const request = { texts: chunks.map((chunk) => ({ id: chunk.id, text: embeddingInput(cwd, chunk) })), maxBatch: V4_EMBED_BATCH };
|
|
449
450
|
const env = { ...process.env, ...modelDirectory ? { MMI_REPO_INDEXER_MODEL_DIR: modelDirectory } : {} };
|
|
450
451
|
const result = (0, import_node_child_process3.spawnSync)(process.execPath, [file], { input: JSON.stringify(request), encoding: "utf8", windowsHide: true, timeout: V4_EMBED_TIMEOUT_MS, maxBuffer: V4_MAX_ARTIFACT_BYTES, env });
|
|
451
452
|
if (result.error || result.status !== 0) {
|
|
452
|
-
const
|
|
453
|
-
|
|
453
|
+
const cleanExit = result.error === void 0 && result.signal === void 0 && typeof result.status === "number" && result.status !== 0;
|
|
454
|
+
let code;
|
|
455
|
+
let stderrTail;
|
|
456
|
+
if (cleanExit) {
|
|
457
|
+
try {
|
|
458
|
+
code = JSON.parse(String(result.stdout))?.code;
|
|
459
|
+
} catch {
|
|
460
|
+
}
|
|
461
|
+
stderrTail = String(result.stderr).trim().slice(-400) || void 0;
|
|
462
|
+
}
|
|
463
|
+
return { ok: false, code, stderrTail, cleanExit, detail: result.error?.message ?? result.signal ?? `exit status ${result.status ?? "unknown"}` };
|
|
454
464
|
}
|
|
455
465
|
try {
|
|
456
466
|
const response = JSON.parse(result.stdout);
|
|
457
|
-
if (!response.ok || !response.provenance || !Array.isArray(response.embeddings)) return {
|
|
467
|
+
if (!response.ok || !response.provenance || !Array.isArray(response.embeddings)) return { ok: false, reason: "embeddings-unavailable" };
|
|
458
468
|
const provenance = {
|
|
459
469
|
provider: response.provenance.provider,
|
|
460
470
|
model: response.provenance.model,
|
|
@@ -465,10 +475,23 @@ function runEmbedder(cwd, chunks, modelDirectory, createdAt) {
|
|
|
465
475
|
};
|
|
466
476
|
const byId = new Map(response.embeddings.map((e) => [e.id, e.vector]));
|
|
467
477
|
const embeddings = chunks.map((chunk) => ({ chunkId: chunk.id, vector: byId.get(chunk.id), provenance })).filter((e) => Array.isArray(e.vector) && e.vector.length === provenance.dimensions && e.vector.every(Number.isFinite));
|
|
468
|
-
return embeddings.length === chunks.length ? { embeddings } : {
|
|
478
|
+
return embeddings.length === chunks.length ? { ok: true, embeddings } : { ok: false, reason: "partial-coverage" };
|
|
469
479
|
} catch {
|
|
470
|
-
return {
|
|
480
|
+
return { ok: false, reason: "embeddings-unavailable" };
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function runEmbedder(cwd, chunks, modelDirectory, createdAt) {
|
|
484
|
+
if (!chunks.length) return { embeddings: [] };
|
|
485
|
+
let last;
|
|
486
|
+
for (let attempt = 1; attempt <= V4_EMBED_RETRY + 1; attempt++) {
|
|
487
|
+
last = runEmbedderOnce(cwd, chunks, modelDirectory, createdAt);
|
|
488
|
+
if (last.ok) return { embeddings: last.embeddings, reason: last.reason };
|
|
489
|
+
if (attempt > V4_EMBED_RETRY || last.cleanExit !== true) break;
|
|
471
490
|
}
|
|
491
|
+
const runner = last;
|
|
492
|
+
const code = runner.code ? ` code=${runner.code}` : "";
|
|
493
|
+
const stderr = runner.stderrTail ? ` stderr=${runner.stderrTail}` : "";
|
|
494
|
+
throw new Error(`repo-index v4 embedding runner failed: ${runner.detail ?? "unknown"}${code}${stderr}`);
|
|
472
495
|
}
|
|
473
496
|
async function buildRepoIndexV4(cwd, repo, opts = {}) {
|
|
474
497
|
const { commit, defaultBranch, createdAt } = gitInfo(cwd);
|
package/package.json
CHANGED