@mutmutco/cli 4.5.42 → 4.5.44
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 +553 -194
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -5496,8 +5496,8 @@ function resolveVerbAliasShim(argv) {
|
|
|
5496
5496
|
}
|
|
5497
5497
|
|
|
5498
5498
|
// src/command-composition.ts
|
|
5499
|
-
var
|
|
5500
|
-
var
|
|
5499
|
+
var import_node_os23 = require("node:os");
|
|
5500
|
+
var import_node_path52 = require("node:path");
|
|
5501
5501
|
|
|
5502
5502
|
// src/board-read.ts
|
|
5503
5503
|
var import_node_fs13 = require("node:fs");
|
|
@@ -7620,6 +7620,33 @@ function cadenceContainsCron(cadence, cron) {
|
|
|
7620
7620
|
if (!cronTokens.length || cronTokens.length > cadenceTokens.length) return false;
|
|
7621
7621
|
return cadenceTokens.some((_, i) => i + cronTokens.length <= cadenceTokens.length && cronTokens.every((t, j) => cadenceTokens[i + j] === t));
|
|
7622
7622
|
}
|
|
7623
|
+
var DECLARED_UNSCHEDULED = /* @__PURE__ */ new Set(["on demand", "on-demand", "manual", "manual only", "event-driven"]);
|
|
7624
|
+
var CRON_FIELD2 = /^[0-9A-Za-z*,/\-]+$/;
|
|
7625
|
+
var DOW_NAMES = /* @__PURE__ */ new Set(["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]);
|
|
7626
|
+
function dowOk(dow) {
|
|
7627
|
+
return dow.split(",").every((part) => {
|
|
7628
|
+
const slash2 = part.indexOf("/");
|
|
7629
|
+
const step = slash2 === -1 ? "" : part.slice(slash2);
|
|
7630
|
+
if (step !== "" && !/^\/[1-9]\d*$/.test(step)) return false;
|
|
7631
|
+
const tokens2 = (slash2 === -1 ? part : part.slice(0, slash2)).split("-");
|
|
7632
|
+
if (tokens2.length > 2 || tokens2.some((t) => t === "")) return false;
|
|
7633
|
+
return tokens2.every((t) => t === "*" || (/^\d+$/.test(t) ? Number(t) <= 7 : DOW_NAMES.has(t.toUpperCase())));
|
|
7634
|
+
});
|
|
7635
|
+
}
|
|
7636
|
+
function cadenceProblem(cadence) {
|
|
7637
|
+
const c = cadence.trim();
|
|
7638
|
+
const cut = c.search(/[(+]/);
|
|
7639
|
+
const lead = (cut === -1 ? c : c.slice(0, cut)).trim();
|
|
7640
|
+
if (DECLARED_UNSCHEDULED.has(lead.toLowerCase())) return null;
|
|
7641
|
+
if (/^((?:rate|cron)\([^()]+\))(?:\s*[(+].*)?$/is.test(c)) return null;
|
|
7642
|
+
const f = lead.split(/\s+/);
|
|
7643
|
+
if (f.length !== 5) return `cadence not mappable to a Scheduler expression: ${JSON.stringify(cadence)}`;
|
|
7644
|
+
const [minute, hour, dom, month, dow] = f;
|
|
7645
|
+
if (![minute, hour, dom, month].every((x) => CRON_FIELD2.test(x)) || dom !== "*" && dow !== "*" || !dowOk(dow)) {
|
|
7646
|
+
return `cadence not mappable to a Scheduler expression: ${JSON.stringify(cadence)}`;
|
|
7647
|
+
}
|
|
7648
|
+
return null;
|
|
7649
|
+
}
|
|
7623
7650
|
function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
|
|
7624
7651
|
const crons = extractWorkflowCrons(yamlText);
|
|
7625
7652
|
const header = parseScheduleHeader(yamlText);
|
|
@@ -7638,6 +7665,10 @@ function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
|
|
|
7638
7665
|
if (!SCHEDULE_EXECUTORS.has(h.executor)) {
|
|
7639
7666
|
throw new Error(`schedules register: ${expectedId} header \`executor: ${h.executor}\` is not a member of the closed executor taxonomy (${[...SCHEDULE_EXECUTORS].join(", ")}) \u2014 an unknown executor would register a row the harbour can never dispatch (docs/schedules.md "The entry template", H2 #4429).`);
|
|
7640
7667
|
}
|
|
7668
|
+
const problem = cadenceProblem(h.cadence);
|
|
7669
|
+
if (problem) {
|
|
7670
|
+
throw new Error(`schedules register: ${expectedId} ${problem} \u2014 lead with a 5-field cron, rate(...) or cron(...), then any note in (...) or after +; or declare one of: ${[...DECLARED_UNSCHEDULED].join(", ")} (docs/schedules.md "The entry template", #7415).`);
|
|
7671
|
+
}
|
|
7641
7672
|
for (const cron of crons) {
|
|
7642
7673
|
if (!cadenceContainsCron(h.cadence, cron)) {
|
|
7643
7674
|
throw new Error(`schedules register: ${expectedId} cadence \`${h.cadence}\` does not contain the workflow cron \`${cron}\` (whole-token comparison) \u2014 the declared cadence disagrees with the actual schedule.`);
|
|
@@ -14366,7 +14397,7 @@ function loadBootstrapSeeds(manifestJson) {
|
|
|
14366
14397
|
};
|
|
14367
14398
|
}
|
|
14368
14399
|
function renderSeed(template, vars) {
|
|
14369
|
-
return template.replace(PLACEHOLDER_RE, (match, key) => key in vars ? vars[key] : match);
|
|
14400
|
+
return template.replace(PLACEHOLDER_RE, (match, key) => key in vars ? vars[key].replace(PLACEHOLDER_RE, (inner, innerKey) => innerKey !== key && innerKey in vars ? vars[innerKey] : inner) : match);
|
|
14370
14401
|
}
|
|
14371
14402
|
function missingPlaceholders(rendered) {
|
|
14372
14403
|
const out = /* @__PURE__ */ new Set();
|
|
@@ -15370,7 +15401,10 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
|
|
|
15370
15401
|
GATE_MAX_SECONDS: DEFAULT_GATE_MAX_SECONDS,
|
|
15371
15402
|
// The pin is the CLI's blessed SHA, not an operator knob — one central place to bump (#3178).
|
|
15372
15403
|
GATE_BUDGET_SHA: BLESSED_RUN_WITH_BUDGET_SHA,
|
|
15373
|
-
GATE_RUNNER_SHA: BLESSED_RUNNER_GATE_SHA
|
|
15404
|
+
GATE_RUNNER_SHA: BLESSED_RUNNER_GATE_SHA,
|
|
15405
|
+
// #7407: repo-declared service containers and extra check steps; empty renders today's gate byte-for-byte.
|
|
15406
|
+
GATE_SERVICES_YAML: "",
|
|
15407
|
+
GATE_EXTRA_STEPS_YAML: ""
|
|
15374
15408
|
};
|
|
15375
15409
|
const track = releaseTrack ?? (cls === "content" ? "trunk" : "full");
|
|
15376
15410
|
const trackBranches = track === "trunk" ? ["main"] : track === "direct" ? ["development", "main"] : ["development", "rc", "main"];
|
|
@@ -15417,6 +15451,112 @@ function withDerivedRepoVars(vars, parsed, cls, releaseTrack, requiredCheckBranc
|
|
|
15417
15451
|
if (!out.GATE_AFFECTED_CMD.trim()) out.GATE_AFFECTED_CMD = out.GATE_CMD;
|
|
15418
15452
|
return out;
|
|
15419
15453
|
}
|
|
15454
|
+
var GATE_SERVICE_KEYS = /* @__PURE__ */ new Set(["image", "env", "ports", "options", "tmpfs"]);
|
|
15455
|
+
var GATE_STEP_KEYS = /* @__PURE__ */ new Set(["name", "command", "env", "maxSeconds"]);
|
|
15456
|
+
function gateLine(value, field) {
|
|
15457
|
+
if (typeof value !== "string" || !value.trim() || /[\r\n]/.test(value)) throw new Error(`${field} must be a non-empty single-line string`);
|
|
15458
|
+
return value.trim();
|
|
15459
|
+
}
|
|
15460
|
+
function gateEnv(value, field) {
|
|
15461
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${field} must be an object of NAME: "value" strings`);
|
|
15462
|
+
const out = {};
|
|
15463
|
+
for (const [name, entry] of Object.entries(value)) {
|
|
15464
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) throw new Error(`${field}.${name} is not a valid environment variable name`);
|
|
15465
|
+
if (typeof entry !== "string" || /[\r\n]/.test(entry)) throw new Error(`${field}.${name} must be a single-line string`);
|
|
15466
|
+
out[name] = entry;
|
|
15467
|
+
}
|
|
15468
|
+
return out;
|
|
15469
|
+
}
|
|
15470
|
+
function gateSeconds(value, field) {
|
|
15471
|
+
const text = typeof value === "number" ? String(value) : value;
|
|
15472
|
+
if (typeof text !== "string" || !/^\d+$/.test(text.trim()) || Number(text) <= 0) throw new Error(`${field} must be a positive whole number of seconds`);
|
|
15473
|
+
return text.trim();
|
|
15474
|
+
}
|
|
15475
|
+
function validateGateExtras(gate) {
|
|
15476
|
+
const out = {};
|
|
15477
|
+
if (gate.services !== void 0) {
|
|
15478
|
+
if (!gate.services || typeof gate.services !== "object" || Array.isArray(gate.services)) throw new Error("gate.services must be an object keyed by service name");
|
|
15479
|
+
out.services = {};
|
|
15480
|
+
for (const [name, raw] of Object.entries(gate.services)) {
|
|
15481
|
+
const field = `gate.services.${name}`;
|
|
15482
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`${field}: service name must be lowercase letters, digits, - or _`);
|
|
15483
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`${field} must be an object with an image`);
|
|
15484
|
+
const service = raw;
|
|
15485
|
+
for (const key of Object.keys(service)) if (!GATE_SERVICE_KEYS.has(key)) throw new Error(`${field}.${key} is not a service field (expected image/env/ports/options/tmpfs)`);
|
|
15486
|
+
const parsed = { image: gateLine(service.image, `${field}.image`) };
|
|
15487
|
+
if (service.env !== void 0) parsed.env = gateEnv(service.env, `${field}.env`);
|
|
15488
|
+
if (service.ports !== void 0) {
|
|
15489
|
+
if (!Array.isArray(service.ports) || service.ports.length === 0) throw new Error(`${field}.ports must be a non-empty array`);
|
|
15490
|
+
parsed.ports = service.ports.map((port, index) => {
|
|
15491
|
+
const ok = typeof port === "number" && Number.isInteger(port) && port > 0 && port < 65536 || typeof port === "string" && /^\d{1,5}(:\d{1,5})?$/.test(port);
|
|
15492
|
+
if (!ok) throw new Error(`${field}.ports[${index}] must be a port number or "host:container"`);
|
|
15493
|
+
return port;
|
|
15494
|
+
});
|
|
15495
|
+
}
|
|
15496
|
+
if (service.options !== void 0) parsed.options = gateLine(service.options, `${field}.options`);
|
|
15497
|
+
if (service.tmpfs !== void 0) {
|
|
15498
|
+
if (!Array.isArray(service.tmpfs) || service.tmpfs.length === 0) throw new Error(`${field}.tmpfs must be a non-empty array of paths`);
|
|
15499
|
+
parsed.tmpfs = service.tmpfs.map((path2, index) => gateLine(path2, `${field}.tmpfs[${index}]`));
|
|
15500
|
+
}
|
|
15501
|
+
out.services[name] = parsed;
|
|
15502
|
+
}
|
|
15503
|
+
}
|
|
15504
|
+
if (gate.steps !== void 0) {
|
|
15505
|
+
if (!Array.isArray(gate.steps)) throw new Error("gate.steps must be an array");
|
|
15506
|
+
out.steps = gate.steps.map((raw, index) => {
|
|
15507
|
+
const field = `gate.steps[${index}]`;
|
|
15508
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`${field} must be an object with name and command`);
|
|
15509
|
+
const step = raw;
|
|
15510
|
+
for (const key of Object.keys(step)) if (!GATE_STEP_KEYS.has(key)) throw new Error(`${field}.${key} is not a step field (expected name/command/env/maxSeconds)`);
|
|
15511
|
+
const parsed = { name: gateLine(step.name, `${field}.name`), command: gateLine(step.command, `${field}.command`) };
|
|
15512
|
+
if (step.env !== void 0) parsed.env = gateEnv(step.env, `${field}.env`);
|
|
15513
|
+
if (step.maxSeconds !== void 0) parsed.maxSeconds = gateSeconds(step.maxSeconds, `${field}.maxSeconds`);
|
|
15514
|
+
return parsed;
|
|
15515
|
+
});
|
|
15516
|
+
}
|
|
15517
|
+
return out;
|
|
15518
|
+
}
|
|
15519
|
+
function yamlScalar(value) {
|
|
15520
|
+
const plainSafe = /^[A-Za-z0-9_./$(][^\r\n]*$/.test(value) && !/: |\s#|[\s:]$/.test(value) && !/^(?:true|false|yes|no|on|off|null|~|[-+]?[\d.][\d_.eE+-]*)$/i.test(value);
|
|
15521
|
+
return plainSafe ? value : JSON.stringify(value);
|
|
15522
|
+
}
|
|
15523
|
+
function envYaml(env, indent) {
|
|
15524
|
+
return Object.entries(env).map(([name, value]) => `
|
|
15525
|
+
${indent}${name}: ${yamlScalar(value)}`).join("");
|
|
15526
|
+
}
|
|
15527
|
+
function gateServicesYaml(services) {
|
|
15528
|
+
let out = "\n services:";
|
|
15529
|
+
for (const [name, service] of Object.entries(services)) {
|
|
15530
|
+
out += `
|
|
15531
|
+
${name}:
|
|
15532
|
+
image: ${yamlScalar(service.image)}`;
|
|
15533
|
+
if (service.env && Object.keys(service.env).length) out += `
|
|
15534
|
+
env:${envYaml(service.env, " ")}`;
|
|
15535
|
+
if (service.ports) out += `
|
|
15536
|
+
ports:${service.ports.map((port) => `
|
|
15537
|
+
- ${typeof port === "number" ? port : yamlScalar(port)}`).join("")}`;
|
|
15538
|
+
const options = [...service.options ? [service.options] : [], ...(service.tmpfs ?? []).map((path2) => `--tmpfs ${path2}`)];
|
|
15539
|
+
if (options.length) out += `
|
|
15540
|
+
options: >-${options.map((line) => `
|
|
15541
|
+
${line}`).join("")}`;
|
|
15542
|
+
}
|
|
15543
|
+
return out;
|
|
15544
|
+
}
|
|
15545
|
+
function gateExtraStepsYaml(steps) {
|
|
15546
|
+
return steps.map((step) => [
|
|
15547
|
+
`
|
|
15548
|
+
- name: ${yamlScalar(step.name)}`,
|
|
15549
|
+
step.env && Object.keys(step.env).length ? `
|
|
15550
|
+
env:${envYaml(step.env, " ")}` : "",
|
|
15551
|
+
"\n uses: mutmutco/MMI-Hub/.github/actions/run-with-budget@{{GATE_BUDGET_SHA}}",
|
|
15552
|
+
"\n with:",
|
|
15553
|
+
`
|
|
15554
|
+
command: ${yamlScalar(step.command)}`,
|
|
15555
|
+
`
|
|
15556
|
+
max-seconds: ${step.maxSeconds ?? "{{GATE_MAX_SECONDS}}"}`,
|
|
15557
|
+
"\n working-directory: {{GATE_WORKDIR}}"
|
|
15558
|
+
].join("")).join("");
|
|
15559
|
+
}
|
|
15420
15560
|
function gateConfigToVars(gate) {
|
|
15421
15561
|
const out = {};
|
|
15422
15562
|
if (!gate || typeof gate !== "object") return out;
|
|
@@ -15428,6 +15568,9 @@ function gateConfigToVars(gate) {
|
|
|
15428
15568
|
if (typeof gate.pyVersion === "string" && gate.pyVersion.trim()) out.GATE_PY_VERSION = gate.pyVersion;
|
|
15429
15569
|
const seconds = typeof gate.maxSeconds === "number" ? String(gate.maxSeconds) : gate.maxSeconds;
|
|
15430
15570
|
if (typeof seconds === "string" && /^\d+$/.test(seconds.trim()) && Number(seconds) > 0) out.GATE_MAX_SECONDS = seconds.trim();
|
|
15571
|
+
const extras = validateGateExtras(gate);
|
|
15572
|
+
if (extras.services && Object.keys(extras.services).length) out.GATE_SERVICES_YAML = gateServicesYaml(extras.services);
|
|
15573
|
+
if (extras.steps?.length) out.GATE_EXTRA_STEPS_YAML = gateExtraStepsYaml(extras.steps);
|
|
15431
15574
|
return out;
|
|
15432
15575
|
}
|
|
15433
15576
|
function seedMatchesDeployModel(seed, deployModel) {
|
|
@@ -16871,10 +17014,10 @@ var rollout_plan_default = {
|
|
|
16871
17014
|
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)."
|
|
16872
17015
|
},
|
|
16873
17016
|
baseline: {
|
|
16874
|
-
version: "4.5.
|
|
16875
|
-
tag: "v4.5.
|
|
16876
|
-
commit: "
|
|
16877
|
-
npm: "@mutmutco/cli@4.5.
|
|
17017
|
+
version: "4.5.44",
|
|
17018
|
+
tag: "v4.5.44",
|
|
17019
|
+
commit: "dda7c3cc34f2",
|
|
17020
|
+
npm: "@mutmutco/cli@4.5.44"
|
|
16878
17021
|
},
|
|
16879
17022
|
exitCriterion: "fleet-n-of-n",
|
|
16880
17023
|
hubOnlyShortcut: "forbidden",
|
|
@@ -16891,14 +17034,14 @@ var rollout_plan_default = {
|
|
|
16891
17034
|
repo: "mutmutco/mmi-hub",
|
|
16892
17035
|
role: "canary",
|
|
16893
17036
|
schedule: "train",
|
|
16894
|
-
v3Target: "v4.5.
|
|
17037
|
+
v3Target: "v4.5.44"
|
|
16895
17038
|
}
|
|
16896
17039
|
],
|
|
16897
17040
|
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.",
|
|
16898
17041
|
rollback: {
|
|
16899
17042
|
independent: true,
|
|
16900
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
16901
|
-
v3Target: "v4.5.
|
|
17043
|
+
mechanism: "npm dist-tag latest -> 4.5.44 and redeploy the Hub Lambda from tag v4.5.44 (dda7c3cc34f2); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17044
|
+
v3Target: "v4.5.44 (@mutmutco/cli@4.5.44, tag commit dda7c3cc34f2 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
16902
17045
|
}
|
|
16903
17046
|
},
|
|
16904
17047
|
{
|
|
@@ -27801,8 +27944,15 @@ async function seedGateYml(repo, deps, meta, result) {
|
|
|
27801
27944
|
result.skipped.push(`gate.yml missing \u2014 no registry gate config; set \`mmi-cli oracle org project set ${repo} --var gate={...}\` first, then re-run reconcile`);
|
|
27802
27945
|
return "none";
|
|
27803
27946
|
}
|
|
27947
|
+
let gateVars;
|
|
27948
|
+
try {
|
|
27949
|
+
gateVars = gateConfigToVars(gate);
|
|
27950
|
+
} catch (e) {
|
|
27951
|
+
result.errors.push(`gate.yml re-seed: registry gate config is invalid \u2014 ${e.message}`);
|
|
27952
|
+
return "none";
|
|
27953
|
+
}
|
|
27804
27954
|
const derivedVars = withDerivedRepoVars(
|
|
27805
|
-
{ ...
|
|
27955
|
+
{ ...gateVars, REPO_SLUG: parsed.slug },
|
|
27806
27956
|
parsed,
|
|
27807
27957
|
"deployable",
|
|
27808
27958
|
releaseTrack,
|
|
@@ -29577,8 +29727,8 @@ function registerBoardCommands(program3) {
|
|
|
29577
29727
|
|
|
29578
29728
|
// src/bootstrap-commands.ts
|
|
29579
29729
|
var import_node_fs28 = require("node:fs");
|
|
29580
|
-
var
|
|
29581
|
-
var
|
|
29730
|
+
var import_node_os17 = require("node:os");
|
|
29731
|
+
var import_node_path28 = require("node:path");
|
|
29582
29732
|
init_cli_shared();
|
|
29583
29733
|
init_clean_exit();
|
|
29584
29734
|
|
|
@@ -29890,6 +30040,134 @@ async function assignPersistedPortRange(repo, slug, reg, opts) {
|
|
|
29890
30040
|
|
|
29891
30041
|
// src/box-commands.ts
|
|
29892
30042
|
var import_node_fs27 = require("node:fs");
|
|
30043
|
+
var import_node_child_process10 = require("node:child_process");
|
|
30044
|
+
var import_node_os16 = require("node:os");
|
|
30045
|
+
var import_node_path27 = require("node:path");
|
|
30046
|
+
|
|
30047
|
+
// src/box-control.ts
|
|
30048
|
+
var import_node_crypto8 = require("node:crypto");
|
|
30049
|
+
var MANIFEST_PATH = "infra/box-control/manifest.json";
|
|
30050
|
+
var ALLOWED_POST = /* @__PURE__ */ new Set(["systemctl daemon-reload"]);
|
|
30051
|
+
function parseManifest(text) {
|
|
30052
|
+
const raw = JSON.parse(text);
|
|
30053
|
+
if (!Array.isArray(raw.files)) throw new Error('box control manifest: "files" must be an array');
|
|
30054
|
+
const seen = /* @__PURE__ */ new Set();
|
|
30055
|
+
return raw.files.map((f, i) => {
|
|
30056
|
+
const where = `box control manifest: files[${i}]`;
|
|
30057
|
+
if (typeof f.src !== "string" || !f.src || f.src.startsWith("/") || f.src.includes("..")) throw new Error(`${where}: src must be a repo-relative path`);
|
|
30058
|
+
if (f.mode !== "0644" && f.mode !== "0755") throw new Error(`${where} (${f.src}): mode must be "0644" or "0755"`);
|
|
30059
|
+
const targets = [];
|
|
30060
|
+
if (Array.isArray(f.targets)) targets.push(...f.targets);
|
|
30061
|
+
if (f.lanes !== void 0) targets.push(...expandLanes(f.lanes, where));
|
|
30062
|
+
if (!targets.length) throw new Error(`${where} (${f.src}): needs targets or lanes`);
|
|
30063
|
+
const ownSysctl = typeof f.post === "string" && /^sysctl -p (\/etc\/sysctl\.d\/[A-Za-z0-9._-]+\.conf)$/.exec(f.post);
|
|
30064
|
+
const postOk = f.post === void 0 || typeof f.post === "string" && ALLOWED_POST.has(f.post) || !!ownSysctl && targets.every((t) => t.dest === ownSysctl[1]);
|
|
30065
|
+
if (!postOk) {
|
|
30066
|
+
throw new Error(`${where} (${f.src}): post must be one of ${[...ALLOWED_POST].join(", ")}, or \`sysctl -p <its own /etc/sysctl.d dest>\``);
|
|
30067
|
+
}
|
|
30068
|
+
for (const t of targets) {
|
|
30069
|
+
if (typeof t.box !== "string" || !/^[a-z0-9-]+$/.test(t.box)) throw new Error(`${where} (${f.src}): bad box name`);
|
|
30070
|
+
if (typeof t.dest !== "string" || !/^\/[A-Za-z0-9._\/-]+$/.test(t.dest) || t.dest.includes("..")) {
|
|
30071
|
+
throw new Error(`${where} (${f.src}): dest must be an absolute path of plain characters`);
|
|
30072
|
+
}
|
|
30073
|
+
if (t.dest.startsWith("/opt/mmi-control/secrets") || t.dest.endsWith(".env")) {
|
|
30074
|
+
throw new Error(`${where} (${f.src}): ${t.dest} is vault-owned and cannot be a manifest destination`);
|
|
30075
|
+
}
|
|
30076
|
+
const key = `${t.box}:${t.dest}`;
|
|
30077
|
+
if (seen.has(key)) throw new Error(`${where} (${f.src}): duplicate destination ${key}`);
|
|
30078
|
+
seen.add(key);
|
|
30079
|
+
}
|
|
30080
|
+
return { src: f.src, mode: f.mode, ...f.post ? { post: f.post } : {}, targets };
|
|
30081
|
+
});
|
|
30082
|
+
}
|
|
30083
|
+
function expandLanes(l, where) {
|
|
30084
|
+
if (!l || typeof l.box !== "string" || !Number.isInteger(l.from) || !Number.isInteger(l.to) || l.from < 1 || l.to < l.from || typeof l.dest !== "string" || !l.dest.includes("{N}")) {
|
|
30085
|
+
throw new Error(`${where}: lanes needs box, integer from <= to, and a dest containing {N}`);
|
|
30086
|
+
}
|
|
30087
|
+
const out = [];
|
|
30088
|
+
for (let n = l.from; n <= l.to; n += 1) out.push({ box: l.box, dest: l.dest.replace("{N}", String(n)) });
|
|
30089
|
+
return out;
|
|
30090
|
+
}
|
|
30091
|
+
function filesForBox(files, box) {
|
|
30092
|
+
return files.flatMap((file) => file.targets.filter((t) => t.box === box).map((t) => ({ file, dest: t.dest })));
|
|
30093
|
+
}
|
|
30094
|
+
function md5(content) {
|
|
30095
|
+
return (0, import_node_crypto8.createHash)("md5").update(content).digest("hex");
|
|
30096
|
+
}
|
|
30097
|
+
function planRemoteScript(dests) {
|
|
30098
|
+
const lines2 = ["set -u"];
|
|
30099
|
+
for (const d of dests) {
|
|
30100
|
+
lines2.push(`if [ -f '${d}' ]; then printf '%s %s %s\\n' "$(md5sum < '${d}' | cut -d' ' -f1)" "$(stat -c %a '${d}')" '${d}'; else printf 'MISSING - %s\\n' '${d}'; fi`);
|
|
30101
|
+
}
|
|
30102
|
+
lines2.push(`for f in ${ALERT_DIR}/*; do [ -f "$f" ] && printf 'ALERT %s %s\\n' "$(basename "$f")" "$(head -c 300 "$f" | tr '\\n' ' ')"; done; true`);
|
|
30103
|
+
return `${lines2.join("\n")}
|
|
30104
|
+
`;
|
|
30105
|
+
}
|
|
30106
|
+
var ALERT_DIR = "/var/lib/mmi-control/alerts";
|
|
30107
|
+
function parseAlerts(output) {
|
|
30108
|
+
const out = [];
|
|
30109
|
+
for (const line of output.split("\n")) {
|
|
30110
|
+
const m = /^ALERT ([A-Za-z0-9._-]+) ?(.*)$/.exec(line.trim());
|
|
30111
|
+
if (m) out.push({ name: m[1], message: m[2].trim() });
|
|
30112
|
+
}
|
|
30113
|
+
return out;
|
|
30114
|
+
}
|
|
30115
|
+
function parsePlanOutput(output, rows) {
|
|
30116
|
+
const seen = /* @__PURE__ */ new Map();
|
|
30117
|
+
for (const line of output.split("\n")) {
|
|
30118
|
+
const m = /^([0-9a-f]{32}|MISSING) (\d{3,4}|-) (\/\S+)$/.exec(line.trim());
|
|
30119
|
+
if (m) seen.set(m[3], { md5: m[1], mode: m[2] });
|
|
30120
|
+
}
|
|
30121
|
+
return rows.map(({ file, dest, expectedMd5 }) => {
|
|
30122
|
+
const expectedMode = file.mode.replace(/^0/, "");
|
|
30123
|
+
const got = seen.get(dest);
|
|
30124
|
+
const base = { src: file.src, dest, expectedMd5, expectedMode: file.mode };
|
|
30125
|
+
if (!got) return { ...base, status: "unread", installedMd5: null, installedMode: null };
|
|
30126
|
+
if (got.md5 === "MISSING") return { ...base, status: "missing", installedMd5: null, installedMode: null };
|
|
30127
|
+
const status = got.md5 !== expectedMd5 ? "drift" : got.mode !== expectedMode ? "mode-drift" : "in-sync";
|
|
30128
|
+
return { ...base, status, installedMd5: got.md5, installedMode: `0${got.mode}` };
|
|
30129
|
+
});
|
|
30130
|
+
}
|
|
30131
|
+
function selectApplyRows(rows, sel) {
|
|
30132
|
+
const unknown = sel.only.filter((s) => !rows.some((r) => r.file.src === s));
|
|
30133
|
+
if (unknown.length) return `not a manifest src for this box: ${unknown.join(", ")}`;
|
|
30134
|
+
const chosen = rows.filter((r) => sel.only.includes(r.file.src));
|
|
30135
|
+
const strayDest = sel.dest.filter((d) => !chosen.some((r) => r.dest === d));
|
|
30136
|
+
if (strayDest.length) return `--dest is not a destination of the chosen --only source(s): ${strayDest.join(", ")}`;
|
|
30137
|
+
const out = [];
|
|
30138
|
+
for (const src of sel.only) {
|
|
30139
|
+
const targets = chosen.filter((r) => r.file.src === src);
|
|
30140
|
+
if (targets.length === 1 || sel.allTargets) {
|
|
30141
|
+
out.push(...targets);
|
|
30142
|
+
continue;
|
|
30143
|
+
}
|
|
30144
|
+
const named = targets.filter((r) => sel.dest.includes(r.dest));
|
|
30145
|
+
if (!named.length) {
|
|
30146
|
+
return `${src} has ${targets.length} destinations on this box; name each with --dest <path>, or pass --all-targets`;
|
|
30147
|
+
}
|
|
30148
|
+
out.push(...named);
|
|
30149
|
+
}
|
|
30150
|
+
return out;
|
|
30151
|
+
}
|
|
30152
|
+
function backupPath(dest, date) {
|
|
30153
|
+
if (dest.startsWith("/etc/cron.d/")) return `/root/${dest.slice("/etc/cron.d/".length)}.cron.bak-${date}`;
|
|
30154
|
+
return `${dest}.bak-${date}`;
|
|
30155
|
+
}
|
|
30156
|
+
function applyRemoteScript(items, date) {
|
|
30157
|
+
const lines2 = ["set -eu", "tmp=$(mktemp)", `trap 'rm -f "$tmp"' EXIT`];
|
|
30158
|
+
for (const it of items) {
|
|
30159
|
+
const b64 = Buffer.from(it.content).toString("base64");
|
|
30160
|
+
lines2.push(`if [ -f '${it.dest}' ]; then cp -p '${it.dest}' '${backupPath(it.dest, date)}'; fi`);
|
|
30161
|
+
lines2.push(`printf '%s' '${b64}' | base64 -d > "$tmp"`);
|
|
30162
|
+
lines2.push(`install -D -m ${it.mode} "$tmp" '${it.dest}'`);
|
|
30163
|
+
}
|
|
30164
|
+
for (const post of [...new Set(items.map((i) => i.post).filter((p) => !!p))]) lines2.push(post);
|
|
30165
|
+
lines2.push(planRemoteScript(items.map((i) => i.dest)).replace(/^set -u\n/, ""));
|
|
30166
|
+
return `${lines2.join("\n")}
|
|
30167
|
+
`;
|
|
30168
|
+
}
|
|
30169
|
+
|
|
30170
|
+
// src/box-commands.ts
|
|
29893
30171
|
init_clean_exit();
|
|
29894
30172
|
|
|
29895
30173
|
// src/secrets-commands.ts
|
|
@@ -30299,14 +30577,20 @@ function parseGateVar(raw) {
|
|
|
30299
30577
|
const map = parsed;
|
|
30300
30578
|
const out = {};
|
|
30301
30579
|
for (const [key, value] of Object.entries(map)) {
|
|
30302
|
-
if (key === "
|
|
30580
|
+
if (key === "services" || key === "steps") {
|
|
30581
|
+
try {
|
|
30582
|
+
Object.assign(out, validateGateExtras({ [key]: value }));
|
|
30583
|
+
} catch (error) {
|
|
30584
|
+
throw new Error(`org project set: ${error.message}`);
|
|
30585
|
+
}
|
|
30586
|
+
} else if (key === "runtime") {
|
|
30303
30587
|
if (value !== "node" && value !== "python") throw new Error('org project set: gate.runtime must be "node" or "python"');
|
|
30304
30588
|
out.runtime = value;
|
|
30305
30589
|
} else if (key === "cmd" || key === "affectedCmd" || key === "workdir" || key === "cacheDepPath" || key === "pyVersion") {
|
|
30306
30590
|
if (typeof value !== "string" || !value.trim()) throw new Error(`org project set: gate.${key} must be a non-empty string`);
|
|
30307
30591
|
out[key] = value.trim();
|
|
30308
30592
|
} else {
|
|
30309
|
-
throw new Error(`org project set: gate key "${key}" \u2014 expected only runtime/cmd/affectedCmd/workdir/cacheDepPath/pyVersion`);
|
|
30593
|
+
throw new Error(`org project set: gate key "${key}" \u2014 expected only runtime/cmd/affectedCmd/workdir/cacheDepPath/pyVersion/services/steps`);
|
|
30310
30594
|
}
|
|
30311
30595
|
}
|
|
30312
30596
|
return out;
|
|
@@ -31479,6 +31763,81 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
31479
31763
|
await failGraceful(e.message);
|
|
31480
31764
|
}
|
|
31481
31765
|
});
|
|
31766
|
+
box.command("control <name>").description("box-side control files (#7402): --plan (default, read-only) reports drift from the repo; --apply --only <src> installs with backups").option("--plan", "read-only drift report (the default)").option("--apply", "install the files named by --only on this box, backing up each previous copy").option("--only <src>", "with --apply: a manifest src to install (repeatable; approval is file by file)", (v, acc) => [...acc, v], []).option("--dest <path>", "with --apply: a destination of a multi-target --only source to install (repeatable, #7409)", (v, acc) => [...acc, v], []).option("--all-targets", "with --apply: install a multi-target --only source to every one of its destinations on this box").option("--ref <ref>", "git ref the repo content is read from", "origin/development").option("--json", "machine-readable output").action(async (name, o) => {
|
|
31767
|
+
try {
|
|
31768
|
+
if (o.apply && o.plan) throw new UsageError("box control: --plan and --apply are exclusive");
|
|
31769
|
+
if (o.apply && !o.only.length) throw new UsageError("box control: --apply needs --only <src> for each file to install; approval is file by file");
|
|
31770
|
+
if (!o.apply && (o.only.length || o.dest.length || o.allTargets)) throw new UsageError("box control: --only, --dest and --all-targets apply to --apply");
|
|
31771
|
+
const show = hubGitShow(o.ref);
|
|
31772
|
+
const files = parseManifest(show(MANIFEST_PATH).toString("utf8"));
|
|
31773
|
+
const { boxes, incomplete } = await fetchInventory();
|
|
31774
|
+
const found = lookupBox(name, boxes, incomplete);
|
|
31775
|
+
if (found.kind !== "found") {
|
|
31776
|
+
const why = found.kind === "unknown" ? `the inventory is incomplete (${found.reasons.join("; ")})` : `no box named '${name}'`;
|
|
31777
|
+
throw new UnreadableError(`box control: ${why}`);
|
|
31778
|
+
}
|
|
31779
|
+
const rows = filesForBox(files, found.box.name).map(({ file, dest }) => {
|
|
31780
|
+
const content = show(file.src);
|
|
31781
|
+
return { file, dest, content, expectedMd5: md5(content) };
|
|
31782
|
+
});
|
|
31783
|
+
if (!rows.length) throw new UsageError(`box control: ${MANIFEST_PATH} lists no files for ${found.box.name}`);
|
|
31784
|
+
let probeRows = rows;
|
|
31785
|
+
if (o.apply) {
|
|
31786
|
+
const picked = selectApplyRows(rows, { only: o.only, dest: o.dest, allTargets: !!o.allTargets });
|
|
31787
|
+
if (typeof picked === "string") throw new UsageError(`box control: ${picked}`);
|
|
31788
|
+
probeRows = picked;
|
|
31789
|
+
}
|
|
31790
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "");
|
|
31791
|
+
const script = o.apply ? applyRemoteScript(probeRows.map((r) => ({ dest: r.dest, mode: r.file.mode, content: r.content, post: r.file.post })), date) : planRemoteScript(probeRows.map((r) => r.dest));
|
|
31792
|
+
const output = runOnBox(found.box, script);
|
|
31793
|
+
const results = parsePlanOutput(output, probeRows);
|
|
31794
|
+
const drift = results.filter((r) => r.status !== "in-sync").length;
|
|
31795
|
+
const unread = results.some((r) => r.status === "unread");
|
|
31796
|
+
const alerts = parseAlerts(output);
|
|
31797
|
+
const payload = { box: found.box.name, ref: o.ref, mode: o.apply ? "apply" : "plan", files: results, drift, alerts, ok: drift === 0 && alerts.length === 0 };
|
|
31798
|
+
if (o.json) console.log(JSON.stringify(payload, null, 2));
|
|
31799
|
+
else console.log([formatControl(payload.mode, found.box.name, results), ...alerts.map((a) => `ALERT ${a.name}: ${a.message}`)].join("\n"));
|
|
31800
|
+
process.exitCode = unread ? 2 : drift || alerts.length ? 1 : 0;
|
|
31801
|
+
} catch (e) {
|
|
31802
|
+
if (o.json) console.log(JSON.stringify({ box: name, ref: o.ref, ok: false, error: e.message }, null, 2));
|
|
31803
|
+
else console.error(e.message);
|
|
31804
|
+
process.exitCode = 2;
|
|
31805
|
+
}
|
|
31806
|
+
});
|
|
31807
|
+
}
|
|
31808
|
+
var UsageError = class extends Error {
|
|
31809
|
+
};
|
|
31810
|
+
var UnreadableError = class extends Error {
|
|
31811
|
+
};
|
|
31812
|
+
function hubGitShow(ref) {
|
|
31813
|
+
const git3 = (args) => (0, import_node_child_process10.execFileSync)("git", args, { maxBuffer: 64 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
31814
|
+
try {
|
|
31815
|
+
git3(["cat-file", "-e", `HEAD:${MANIFEST_PATH}`]);
|
|
31816
|
+
} catch {
|
|
31817
|
+
throw new UsageError(`box control: run this from an MMI-Hub checkout (no ${MANIFEST_PATH} here)`);
|
|
31818
|
+
}
|
|
31819
|
+
if (ref.startsWith("origin/")) git3(["fetch", "--quiet", "origin"]);
|
|
31820
|
+
return (path2) => git3(["show", `${ref}:${path2}`]);
|
|
31821
|
+
}
|
|
31822
|
+
function runOnBox(found, script) {
|
|
31823
|
+
const dir = (0, import_node_fs27.mkdtempSync)((0, import_node_path27.join)((0, import_node_os16.tmpdir)(), "mmi-box-control-"));
|
|
31824
|
+
try {
|
|
31825
|
+
const recipe = (0, import_node_path27.join)(dir, "recipe.sh");
|
|
31826
|
+
(0, import_node_fs27.writeFileSync)(recipe, sshRecipeScript(found, "sh -s"), "utf8");
|
|
31827
|
+
const r = (0, import_node_child_process10.spawnSync)("bash", [recipe], { input: script, encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 18e4, windowsHide: true });
|
|
31828
|
+
if (r.status !== 0) {
|
|
31829
|
+
throw new UnreadableError(`box control: the ssh session to ${found.name} failed (exit ${r.status ?? r.signal}): ${(r.stderr || "").trim().split("\n").slice(-3).join(" | ")}`);
|
|
31830
|
+
}
|
|
31831
|
+
return r.stdout;
|
|
31832
|
+
} finally {
|
|
31833
|
+
(0, import_node_fs27.rmSync)(dir, { recursive: true, force: true });
|
|
31834
|
+
}
|
|
31835
|
+
}
|
|
31836
|
+
function formatControl(mode, box, rows) {
|
|
31837
|
+
const lines2 = rows.map((r) => `${r.status.padEnd(10)} ${r.dest} <- ${r.src}`);
|
|
31838
|
+
const bad = rows.filter((r) => r.status !== "in-sync").length;
|
|
31839
|
+
lines2.push(`${box}: ${mode} \u2014 ${rows.length} file(s), ${bad} not in sync`);
|
|
31840
|
+
return lines2.join("\n");
|
|
31482
31841
|
}
|
|
31483
31842
|
|
|
31484
31843
|
// src/bootstrap-box-ports.ts
|
|
@@ -31599,7 +31958,7 @@ function renderDeployPortDoctor(report) {
|
|
|
31599
31958
|
}
|
|
31600
31959
|
|
|
31601
31960
|
// src/bootstrap-drift.ts
|
|
31602
|
-
var
|
|
31961
|
+
var import_node_crypto9 = require("node:crypto");
|
|
31603
31962
|
function byteComparableSeeds(manifest, cls) {
|
|
31604
31963
|
return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
|
|
31605
31964
|
}
|
|
@@ -31619,7 +31978,7 @@ function compareSeedBytes(hubContent, repoContent) {
|
|
|
31619
31978
|
return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
|
|
31620
31979
|
}
|
|
31621
31980
|
function seedContentHash(content) {
|
|
31622
|
-
return (0,
|
|
31981
|
+
return (0, import_node_crypto9.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
|
|
31623
31982
|
}
|
|
31624
31983
|
function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
|
|
31625
31984
|
const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
|
|
@@ -31781,7 +32140,7 @@ function renderPropagationReport(plan) {
|
|
|
31781
32140
|
}
|
|
31782
32141
|
|
|
31783
32142
|
// src/bootstrap-propagation-identity.ts
|
|
31784
|
-
var
|
|
32143
|
+
var import_node_crypto10 = require("node:crypto");
|
|
31785
32144
|
var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
|
|
31786
32145
|
var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
|
|
31787
32146
|
function safeBranchPart(value, maxLength, fallback) {
|
|
@@ -31794,7 +32153,7 @@ function repoSlug2(repo) {
|
|
|
31794
32153
|
function propagationBranch(repo, target) {
|
|
31795
32154
|
const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
|
|
31796
32155
|
const targetPart = safeBranchPart(target, 48, "target");
|
|
31797
|
-
const hash = (0,
|
|
32156
|
+
const hash = (0, import_node_crypto10.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
|
|
31798
32157
|
return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
|
|
31799
32158
|
}
|
|
31800
32159
|
function legacyPropagationBranch(repo) {
|
|
@@ -32950,7 +33309,7 @@ function renderBootstrapVerifyReport(report) {
|
|
|
32950
33309
|
}
|
|
32951
33310
|
|
|
32952
33311
|
// src/bootstrap-seed-delivery.ts
|
|
32953
|
-
var
|
|
33312
|
+
var import_node_crypto11 = require("node:crypto");
|
|
32954
33313
|
init_github_client();
|
|
32955
33314
|
var SHA = /^[a-f0-9]{40}$/;
|
|
32956
33315
|
async function readSeedFile(repo, target, ref, client) {
|
|
@@ -32983,7 +33342,7 @@ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, clien
|
|
|
32983
33342
|
}
|
|
32984
33343
|
const baseSha = base?.object?.sha;
|
|
32985
33344
|
if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
|
|
32986
|
-
const identity = (0,
|
|
33345
|
+
const identity = (0, import_node_crypto11.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
|
|
32987
33346
|
return { ...planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`), baseSha, baseBranch };
|
|
32988
33347
|
}
|
|
32989
33348
|
async function verifyTargetedSeedBranch(repo, plan, target, client, allowMissing = false) {
|
|
@@ -33378,7 +33737,7 @@ function registerBootstrapCommands(program3) {
|
|
|
33378
33737
|
const readFile9 = (p) => (0, import_node_fs28.existsSync)(p) ? (0, import_node_fs28.readFileSync)(p, "utf8") : null;
|
|
33379
33738
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
33380
33739
|
const putSeed = async (target, content, ref, sha) => {
|
|
33381
|
-
const tmp = (0,
|
|
33740
|
+
const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
33382
33741
|
(0, import_node_fs28.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
|
|
33383
33742
|
try {
|
|
33384
33743
|
await gh2(contentPutInputArgs(repo, target, tmp));
|
|
@@ -34125,7 +34484,7 @@ LIVE apply to ${repo}:
|
|
|
34125
34484
|
} catch {
|
|
34126
34485
|
existingSha = void 0;
|
|
34127
34486
|
}
|
|
34128
|
-
const tmp = (0,
|
|
34487
|
+
const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
34129
34488
|
const desiredContent = desiredByRepo.get(rec.repo);
|
|
34130
34489
|
if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
|
|
34131
34490
|
(0, import_node_fs28.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
|
|
@@ -34300,7 +34659,7 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
34300
34659
|
} catch {
|
|
34301
34660
|
existingSha = void 0;
|
|
34302
34661
|
}
|
|
34303
|
-
const tmp = (0,
|
|
34662
|
+
const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
34304
34663
|
(0, import_node_fs28.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
|
|
34305
34664
|
try {
|
|
34306
34665
|
await gh2(contentPutInputArgs(repo, seed.target, tmp));
|
|
@@ -34586,8 +34945,8 @@ function formatSuiteStatus(report) {
|
|
|
34586
34945
|
|
|
34587
34946
|
// src/suite-train.ts
|
|
34588
34947
|
var import_node_fs29 = require("node:fs");
|
|
34589
|
-
var
|
|
34590
|
-
var
|
|
34948
|
+
var import_node_path29 = require("node:path");
|
|
34949
|
+
var import_node_crypto12 = require("node:crypto");
|
|
34591
34950
|
var SCHEMA = "mmi-suite-cut/v1";
|
|
34592
34951
|
var SHA2 = /^[a-f0-9]{40}$/i;
|
|
34593
34952
|
var RC_TAG = /^v\d+\.\d+\.\d+-rc\.\d+$/;
|
|
@@ -34622,8 +34981,8 @@ function readSuiteCut(path2) {
|
|
|
34622
34981
|
return record;
|
|
34623
34982
|
}
|
|
34624
34983
|
function writeSuiteCut(path2, record) {
|
|
34625
|
-
(0, import_node_fs29.mkdirSync)((0,
|
|
34626
|
-
const temp = `${path2}.${(0,
|
|
34984
|
+
(0, import_node_fs29.mkdirSync)((0, import_node_path29.dirname)(path2), { recursive: true });
|
|
34985
|
+
const temp = `${path2}.${(0, import_node_crypto12.randomBytes)(8).toString("hex")}.tmp`;
|
|
34627
34986
|
try {
|
|
34628
34987
|
(0, import_node_fs29.writeFileSync)(temp, JSON.stringify(record, null, 2), { flag: "wx" });
|
|
34629
34988
|
(0, import_node_fs29.renameSync)(temp, path2);
|
|
@@ -34828,7 +35187,7 @@ async function suiteRelease(deps, ownerGo) {
|
|
|
34828
35187
|
|
|
34829
35188
|
// src/suite-train-io.ts
|
|
34830
35189
|
var import_node_fs43 = require("node:fs");
|
|
34831
|
-
var
|
|
35190
|
+
var import_node_path43 = require("node:path");
|
|
34832
35191
|
init_cli_shared();
|
|
34833
35192
|
|
|
34834
35193
|
// src/command-register-train-operations.ts
|
|
@@ -35108,9 +35467,9 @@ A hotfix branch is cut from origin/main and carries ONLY the fixes --from names,
|
|
|
35108
35467
|
async function preflightHotfixBatchPicks(deps, sources, pickTolerated) {
|
|
35109
35468
|
if (sources.length === 0) return { failures: [], skipped: [] };
|
|
35110
35469
|
const { mkdtemp: mkdtemp2, rm: rm2 } = await import("node:fs/promises");
|
|
35111
|
-
const { tmpdir:
|
|
35112
|
-
const { join:
|
|
35113
|
-
const dir = await mkdtemp2(
|
|
35470
|
+
const { tmpdir: tmpdir11 } = await import("node:os");
|
|
35471
|
+
const { join: join45 } = await import("node:path");
|
|
35472
|
+
const dir = await mkdtemp2(join45(tmpdir11(), "mmi-hotfix-preflight-"));
|
|
35114
35473
|
const wt = { run: (file, args) => deps.run(file, file === "git" ? ["-C", dir, ...args] : args) };
|
|
35115
35474
|
const failures = [];
|
|
35116
35475
|
const skipped = [];
|
|
@@ -36309,13 +36668,13 @@ async function findInFlightHotfixVersion(deps, ctx, latestMainTag, workflows = H
|
|
|
36309
36668
|
}
|
|
36310
36669
|
|
|
36311
36670
|
// src/hotfix-coverage.ts
|
|
36312
|
-
var
|
|
36671
|
+
var import_node_child_process11 = require("node:child_process");
|
|
36313
36672
|
var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
|
|
36314
36673
|
var GIT_MAX_BUFFER = 512 * 1024 * 1024;
|
|
36315
36674
|
function checkHotfixCoverage(options = {}) {
|
|
36316
36675
|
const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
|
|
36317
36676
|
const ack = (options.ack ?? []).filter(Boolean);
|
|
36318
|
-
const git3 = options.git ?? ((args, opts) => (0,
|
|
36677
|
+
const git3 = options.git ?? ((args, opts) => (0, import_node_child_process11.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"], maxBuffer: GIT_MAX_BUFFER }));
|
|
36319
36678
|
const revList = (range) => {
|
|
36320
36679
|
const out = git3(["rev-list", "--no-merges", range]).trim();
|
|
36321
36680
|
return out ? out.split("\n") : [];
|
|
@@ -36383,7 +36742,7 @@ function checkHotfixCoverage(options = {}) {
|
|
|
36383
36742
|
}
|
|
36384
36743
|
function checkHotfixCarries(options) {
|
|
36385
36744
|
const { cwd = process.cwd(), branch, baseRef, targets } = options;
|
|
36386
|
-
const git3 = options.git ?? ((args, opts) => (0,
|
|
36745
|
+
const git3 = options.git ?? ((args, opts) => (0, import_node_child_process11.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"], maxBuffer: GIT_MAX_BUFFER }));
|
|
36387
36746
|
const isAncestor = (sha, ref) => {
|
|
36388
36747
|
try {
|
|
36389
36748
|
git3(["merge-base", "--is-ancestor", sha, ref]);
|
|
@@ -36416,8 +36775,8 @@ function checkHotfixCarries(options) {
|
|
|
36416
36775
|
|
|
36417
36776
|
// src/merge-cleanup.ts
|
|
36418
36777
|
var import_node_fs31 = require("node:fs");
|
|
36419
|
-
var
|
|
36420
|
-
var
|
|
36778
|
+
var import_node_path31 = require("node:path");
|
|
36779
|
+
var import_node_os18 = require("node:os");
|
|
36421
36780
|
init_cli_shared();
|
|
36422
36781
|
init_github_client();
|
|
36423
36782
|
|
|
@@ -36706,12 +37065,12 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
36706
37065
|
}
|
|
36707
37066
|
|
|
36708
37067
|
// src/test-policy-core.ts
|
|
36709
|
-
var
|
|
37068
|
+
var import_node_child_process13 = require("node:child_process");
|
|
36710
37069
|
var import_node_fs30 = require("node:fs");
|
|
36711
|
-
var
|
|
37070
|
+
var import_node_path30 = require("node:path");
|
|
36712
37071
|
|
|
36713
37072
|
// src/test-command-policy-shared.mjs
|
|
36714
|
-
var
|
|
37073
|
+
var import_node_child_process12 = require("node:child_process");
|
|
36715
37074
|
var TEST_COMMAND_CLASS = "test";
|
|
36716
37075
|
var TRAILER_KEY = "Test-Policy-Override";
|
|
36717
37076
|
var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
|
|
@@ -36828,7 +37187,7 @@ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, overrid
|
|
|
36828
37187
|
};
|
|
36829
37188
|
}
|
|
36830
37189
|
function git(args, cwd) {
|
|
36831
|
-
return (0,
|
|
37190
|
+
return (0, import_node_child_process12.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
36832
37191
|
}
|
|
36833
37192
|
function parseScope(value) {
|
|
36834
37193
|
const scoped = /^\[([^\]]*)\]\s*([\s\S]*)$/.exec(value);
|
|
@@ -37184,7 +37543,7 @@ function isMeaningfulRow(file) {
|
|
|
37184
37543
|
return file.meaningful !== false;
|
|
37185
37544
|
}
|
|
37186
37545
|
function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
37187
|
-
const raw = readFile9((0,
|
|
37546
|
+
const raw = readFile9((0, import_node_path30.join)(root, POLICY_FILE));
|
|
37188
37547
|
if (raw == null) return { mandatory: [], declared: false };
|
|
37189
37548
|
return parsePolicy(raw, POLICY_FILE);
|
|
37190
37549
|
}
|
|
@@ -37290,11 +37649,11 @@ function classify(changed, policy, present = () => false) {
|
|
|
37290
37649
|
return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
|
|
37291
37650
|
}
|
|
37292
37651
|
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs30.existsSync)(path2)) {
|
|
37293
|
-
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0,
|
|
37652
|
+
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path30.join)(root, p)));
|
|
37294
37653
|
}
|
|
37295
37654
|
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs30.existsSync)(path2)) {
|
|
37296
37655
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
37297
|
-
return [...new Set(declared)].filter((p) => !exists((0,
|
|
37656
|
+
return [...new Set(declared)].filter((p) => !exists((0, import_node_path30.join)(root, p)));
|
|
37298
37657
|
}
|
|
37299
37658
|
function evaluate(changed, policy, present = () => false) {
|
|
37300
37659
|
const { mandatoryHits, untestedHits, meaningfulTestChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
@@ -37327,14 +37686,14 @@ function evaluate(changed, policy, present = () => false) {
|
|
|
37327
37686
|
return findings;
|
|
37328
37687
|
}
|
|
37329
37688
|
function git2(args, cwd) {
|
|
37330
|
-
return (0,
|
|
37689
|
+
return (0, import_node_child_process13.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
37331
37690
|
}
|
|
37332
37691
|
var COAUTHOR_KEY = "Co-authored-by";
|
|
37333
37692
|
var GH_MESSAGE_SEPARATOR = /^-{5,}$/;
|
|
37334
37693
|
var LIFTED_KEYS = new RegExp(`^(?:${TRAILER_KEY}|${COAUTHOR_KEY}):`, "i");
|
|
37335
37694
|
function parseTrailers(message2, cwd) {
|
|
37336
37695
|
try {
|
|
37337
|
-
return (0,
|
|
37696
|
+
return (0, import_node_child_process13.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
|
|
37338
37697
|
windowsHide: true,
|
|
37339
37698
|
cwd,
|
|
37340
37699
|
input: message2,
|
|
@@ -37440,7 +37799,7 @@ function runTestPolicy(root, deps = {}) {
|
|
|
37440
37799
|
const raw = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
|
|
37441
37800
|
const changed = deps.changed ? raw : annotateChangeMeaning(raw, policy, (path2) => ({
|
|
37442
37801
|
before: blobAt(base, path2, root),
|
|
37443
|
-
after: readFileOrNull2((0,
|
|
37802
|
+
after: readFileOrNull2((0, import_node_path30.join)(root, path2))
|
|
37444
37803
|
}));
|
|
37445
37804
|
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride2(base, root) : { override: null, refusals: [] };
|
|
37446
37805
|
const policyRefusals = policy.declared === false ? [{
|
|
@@ -37450,8 +37809,8 @@ function runTestPolicy(root, deps = {}) {
|
|
|
37450
37809
|
A gate with no policy cannot decide which test commands are required or permitted.
|
|
37451
37810
|
` + (deps.base === HOTFIX_DIFF_BASE ? ` Hotfix lane (--base ${HOTFIX_DIFF_BASE}): pass --policy-ref <exact-${HOTFIX_POLICY_REF}-commit> so the current policy is read from ${HOTFIX_POLICY_REF}.` : ` Restore ${POLICY_FILE} on this branch; the policy file belongs on every non-hotfix lane.`)
|
|
37452
37811
|
}] : [];
|
|
37453
|
-
const present = (path2) => exists((0,
|
|
37454
|
-
const policyTree = policySource.sha ? new Set(git2(["ls-tree", "-r", "-z", "--name-only", policySource.sha], root).split("\0").filter(Boolean).map((p) => (0,
|
|
37812
|
+
const present = (path2) => exists((0, import_node_path30.join)(root, path2));
|
|
37813
|
+
const policyTree = policySource.sha ? new Set(git2(["ls-tree", "-r", "-z", "--name-only", policySource.sha], root).split("\0").filter(Boolean).map((p) => (0, import_node_path30.join)(root, p))) : null;
|
|
37455
37814
|
const policyTreeExists = policyTree ? (abs) => policyTree.has(abs) : exists;
|
|
37456
37815
|
const where = policySource.sha ? `${policySource.ref}@${policySource.sha}` : "this worktree";
|
|
37457
37816
|
const removedByThisDiff = removedPaths(changed);
|
|
@@ -37696,8 +38055,8 @@ function resolveSquashMergeBodyText(commits, allowedClosing, cwd) {
|
|
|
37696
38055
|
return rewritten === base ? null : rewritten;
|
|
37697
38056
|
}
|
|
37698
38057
|
function writeSquashBodyFile(body) {
|
|
37699
|
-
const dir = (0, import_node_fs31.mkdtempSync)((0,
|
|
37700
|
-
const path2 = (0,
|
|
38058
|
+
const dir = (0, import_node_fs31.mkdtempSync)((0, import_node_path31.join)((0, import_node_os18.tmpdir)(), "mmi-squash-body-"));
|
|
38059
|
+
const path2 = (0, import_node_path31.join)(dir, "body.txt");
|
|
37701
38060
|
(0, import_node_fs31.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
|
|
37702
38061
|
`, "utf8");
|
|
37703
38062
|
return { path: path2, cleanup: () => {
|
|
@@ -37910,7 +38269,7 @@ async function deleteMergedRemoteBranch(options) {
|
|
|
37910
38269
|
|
|
37911
38270
|
// src/project-info-sync.ts
|
|
37912
38271
|
var import_node_fs32 = require("node:fs");
|
|
37913
|
-
var
|
|
38272
|
+
var import_node_path32 = require("node:path");
|
|
37914
38273
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
37915
38274
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
37916
38275
|
projectV2 { id }
|
|
@@ -37957,7 +38316,7 @@ function sharedName(entries, fallback) {
|
|
|
37957
38316
|
}
|
|
37958
38317
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
37959
38318
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
37960
|
-
const readmePath = (0,
|
|
38319
|
+
const readmePath = (0, import_node_path32.join)(repoRoot2, "README.md");
|
|
37961
38320
|
if (!(0, import_node_fs32.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
37962
38321
|
const entries = entriesFor(project2, projects);
|
|
37963
38322
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
@@ -37983,8 +38342,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
37983
38342
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
37984
38343
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
37985
38344
|
const orgDocs = [
|
|
37986
|
-
(0, import_node_fs32.existsSync)((0,
|
|
37987
|
-
(0, import_node_fs32.existsSync)((0,
|
|
38345
|
+
(0, import_node_fs32.existsSync)((0, import_node_path32.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
|
|
38346
|
+
(0, import_node_fs32.existsSync)((0, import_node_path32.join)(repoRoot2, "docs", "Architecture", "agentic-dev-environment.md")) ? `- [Agentic development environment](${targetBase}/blob/${targetBranch}/docs/Architecture/agentic-dev-environment.md)` : ""
|
|
37988
38347
|
].filter(Boolean);
|
|
37989
38348
|
if (orgDocs.length) lines2.push("", "## Organisation docs", "", ...orgDocs);
|
|
37990
38349
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines2.join("\n")}
|
|
@@ -38007,7 +38366,7 @@ async function syncProjectInfo(plan, client, apply) {
|
|
|
38007
38366
|
|
|
38008
38367
|
// src/train-commands.ts
|
|
38009
38368
|
var import_node_fs33 = require("node:fs");
|
|
38010
|
-
var
|
|
38369
|
+
var import_node_path33 = require("node:path");
|
|
38011
38370
|
init_cli_shared();
|
|
38012
38371
|
init_clean_exit();
|
|
38013
38372
|
init_client_version();
|
|
@@ -38022,7 +38381,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
38022
38381
|
}
|
|
38023
38382
|
function readRepoVersion() {
|
|
38024
38383
|
try {
|
|
38025
|
-
return JSON.parse((0, import_node_fs33.readFileSync)((0,
|
|
38384
|
+
return JSON.parse((0, import_node_fs33.readFileSync)((0, import_node_path33.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
38026
38385
|
} catch {
|
|
38027
38386
|
return void 0;
|
|
38028
38387
|
}
|
|
@@ -38133,7 +38492,7 @@ function releaseProgressVerdict(status, subject = "Release") {
|
|
|
38133
38492
|
|
|
38134
38493
|
// src/suite-pin.ts
|
|
38135
38494
|
var import_promises5 = require("node:fs/promises");
|
|
38136
|
-
var
|
|
38495
|
+
var import_node_path34 = require("node:path");
|
|
38137
38496
|
async function openSuiteCutPinPr(deps, input) {
|
|
38138
38497
|
if (!/^[a-f0-9]{40}$/.test(input.cut) || !input.pins.length) throw new Error("suite pin PR needs an anchored cut and at least one module");
|
|
38139
38498
|
const ids = input.pins.map((p) => p.module);
|
|
@@ -38169,15 +38528,15 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
38169
38528
|
const row = rows.find((r) => r.id === p.module);
|
|
38170
38529
|
return row?.bundles.rc === p.rc && row?.bundles.main === p.main;
|
|
38171
38530
|
})) throw new Error("pins already match without a merged cut PR \u2014 provenance unverified");
|
|
38172
|
-
await (0, import_promises5.mkdir)((0,
|
|
38173
|
-
scratch = await (0, import_promises5.mkdtemp)((0,
|
|
38531
|
+
await (0, import_promises5.mkdir)((0, import_node_path34.join)(deps.root, "tmp"), { recursive: true });
|
|
38532
|
+
scratch = await (0, import_promises5.mkdtemp)((0, import_node_path34.join)(deps.root, "tmp", "suite-pin-"));
|
|
38174
38533
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
38175
38534
|
await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
38176
38535
|
if ((await run("git", ["rev-parse", "HEAD"])).trim().toLowerCase() !== input.cut.toLowerCase())
|
|
38177
38536
|
throw new Error("MM-Work development moved after the suite snapshot; no pin PR opened");
|
|
38178
38537
|
await run("git", ["switch", "-c", branch]);
|
|
38179
38538
|
for (const stage of ["rc", "main"]) {
|
|
38180
|
-
const path2 = (0,
|
|
38539
|
+
const path2 = (0, import_node_path34.join)(scratch, `${stage}-pins.json`);
|
|
38181
38540
|
await (0, import_promises5.writeFile)(path2, JSON.stringify(input.pins.map((p) => ({ module: p.module, url: p[stage] }))));
|
|
38182
38541
|
await run("node", ["scripts/pin-modules.mjs", "--stage", stage, "--pins", path2]);
|
|
38183
38542
|
}
|
|
@@ -38223,8 +38582,8 @@ async function openSuitePinPr(deps, input) {
|
|
|
38223
38582
|
url.searchParams.set("v", tag);
|
|
38224
38583
|
const pin = url.toString();
|
|
38225
38584
|
const branch = `suite-pin/${stage}-${member.id}-${tag}`;
|
|
38226
|
-
await (0, import_promises5.mkdir)((0,
|
|
38227
|
-
scratch = await (0, import_promises5.mkdtemp)((0,
|
|
38585
|
+
await (0, import_promises5.mkdir)((0, import_node_path34.join)(deps.root, "tmp"), { recursive: true });
|
|
38586
|
+
scratch = await (0, import_promises5.mkdtemp)((0, import_node_path34.join)(deps.root, "tmp", "suite-pin-"));
|
|
38228
38587
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
38229
38588
|
await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
38230
38589
|
const existing = JSON.parse(await run("gh", ["pr", "list", "--repo", SUITE_REPO, "--head", branch, "--state", "all", "--json", "url,state"]));
|
|
@@ -38267,7 +38626,7 @@ async function openSuitePinPr(deps, input) {
|
|
|
38267
38626
|
}
|
|
38268
38627
|
|
|
38269
38628
|
// src/command-register-collaboration.ts
|
|
38270
|
-
var
|
|
38629
|
+
var import_node_child_process17 = require("node:child_process");
|
|
38271
38630
|
var import_node_fs42 = require("node:fs");
|
|
38272
38631
|
var import_promises7 = require("node:fs/promises");
|
|
38273
38632
|
init_clean_exit();
|
|
@@ -38292,11 +38651,11 @@ function spawnDetachedSelf(args, deps, opts = {}) {
|
|
|
38292
38651
|
}
|
|
38293
38652
|
|
|
38294
38653
|
// src/command-register-collaboration.ts
|
|
38295
|
-
var
|
|
38654
|
+
var import_node_path42 = require("node:path");
|
|
38296
38655
|
init_house_map();
|
|
38297
38656
|
|
|
38298
38657
|
// src/issue-body.ts
|
|
38299
|
-
var
|
|
38658
|
+
var import_node_os19 = require("node:os");
|
|
38300
38659
|
init_error_codes();
|
|
38301
38660
|
var TextArgError = class extends Error {
|
|
38302
38661
|
constructor(message2, code, offendingFlag) {
|
|
@@ -38309,7 +38668,7 @@ var TextArgError = class extends Error {
|
|
|
38309
38668
|
offendingFlag;
|
|
38310
38669
|
};
|
|
38311
38670
|
function emptyStdinMessage(fileFlag) {
|
|
38312
|
-
if ((0,
|
|
38671
|
+
if ((0, import_node_os19.platform)() === "win32") {
|
|
38313
38672
|
return `${fileFlag} - read empty stdin (on Windows, ${fileFlag} - is unreliable through the npm .cmd shim \u2014 use ${fileFlag} <path>, or pipe to \`node cli/dist/index.cjs\` directly)`;
|
|
38314
38673
|
}
|
|
38315
38674
|
return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
|
|
@@ -38402,7 +38761,7 @@ init_github_client();
|
|
|
38402
38761
|
|
|
38403
38762
|
// src/issue-commands.ts
|
|
38404
38763
|
var import_node_fs34 = require("node:fs");
|
|
38405
|
-
var
|
|
38764
|
+
var import_node_crypto13 = require("node:crypto");
|
|
38406
38765
|
init_cli_shared();
|
|
38407
38766
|
init_clean_exit();
|
|
38408
38767
|
init_error_codes();
|
|
@@ -38875,7 +39234,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
38875
39234
|
const identity = `${spec.type}
|
|
38876
39235
|
${spec.title.trim()}
|
|
38877
39236
|
${spec.body ?? ""}`;
|
|
38878
|
-
const hash = (0,
|
|
39237
|
+
const hash = (0, import_node_crypto13.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
38879
39238
|
return `${batchKey}:${hash}`;
|
|
38880
39239
|
}
|
|
38881
39240
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -39335,7 +39694,7 @@ ${lines2}`, {
|
|
|
39335
39694
|
|
|
39336
39695
|
// src/post-merge-recon.ts
|
|
39337
39696
|
var import_node_fs35 = require("node:fs");
|
|
39338
|
-
var
|
|
39697
|
+
var import_node_path35 = require("node:path");
|
|
39339
39698
|
|
|
39340
39699
|
// src/cross-repo-filing-issue.ts
|
|
39341
39700
|
init_github_client();
|
|
@@ -39502,7 +39861,7 @@ function buildPostMergeReconRecovery(input) {
|
|
|
39502
39861
|
}
|
|
39503
39862
|
function writePostMergeReconRecovery(cwd, recovery) {
|
|
39504
39863
|
const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
|
|
39505
|
-
(0, import_node_fs35.mkdirSync)((0,
|
|
39864
|
+
(0, import_node_fs35.mkdirSync)((0, import_node_path35.dirname)(path2), { recursive: true });
|
|
39506
39865
|
(0, import_node_fs35.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
|
|
39507
39866
|
`, "utf8");
|
|
39508
39867
|
return path2;
|
|
@@ -39937,10 +40296,10 @@ function registerPrLifecycleCommands(program3) {
|
|
|
39937
40296
|
}
|
|
39938
40297
|
|
|
39939
40298
|
// src/review-verdict.ts
|
|
39940
|
-
var
|
|
40299
|
+
var import_node_child_process14 = require("node:child_process");
|
|
39941
40300
|
var import_node_fs36 = require("node:fs");
|
|
39942
|
-
var
|
|
39943
|
-
var
|
|
40301
|
+
var import_node_os20 = require("node:os");
|
|
40302
|
+
var import_node_path36 = require("node:path");
|
|
39944
40303
|
init_cli_shared();
|
|
39945
40304
|
var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
|
|
39946
40305
|
var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
|
|
@@ -40037,8 +40396,8 @@ async function checkPrReview(number, repo, head, deps = {
|
|
|
40037
40396
|
}
|
|
40038
40397
|
function computePrPatchId(number, repo) {
|
|
40039
40398
|
return new Promise((resolve7, reject) => {
|
|
40040
|
-
const gh2 = (0,
|
|
40041
|
-
const git3 = (0,
|
|
40399
|
+
const gh2 = (0, import_node_child_process14.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
40400
|
+
const git3 = (0, import_node_child_process14.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
40042
40401
|
let out = "";
|
|
40043
40402
|
let ghErr = "";
|
|
40044
40403
|
let gitErr = "";
|
|
@@ -40105,8 +40464,8 @@ async function readPrIssueComments(number, repo) {
|
|
|
40105
40464
|
return comments;
|
|
40106
40465
|
}
|
|
40107
40466
|
async function postPrCommentFromFile(number, repo, body) {
|
|
40108
|
-
const dir = (0, import_node_fs36.mkdtempSync)((0,
|
|
40109
|
-
const path2 = (0,
|
|
40467
|
+
const dir = (0, import_node_fs36.mkdtempSync)((0, import_node_path36.join)((0, import_node_os20.tmpdir)(), "mmi-review-verdict-"));
|
|
40468
|
+
const path2 = (0, import_node_path36.join)(dir, "body.md");
|
|
40110
40469
|
try {
|
|
40111
40470
|
(0, import_node_fs36.writeFileSync)(path2, body, "utf8");
|
|
40112
40471
|
const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
@@ -40120,7 +40479,7 @@ async function postPrCommentFromFile(number, repo, body) {
|
|
|
40120
40479
|
}
|
|
40121
40480
|
|
|
40122
40481
|
// src/pr-create-docs-check.ts
|
|
40123
|
-
var
|
|
40482
|
+
var import_node_child_process15 = require("node:child_process");
|
|
40124
40483
|
init_cli_shared();
|
|
40125
40484
|
var GIT_TIMEOUT_MS2 = 15e3;
|
|
40126
40485
|
function catFileBatch(root, ref, paths) {
|
|
@@ -40128,7 +40487,7 @@ function catFileBatch(root, ref, paths) {
|
|
|
40128
40487
|
return new Promise((resolve7) => {
|
|
40129
40488
|
const chunks = [];
|
|
40130
40489
|
let settled = false;
|
|
40131
|
-
const child2 = (0,
|
|
40490
|
+
const child2 = (0, import_node_child_process15.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
|
|
40132
40491
|
const finish = () => {
|
|
40133
40492
|
if (settled) return;
|
|
40134
40493
|
settled = true;
|
|
@@ -40340,17 +40699,17 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
|
|
|
40340
40699
|
|
|
40341
40700
|
// src/worktree-residue-sweep.ts
|
|
40342
40701
|
var import_node_fs41 = require("node:fs");
|
|
40343
|
-
var
|
|
40702
|
+
var import_node_path41 = require("node:path");
|
|
40344
40703
|
|
|
40345
40704
|
// src/worktree-merge-cleanup.ts
|
|
40346
40705
|
var import_node_fs40 = require("node:fs");
|
|
40347
|
-
var
|
|
40706
|
+
var import_node_path40 = require("node:path");
|
|
40348
40707
|
init_cli_shared();
|
|
40349
40708
|
|
|
40350
40709
|
// src/stage-runner.ts
|
|
40351
|
-
var
|
|
40710
|
+
var import_node_child_process16 = require("node:child_process");
|
|
40352
40711
|
var import_node_fs38 = require("node:fs");
|
|
40353
|
-
var
|
|
40712
|
+
var import_node_path38 = require("node:path");
|
|
40354
40713
|
var import_node_net = require("node:net");
|
|
40355
40714
|
var import_node_util5 = require("node:util");
|
|
40356
40715
|
|
|
@@ -40361,7 +40720,7 @@ function normalizeEol2(s) {
|
|
|
40361
40720
|
|
|
40362
40721
|
// src/env-detection.ts
|
|
40363
40722
|
var import_node_fs37 = require("node:fs");
|
|
40364
|
-
var
|
|
40723
|
+
var import_node_path37 = require("node:path");
|
|
40365
40724
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".jerv", ".pi", ".venv", "venv"]);
|
|
40366
40725
|
function isEnvBaseName(name) {
|
|
40367
40726
|
return name.toLowerCase() === ".env";
|
|
@@ -40379,13 +40738,13 @@ function findEnvFiles(root) {
|
|
|
40379
40738
|
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
40380
40739
|
if (entry.isDirectory()) {
|
|
40381
40740
|
if (SKIPPED_DIRS.has(entry.name)) continue;
|
|
40382
|
-
walk2((0,
|
|
40741
|
+
walk2((0, import_node_path37.join)(dir, entry.name), relPath);
|
|
40383
40742
|
continue;
|
|
40384
40743
|
}
|
|
40385
40744
|
if (isEnvBaseName(entry.name)) {
|
|
40386
40745
|
let isDir = false;
|
|
40387
40746
|
try {
|
|
40388
|
-
isDir = (0, import_node_fs37.lstatSync)((0,
|
|
40747
|
+
isDir = (0, import_node_fs37.lstatSync)((0, import_node_path37.join)(dir, entry.name)).isDirectory();
|
|
40389
40748
|
} catch {
|
|
40390
40749
|
}
|
|
40391
40750
|
if (!isDir) found.push(relPath);
|
|
@@ -40442,15 +40801,15 @@ function stageComposeFileEnv(files) {
|
|
|
40442
40801
|
}
|
|
40443
40802
|
|
|
40444
40803
|
// src/stage-runner.ts
|
|
40445
|
-
var execFileP3 = (0, import_node_util5.promisify)(
|
|
40804
|
+
var execFileP3 = (0, import_node_util5.promisify)(import_node_child_process16.execFile);
|
|
40446
40805
|
var DOCKER_TIMEOUT_MS = 15e3;
|
|
40447
40806
|
function stageBash() {
|
|
40448
40807
|
if (process.platform !== "win32") return "bash";
|
|
40449
40808
|
const configured = process.env.SHELL;
|
|
40450
|
-
if (configured && (0,
|
|
40809
|
+
if (configured && (0, import_node_path38.isAbsolute)(configured) && /[/\\]bash(?:\.exe)?$/i.test(configured) && !/[/\\](?:System32|Sysnative)[/\\]bash(?:\.exe)?$/i.test(configured) && (0, import_node_fs38.existsSync)(configured)) return configured;
|
|
40451
40810
|
const candidates = [
|
|
40452
|
-
(0,
|
|
40453
|
-
...process.env.LOCALAPPDATA ? [(0,
|
|
40811
|
+
(0, import_node_path38.join)(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
|
|
40812
|
+
...process.env.LOCALAPPDATA ? [(0, import_node_path38.join)(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")] : []
|
|
40454
40813
|
];
|
|
40455
40814
|
const bash = candidates.find((path2) => (0, import_node_fs38.existsSync)(path2));
|
|
40456
40815
|
if (!bash) throw new Error("Local stages require Git Bash on Windows. Install Git for Windows or set SHELL to its absolute bash.exe path.");
|
|
@@ -40583,7 +40942,7 @@ function appendForceRecreate(up) {
|
|
|
40583
40942
|
return `${up.trimEnd()} --force-recreate`;
|
|
40584
40943
|
}
|
|
40585
40944
|
function stageStatePath(cwd = process.cwd()) {
|
|
40586
|
-
return (0,
|
|
40945
|
+
return (0, import_node_path38.join)(cwd, "tmp", "stage", "state.json");
|
|
40587
40946
|
}
|
|
40588
40947
|
function normPath(path2) {
|
|
40589
40948
|
return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
@@ -40800,7 +41159,7 @@ function stageProcessEnv(stagePort, extraEnv) {
|
|
|
40800
41159
|
}
|
|
40801
41160
|
function composeResolvesPort(cwd) {
|
|
40802
41161
|
if (process.env.PORT) return true;
|
|
40803
|
-
const envFile = (0,
|
|
41162
|
+
const envFile = (0, import_node_path38.join)(cwd, ".env");
|
|
40804
41163
|
return (0, import_node_fs38.existsSync)(envFile) && envFileKeys((0, import_node_fs38.readFileSync)(envFile, "utf8")).has("PORT");
|
|
40805
41164
|
}
|
|
40806
41165
|
function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
@@ -40814,13 +41173,13 @@ function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
|
40814
41173
|
function stageComposeFiles(cwd) {
|
|
40815
41174
|
const files = ["docker-compose.yml"];
|
|
40816
41175
|
for (const conventional of ["docker-compose.override.yml", "docker-compose.override.yaml"]) {
|
|
40817
|
-
if ((0, import_node_fs38.existsSync)((0,
|
|
41176
|
+
if ((0, import_node_fs38.existsSync)((0, import_node_path38.join)(cwd, conventional))) files.push(conventional);
|
|
40818
41177
|
}
|
|
40819
41178
|
return [...files, RUNTIME_ENV_OVERRIDE_RELPATH];
|
|
40820
41179
|
}
|
|
40821
41180
|
async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
40822
41181
|
const keys = Object.keys(vaultEnvMerge ?? {});
|
|
40823
|
-
if (!keys.length || !(0, import_node_fs38.existsSync)((0,
|
|
41182
|
+
if (!keys.length || !(0, import_node_fs38.existsSync)((0, import_node_path38.join)(cwd, "docker-compose.yml"))) return {};
|
|
40824
41183
|
let configJson;
|
|
40825
41184
|
try {
|
|
40826
41185
|
const { stdout } = await execFileP3("docker", ["compose", "config", "--format", "json"], {
|
|
@@ -40836,15 +41195,15 @@ async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
|
40836
41195
|
}
|
|
40837
41196
|
const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys);
|
|
40838
41197
|
if (!body) return {};
|
|
40839
|
-
const overridePath = (0,
|
|
40840
|
-
(0, import_node_fs38.mkdirSync)((0,
|
|
41198
|
+
const overridePath = (0, import_node_path38.join)(cwd, RUNTIME_ENV_OVERRIDE_RELPATH);
|
|
41199
|
+
(0, import_node_fs38.mkdirSync)((0, import_node_path38.join)(cwd, "tmp", "stage"), { recursive: true });
|
|
40841
41200
|
(0, import_node_fs38.writeFileSync)(overridePath, body, "utf8");
|
|
40842
41201
|
return stageComposeFileEnv(stageComposeFiles(cwd));
|
|
40843
41202
|
}
|
|
40844
41203
|
async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
40845
41204
|
if (!config.ensureEnv) return;
|
|
40846
|
-
const target = (0,
|
|
40847
|
-
const example = (0,
|
|
41205
|
+
const target = (0, import_node_path38.join)(cwd, config.ensureEnv.target);
|
|
41206
|
+
const example = (0, import_node_path38.join)(cwd, config.ensureEnv.example);
|
|
40848
41207
|
if (!(0, import_node_fs38.existsSync)(target) && (0, import_node_fs38.existsSync)(example)) {
|
|
40849
41208
|
(0, import_node_fs38.copyFileSync)(example, target);
|
|
40850
41209
|
} else if ((0, import_node_fs38.existsSync)(target) && (0, import_node_fs38.existsSync)(example)) {
|
|
@@ -41025,7 +41384,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
41025
41384
|
let up = sub(config.up.trim());
|
|
41026
41385
|
if (opts.forceRecreate) up = appendForceRecreate(up);
|
|
41027
41386
|
const identity = await resolveStageIdentity(cwd);
|
|
41028
|
-
const child2 = (0,
|
|
41387
|
+
const child2 = (0, import_node_child_process16.spawn)(bash, ["-c", up], {
|
|
41029
41388
|
cwd,
|
|
41030
41389
|
// POSIX-only: the process group exists for the group-kill in stopStage. On win32 teardown is
|
|
41031
41390
|
// `taskkill /T /F` (no group needed), and detached+shell defeats windowsHide — every spawn would
|
|
@@ -41135,7 +41494,7 @@ async function runStage(config = {}, opts = {}) {
|
|
|
41135
41494
|
|
|
41136
41495
|
// src/worktree-evidence-archive.ts
|
|
41137
41496
|
var import_node_fs39 = require("node:fs");
|
|
41138
|
-
var
|
|
41497
|
+
var import_node_path39 = require("node:path");
|
|
41139
41498
|
var JERV_ARTIFACT_RUN_SCOPE_ENV_VARS = ["JERV_RUN_ID", ...SESSION_ID_ENV_VARS];
|
|
41140
41499
|
function sanitizeArchiveSegment(value, max = 80) {
|
|
41141
41500
|
const scrubbed = value.replace(/[^A-Za-z0-9._@+-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -41167,7 +41526,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
|
|
|
41167
41526
|
const resolveRoot = deps.resolveArchiveRoot ?? repoRuntimeStatePath;
|
|
41168
41527
|
if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
|
|
41169
41528
|
if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
|
|
41170
|
-
const source = (0,
|
|
41529
|
+
const source = (0, import_node_path39.join)(args.worktreePath, ".jerv");
|
|
41171
41530
|
if (!exists(source)) return { status: "absent" };
|
|
41172
41531
|
if (!isDirectory(source)) return { status: "skipped", reason: "jerv-not-a-directory" };
|
|
41173
41532
|
const runScope = resolveArtifactRunScope(env);
|
|
@@ -41175,7 +41534,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
|
|
|
41175
41534
|
const stamp = now().toISOString().replace(/[:.]/g, "-");
|
|
41176
41535
|
const dest = resolveRoot(args.primaryRoot, "jerv-artifacts", runScope, branchSlug, stamp, ".jerv");
|
|
41177
41536
|
try {
|
|
41178
|
-
mkdirp((0,
|
|
41537
|
+
mkdirp((0, import_node_path39.dirname)(dest));
|
|
41179
41538
|
copyDir(source, dest);
|
|
41180
41539
|
if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
|
|
41181
41540
|
return { status: "archived", path: dest, runScope };
|
|
@@ -41191,7 +41550,7 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
|
|
|
41191
41550
|
const exists = deps.exists ?? import_node_fs39.existsSync;
|
|
41192
41551
|
const stat4 = deps.stat ?? defaultStat;
|
|
41193
41552
|
const readdir2 = deps.readdir ?? import_node_fs39.readdirSync;
|
|
41194
|
-
const tmpRoot = (0,
|
|
41553
|
+
const tmpRoot = (0, import_node_path39.join)(worktreePath, "tmp");
|
|
41195
41554
|
if (!exists(tmpRoot)) return [];
|
|
41196
41555
|
const entries = [];
|
|
41197
41556
|
const walk2 = (dir) => {
|
|
@@ -41202,14 +41561,14 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
|
|
|
41202
41561
|
return;
|
|
41203
41562
|
}
|
|
41204
41563
|
for (const name of names) {
|
|
41205
|
-
const full = (0,
|
|
41564
|
+
const full = (0, import_node_path39.join)(dir, name);
|
|
41206
41565
|
let st;
|
|
41207
41566
|
try {
|
|
41208
41567
|
st = stat4(full);
|
|
41209
41568
|
} catch {
|
|
41210
41569
|
continue;
|
|
41211
41570
|
}
|
|
41212
|
-
const relPath = (0,
|
|
41571
|
+
const relPath = (0, import_node_path39.relative)(worktreePath, full).replace(/\\/g, "/");
|
|
41213
41572
|
if (st.isDirectory()) {
|
|
41214
41573
|
if (st.mtimeMs > newerThanMs) entries.push({ relPath, bytes: 0, mtimeMs: st.mtimeMs });
|
|
41215
41574
|
walk2(full);
|
|
@@ -41234,7 +41593,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
|
|
|
41234
41593
|
if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
|
|
41235
41594
|
if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
|
|
41236
41595
|
const scanned = scanWorktreeTmpEvidence(args.worktreePath, args.newerThanMs, deps);
|
|
41237
|
-
const source = (0,
|
|
41596
|
+
const source = (0, import_node_path39.join)(args.worktreePath, "tmp");
|
|
41238
41597
|
if (!scanned.length) return { status: "absent" };
|
|
41239
41598
|
if (!exists(source)) return { status: "absent" };
|
|
41240
41599
|
const runScope = resolveArtifactRunScope(env);
|
|
@@ -41242,7 +41601,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
|
|
|
41242
41601
|
const stamp = now().toISOString().replace(/[:.]/g, "-");
|
|
41243
41602
|
const dest = resolveRoot(args.primaryRoot, "worktree-artifacts", runScope, branchSlug, stamp, "tmp");
|
|
41244
41603
|
try {
|
|
41245
|
-
mkdirp((0,
|
|
41604
|
+
mkdirp((0, import_node_path39.dirname)(dest));
|
|
41246
41605
|
copyDir(source, dest);
|
|
41247
41606
|
if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
|
|
41248
41607
|
const bytes = scanned.reduce((sum, e) => sum + e.bytes, 0);
|
|
@@ -41311,7 +41670,7 @@ function normPath2(p) {
|
|
|
41311
41670
|
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
41312
41671
|
}
|
|
41313
41672
|
function unlinkNodeModulesJunction(wtPath) {
|
|
41314
|
-
const nm = (0,
|
|
41673
|
+
const nm = (0, import_node_path40.join)(wtPath, "node_modules");
|
|
41315
41674
|
try {
|
|
41316
41675
|
if ((0, import_node_fs40.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs40.rmdirSync)(nm);
|
|
41317
41676
|
return { ok: true };
|
|
@@ -41448,7 +41807,7 @@ async function preCleanWorktreeForRemoval(wtPath, execGit) {
|
|
|
41448
41807
|
}
|
|
41449
41808
|
async function listNestedIgnoredNodeModules(wtPath, execGit) {
|
|
41450
41809
|
const out = await execGit(["-C", wtPath, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory"]).catch(() => "");
|
|
41451
|
-
return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0,
|
|
41810
|
+
return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0, import_node_path40.join)(wtPath, line.slice(0, -1)));
|
|
41452
41811
|
}
|
|
41453
41812
|
function safeRemoveTree(path2) {
|
|
41454
41813
|
const stat4 = (0, import_node_fs40.lstatSync)(path2);
|
|
@@ -41461,7 +41820,7 @@ function safeRemoveTree(path2) {
|
|
|
41461
41820
|
return;
|
|
41462
41821
|
}
|
|
41463
41822
|
if (stat4.isDirectory()) {
|
|
41464
|
-
for (const entry of (0, import_node_fs40.readdirSync)(path2)) safeRemoveTree((0,
|
|
41823
|
+
for (const entry of (0, import_node_fs40.readdirSync)(path2)) safeRemoveTree((0, import_node_path40.join)(path2, entry));
|
|
41465
41824
|
(0, import_node_fs40.rmdirSync)(path2);
|
|
41466
41825
|
return;
|
|
41467
41826
|
}
|
|
@@ -41469,7 +41828,7 @@ function safeRemoveTree(path2) {
|
|
|
41469
41828
|
}
|
|
41470
41829
|
async function removeWorktreeNodeModules(wtPath) {
|
|
41471
41830
|
try {
|
|
41472
|
-
safeRemoveTree((0,
|
|
41831
|
+
safeRemoveTree((0, import_node_path40.join)(wtPath, "node_modules"));
|
|
41473
41832
|
return { ok: true };
|
|
41474
41833
|
} catch (e) {
|
|
41475
41834
|
if (e.code === "ENOENT") return { ok: true };
|
|
@@ -41513,7 +41872,7 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
|
41513
41872
|
return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
|
|
41514
41873
|
}
|
|
41515
41874
|
for (const entry of entries) {
|
|
41516
|
-
const child2 = (0,
|
|
41875
|
+
const child2 = (0, import_node_path40.join)(dir, entry.name);
|
|
41517
41876
|
if (entry.isSymbolicLink()) {
|
|
41518
41877
|
let target = "";
|
|
41519
41878
|
try {
|
|
@@ -41840,7 +42199,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
41840
42199
|
const preHelperGuard = unlinkEscapingReparsePoints(wtPath, options.primaryRoot);
|
|
41841
42200
|
if (!preHelperGuard.ok) return refuseReparseEscape(preHelperGuard.error);
|
|
41842
42201
|
unlinkedReparsePoints.push(...preHelperGuard.unlinked);
|
|
41843
|
-
if (pathExists((0,
|
|
42202
|
+
if (pathExists((0, import_node_path40.join)(wtPath, "node_modules"))) {
|
|
41844
42203
|
const nmRemoved = await (options.removeRealNodeModules ?? removeWorktreeNodeModules)(wtPath);
|
|
41845
42204
|
if (!nmRemoved.ok) {
|
|
41846
42205
|
report.worktree = {
|
|
@@ -41980,7 +42339,7 @@ function norm(p) {
|
|
|
41980
42339
|
return process.platform === "win32" ? s.toLowerCase() : s;
|
|
41981
42340
|
}
|
|
41982
42341
|
function residueLedgerPath(gitCommonDir) {
|
|
41983
|
-
return (0,
|
|
42342
|
+
return (0, import_node_path41.join)(gitCommonDir, RESIDUE_LEDGER_FILE);
|
|
41984
42343
|
}
|
|
41985
42344
|
function readPaths(file, deps) {
|
|
41986
42345
|
try {
|
|
@@ -41993,7 +42352,7 @@ function readPaths(file, deps) {
|
|
|
41993
42352
|
function writePaths(file, paths, deps) {
|
|
41994
42353
|
try {
|
|
41995
42354
|
const write = deps.writeLedger ?? ((f, t) => {
|
|
41996
|
-
(0, import_node_fs41.mkdirSync)((0,
|
|
42355
|
+
(0, import_node_fs41.mkdirSync)((0, import_node_path41.dirname)(f), { recursive: true });
|
|
41997
42356
|
(0, import_node_fs41.writeFileSync)(f, t, "utf8");
|
|
41998
42357
|
});
|
|
41999
42358
|
write(file, `${JSON.stringify([...new Set(paths)], null, 1)}
|
|
@@ -42044,7 +42403,7 @@ async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
|
42044
42403
|
}
|
|
42045
42404
|
if (recorded.length) writePaths(file, remaining, deps);
|
|
42046
42405
|
const roots = /* @__PURE__ */ new Set();
|
|
42047
|
-
for (const p of [...registered.slice(1), ...recorded]) roots.add((0,
|
|
42406
|
+
for (const p of [...registered.slice(1), ...recorded]) roots.add((0, import_node_path41.dirname)(p));
|
|
42048
42407
|
const keptKeys = new Set(remaining.map(norm));
|
|
42049
42408
|
for (const root of roots) {
|
|
42050
42409
|
if (!isDir(root) || live.has(norm(root)) || norm(root) === primary) continue;
|
|
@@ -42055,7 +42414,7 @@ async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
|
42055
42414
|
continue;
|
|
42056
42415
|
}
|
|
42057
42416
|
for (const name of children) {
|
|
42058
|
-
const child2 = (0,
|
|
42417
|
+
const child2 = (0, import_node_path41.join)(root, name);
|
|
42059
42418
|
const key = norm(child2);
|
|
42060
42419
|
if (live.has(key) || key === primary || keptKeys.has(key) || !isDir(child2)) continue;
|
|
42061
42420
|
let entries;
|
|
@@ -42123,10 +42482,10 @@ function argvWantsJson2() {
|
|
|
42123
42482
|
return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
42124
42483
|
}
|
|
42125
42484
|
function hubRoot() {
|
|
42126
|
-
const fromPkg = (0,
|
|
42485
|
+
const fromPkg = (0, import_node_path42.join)(__dirname, "..", "..");
|
|
42127
42486
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
42128
|
-
if ((0, import_node_fs42.existsSync)((0,
|
|
42129
|
-
if ((0, import_node_fs42.existsSync)((0,
|
|
42487
|
+
if ((0, import_node_fs42.existsSync)((0, import_node_path42.join)(fromPkg, marker))) return fromPkg;
|
|
42488
|
+
if ((0, import_node_fs42.existsSync)((0, import_node_path42.join)(process.cwd(), marker))) return process.cwd();
|
|
42130
42489
|
return null;
|
|
42131
42490
|
}
|
|
42132
42491
|
function ciAuditDeps() {
|
|
@@ -42138,7 +42497,7 @@ function ciAuditDeps() {
|
|
|
42138
42497
|
getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
|
|
42139
42498
|
readSeedFile: (path2) => {
|
|
42140
42499
|
if (!root) return null;
|
|
42141
|
-
const fullPath = (0,
|
|
42500
|
+
const fullPath = (0, import_node_path42.join)(root, path2);
|
|
42142
42501
|
return (0, import_node_fs42.existsSync)(fullPath) ? (0, import_node_fs42.readFileSync)(fullPath, "utf8") : null;
|
|
42143
42502
|
}
|
|
42144
42503
|
};
|
|
@@ -42246,7 +42605,7 @@ function scheduleRelatedDiscovery(o) {
|
|
|
42246
42605
|
try {
|
|
42247
42606
|
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
42248
42607
|
if (o.repo) args.push("--repo", o.repo);
|
|
42249
|
-
spawnDetachedSelf(args, { spawn:
|
|
42608
|
+
spawnDetachedSelf(args, { spawn: import_node_child_process17.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
42250
42609
|
} catch {
|
|
42251
42610
|
}
|
|
42252
42611
|
}
|
|
@@ -42897,11 +43256,11 @@ ${list}`);
|
|
|
42897
43256
|
}
|
|
42898
43257
|
});
|
|
42899
43258
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
42900
|
-
const wfDir = (0,
|
|
43259
|
+
const wfDir = (0, import_node_path42.join)(cwd, ".github", "workflows");
|
|
42901
43260
|
if (!(0, import_node_fs42.existsSync)(wfDir)) return [];
|
|
42902
43261
|
return (0, import_node_fs42.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
42903
43262
|
try {
|
|
42904
|
-
return workflowReportsPrChecks((0, import_node_fs42.readFileSync)((0,
|
|
43263
|
+
return workflowReportsPrChecks((0, import_node_fs42.readFileSync)((0, import_node_path42.join)(wfDir, name), "utf8"));
|
|
42905
43264
|
} catch {
|
|
42906
43265
|
return true;
|
|
42907
43266
|
}
|
|
@@ -43406,7 +43765,7 @@ ${list}`);
|
|
|
43406
43765
|
const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
|
|
43407
43766
|
let foreignCheckout;
|
|
43408
43767
|
if (foreignCwd) {
|
|
43409
|
-
const sibling = (0,
|
|
43768
|
+
const sibling = (0, import_node_path42.join)((0, import_node_path42.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
|
|
43410
43769
|
const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
|
|
43411
43770
|
if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
|
|
43412
43771
|
const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
|
|
@@ -43615,7 +43974,7 @@ ${list}`);
|
|
|
43615
43974
|
let residueSweep;
|
|
43616
43975
|
if (!(foreignCwd && !foreignCheckout)) {
|
|
43617
43976
|
try {
|
|
43618
|
-
gitCommonDir = (0,
|
|
43977
|
+
gitCommonDir = (0, import_node_path42.resolve)(primaryRoot, (await execFileP("git", ["-C", primaryRoot, "rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim());
|
|
43619
43978
|
residueSweep = await sweepWorktreeResidue(gitCommonDir, beforeWorktrees.map((w) => w.path));
|
|
43620
43979
|
} catch {
|
|
43621
43980
|
}
|
|
@@ -44709,15 +45068,15 @@ function rcRunCauseFromLog(log) {
|
|
|
44709
45068
|
}
|
|
44710
45069
|
function suiteTrainIo(root, summaryFile) {
|
|
44711
45070
|
const recordPath = repoRuntimeStatePath(root, "suite", "cut.json");
|
|
44712
|
-
const base = (0,
|
|
45071
|
+
const base = (0, import_node_path43.join)(repoRuntimeStatePath(root, "suite"), "checkouts");
|
|
44713
45072
|
const checkout = async (repo) => {
|
|
44714
45073
|
if (repo === SUITE_REPO) return root;
|
|
44715
|
-
const target = (0,
|
|
45074
|
+
const target = (0, import_node_path43.join)(base, repo.split("/")[1]);
|
|
44716
45075
|
if (!(0, import_node_fs43.existsSync)(target)) {
|
|
44717
45076
|
(0, import_node_fs43.mkdirSync)(base, { recursive: true });
|
|
44718
45077
|
await exec("git", ["clone", "--quiet", "--branch", "development", `https://github.com/${repo}.git`, target], root, WATCH_TIMEOUT);
|
|
44719
45078
|
}
|
|
44720
|
-
if (!(0, import_node_fs43.existsSync)((0,
|
|
45079
|
+
if (!(0, import_node_fs43.existsSync)((0, import_node_path43.join)(target, ".git"))) throw new Error(`${repo}: suite checkout has no .git at ${target}; stop`);
|
|
44721
45080
|
const origin = await exec("git", ["remote", "get-url", "origin"], target);
|
|
44722
45081
|
if (!origin.toLowerCase().endsWith(`/${repo.toLowerCase()}.git`) && !origin.toLowerCase().endsWith(`/${repo.toLowerCase()}`))
|
|
44723
45082
|
throw new Error(`${repo}: suite checkout origin mismatch at ${target}; stop`);
|
|
@@ -45106,8 +45465,8 @@ async function collectWaveStatus(deps) {
|
|
|
45106
45465
|
init_github_client();
|
|
45107
45466
|
init_cli_shared();
|
|
45108
45467
|
var import_node_fs46 = require("node:fs");
|
|
45109
|
-
var
|
|
45110
|
-
var
|
|
45468
|
+
var import_node_os21 = require("node:os");
|
|
45469
|
+
var import_node_path44 = require("node:path");
|
|
45111
45470
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
45112
45471
|
async function collectStatus() {
|
|
45113
45472
|
const repo = await resolveRepo();
|
|
@@ -45296,10 +45655,10 @@ async function collectOnboardStatus(opts = {}) {
|
|
|
45296
45655
|
else if (top) nextCommand = `mmi-cli oracle board claim ${top.number} # ${top.title}`;
|
|
45297
45656
|
else nextCommand = "mmi-cli oracle board read \u2014 no claimable items found";
|
|
45298
45657
|
}
|
|
45299
|
-
const home = (0,
|
|
45658
|
+
const home = (0, import_node_os21.homedir)();
|
|
45300
45659
|
const plugin = onboardPluginGate({
|
|
45301
|
-
readKnown: () => readFileSyncSafe((0,
|
|
45302
|
-
readSettings: () => readFileSyncSafe((0,
|
|
45660
|
+
readKnown: () => readFileSyncSafe((0, import_node_path44.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs46.readFileSync),
|
|
45661
|
+
readSettings: () => readFileSyncSafe((0, import_node_path44.join)(home, ".claude", "settings.json"), import_node_fs46.readFileSync)
|
|
45303
45662
|
});
|
|
45304
45663
|
return {
|
|
45305
45664
|
guide: "Read the MMI org-guide skill before project work; load only the relevant topic. Use mmi-help for a short introduction.",
|
|
@@ -48413,7 +48772,7 @@ function registerSessionReport(program3) {
|
|
|
48413
48772
|
|
|
48414
48773
|
// src/stage-commands.ts
|
|
48415
48774
|
var import_node_fs47 = require("node:fs");
|
|
48416
|
-
var
|
|
48775
|
+
var import_node_path45 = require("node:path");
|
|
48417
48776
|
init_cli_shared();
|
|
48418
48777
|
init_clean_exit();
|
|
48419
48778
|
|
|
@@ -48659,8 +49018,8 @@ function registerStageCommands(program3) {
|
|
|
48659
49018
|
return {
|
|
48660
49019
|
resolution: decideStage({
|
|
48661
49020
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
48662
|
-
hasCompose: (0, import_node_fs47.existsSync)((0,
|
|
48663
|
-
hasEnvExample: (0, import_node_fs47.existsSync)((0,
|
|
49021
|
+
hasCompose: (0, import_node_fs47.existsSync)((0, import_node_path45.join)(process.cwd(), "docker-compose.yml")),
|
|
49022
|
+
hasEnvExample: (0, import_node_fs47.existsSync)((0, import_node_path45.join)(process.cwd(), ".env.example")),
|
|
48664
49023
|
repo
|
|
48665
49024
|
}),
|
|
48666
49025
|
project: project2,
|
|
@@ -48944,20 +49303,20 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
|
|
|
48944
49303
|
}
|
|
48945
49304
|
|
|
48946
49305
|
// src/tenant-artifact.ts
|
|
48947
|
-
var
|
|
49306
|
+
var import_node_crypto14 = require("node:crypto");
|
|
48948
49307
|
var import_node_fs48 = require("node:fs");
|
|
48949
49308
|
var import_promises10 = require("node:fs/promises");
|
|
48950
|
-
var
|
|
49309
|
+
var import_node_path46 = require("node:path");
|
|
48951
49310
|
var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
48952
49311
|
var MAX_BYTES = 5 * 1024 * 1024 * 1024;
|
|
48953
49312
|
async function sha256File(path2) {
|
|
48954
|
-
const hash = (0,
|
|
49313
|
+
const hash = (0, import_node_crypto14.createHash)("sha256");
|
|
48955
49314
|
for await (const chunk of (0, import_node_fs48.createReadStream)(path2)) hash.update(chunk);
|
|
48956
49315
|
return hash.digest("hex");
|
|
48957
49316
|
}
|
|
48958
49317
|
async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
48959
49318
|
if (!["dev", "rc", "main"].includes(stage)) throw new Error("tenant artifact put: <stage> must be dev, rc, or main");
|
|
48960
|
-
const path2 = (0,
|
|
49319
|
+
const path2 = (0, import_node_path46.resolve)(inputPath);
|
|
48961
49320
|
const info = await (0, import_promises10.stat)(path2);
|
|
48962
49321
|
if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
|
|
48963
49322
|
if (!Number.isSafeInteger(info.size) || info.size < 1 || info.size > MAX_BYTES) throw new Error(`tenant artifact put: file must be 1..${MAX_BYTES} bytes`);
|
|
@@ -49147,7 +49506,7 @@ async function resolveWhoami(deps) {
|
|
|
49147
49506
|
}
|
|
49148
49507
|
|
|
49149
49508
|
// src/command-register-developer.ts
|
|
49150
|
-
var
|
|
49509
|
+
var import_node_path51 = require("node:path");
|
|
49151
49510
|
init_house_map();
|
|
49152
49511
|
init_hub_url();
|
|
49153
49512
|
|
|
@@ -49180,27 +49539,27 @@ async function executeWaveLand(plan, deps) {
|
|
|
49180
49539
|
}
|
|
49181
49540
|
|
|
49182
49541
|
// src/dist-drift.ts
|
|
49183
|
-
var
|
|
49184
|
-
var
|
|
49542
|
+
var import_node_child_process18 = require("node:child_process");
|
|
49543
|
+
var import_node_crypto16 = require("node:crypto");
|
|
49185
49544
|
var import_node_fs50 = require("node:fs");
|
|
49186
|
-
var
|
|
49187
|
-
var
|
|
49545
|
+
var import_node_os22 = require("node:os");
|
|
49546
|
+
var import_node_path48 = require("node:path");
|
|
49188
49547
|
|
|
49189
49548
|
// ../scripts/distribution-digest.mjs
|
|
49190
|
-
var
|
|
49549
|
+
var import_node_crypto15 = require("node:crypto");
|
|
49191
49550
|
var import_node_fs49 = require("node:fs");
|
|
49192
|
-
var
|
|
49551
|
+
var import_node_path47 = require("node:path");
|
|
49193
49552
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
49194
49553
|
function repoPath(root, declaredPath, label) {
|
|
49195
|
-
const absoluteRoot = (0,
|
|
49196
|
-
const target = (0,
|
|
49197
|
-
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${
|
|
49554
|
+
const absoluteRoot = (0, import_node_path47.resolve)(root);
|
|
49555
|
+
const target = (0, import_node_path47.resolve)(root, declaredPath);
|
|
49556
|
+
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path47.sep}`)) {
|
|
49198
49557
|
throw new Error(`${label} ${declaredPath} escapes the repository root`);
|
|
49199
49558
|
}
|
|
49200
49559
|
return target;
|
|
49201
49560
|
}
|
|
49202
49561
|
function digestFiles(files) {
|
|
49203
|
-
const hash = (0,
|
|
49562
|
+
const hash = (0, import_node_crypto15.createHash)("sha256");
|
|
49204
49563
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
49205
49564
|
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs49.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs49.readFileSync)(file.absolute);
|
|
49206
49565
|
hash.update(file.relative, "utf8");
|
|
@@ -49230,7 +49589,7 @@ var DIST_ARTIFACTS = [
|
|
|
49230
49589
|
];
|
|
49231
49590
|
var BOM_DIST_TREE_ID = "mmi-cli-dist";
|
|
49232
49591
|
var ABSENT = "absent";
|
|
49233
|
-
var sha256 = (bytes) => `sha256:${(0,
|
|
49592
|
+
var sha256 = (bytes) => `sha256:${(0, import_node_crypto16.createHash)("sha256").update(bytes).digest("hex")}`;
|
|
49234
49593
|
function artifactDrift(path2, committedBytes, rebuiltBytes) {
|
|
49235
49594
|
const committed = committedBytes ? sha256(committedBytes) : ABSENT;
|
|
49236
49595
|
const rebuiltExpected = rebuiltBytes ? sha256(rebuiltBytes) : ABSENT;
|
|
@@ -49308,7 +49667,7 @@ function walkFiles(root) {
|
|
|
49308
49667
|
const files = [];
|
|
49309
49668
|
const walk2 = (directory) => {
|
|
49310
49669
|
for (const entry of (0, import_node_fs50.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
49311
|
-
const child2 = (0,
|
|
49670
|
+
const child2 = (0, import_node_path48.join)(directory, entry.name);
|
|
49312
49671
|
if (entry.isDirectory()) walk2(child2);
|
|
49313
49672
|
else files.push(child2);
|
|
49314
49673
|
}
|
|
@@ -49318,14 +49677,14 @@ function walkFiles(root) {
|
|
|
49318
49677
|
}
|
|
49319
49678
|
function bomPathFor(root) {
|
|
49320
49679
|
try {
|
|
49321
|
-
const registry2 = JSON.parse((0, import_node_fs50.readFileSync)((0,
|
|
49322
|
-
return (0,
|
|
49680
|
+
const registry2 = JSON.parse((0, import_node_fs50.readFileSync)((0, import_node_path48.join)(root, "surfaces.json"), "utf8"));
|
|
49681
|
+
return (0, import_node_path48.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
|
|
49323
49682
|
} catch {
|
|
49324
|
-
return (0,
|
|
49683
|
+
return (0, import_node_path48.join)(root, "distribution-bom.json");
|
|
49325
49684
|
}
|
|
49326
49685
|
}
|
|
49327
49686
|
function rebuildTo(packageRoot, outDir) {
|
|
49328
|
-
(0,
|
|
49687
|
+
(0, import_node_child_process18.execFileSync)(process.execPath, ["build.mjs"], {
|
|
49329
49688
|
cwd: packageRoot,
|
|
49330
49689
|
env: { ...process.env, MMI_DIST_OUTDIR: outDir },
|
|
49331
49690
|
windowsHide: true,
|
|
@@ -49334,28 +49693,28 @@ function rebuildTo(packageRoot, outDir) {
|
|
|
49334
49693
|
});
|
|
49335
49694
|
}
|
|
49336
49695
|
function runDistStatus(root) {
|
|
49337
|
-
const stage = (0, import_node_fs50.mkdtempSync)((0,
|
|
49696
|
+
const stage = (0, import_node_fs50.mkdtempSync)((0, import_node_path48.join)((0, import_node_os22.tmpdir)(), "mmi-dist-drift-"));
|
|
49338
49697
|
let overlayCount = 0;
|
|
49339
49698
|
try {
|
|
49340
|
-
const cliOut = (0,
|
|
49341
|
-
const hubOut = (0,
|
|
49342
|
-
rebuildTo((0,
|
|
49343
|
-
rebuildTo((0,
|
|
49699
|
+
const cliOut = (0, import_node_path48.join)(stage, "cli-dist");
|
|
49700
|
+
const hubOut = (0, import_node_path48.join)(stage, "hub-dist");
|
|
49701
|
+
rebuildTo((0, import_node_path48.join)(root, "cli"), cliOut);
|
|
49702
|
+
rebuildTo((0, import_node_path48.join)(root, "updater"), hubOut);
|
|
49344
49703
|
const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
|
|
49345
49704
|
const rebuilt = (path2) => {
|
|
49346
49705
|
const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
|
|
49347
|
-
return spec ? readOrNull((0,
|
|
49706
|
+
return spec ? readOrNull((0, import_node_path48.join)(outDirFor(spec.packageDir), spec.output)) : null;
|
|
49348
49707
|
};
|
|
49349
|
-
const committed = (path2) => readOrNull((0,
|
|
49350
|
-
const tree = (path2) => readOrNull((0,
|
|
49351
|
-
const distRoot = (0,
|
|
49352
|
-
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0,
|
|
49708
|
+
const committed = (path2) => readOrNull((0, import_node_path48.join)(root, path2));
|
|
49709
|
+
const tree = (path2) => readOrNull((0, import_node_path48.join)(root, path2));
|
|
49710
|
+
const distRoot = (0, import_node_path48.join)(root, "cli", "dist");
|
|
49711
|
+
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path48.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
|
|
49353
49712
|
const bom = JSON.parse((0, import_node_fs50.readFileSync)(bomPathFor(root), "utf8"));
|
|
49354
49713
|
const digest = (entries) => {
|
|
49355
|
-
const overlay = (0,
|
|
49714
|
+
const overlay = (0, import_node_path48.join)(stage, `overlay-${overlayCount++}`);
|
|
49356
49715
|
for (const entry of entries) {
|
|
49357
|
-
const target = (0,
|
|
49358
|
-
(0, import_node_fs50.mkdirSync)((0,
|
|
49716
|
+
const target = (0, import_node_path48.join)(overlay, entry.path);
|
|
49717
|
+
(0, import_node_fs50.mkdirSync)((0, import_node_path48.dirname)(target), { recursive: true });
|
|
49359
49718
|
(0, import_node_fs50.writeFileSync)(target, entry.bytes);
|
|
49360
49719
|
}
|
|
49361
49720
|
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
@@ -49441,7 +49800,7 @@ init_hub_auth();
|
|
|
49441
49800
|
|
|
49442
49801
|
// src/schedules-lift-command.ts
|
|
49443
49802
|
var import_promises11 = require("node:fs/promises");
|
|
49444
|
-
var
|
|
49803
|
+
var import_node_path49 = require("node:path");
|
|
49445
49804
|
init_clean_exit();
|
|
49446
49805
|
init_cli_shared();
|
|
49447
49806
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
@@ -49470,7 +49829,7 @@ async function readWorkflowFiles(dir) {
|
|
|
49470
49829
|
const files = [];
|
|
49471
49830
|
for (const name of names.sort()) {
|
|
49472
49831
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
49473
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises11.readFile)((0,
|
|
49832
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises11.readFile)((0, import_node_path49.join)(dir, name), "utf8") });
|
|
49474
49833
|
}
|
|
49475
49834
|
return files;
|
|
49476
49835
|
}
|
|
@@ -49552,9 +49911,9 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
49552
49911
|
}
|
|
49553
49912
|
|
|
49554
49913
|
// src/spawn-policy-core.ts
|
|
49555
|
-
var
|
|
49914
|
+
var import_node_child_process19 = require("node:child_process");
|
|
49556
49915
|
var import_node_fs51 = require("node:fs");
|
|
49557
|
-
var
|
|
49916
|
+
var import_node_path50 = require("node:path");
|
|
49558
49917
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
49559
49918
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
49560
49919
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -49623,7 +49982,7 @@ function findViolationsInSource(raw) {
|
|
|
49623
49982
|
return found;
|
|
49624
49983
|
}
|
|
49625
49984
|
function policedFiles(root) {
|
|
49626
|
-
const r = (0,
|
|
49985
|
+
const r = (0, import_node_child_process19.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
49627
49986
|
cwd: root,
|
|
49628
49987
|
encoding: "utf8",
|
|
49629
49988
|
windowsHide: true,
|
|
@@ -49640,7 +49999,7 @@ function runSpawnPolicy(root) {
|
|
|
49640
49999
|
for (const file of files) {
|
|
49641
50000
|
let raw;
|
|
49642
50001
|
try {
|
|
49643
|
-
raw = (0, import_node_fs51.readFileSync)((0,
|
|
50002
|
+
raw = (0, import_node_fs51.readFileSync)((0, import_node_path50.join)(root, file), "utf8");
|
|
49644
50003
|
} catch {
|
|
49645
50004
|
continue;
|
|
49646
50005
|
}
|
|
@@ -49660,7 +50019,7 @@ function runSpawnPolicy(root) {
|
|
|
49660
50019
|
function registerDeveloperCommands(program3) {
|
|
49661
50020
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
49662
50021
|
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) => {
|
|
49663
|
-
const path2 = (0,
|
|
50022
|
+
const path2 = (0, import_node_path51.join)(process.cwd(), ".gitignore");
|
|
49664
50023
|
const current = (0, import_node_fs52.existsSync)(path2) ? (0, import_node_fs52.readFileSync)(path2, "utf8") : null;
|
|
49665
50024
|
const plan = planManagedGitignore(current);
|
|
49666
50025
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
@@ -49935,12 +50294,12 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
49935
50294
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
49936
50295
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
49937
50296
|
function envHealLockPath(home) {
|
|
49938
|
-
return (0,
|
|
50297
|
+
return (0, import_node_path52.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
49939
50298
|
}
|
|
49940
50299
|
async function withEnvHealLock(what, run) {
|
|
49941
50300
|
try {
|
|
49942
50301
|
return await withFileLock(
|
|
49943
|
-
envHealLockPath((0,
|
|
50302
|
+
envHealLockPath((0, import_node_os23.homedir)()),
|
|
49944
50303
|
{ staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
|
|
49945
50304
|
run
|
|
49946
50305
|
);
|
|
@@ -50037,7 +50396,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
50037
50396
|
const configRoot = surfaceConfigRoot(surface);
|
|
50038
50397
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
50039
50398
|
const plan = buildPluginCachePlan(
|
|
50040
|
-
(0,
|
|
50399
|
+
(0, import_node_os23.homedir)(),
|
|
50041
50400
|
running,
|
|
50042
50401
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
50043
50402
|
{ configRoot, includeStaging: surface !== "codex" }
|
|
@@ -50065,7 +50424,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
50065
50424
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
50066
50425
|
const installed = installedActivePluginVersion(surface);
|
|
50067
50426
|
const plan = buildPluginCachePlan(
|
|
50068
|
-
(0,
|
|
50427
|
+
(0, import_node_os23.homedir)(),
|
|
50069
50428
|
running,
|
|
50070
50429
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
50071
50430
|
{ configRoot, includeStaging: surface !== "codex", installedVersion: installed }
|
|
@@ -50107,7 +50466,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
50107
50466
|
// has no generated routing index would
|
|
50108
50467
|
// get a permanent — demanding an artifact it never asked for.
|
|
50109
50468
|
docsIndexState: (root) => {
|
|
50110
|
-
if (!(0, import_node_fs53.existsSync)((0,
|
|
50469
|
+
if (!(0, import_node_fs53.existsSync)((0, import_node_path52.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
50111
50470
|
const real = createDocsIndexDeps(root);
|
|
50112
50471
|
let docs;
|
|
50113
50472
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -50116,7 +50475,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
50116
50475
|
},
|
|
50117
50476
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
50118
50477
|
healDocsIndex: (root) => {
|
|
50119
|
-
if (!(0, import_node_fs53.existsSync)((0,
|
|
50478
|
+
if (!(0, import_node_fs53.existsSync)((0, import_node_path52.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
50120
50479
|
const real = createDocsIndexDeps(root);
|
|
50121
50480
|
let docs;
|
|
50122
50481
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -51203,16 +51562,16 @@ function ciAuditDeps2() {
|
|
|
51203
51562
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
51204
51563
|
readSeedFile: (path2) => {
|
|
51205
51564
|
if (!root) return null;
|
|
51206
|
-
const fullPath = (0,
|
|
51565
|
+
const fullPath = (0, import_node_path52.join)(root, path2);
|
|
51207
51566
|
return (0, import_node_fs53.existsSync)(fullPath) ? (0, import_node_fs53.readFileSync)(fullPath, "utf8") : null;
|
|
51208
51567
|
}
|
|
51209
51568
|
};
|
|
51210
51569
|
}
|
|
51211
51570
|
function hubRoot2() {
|
|
51212
|
-
const fromPkg = (0,
|
|
51571
|
+
const fromPkg = (0, import_node_path52.join)(__dirname, "..", "..");
|
|
51213
51572
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
51214
|
-
if ((0, import_node_fs53.existsSync)((0,
|
|
51215
|
-
if ((0, import_node_fs53.existsSync)((0,
|
|
51573
|
+
if ((0, import_node_fs53.existsSync)((0, import_node_path52.join)(fromPkg, marker))) return fromPkg;
|
|
51574
|
+
if ((0, import_node_fs53.existsSync)((0, import_node_path52.join)(process.cwd(), marker))) return process.cwd();
|
|
51216
51575
|
return null;
|
|
51217
51576
|
}
|
|
51218
51577
|
registerQueryCommands(program2);
|
|
@@ -51353,7 +51712,7 @@ function directoryBytes(path2) {
|
|
|
51353
51712
|
return 0;
|
|
51354
51713
|
}
|
|
51355
51714
|
for (const entry of entries) {
|
|
51356
|
-
const child2 = (0,
|
|
51715
|
+
const child2 = (0, import_node_path52.join)(path2, entry.name);
|
|
51357
51716
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
51358
51717
|
else {
|
|
51359
51718
|
try {
|
|
@@ -51383,7 +51742,7 @@ function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
|
51383
51742
|
dirBytes,
|
|
51384
51743
|
listStagingDirs: (root) => (0, import_node_fs53.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
51385
51744
|
try {
|
|
51386
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
51745
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path52.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs53.statSync)(p).mtimeMs) };
|
|
51387
51746
|
} catch {
|
|
51388
51747
|
return { name: d.name, mtimeMs: Date.now() };
|
|
51389
51748
|
}
|
|
@@ -51397,7 +51756,7 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
51397
51756
|
return {
|
|
51398
51757
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
51399
51758
|
mtimeMs: (name) => {
|
|
51400
|
-
const p = (0,
|
|
51759
|
+
const p = (0, import_node_path52.join)(stagingRoot, name);
|
|
51401
51760
|
if (!(0, import_node_fs53.existsSync)(p)) return null;
|
|
51402
51761
|
try {
|
|
51403
51762
|
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs53.statSync)(q).mtimeMs);
|
|
@@ -51420,7 +51779,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
51420
51779
|
return;
|
|
51421
51780
|
}
|
|
51422
51781
|
const plan = buildPluginCachePlan(
|
|
51423
|
-
(0,
|
|
51782
|
+
(0, import_node_os23.homedir)(),
|
|
51424
51783
|
running,
|
|
51425
51784
|
pluginCacheFsDeps(configRoot, directoryBytes),
|
|
51426
51785
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|