@mutmutco/cli 4.3.56 → 4.3.58

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.
Files changed (2) hide show
  1. package/dist/main.cjs +101 -269
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -6046,6 +6046,15 @@ var REGISTRY_FETCH_TIMEOUT_MS = 8e3;
6046
6046
  init_fetch_retry();
6047
6047
  init_client_version();
6048
6048
 
6049
+ // ../shared/schedule-inventory.ts
6050
+ function classifyLlmValue(raw) {
6051
+ const value = raw.trim().toLowerCase();
6052
+ if (/^no\b|^none\b/.test(value)) return "no";
6053
+ if (/^yes\b/.test(value)) return "yes";
6054
+ if (/^embeddings\b/.test(value)) return "embeddings";
6055
+ return "unknown";
6056
+ }
6057
+
6049
6058
  // src/workflow-context.ts
6050
6059
  function parseWorkflowJobs(yaml) {
6051
6060
  const lines2 = yaml.split(/\r?\n/);
@@ -6324,25 +6333,10 @@ function resolvedLabel(entry, now) {
6324
6333
  var ORG = "mutmutco";
6325
6334
  var DOC_START_MARKER = "<!-- schedules:inventory:start -->";
6326
6335
  var DOC_END_MARKER = "<!-- schedules:inventory:end -->";
6327
- function isJervResource(name) {
6328
- return /^jerv-memory(?:-|$)/i.test(name);
6329
- }
6330
6336
  function llmFromHeader(yamlText) {
6331
6337
  const m = /^#\s*llm:\s*(.+)$/im.exec(yamlText);
6332
6338
  return m ? classifyLlmValue(m[1]) : "unknown";
6333
6339
  }
6334
- function classifyLlmValue(raw) {
6335
- const value = raw.trim().toLowerCase();
6336
- if (/^no\b|^none\b/.test(value)) return "no";
6337
- if (/^yes\b/.test(value)) return "yes";
6338
- if (/^embeddings\b/.test(value)) return "embeddings";
6339
- return "unknown";
6340
- }
6341
- function readLlmDeclaration(text) {
6342
- if (!text) return "unknown";
6343
- const m = /\bllm:\s*(.+)/i.exec(text);
6344
- return m ? classifyLlmValue(m[1]) : "unknown";
6345
- }
6346
6340
  function extractWorkflowCrons(yamlText) {
6347
6341
  const crons = [];
6348
6342
  for (const rawLine of yamlText.split("\n")) {
@@ -6366,126 +6360,6 @@ function workflowEntry(repo, workflowPath, yamlText) {
6366
6360
  harbourUnmanaged: isHarbourUnmanaged(yamlText)
6367
6361
  };
6368
6362
  }
6369
- function awsRuleEntries(payload) {
6370
- if (!payload || typeof payload !== "object") {
6371
- throw new Error("aws events list-rules answered a non-object payload \u2014 refusing to read it as zero rules");
6372
- }
6373
- const { Rules: rules } = payload;
6374
- if (!Array.isArray(rules)) {
6375
- throw new Error("aws events list-rules answered no Rules array \u2014 refusing to read it as zero rules");
6376
- }
6377
- const entries = [];
6378
- for (const node of rules) {
6379
- if (!node || typeof node !== "object") {
6380
- throw new Error("aws events list-rules returned a non-object rule \u2014 refusing to read it as no rule");
6381
- }
6382
- const { Name, ScheduleExpression, State, Description } = node;
6383
- if (typeof Name !== "string") {
6384
- throw new Error("aws events list-rules returned a rule with no Name string \u2014 refusing to read it as no rule");
6385
- }
6386
- if (typeof ScheduleExpression !== "string") continue;
6387
- if (State !== "ENABLED" || isJervResource(Name)) continue;
6388
- entries.push({
6389
- name: Name,
6390
- cadence: ScheduleExpression,
6391
- executor: "aws-eventbridge-rule",
6392
- // An AWS rule declares `llm:` in its own Description (the shared convention) — a rule with no
6393
- // Description reads `unknown`, and stack-internal plumbing like the Lambda warmer should say
6394
- // `llm: no` there to go green.
6395
- llm: readLlmDeclaration(typeof Description === "string" ? Description : void 0),
6396
- resolved: "live",
6397
- source: `aws events rule ${Name} (eu-central-1)`
6398
- });
6399
- }
6400
- return entries;
6401
- }
6402
- function lambdaTargetFromArn(arn) {
6403
- const marker = ":function:";
6404
- const i = arn.indexOf(marker);
6405
- if (i < 0) return { functionName: arn.split(":").pop() ?? "" };
6406
- const rest = arn.slice(i + marker.length);
6407
- const q = rest.indexOf(":");
6408
- if (q < 0) return { functionName: rest };
6409
- return { functionName: rest.slice(0, q), qualifier: rest.slice(q + 1) };
6410
- }
6411
- function awsScheduleEntry(payload) {
6412
- if (!payload || typeof payload !== "object") {
6413
- throw new Error("aws scheduler get-schedule answered a non-object payload \u2014 refusing to read it as no schedule");
6414
- }
6415
- const { Name, ScheduleExpression, State, Target, Description, CreationDate } = payload;
6416
- if (typeof Name !== "string" || typeof ScheduleExpression !== "string") {
6417
- throw new Error("aws scheduler get-schedule answered no Name/ScheduleExpression strings \u2014 refusing to read it as no schedule");
6418
- }
6419
- if (State !== "ENABLED" || isJervResource(Name)) return null;
6420
- let target = "";
6421
- let qualifier;
6422
- let scheduleId;
6423
- if (Target && typeof Target === "object") {
6424
- const { Arn, Input } = Target;
6425
- if (typeof Arn === "string") {
6426
- const parsed = lambdaTargetFromArn(Arn);
6427
- target = parsed.functionName;
6428
- qualifier = parsed.qualifier;
6429
- }
6430
- if (typeof Input === "string") {
6431
- try {
6432
- const parsed = JSON.parse(Input);
6433
- if (typeof parsed?.scheduleId === "string" && parsed.scheduleId) scheduleId = parsed.scheduleId;
6434
- } catch {
6435
- }
6436
- }
6437
- }
6438
- const sourceBase = `aws scheduler schedule ${Name} (eu-central-1)`;
6439
- const source = qualifier && target ? `${sourceBase} \u2192 ${target}:${qualifier}` : sourceBase;
6440
- const armedAt = armedAtFromCreationDate(CreationDate);
6441
- return {
6442
- name: Name,
6443
- cadence: ScheduleExpression,
6444
- executor: target ? `aws-scheduler \u2192 ${target}` : "aws-scheduler",
6445
- llm: readLlmDeclaration(typeof Description === "string" ? Description : void 0),
6446
- resolved: "live",
6447
- source,
6448
- ...scheduleId ? { scheduleId } : {},
6449
- ...armedAt ? { armedAt } : {}
6450
- };
6451
- }
6452
- function armedAtFromCreationDate(creationDate) {
6453
- if (creationDate instanceof Date) {
6454
- return Number.isNaN(creationDate.getTime()) ? void 0 : creationDate.toISOString().slice(0, 10);
6455
- }
6456
- if (typeof creationDate === "string" && creationDate.trim()) {
6457
- const parsed = Date.parse(creationDate);
6458
- return Number.isNaN(parsed) ? void 0 : new Date(parsed).toISOString().slice(0, 10);
6459
- }
6460
- if (typeof creationDate === "number" && Number.isFinite(creationDate)) {
6461
- const ms = creationDate > 1e12 ? creationDate : creationDate * 1e3;
6462
- const date = new Date(ms);
6463
- return Number.isNaN(date.getTime()) ? void 0 : date.toISOString().slice(0, 10);
6464
- }
6465
- return void 0;
6466
- }
6467
- function awsScheduleRefs(payload) {
6468
- if (!payload || typeof payload !== "object") {
6469
- throw new Error("aws scheduler list-schedules answered a non-object payload \u2014 refusing to read it as zero schedules");
6470
- }
6471
- const { Schedules: schedules } = payload;
6472
- if (!Array.isArray(schedules)) {
6473
- throw new Error("aws scheduler list-schedules answered no Schedules array \u2014 refusing to read it as zero schedules");
6474
- }
6475
- const refs = [];
6476
- for (const s of schedules) {
6477
- if (!s || typeof s !== "object") {
6478
- throw new Error("aws scheduler list-schedules returned a non-object schedule \u2014 refusing to read it as no schedule");
6479
- }
6480
- const { Name, GroupName } = s;
6481
- if (typeof Name !== "string") {
6482
- throw new Error("aws scheduler list-schedules returned a schedule with no Name string \u2014 refusing to read it as no schedule");
6483
- }
6484
- if (isJervResource(Name)) continue;
6485
- refs.push(typeof GroupName === "string" && GroupName !== "default" && GroupName !== "" ? { name: Name, group: GroupName } : { name: Name });
6486
- }
6487
- return refs;
6488
- }
6489
6363
  function decorateAwsModel(entries, registry2, verdicts) {
6490
6364
  if (!registry2 || registry2.length === 0) return [...entries];
6491
6365
  const rowById = new Map(registry2.map((r) => [r.id, r]));
@@ -6960,6 +6834,24 @@ async function fetchDeployFactsBySlug(slug, deps) {
6960
6834
  return null;
6961
6835
  }
6962
6836
  }
6837
+ async function fetchScheduleInventory(deps) {
6838
+ try {
6839
+ if (!deps.baseUrl) return null;
6840
+ const token = await deps.token();
6841
+ if (!token) return null;
6842
+ const res = await retriedFetch(deps, `${deps.baseUrl.replace(/\/$/, "")}/schedules/inventory`, {
6843
+ method: "GET",
6844
+ headers: { Authorization: `Bearer ${token}` }
6845
+ });
6846
+ if (!res.ok) return null;
6847
+ const body = await res.json();
6848
+ if (!body || !Array.isArray(body.entries) || !Array.isArray(body.incomplete) || !body.incomplete.every((v) => typeof v === "string") || typeof body.schedulerRead !== "boolean" || !body.schedulerRead && body.incomplete.length === 0) return null;
6849
+ if (!body.entries.every((e) => e && typeof e.name === "string" && typeof e.cadence === "string" && typeof e.executor === "string" && typeof e.source === "string" && e.resolved === "live" && ["yes", "no", "embeddings", "unknown"].includes(e.llm) && (e.scheduleId === void 0 || typeof e.scheduleId === "string") && (e.armedAt === void 0 || typeof e.armedAt === "string"))) return null;
6850
+ return body;
6851
+ } catch {
6852
+ return null;
6853
+ }
6854
+ }
6963
6855
  async function fetchSchedulesList(deps) {
6964
6856
  if (!deps.baseUrl) return null;
6965
6857
  const token = await deps.token();
@@ -7071,6 +6963,9 @@ async function tenantReconcile(payload, deps) {
7071
6963
  async function tenantDeploy(payload, deps) {
7072
6964
  return postJson("/tenant-deploy", payload, deps, "POST", { noRetry: true, timeoutMs: TENANT_DEPLOY_TIMEOUT_MS });
7073
6965
  }
6966
+ async function releaseAnnouncement(payload, deps) {
6967
+ return postJson("/release-announcement", payload, deps, "POST", { noRetry: true, timeoutMs: 3e4 });
6968
+ }
7074
6969
  async function actionsCanary(payload, deps) {
7075
6970
  return postJson("/actions-canary", payload, deps, "POST", { noRetry: true });
7076
6971
  }
@@ -15877,10 +15772,10 @@ var rollout_plan_default = {
15877
15772
  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)."
15878
15773
  },
15879
15774
  baseline: {
15880
- version: "4.3.56",
15881
- tag: "v4.3.56",
15882
- commit: "497a375fabfc",
15883
- npm: "@mutmutco/cli@4.3.56"
15775
+ version: "4.3.58",
15776
+ tag: "v4.3.58",
15777
+ commit: "aa4958ac3926",
15778
+ npm: "@mutmutco/cli@4.3.58"
15884
15779
  },
15885
15780
  exitCriterion: "fleet-n-of-n",
15886
15781
  hubOnlyShortcut: "forbidden",
@@ -15897,14 +15792,14 @@ var rollout_plan_default = {
15897
15792
  repo: "mutmutco/mmi-hub",
15898
15793
  role: "canary",
15899
15794
  schedule: "train",
15900
- v3Target: "v4.3.56"
15795
+ v3Target: "v4.3.58"
15901
15796
  }
15902
15797
  ],
15903
15798
  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.",
15904
15799
  rollback: {
15905
15800
  independent: true,
15906
- mechanism: "npm dist-tag latest -> 4.3.56 and redeploy the Hub Lambda from tag v4.3.56 (497a375fabfc); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15907
- v3Target: "v4.3.56 (@mutmutco/cli@4.3.56, tag commit 497a375fabfc \u2014 last known-good release carrying the repo-index v4-only contract)"
15801
+ mechanism: "npm dist-tag latest -> 4.3.58 and redeploy the Hub Lambda from tag v4.3.58 (aa4958ac3926); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15802
+ v3Target: "v4.3.58 (@mutmutco/cli@4.3.58, tag commit aa4958ac3926 \u2014 last known-good release carrying the repo-index v4-only contract)"
15908
15803
  }
15909
15804
  },
15910
15805
  {
@@ -26539,6 +26434,11 @@ function rcandVersionStep(targets) {
26539
26434
  function trainPlan(command, options = {}) {
26540
26435
  const isHub = options.repo?.toLowerCase() === "mutmutco/mmi-hub";
26541
26436
  const isDirect = options.releaseTrack === "direct" || options.releaseTrack === void 0 && isHub;
26437
+ const authority = {
26438
+ label: isHub ? "verify Hub master-admin train authority" : "verify project-admin or master train authority on this repo",
26439
+ command: "mmi-cli oracle org access role <owner/repo> --json",
26440
+ gated: true
26441
+ };
26542
26442
  if (command === "rcand") {
26543
26443
  if (isDirect) {
26544
26444
  return [
@@ -26546,7 +26446,7 @@ function trainPlan(command, options = {}) {
26546
26446
  ];
26547
26447
  }
26548
26448
  return [
26549
- { label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
26449
+ authority,
26550
26450
  { label: "verify current branch is development", gated: true },
26551
26451
  rcandVersionStep(options),
26552
26452
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
@@ -26559,7 +26459,7 @@ function trainPlan(command, options = {}) {
26559
26459
  if (command === "release") {
26560
26460
  if (isDirect) {
26561
26461
  return [
26562
- { label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
26462
+ authority,
26563
26463
  { label: "verify current branch is development", gated: true },
26564
26464
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
26565
26465
  { label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
@@ -26574,7 +26474,7 @@ function trainPlan(command, options = {}) {
26574
26474
  }
26575
26475
  if (options.dev) {
26576
26476
  return [
26577
- { label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
26477
+ authority,
26578
26478
  { label: "verify current branch is development", gated: true },
26579
26479
  { label: "guard: refuse if origin/rc carries content not in development (a dev -> main release would drop it)", command: "git rev-list --count --right-only --cherry-pick --no-merges origin/development...origin/rc", gated: true },
26580
26480
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
@@ -26590,7 +26490,7 @@ function trainPlan(command, options = {}) {
26590
26490
  ];
26591
26491
  }
26592
26492
  return [
26593
- { label: "verify operator is a master-admin org owner", command: "gh api orgs/<owner>/memberships/<login> --jq .role", gated: true },
26493
+ authority,
26594
26494
  { label: "verify current branch is rc", gated: true },
26595
26495
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
26596
26496
  { label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
@@ -26605,6 +26505,7 @@ function trainPlan(command, options = {}) {
26605
26505
  ];
26606
26506
  }
26607
26507
  return [
26508
+ authority,
26608
26509
  { label: "verify the fix is merged on development (the only hotfix origin)", gated: true },
26609
26510
  // #6068: hotfix start/release run the SAME shared preflight as release/rcand, before any git mutation.
26610
26511
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
@@ -30114,7 +30015,7 @@ function buildSetDeployPatch(_slug, input) {
30114
30015
  }
30115
30016
  var DEPLOY_STAGE_BRANCH = { dev: "development", rc: "rc", main: "main" };
30116
30017
  function filelessTransitionGuide(repo, stage) {
30117
- const authority = stage === "main" ? "Authority: main is master-admin only." : "Authority: a project-admin may complete this for their own repo; master may also do it.";
30018
+ const authority = "Authority: a project-admin may complete this for their own repo; master may also do it.";
30118
30019
  return [
30119
30020
  `Fileless transition for ${repo} (${stage}):`,
30120
30021
  ` ${authority}`,
@@ -35588,15 +35489,9 @@ function writeError(res) {
35588
35489
 
35589
35490
  // src/schedules-commands.ts
35590
35491
  var import_promises5 = require("node:fs/promises");
35591
- var import_node_child_process11 = require("node:child_process");
35592
- var import_node_util6 = require("node:util");
35593
35492
  init_clean_exit();
35594
35493
  init_github_client();
35595
35494
  init_cli_shared();
35596
- var execFileP4 = (0, import_node_util6.promisify)(import_node_child_process11.execFile);
35597
- var AWS_REGION = "eu-central-1";
35598
- var AWS_TIMEOUT_MS = 3e4;
35599
- var AWS_RETRY_DELAY_MS = 1500;
35600
35495
  async function listWorkflows(client, repo) {
35601
35496
  const all = [];
35602
35497
  for (let page = 1; ; page += 1) {
@@ -35693,44 +35588,11 @@ async function githubEntries(client) {
35693
35588
  drift.push(...reconciliation.map(renderDrift));
35694
35589
  return { entries, incomplete, drift, reconciliation, readRepos, workflowNames: [...workflows.map((w) => w.name), ...unreadableNames], disabledWorkflowNames };
35695
35590
  }
35696
- async function awsJson(args) {
35697
- const run = async () => {
35698
- const { stdout } = await execFileP4("aws", [...args, "--region", AWS_REGION, "--output", "json"], {
35699
- encoding: "utf8",
35700
- windowsHide: true,
35701
- timeout: AWS_TIMEOUT_MS
35702
- });
35703
- return JSON.parse(stdout);
35704
- };
35705
- try {
35706
- return await run();
35707
- } catch {
35708
- await new Promise((resolve7) => setTimeout(resolve7, AWS_RETRY_DELAY_MS));
35709
- return run();
35710
- }
35711
- }
35712
35591
  async function awsEntries() {
35713
- const entries = [];
35714
- const incomplete = [];
35715
- try {
35716
- entries.push(...awsRuleEntries(await awsJson(["events", "list-rules"])));
35717
- } catch (e) {
35718
- incomplete.push(`aws: events list-rules failed \u2014 ${e.message}`);
35719
- }
35720
- let schedulerRead = false;
35721
- try {
35722
- const refs = awsScheduleRefs(await awsJson(["scheduler", "list-schedules"]));
35723
- for (const ref of refs) {
35724
- const args = ["scheduler", "get-schedule", "--name", ref.name, ...ref.group ? ["--group-name", ref.group] : []];
35725
- const entry = awsScheduleEntry(await awsJson(args));
35726
- if (entry) entries.push(entry);
35727
- }
35728
- schedulerRead = true;
35729
- } catch (e) {
35730
- incomplete.push(`aws: scheduler listing failed \u2014 ${e.message}`);
35731
- }
35732
- const reconciliation = unlauncheredLlmDrifts(entries);
35733
- return { entries, incomplete, drift: reconciliation.map(renderDrift), reconciliation, schedulerRead };
35592
+ const inventory = await fetchScheduleInventory(registryClientDeps(await loadConfig()));
35593
+ if (!inventory) return { entries: [], incomplete: ["aws: Hub schedule inventory unavailable or unauthenticated"], drift: [], reconciliation: [], schedulerRead: false };
35594
+ const reconciliation = unlauncheredLlmDrifts(inventory.entries);
35595
+ return { ...inventory, reconciliation, drift: reconciliation.map(renderDrift) };
35734
35596
  }
35735
35597
  async function readOrFail(read) {
35736
35598
  try {
@@ -36587,8 +36449,13 @@ async function resolveStageBuildSecrets(input) {
36587
36449
  out[envKey] = fromVault;
36588
36450
  continue;
36589
36451
  }
36452
+ const fromGitHub = await input.githubToken?.();
36453
+ if (fromGitHub) {
36454
+ out[envKey] = fromGitHub;
36455
+ continue;
36456
+ }
36590
36457
  missing.push(
36591
- `${envKey}=${GITHUB_PACKAGES_TOKEN_REF} (central deploy mints this; local /stage needs process env ${envKey} or a stageless project vault secret ${envKey} \u2014 a GitHub PAT with read:packages for npm.pkg.github.com; declare+set via vault secrets, never commit the value)`
36458
+ `${envKey}=${GITHUB_PACKAGES_TOKEN_REF} (sign in with gh auth login using a package-readable GitHub identity, or provide process env ${envKey} or stageless project vault secret ${envKey}; npm.pkg.github.com still requires package read permission)`
36592
36459
  );
36593
36460
  continue;
36594
36461
  }
@@ -36622,6 +36489,7 @@ async function fetchVaultRef(fetchVault, ref) {
36622
36489
  }
36623
36490
 
36624
36491
  // src/stage-commands.ts
36492
+ init_github_client();
36625
36493
  function registerStageCommands(program3) {
36626
36494
  function stagePortFromArgv() {
36627
36495
  const raw = rawValue("--port", "");
@@ -36681,7 +36549,8 @@ function registerStageCommands(program3) {
36681
36549
  const d = makeSecretsDeps(cfg);
36682
36550
  const merge = await resolveStageBuildSecrets({
36683
36551
  requiredBuildSecrets: required,
36684
- fetchVault: (key, opts) => fetchSecretValue(d, key, opts ?? {})
36552
+ fetchVault: (key, opts) => fetchSecretValue(d, key, opts ?? {}),
36553
+ githubToken
36685
36554
  });
36686
36555
  return Object.keys(merge).length ? merge : void 0;
36687
36556
  }
@@ -37083,7 +36952,7 @@ function renderVerifySecrets(body) {
37083
36952
  }
37084
36953
 
37085
36954
  // src/command-register-collaboration.ts
37086
- var import_node_child_process16 = require("node:child_process");
36955
+ var import_node_child_process15 = require("node:child_process");
37087
36956
  var import_node_fs41 = require("node:fs");
37088
36957
  var import_promises7 = require("node:fs/promises");
37089
36958
  init_clean_exit();
@@ -37469,12 +37338,12 @@ function boardAdvanceFailureMessage(result) {
37469
37338
  }
37470
37339
 
37471
37340
  // src/test-policy-core.ts
37472
- var import_node_child_process13 = require("node:child_process");
37341
+ var import_node_child_process12 = require("node:child_process");
37473
37342
  var import_node_fs35 = require("node:fs");
37474
37343
  var import_node_path33 = require("node:path");
37475
37344
 
37476
37345
  // src/test-command-policy-shared.mjs
37477
- var import_node_child_process12 = require("node:child_process");
37346
+ var import_node_child_process11 = require("node:child_process");
37478
37347
  var TEST_COMMAND_CLASS = "test";
37479
37348
  var TRAILER_KEY = "Test-Policy-Override";
37480
37349
  var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
@@ -37591,7 +37460,7 @@ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, overrid
37591
37460
  };
37592
37461
  }
37593
37462
  function git(args, cwd) {
37594
- return (0, import_node_child_process12.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
37463
+ return (0, import_node_child_process11.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
37595
37464
  }
37596
37465
  function parseScope(value) {
37597
37466
  const scoped = /^\[([^\]]*)\]\s*([\s\S]*)$/.exec(value);
@@ -38090,14 +37959,14 @@ function evaluate(changed, policy, present = () => false) {
38090
37959
  return findings;
38091
37960
  }
38092
37961
  function git2(args, cwd) {
38093
- return (0, import_node_child_process13.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
37962
+ return (0, import_node_child_process12.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
38094
37963
  }
38095
37964
  var COAUTHOR_KEY = "Co-authored-by";
38096
37965
  var GH_MESSAGE_SEPARATOR = /^-{5,}$/;
38097
37966
  var LIFTED_KEYS = new RegExp(`^(?:${TRAILER_KEY}|${COAUTHOR_KEY}):`, "i");
38098
37967
  function parseTrailers(message2, cwd) {
38099
37968
  try {
38100
- return (0, import_node_child_process13.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
37969
+ return (0, import_node_child_process12.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
38101
37970
  windowsHide: true,
38102
37971
  cwd,
38103
37972
  input: message2,
@@ -38853,7 +38722,7 @@ function postMergeReconWarnings(input) {
38853
38722
  }
38854
38723
 
38855
38724
  // src/review-verdict.ts
38856
- var import_node_child_process14 = require("node:child_process");
38725
+ var import_node_child_process13 = require("node:child_process");
38857
38726
  var import_node_fs38 = require("node:fs");
38858
38727
  var import_node_os19 = require("node:os");
38859
38728
  var import_node_path36 = require("node:path");
@@ -38953,8 +38822,8 @@ async function checkPrReview(number, repo, head, deps = {
38953
38822
  }
38954
38823
  function computePrPatchId(number, repo) {
38955
38824
  return new Promise((resolve7, reject) => {
38956
- const gh = (0, import_node_child_process14.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
38957
- const git3 = (0, import_node_child_process14.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
38825
+ const gh = (0, import_node_child_process13.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
38826
+ const git3 = (0, import_node_child_process13.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
38958
38827
  let out = "";
38959
38828
  let ghErr = "";
38960
38829
  let gitErr = "";
@@ -39036,7 +38905,7 @@ async function postPrCommentFromFile(number, repo, body) {
39036
38905
  }
39037
38906
 
39038
38907
  // src/pr-create-docs-check.ts
39039
- var import_node_child_process15 = require("node:child_process");
38908
+ var import_node_child_process14 = require("node:child_process");
39040
38909
  init_cli_shared();
39041
38910
  var GIT_TIMEOUT_MS2 = 15e3;
39042
38911
  function catFileBatch(root, ref, paths) {
@@ -39044,7 +38913,7 @@ function catFileBatch(root, ref, paths) {
39044
38913
  return new Promise((resolve7) => {
39045
38914
  const chunks = [];
39046
38915
  let settled = false;
39047
- const child2 = (0, import_node_child_process15.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
38916
+ const child2 = (0, import_node_child_process14.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
39048
38917
  const finish = () => {
39049
38918
  if (settled) return;
39050
38919
  settled = true;
@@ -40191,7 +40060,7 @@ function scheduleRelatedDiscovery(o) {
40191
40060
  try {
40192
40061
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
40193
40062
  if (o.repo) args.push("--repo", o.repo);
40194
- spawnDetachedSelf(args, { spawn: import_node_child_process16.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
40063
+ spawnDetachedSelf(args, { spawn: import_node_child_process15.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
40195
40064
  } catch {
40196
40065
  }
40197
40066
  }
@@ -41887,7 +41756,7 @@ ${SSH_RECIPE_AGENT_NOTE}`);
41887
41756
  }
41888
41757
 
41889
41758
  // src/dist-drift.ts
41890
- var import_node_child_process17 = require("node:child_process");
41759
+ var import_node_child_process16 = require("node:child_process");
41891
41760
  var import_node_crypto13 = require("node:crypto");
41892
41761
  var import_node_fs44 = require("node:fs");
41893
41762
  var import_node_os20 = require("node:os");
@@ -42032,7 +41901,7 @@ function bomPathFor(root) {
42032
41901
  }
42033
41902
  }
42034
41903
  function rebuildTo(packageRoot, outDir) {
42035
- (0, import_node_child_process17.execFileSync)(process.execPath, ["build.mjs"], {
41904
+ (0, import_node_child_process16.execFileSync)(process.execPath, ["build.mjs"], {
42036
41905
  cwd: packageRoot,
42037
41906
  env: { ...process.env, MMI_DIST_OUTDIR: outDir },
42038
41907
  windowsHide: true,
@@ -42259,7 +42128,7 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
42259
42128
  }
42260
42129
 
42261
42130
  // src/spawn-policy-core.ts
42262
- var import_node_child_process18 = require("node:child_process");
42131
+ var import_node_child_process17 = require("node:child_process");
42263
42132
  var import_node_fs45 = require("node:fs");
42264
42133
  var import_node_path43 = require("node:path");
42265
42134
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
@@ -42330,7 +42199,7 @@ function findViolationsInSource(raw) {
42330
42199
  return found;
42331
42200
  }
42332
42201
  function policedFiles(root) {
42333
- const r = (0, import_node_child_process18.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
42202
+ const r = (0, import_node_child_process17.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
42334
42203
  cwd: root,
42335
42204
  encoding: "utf8",
42336
42205
  windowsHide: true,
@@ -42636,50 +42505,6 @@ init_house_map();
42636
42505
  // src/hotfix-apply.ts
42637
42506
  var import_promises9 = require("node:fs/promises");
42638
42507
 
42639
- // src/slack-alert.ts
42640
- var SSM_REGION = "eu-central-1";
42641
- var SSM_TOKEN_PARAM = "/mmi-future/_org/slack/SLACK_BOT_TOKEN";
42642
- var SSM_CHANNEL_PARAM = "/mmi-future/_org/slack/SLACK_ALERTS_CHANNEL";
42643
- var SLACK_TIMEOUT_MS = 1e4;
42644
- async function readSsmParameter(deps, name, decrypt) {
42645
- const args = [
42646
- "ssm",
42647
- "get-parameter",
42648
- "--region",
42649
- SSM_REGION,
42650
- "--name",
42651
- name,
42652
- "--query",
42653
- "Parameter.Value",
42654
- "--output",
42655
- "text",
42656
- ...decrypt ? ["--with-decryption"] : []
42657
- ];
42658
- const value = (await deps.run("aws", args)).trim();
42659
- if (!value || value === "None") throw new Error(`SSM parameter ${name} is empty`);
42660
- return value;
42661
- }
42662
- async function postToChannel(deps, channel, text) {
42663
- const resolved = channel.trim();
42664
- if (!resolved || resolved === "None") throw new Error("no Slack channel resolved");
42665
- const token = await readSsmParameter(deps, SSM_TOKEN_PARAM, true);
42666
- const fetchImpl = deps.fetchImpl ?? fetch;
42667
- const res = await fetchImpl("https://slack.com/api/chat.postMessage", {
42668
- method: "POST",
42669
- headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json; charset=utf-8" },
42670
- body: JSON.stringify({ channel: resolved, text, unfurl_links: false }),
42671
- signal: AbortSignal.timeout(SLACK_TIMEOUT_MS)
42672
- });
42673
- const json = await res.json().catch(() => ({}));
42674
- if (!json.ok) throw new Error(`slack postMessage failed: ${json.error ?? `http ${res.status}`}`);
42675
- return { status: "posted", note: "posted" };
42676
- }
42677
- async function postToAlertsChannel(deps, text) {
42678
- const channel = await readSsmParameter(deps, SSM_CHANNEL_PARAM, false);
42679
- await postToChannel(deps, channel, text);
42680
- return { status: "posted", note: "posted to the alerts channel" };
42681
- }
42682
-
42683
42508
  // src/release-announce.ts
42684
42509
  var ANNOUNCE_REPO = "mutmutco/MMI-Hub";
42685
42510
  var MAX_BULLETS = 6;
@@ -42786,12 +42611,6 @@ async function announceRelease(deps, args) {
42786
42611
  if (args.summaryFile) {
42787
42612
  if (!deps.readFile) throw new Error("summary file given but deps.readFile is missing");
42788
42613
  lines2 = summaryFileLines(await deps.readFile(args.summaryFile), neutralize);
42789
- if (deps.removeFile) {
42790
- try {
42791
- await deps.removeFile(args.summaryFile);
42792
- } catch {
42793
- }
42794
- }
42795
42614
  }
42796
42615
  if (lines2.length === 0) {
42797
42616
  if (target.kind !== "alerts") {
@@ -42807,12 +42626,16 @@ async function announceRelease(deps, args) {
42807
42626
  releaseUrl,
42808
42627
  ...target.kind === "channel" ? { language: target.language } : {}
42809
42628
  });
42810
- if (target.kind === "alerts") {
42811
- await postToAlertsChannel(deps, text);
42812
- return { status: "announced", note: `announced ${args.tag} to the alerts channel` };
42629
+ const delivery = await deps.postAnnouncement({ repo: args.repo, tag: args.tag, text });
42630
+ if (delivery === "skipped") return { status: "skipped", note: missingChannelNote(args.repo) };
42631
+ if (args.summaryFile && deps.removeFile) {
42632
+ try {
42633
+ await deps.removeFile(args.summaryFile);
42634
+ } catch {
42635
+ }
42813
42636
  }
42814
- await postToChannel(deps, target.channel, text);
42815
- return { status: "announced", note: `announced ${args.tag} to the project release channel` };
42637
+ const destination = target.kind === "alerts" ? "alerts channel" : "project release channel";
42638
+ return { status: "announced", note: `${delivery === "already-posted" ? "already announced" : "announced"} ${args.tag} to the ${destination}` };
42816
42639
  } catch (e) {
42817
42640
  return { status: "failed", note: `announce failed (release unaffected): ${e.message}` };
42818
42641
  }
@@ -43939,12 +43762,12 @@ async function findInFlightHotfixVersion(deps, ctx, latestMainTag, workflows = H
43939
43762
  }
43940
43763
 
43941
43764
  // src/hotfix-coverage.ts
43942
- var import_node_child_process19 = require("node:child_process");
43765
+ var import_node_child_process18 = require("node:child_process");
43943
43766
  var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
43944
43767
  function checkHotfixCoverage(options = {}) {
43945
43768
  const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
43946
43769
  const ack = (options.ack ?? []).filter(Boolean);
43947
- const git3 = options.git ?? ((args, opts) => (0, import_node_child_process19.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
43770
+ const git3 = options.git ?? ((args, opts) => (0, import_node_child_process18.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
43948
43771
  const revList = (range) => {
43949
43772
  const out = git3(["rev-list", "--no-merges", range]).trim();
43950
43773
  return out ? out.split("\n") : [];
@@ -44012,7 +43835,7 @@ function checkHotfixCoverage(options = {}) {
44012
43835
  }
44013
43836
  function checkHotfixCarries(options) {
44014
43837
  const { cwd = process.cwd(), branch, baseRef, targets } = options;
44015
- const git3 = options.git ?? ((args, opts) => (0, import_node_child_process19.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
43838
+ const git3 = options.git ?? ((args, opts) => (0, import_node_child_process18.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
44016
43839
  const isAncestor = (sha, ref) => {
44017
43840
  try {
44018
43841
  git3(["merge-base", "--is-ancestor", sha, ref]);
@@ -44282,6 +44105,15 @@ function trainApplyDeps() {
44282
44105
  // registry releaseChannel META for a product repo — best-effort inside announceRelease itself.
44283
44106
  announce: (args) => announceRelease({
44284
44107
  run: async (file, cmdArgs) => (await execFileP(file, cmdArgs, { timeout: GH_TRAIN_TIMEOUT_MS })).stdout,
44108
+ postAnnouncement: async (payload) => {
44109
+ const res = await releaseAnnouncement(payload, registryClientDeps(await loadConfig()));
44110
+ const body = res.body;
44111
+ if (!res.ok) throw new Error(body?.error ?? `Hub announcement failed (HTTP ${res.status}); verify delivery before retrying`);
44112
+ if (body?.ok !== true || body.status !== "posted" && body?.status !== "already-posted" && body?.status !== "skipped") {
44113
+ throw new Error("Hub announcement returned an unverified delivery result; do not resend");
44114
+ }
44115
+ return body.status;
44116
+ },
44285
44117
  readFile: (path2) => (0, import_promises10.readFile)(path2, "utf8"),
44286
44118
  removeFile: (path2) => (0, import_promises10.unlink)(path2),
44287
44119
  // #6521/#6523: the project's own release channel AND releaseLanguage, read from registry META in ONE
@@ -44573,7 +44405,7 @@ function registerTrainCommandsAndInventory(program3) {
44573
44405
  }
44574
44406
  function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
44575
44407
  for (const commandName of ["rcand", "release"]) {
44576
- const trainCmd = program3.command(commandName).description(`plan ${commandName} train operations; mutations require explicit master-admin approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded master-only train after explicit approval").option("--resume", commandName === "rcand" ? "finish a candidate whose immutable public rc tag passed policy but origin/rc was not pushed (#3881)" : "finish a partial release or its protected post-release alignment without re-cutting, republishing, or redeploying (#3851/#3885)").option("--out <path>", "write the terminal result (--apply, --resume, --abort, --retry-publish) to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)");
44408
+ const trainCmd = program3.command(commandName).description(`plan ${commandName} train operations; mutations require explicit project-admin or master approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded repo-authorized train after explicit approval (Hub: master only)").option("--resume", commandName === "rcand" ? "finish a candidate whose immutable public rc tag passed policy but origin/rc was not pushed (#3881)" : "finish a partial release or its protected post-release alignment without re-cutting, republishing, or redeploying (#3851/#3885)").option("--out <path>", "write the terminal result (--apply, --resume, --abort, --retry-publish) to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)");
44577
44409
  const RELEASE_ONLY_FLAGS = [
44578
44410
  { flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line release Slack summary; required for a new MMI-Hub --apply (#883/#3901) and for a product repo whose registry META sets `releaseChannel` (#6523), in its `releaseLanguage`; nothing is posted to a project channel without it" },
44579
44411
  { flags: "--ack <shas>", description: "comma-separated dev shas a human verified are in the candidate, overriding the hotfix-coverage guard for a conflicted port whose -x trailer was lost (#958)" },
@@ -45914,8 +45746,8 @@ ${filelessTransitionGuide(target, o.stage)}`
45914
45746
  }
45915
45747
  return reportWrite("org project set-deploy", res);
45916
45748
  });
45917
- var projectTenantTasks = project.command("tenant-tasks").description("project-admin self-declare of tenant tasks for your own repo's dev/rc stages (#5316, #5308) \u2014 new-task-only, dev/rc-only; editing an existing task or declaring a main-stage task stays master-only (`org project set --var tenantTasks=...`)");
45918
- projectTenantTasks.command("declare <name>").description('register a NEW tenant task \u2014 becomes runnable via `runtime tenant control \u2026 run-task` on dev/rc. Body shape {service,command[],stages[],timeoutSeconds?,artifact?} \u2014 stages limited to dev/rc here; artifact "required" tasks must carry {artifact} in the command argv.').option("--service <name>", "service in the tenant runtime the task runs in (required unless --var carries it)").option("--command <argv...>", "task command as argv (repeatable; required unless --var carries it)").option("--stages <list>", "comma-separated dev,rc stages this task may run on (required unless --var carries it)").option("--timeout-seconds <n>", "optional wall-clock ceiling 1..3600").option("--artifact <none|required>", "artifact contract (default none; required tasks must contain {artifact} in argv)").option("--var <json>", "full task object {service,command[],stages[],timeoutSeconds?,artifact?} \u2014 use instead of the individual flags").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action(async (name, o) => {
45749
+ var projectTenantTasks = project.command("tenant-tasks").description("project-admin self-declare of tenant tasks for every stage of your own repo \u2014 new-task-only; editing an existing task stays master-only (`org project set --var tenantTasks=...`)");
45750
+ projectTenantTasks.command("declare <name>").description("register a NEW tenant task \u2014 becomes runnable via `runtime tenant control \u2026 run-task` on its declared dev/rc/main stages. Body shape {service,command[],stages[],timeoutSeconds?,artifact?}; required artifact tasks must carry {artifact} in the command argv.").option("--service <name>", "service in the tenant runtime the task runs in (required unless --var carries it)").option("--command <argv...>", "task command as argv (repeatable; required unless --var carries it)").option("--stages <list>", "comma-separated dev,rc,main stages this task may run on (required unless --var carries it)").option("--timeout-seconds <n>", "optional wall-clock ceiling 1..3600").option("--artifact <none|required>", "artifact contract (default none; required tasks must contain {artifact} in argv)").option("--var <json>", "full task object {service,command[],stages[],timeoutSeconds?,artifact?} \u2014 use instead of the individual flags").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action(async (name, o) => {
45919
45751
  const cfg = await loadConfig();
45920
45752
  let target;
45921
45753
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.3.56",
3
+ "version": "4.3.58",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",