@mutmutco/cli 3.139.1 → 3.139.2

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 CHANGED
@@ -7078,9 +7078,15 @@ function gateSeedVars(cls, releaseTrack, runtime = "node") {
7078
7078
  GATE_BUDGET_SHA: BLESSED_RUN_WITH_BUDGET_SHA
7079
7079
  };
7080
7080
  const track = releaseTrack ?? (cls === "content" ? "trunk" : "full");
7081
+ const windowsCompat = {
7082
+ // #5113: opt-in informational windows-latest proof. Default OFF — the CLI fills the rendered job
7083
+ // YAML (or '') at the final layering step; never hand-passed.
7084
+ GATE_WINDOWS_COMPAT_JOB_YAML: ""
7085
+ };
7081
7086
  if (track === "trunk") {
7082
7087
  return {
7083
7088
  ...runtimeVars,
7089
+ ...windowsCompat,
7084
7090
  GATE_PUSH_BRANCHES_YAML: "[main]",
7085
7091
  GATE_FULL_RUN_BRANCH: "main",
7086
7092
  GATE_RULESET_BRANCH_REFS_JSON: '["refs/heads/main"]'
@@ -7089,6 +7095,7 @@ function gateSeedVars(cls, releaseTrack, runtime = "node") {
7089
7095
  if (track === "direct") {
7090
7096
  return {
7091
7097
  ...runtimeVars,
7098
+ ...windowsCompat,
7092
7099
  GATE_PUSH_BRANCHES_YAML: "[development, main]",
7093
7100
  GATE_FULL_RUN_BRANCH: "development",
7094
7101
  GATE_RULESET_BRANCH_REFS_JSON: '["refs/heads/development", "refs/heads/main"]'
@@ -7096,6 +7103,7 @@ function gateSeedVars(cls, releaseTrack, runtime = "node") {
7096
7103
  }
7097
7104
  return {
7098
7105
  ...runtimeVars,
7106
+ ...windowsCompat,
7099
7107
  GATE_PUSH_BRANCHES_YAML: "[development, rc, main]",
7100
7108
  GATE_FULL_RUN_BRANCH: "development",
7101
7109
  GATE_RULESET_BRANCH_REFS_JSON: '["refs/heads/development", "refs/heads/rc", "refs/heads/main"]'
@@ -7112,8 +7120,38 @@ function withDerivedRepoVars(vars, parsed, cls, releaseTrack) {
7112
7120
  for (const [key, value] of Object.entries(gateSeedVars(cls, track, runtime))) {
7113
7121
  out[key] ??= value;
7114
7122
  }
7123
+ if (out.GATE_WINDOWS_COMPAT === "true" && !out.GATE_WINDOWS_COMPAT_JOB_YAML) {
7124
+ out.GATE_WINDOWS_COMPAT_JOB_YAML = windowsCompatJobYaml(out);
7125
+ }
7115
7126
  return out;
7116
7127
  }
7128
+ function windowsCompatJobYaml(vars) {
7129
+ const workdir = vars.GATE_WORKDIR ?? ".";
7130
+ const cmd = vars.GATE_CMD ?? DEFAULT_GATE_CMD;
7131
+ const install = vars.GATE_INSTALL_CMD ?? "npm ci";
7132
+ const fullRunBranch = vars.GATE_FULL_RUN_BRANCH ?? "development";
7133
+ const runtime = vars.GATE_RUNTIME === "python" ? "python" : "node";
7134
+ const setup = runtime === "python" ? ` - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
7135
+ with: { python-version: '${vars.GATE_PY_VERSION ?? DEFAULT_GATE_PY_VERSION}' }` : ` - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
7136
+ with: { node-version: 24, cache: npm, cache-dependency-path: ${vars.GATE_CACHE_DEP_PATH ?? "package-lock.json"} }`;
7137
+ return ` # MMI-Hub#5113: opt-in Windows compatibility proof \u2014 informational only. Do NOT add this job to a
7138
+ # required-contexts ruleset without a deliberate repo decision: the required gate stays the Linux lane
7139
+ # (faster, cheaper, where autonomous agents run). GitHub-hosted Windows minutes cost more than Linux,
7140
+ # hence opt-in. defaults.run.shell=bash is Git for Windows bash on windows-latest, so the check syntax
7141
+ # the Linux gate runs keeps working here.
7142
+ windows-compat:
7143
+ if: \${{ github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref_name == '${fullRunBranch}' || github.ref_name == 'main')) }}
7144
+ runs-on: windows-latest
7145
+ defaults:
7146
+ run: { working-directory: ${workdir}, shell: bash }
7147
+ steps:
7148
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
7149
+ ${setup}
7150
+ - run: ${install}
7151
+ # Fast proof only \u2014 the full suite stays on the Linux gate.
7152
+ - run: ${cmd}
7153
+ `;
7154
+ }
7117
7155
  function gateConfigToVars(gate) {
7118
7156
  const out = {};
7119
7157
  if (!gate || typeof gate !== "object") return out;
@@ -7124,6 +7162,7 @@ function gateConfigToVars(gate) {
7124
7162
  if (typeof gate.pyVersion === "string" && gate.pyVersion.trim()) out.GATE_PY_VERSION = gate.pyVersion;
7125
7163
  const seconds = typeof gate.maxSeconds === "number" ? String(gate.maxSeconds) : gate.maxSeconds;
7126
7164
  if (typeof seconds === "string" && /^\d+$/.test(seconds.trim()) && Number(seconds) > 0) out.GATE_MAX_SECONDS = seconds.trim();
7165
+ if (gate.windowsCompat === true) out.GATE_WINDOWS_COMPAT = "true";
7127
7166
  return out;
7128
7167
  }
7129
7168
  function seedMatchesDeployModel(seed, deployModel) {
@@ -10707,10 +10746,10 @@ var rollout_plan_default = {
10707
10746
  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)."
10708
10747
  },
10709
10748
  baseline: {
10710
- version: "3.139.1",
10711
- tag: "v3.139.1",
10712
- commit: "882483d74da4",
10713
- npm: "@mutmutco/cli@3.139.1"
10749
+ version: "3.139.2",
10750
+ tag: "v3.139.2",
10751
+ commit: "1e69c6815490",
10752
+ npm: "@mutmutco/cli@3.139.2"
10714
10753
  },
10715
10754
  exitCriterion: "fleet-n-of-n",
10716
10755
  hubOnlyShortcut: "forbidden",
@@ -10727,14 +10766,14 @@ var rollout_plan_default = {
10727
10766
  repo: "mutmutco/mmi-hub",
10728
10767
  role: "canary",
10729
10768
  schedule: "train",
10730
- v3Target: "v3.139.1"
10769
+ v3Target: "v3.139.2"
10731
10770
  }
10732
10771
  ],
10733
10772
  rollbackTrigger: "Any red inside the post-cut soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a v3 client refused while the compat window must still admit it (SUPPORTED_MINOR_WINDOW=2, MIN_CLIENT_VERSION 0.0.0 \u2014 D6a), or npm consumer install/doctor failure on the v4 dist.",
10734
10773
  rollback: {
10735
10774
  independent: true,
10736
- mechanism: "npm dist-tag latest -> 3.139.1 and redeploy the Hub Lambda from tag v3.139.1 (882483d74da4); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
10737
- v3Target: "v3.139.1 (@mutmutco/cli@3.139.1, tag commit 882483d74da4 \u2014 the preserved latest-v3 distribution, D6b)"
10775
+ mechanism: "npm dist-tag latest -> 3.139.2 and redeploy the Hub Lambda from tag v3.139.2 (1e69c6815490); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
10776
+ v3Target: "v3.139.2 (@mutmutco/cli@3.139.2, tag commit 1e69c6815490 \u2014 the preserved latest-v3 distribution, D6b)"
10738
10777
  }
10739
10778
  },
10740
10779
  {
@@ -22696,8 +22735,9 @@ var import_node_crypto5 = require("node:crypto");
22696
22735
  var import_node_child_process12 = require("node:child_process");
22697
22736
  var import_node_fs23 = require("node:fs");
22698
22737
  var import_node_path21 = require("node:path");
22699
- var REPO_INDEX_SCHEMA = 1;
22700
- var HARD_DENY = [
22738
+
22739
+ // ../infra/repo-index-path-policy.mjs
22740
+ var HARD_DENY = Object.freeze([
22701
22741
  /(^|\/)\.env(\.|$)/i,
22702
22742
  /(^|\/)\.env\./i,
22703
22743
  /credentials/i,
@@ -22708,7 +22748,20 @@ var HARD_DENY = [
22708
22748
  /(^|\/)id_rsa/i,
22709
22749
  /(^|\/)id_ed25519/i,
22710
22750
  /\.keystore$/i
22711
- ];
22751
+ ]);
22752
+ function isHardDeniedRepoIndexPath(value) {
22753
+ return typeof value !== "string" || HARD_DENY.some((pattern) => pattern.test(value));
22754
+ }
22755
+ function isSafeRepoIndexPath(value) {
22756
+ if (typeof value !== "string" || !value || value.length > 1024) return false;
22757
+ if (value.startsWith("/") || value.includes("\\") || /[\u0000-\u001f\u007f]/u.test(value)) return false;
22758
+ const segments = value.split("/");
22759
+ if (segments.some((segment) => !segment || segment === "." || segment === "..")) return false;
22760
+ return !isHardDeniedRepoIndexPath(value);
22761
+ }
22762
+
22763
+ // src/repo-index.ts
22764
+ var REPO_INDEX_SCHEMA = 1;
22712
22765
  var INDEXABLE_EXT = /* @__PURE__ */ new Set([
22713
22766
  ".ts",
22714
22767
  ".tsx",
@@ -22742,10 +22795,10 @@ function repoIndexStorePath(cwd) {
22742
22795
  return repoRuntimeStatePath(cwd, "repo-index", "index.json");
22743
22796
  }
22744
22797
  function isHardDeniedPath(relPosix) {
22745
- return HARD_DENY.some((re) => re.test(relPosix));
22798
+ return isHardDeniedRepoIndexPath(relPosix);
22746
22799
  }
22747
22800
  function isIndexablePath(relPosix) {
22748
- if (isHardDeniedPath(relPosix)) return false;
22801
+ if (!isSafeRepoIndexPath(relPosix)) return false;
22749
22802
  if (relPosix.startsWith(".git/")) return false;
22750
22803
  if (relPosix.includes("node_modules/")) return false;
22751
22804
  if (relPosix.includes("dist/")) return false;
@@ -34335,6 +34388,39 @@ function planGitignore(current) {
34335
34388
  const { content, changed } = upsertManagedGitignoreBlock(current);
34336
34389
  return changed ? { ok: false, content } : { ok: true };
34337
34390
  }
34391
+ function checkLineEndings(probe) {
34392
+ if (probe.error) {
34393
+ return {
34394
+ ok: true,
34395
+ verified: false,
34396
+ id: "line-endings",
34397
+ label: "line endings",
34398
+ detail: `could not inspect tracked shell scripts: ${probe.error}`,
34399
+ fix: "run `git ls-files --eol -- '*.sh'` from the repository root, then re-run `mmi-cli doctor`"
34400
+ };
34401
+ }
34402
+ const problems = [
34403
+ ...probe.attributesPresent ? [] : [".gitattributes is missing"],
34404
+ ...probe.crlfShellScripts.map((path2) => `${path2} is CRLF in the index`)
34405
+ ];
34406
+ if (!problems.length) {
34407
+ return {
34408
+ ok: true,
34409
+ id: "line-endings",
34410
+ label: "line endings",
34411
+ detail: ".gitattributes present; tracked *.sh index entries are LF"
34412
+ };
34413
+ }
34414
+ return {
34415
+ ok: false,
34416
+ id: "line-endings",
34417
+ label: "line endings",
34418
+ detail: problems.join("; "),
34419
+ fix: "add the canonical .gitattributes if missing, then run `git add --renormalize .`",
34420
+ command: "git add --renormalize .",
34421
+ verbose: probe.crlfShellScripts.map((path2) => `i/crlf: ${path2}`)
34422
+ };
34423
+ }
34338
34424
  async function runDoctorClean(opts, io, deps) {
34339
34425
  const full = !opts.fast && !opts.banner && !opts.preflight;
34340
34426
  const applyEnv = full;
@@ -34468,6 +34554,11 @@ async function runDoctorClean(opts, io, deps) {
34468
34554
  const aws = checkAwsIdentity({ isOrgRepo, probed: probeAws, callerArn });
34469
34555
  if (aws) emitNow(aws);
34470
34556
  }
34557
+ async function runLineEndingsRow() {
34558
+ const root = await deps.repoRoot();
34559
+ const state = deps.lineEndingState?.(root);
34560
+ if (state) emitNow(checkLineEndings(state));
34561
+ }
34471
34562
  async function runGitignoreRow() {
34472
34563
  const current = deps.readGitignore();
34473
34564
  const gi = planGitignore(current);
@@ -34851,6 +34942,7 @@ async function runDoctorClean(opts, io, deps) {
34851
34942
  { id: "aws-identity", when: true, run: runAwsRow },
34852
34943
  { id: "sessionstart-payload", when: true, run: runSessionPayloadRow },
34853
34944
  { id: "claude-binary", when: true, run: runClaudeBinaryRow },
34945
+ { id: "line-endings", when: isOrgRepo, run: runLineEndingsRow },
34854
34946
  { id: "gitignore-block", when: isOrgRepo, run: runGitignoreRow }
34855
34947
  ];
34856
34948
  const maxPasses = 3;
@@ -35143,6 +35235,20 @@ function writeGitignore(content) {
35143
35235
  return false;
35144
35236
  }
35145
35237
  }
35238
+ function lineEndingState(root) {
35239
+ const attributesPresent = (0, import_node_fs41.existsSync)((0, import_node_path38.join)(root, ".gitattributes"));
35240
+ try {
35241
+ const output = (0, import_node_child_process18.execFileSync)("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
35242
+ windowsHide: true,
35243
+ encoding: "utf8",
35244
+ stdio: ["ignore", "pipe", "ignore"]
35245
+ });
35246
+ const crlfShellScripts = output.split(/\r?\n/).filter((line) => line.startsWith("i/crlf ")).map((line) => line.slice(line.indexOf(" ") + 1)).filter(Boolean);
35247
+ return { attributesPresent, crlfShellScripts };
35248
+ } catch (error) {
35249
+ return { attributesPresent, crlfShellScripts: [], error: error.message || "git ls-files --eol failed" };
35250
+ }
35251
+ }
35146
35252
  async function ghInstalled() {
35147
35253
  try {
35148
35254
  await execFileP6("gh", ["--version"]);
@@ -35352,6 +35458,7 @@ function mmiDoctorDeps(opts = {}) {
35352
35458
  currentCliVersion: resolveClientVersion,
35353
35459
  readGitignore,
35354
35460
  writeGitignore,
35461
+ lineEndingState,
35355
35462
  repoRoot,
35356
35463
  executeScratchGc: (root, o) => executeScratchGc(root, { apply: o.apply }),
35357
35464
  // #2759: same fetch+ff-only sync SessionStart runs, wired here too so an on-demand `mmi-cli doctor`
@@ -85,6 +85,30 @@ function defaultIsIgnored(root, relPaths, exec = import_node_child_process.execF
85
85
  var import_node_child_process2 = require("node:child_process");
86
86
  var import_node_path2 = require("node:path");
87
87
 
88
+ // ../infra/repo-index-path-policy.mjs
89
+ var HARD_DENY = Object.freeze([
90
+ /(^|\/)\.env(\.|$)/i,
91
+ /(^|\/)\.env\./i,
92
+ /credentials/i,
93
+ /secrets?\.json$/i,
94
+ /\.pem$/i,
95
+ /\.p12$/i,
96
+ /\.key$/i,
97
+ /(^|\/)id_rsa/i,
98
+ /(^|\/)id_ed25519/i,
99
+ /\.keystore$/i
100
+ ]);
101
+ function isHardDeniedRepoIndexPath(value) {
102
+ return typeof value !== "string" || HARD_DENY.some((pattern) => pattern.test(value));
103
+ }
104
+ function isSafeRepoIndexPath(value) {
105
+ if (typeof value !== "string" || !value || value.length > 1024) return false;
106
+ if (value.startsWith("/") || value.includes("\\") || /[\u0000-\u001f\u007f]/u.test(value)) return false;
107
+ const segments = value.split("/");
108
+ if (segments.some((segment) => !segment || segment === "." || segment === "..")) return false;
109
+ return !isHardDeniedRepoIndexPath(value);
110
+ }
111
+
88
112
  // src/repo-runtime-state.ts
89
113
  var import_node_crypto = require("node:crypto");
90
114
  var import_node_fs = require("node:fs");
@@ -120,18 +144,6 @@ function repoRuntimeStatePath(cwd, ...parts) {
120
144
  }
121
145
 
122
146
  // src/repo-index.ts
123
- var HARD_DENY = [
124
- /(^|\/)\.env(\.|$)/i,
125
- /(^|\/)\.env\./i,
126
- /credentials/i,
127
- /secrets?\.json$/i,
128
- /\.pem$/i,
129
- /\.p12$/i,
130
- /\.key$/i,
131
- /(^|\/)id_rsa/i,
132
- /(^|\/)id_ed25519/i,
133
- /\.keystore$/i
134
- ];
135
147
  var INDEXABLE_EXT = /* @__PURE__ */ new Set([
136
148
  ".ts",
137
149
  ".tsx",
@@ -155,10 +167,10 @@ var INDEXABLE_EXT = /* @__PURE__ */ new Set([
155
167
  ".html"
156
168
  ]);
157
169
  function isHardDeniedPath(relPosix) {
158
- return HARD_DENY.some((re) => re.test(relPosix));
170
+ return isHardDeniedRepoIndexPath(relPosix);
159
171
  }
160
172
  function isIndexablePath(relPosix) {
161
- if (isHardDeniedPath(relPosix)) return false;
173
+ if (!isSafeRepoIndexPath(relPosix)) return false;
162
174
  if (relPosix.startsWith(".git/")) return false;
163
175
  if (relPosix.includes("node_modules/")) return false;
164
176
  if (relPosix.includes("dist/")) return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.139.1",
3
+ "version": "3.139.2",
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",