@stll/oxlint-config 1.1.0 → 1.2.0

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.
@@ -171,7 +171,7 @@ const postgresInstall = String.raw`
171
171
  rm -- "$key"
172
172
  printf '%s\n' '@PGDG_SOURCE@' > /etc/apt/sources.list.d/stll-pgdg.list
173
173
  fi
174
- apt_network update
174
+ apt_refresh_for postgresql-@POSTGRES@ postgresql-client-@POSTGRES@
175
175
  apt_install postgresql-@POSTGRES@ postgresql-client-@POSTGRES@
176
176
  fi
177
177
  postgres_ready || fail 'PostgreSQL toolchain is incomplete or differs from the policy major'
@@ -194,7 +194,7 @@ const valkeyInstall = String.raw`
194
194
  [[ "$(dpkg-deb -f "$scratch/$package.deb" Version)" == '@VALKEY@-1.noble' ]] || fail 'Valkey artifact version mismatch'
195
195
  [[ "$(dpkg-deb -f "$scratch/$package.deb" Architecture)" == "$ARCH" ]] || fail 'Valkey artifact architecture mismatch'
196
196
  done
197
- apt_network update
197
+ apt_refresh_for "$scratch/valkey-server.deb" "$scratch/valkey-tools.deb"
198
198
  apt_install "$scratch/valkey-server.deb" "$scratch/valkey-tools.deb"
199
199
  rm -rf -- "$scratch"
200
200
  fi
@@ -295,6 +295,15 @@ apt_network() {
295
295
  [[ -n "$hosts" ]] || hosts='configured APT hosts'
296
296
  network_run "$hosts" 180 apt-get -o Acquire::http::Timeout=20 -o Acquire::https::Timeout=20 -o Acquire::Retries=3 -o APT::Update::Error-Mode=any "$@"
297
297
  }
298
+ apt_refresh_for() {
299
+ # Skip the refresh only when the install downloads nothing: then no package
300
+ # source is contacted and stale cached indexes cannot point at missing files.
301
+ local uris
302
+ if uris="$(DEBIAN_FRONTEND=noninteractive apt-get --print-uris -qq -y --no-install-recommends install "$@" 2>/dev/null)" && [[ -z "$uris" ]]; then
303
+ return 0
304
+ fi
305
+ apt_network update
306
+ }
298
307
  `;
299
308
  //#endregion
300
309
  //#region src/cloud-setup-artifacts.ts
@@ -414,12 +423,29 @@ const getCloudSetupArtifact = (tool, version) => {
414
423
  if (artifact === void 0) throw new Error(`Unsupported ${tool} version ${version}; supported versions: ${releases.map((release) => release.version).join(", ")}`);
415
424
  return artifact;
416
425
  };
426
+ /** Ubuntu 24.04 packages; apt resolves them from the image's configured sources. */
427
+ const cloudSetupPackages = { time: {
428
+ version: "1.9",
429
+ package: "time",
430
+ binary: "/usr/bin/time",
431
+ identity: "time (GNU Time) "
432
+ } };
433
+ /** Ubuntu 24.04 ships one release per package; other versions are unsupported. */
434
+ const getCloudSetupPackage = (tool, version) => {
435
+ const release = cloudSetupPackages[tool];
436
+ if (release.version !== version) throw new Error(`Unsupported ${tool} version ${version}; supported versions: ${release.version}`);
437
+ return release;
438
+ };
417
439
  //#endregion
418
440
  //#region src/cloud-setup-schema.ts
419
441
  const cloudServices = ["postgres", "valkey"];
420
- const cloudExtraTools = Object.keys(cloudSetupArtifacts).filter((tool) => tool !== "rustup-init" && Object.hasOwn(cloudSetupArtifacts, tool));
442
+ const cloudArtifactTools = Object.keys(cloudSetupArtifacts).filter((tool) => tool !== "rustup-init" && Object.hasOwn(cloudSetupArtifacts, tool));
443
+ const cloudPackageTools = Object.keys(cloudSetupPackages).filter((tool) => Object.hasOwn(cloudSetupPackages, tool));
444
+ const cloudExtraTools = [...cloudArtifactTools, ...cloudPackageTools];
445
+ const isCloudPackageTool = (tool) => cloudPackageTools.some((candidate) => candidate === tool);
421
446
  const object = (input) => typeof input === "object" && input !== null && !Array.isArray(input);
422
447
  const exactPin = (input) => typeof input === "string" && /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/.test(input);
448
+ const exactToolPin = (tool, input) => isCloudPackageTool(tool) ? typeof input === "string" && /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*))?$/.test(input) : exactPin(input);
423
449
  const cloudRustComponentBinaries = {
424
450
  clippy: ["cargo-clippy", "clippy-driver"],
425
451
  rustfmt: ["rustfmt", "cargo-fmt"],
@@ -463,12 +489,14 @@ const parseTools = (input) => {
463
489
  if (Object.hasOwn(input, tool)) throw new Error(`cloud.tools.${tool} must be an exact stable release`);
464
490
  continue;
465
491
  }
466
- if (!exactPin(pin)) throw new Error(`cloud.tools.${tool} must be an exact stable release`);
492
+ if (!exactToolPin(tool, pin)) throw new Error(`cloud.tools.${tool} must be an exact stable release`);
467
493
  result[tool] = pin;
468
494
  }
469
495
  return result;
470
496
  };
471
497
  const cloudInstallCommand = "bun install --frozen-lockfile";
498
+ const cloudInstallCommands = [cloudInstallCommand, `${cloudInstallCommand} --ignore-scripts`];
499
+ const cloudInstall = (value) => cloudInstallCommands.some((command) => value === command);
472
500
  const cloudService = (value) => cloudServices.some((service) => value === service);
473
501
  const parseEnvFile = (input) => {
474
502
  if (!("envFile" in input) || typeof input.envFile !== "string") throw new Error("cloud.envFile must be a canonical repository-relative path");
@@ -489,7 +517,8 @@ const parseCloudSetup = (input) => {
489
517
  };
490
518
  const selected = "services" in input ? input.services : [];
491
519
  if (!Array.isArray(selected)) throw new Error("cloud.services must be an array when provided");
492
- if (!("install" in input) || input.install !== "bun install --frozen-lockfile") throw new Error(`cloud.install must be exactly ${cloudInstallCommand}`);
520
+ if (!("install" in input) || !cloudInstall(input.install)) throw new Error(`cloud.install must be exactly one of: ${cloudInstallCommands.join(", ")}`);
521
+ const install = input.install;
493
522
  const seen = /* @__PURE__ */ new Set();
494
523
  const services = Array.from(selected, (service) => {
495
524
  if (!cloudService(service)) throw new Error("cloud.services contains an unsupported service");
@@ -500,7 +529,7 @@ const parseCloudSetup = (input) => {
500
529
  if (services.length === 0 && "envFile" in input) throw new Error("cloud.envFile is not allowed without services");
501
530
  return {
502
531
  services,
503
- install: cloudInstallCommand,
532
+ install,
504
533
  ...extensions,
505
534
  ...services.length === 0 ? {} : { envFile: parseEnvFile(input) }
506
535
  };
@@ -762,9 +791,12 @@ const resolveCloudToolchains = ({ cloud, files, rust, rustExceptions }) => {
762
791
  }
763
792
  const components = [.../* @__PURE__ */ new Set([...parseCloudRustNames(declared?.["components"], "components"), ...cloud.rust?.components ?? []])].sort();
764
793
  const targets = [.../* @__PURE__ */ new Set([...parseCloudRustNames(declared?.["targets"], "targets"), ...cloud.rust?.targets ?? []])].sort();
765
- const tools = cloudExtraTools.flatMap((tool) => {
766
- const version = cloud.tools?.[tool];
767
- if (version === void 0) return [];
794
+ const resolveTool = (tool, version) => {
795
+ if (isCloudPackageTool(tool)) return {
796
+ type: "package",
797
+ tool,
798
+ release: getCloudSetupPackage(tool, version)
799
+ };
768
800
  if (tool === "cargo-nextest" && !rustEnabled) throw new Error("cloud.tools.cargo-nextest requires a Rust toolchain");
769
801
  if (tool === "wasm-bindgen-cli") {
770
802
  const lock = files["Cargo.lock"];
@@ -774,10 +806,15 @@ const resolveCloudToolchains = ({ cloud, files, rust, rustExceptions }) => {
774
806
  const versions = new Set(packages.flatMap((entry) => record$2(entry) && entry["name"] === "wasm-bindgen" ? [entry["version"]] : []));
775
807
  if (versions.size !== 1 || !versions.has(version)) throw new Error(`cloud.tools.wasm-bindgen-cli ${version} must equal the unique wasm-bindgen crate version in Cargo.lock (found ${[...versions].join(", ") || "none"})`);
776
808
  }
777
- return [{
809
+ return {
810
+ type: "artifact",
778
811
  tool,
779
812
  release: getCloudSetupArtifact(tool, version)
780
- }];
813
+ };
814
+ };
815
+ const tools = cloudExtraTools.flatMap((tool) => {
816
+ const version = cloud.tools?.[tool];
817
+ return version === void 0 ? [] : [resolveTool(tool, version)];
781
818
  });
782
819
  return {
783
820
  rust: rustEnabled && typeof channel === "string" ? {
@@ -835,7 +872,7 @@ rust_ready() {
835
872
  verify_rust() { rust_ready || fail 'Pinned Rust, components or targets are unavailable; run install'; }
836
873
  install_rust() (
837
874
  if ! command -v cc >/dev/null || ! command -v ld >/dev/null || [[ ! -f /usr/include/stdlib.h ]]; then
838
- apt_network update
875
+ apt_refresh_for build-essential
839
876
  apt_install build-essential
840
877
  fi
841
878
  local artifact_url artifact_sha
@@ -872,14 +909,31 @@ install_rust() (
872
909
  )
873
910
  `.replaceAll("@RUSTUP@", rust.installer.version).replaceAll("@RUST@", rust.channel).replace("@RUSTUP_ARTIFACT@", artifactSelection(rust.installer)).replace("@RUST_ARGS@", args).replace("@RUST_PROXIES@", Object.entries(cloudRustComponentBinaries).flatMap(([component, binaries]) => rust.components.includes(component) ? Array.from(binaries) : []).join(" ")).replace("@VERIFY_COMPONENTS@", rust.components.map((name) => `rust_run local "$CARGO_HOME/bin/rustup" component list --toolchain ${quote(rust.channel)} --installed | awk -v name=${quote(name)} -v host="$host" '$1 == name || $1 == name "-" host { found=1 } END { exit !found }' || return 1`).join("\n ")).replace("@VERIFY_TARGETS@", rust.targets.map((name) => `rust_run local "$CARGO_HOME/bin/rustup" target list --toolchain ${quote(rust.channel)} --installed | awk -v name=${quote(name)} '$1 == name { found=1 } END { exit !found }' || return 1`).join("\n "));
874
911
  };
875
- const renderCloudToolInstall = ({ rust, tools }) => {
876
- const installs = tools.map(({ tool, release }) => {
877
- const directory = `/opt/stll-cloud/tools/${tool}/${release.version}`;
878
- const command = tool === "wasm-bindgen-cli" ? "wasm-bindgen" : tool;
879
- const marker = `${tool === "cargo-nextest" ? "cargo-nextest" : command} ${release.version}`;
880
- return {
881
- name: tool.replaceAll("-", "_"),
882
- text: `verify_${tool.replaceAll("-", "_")}() {
912
+ const packageToolInstall = ({ tool, release }) => {
913
+ const name = tool.replaceAll("-", "_");
914
+ return {
915
+ name,
916
+ text: `verify_${name}() {
917
+ [[ -x ${quote(release.binary)} && "$(${quote(release.binary)} --version 2>/dev/null)" == ${quote(release.identity)}* ]] || return 1
918
+ [[ "$(dpkg-query -W -f='\${Version}' ${quote(release.package)} 2>/dev/null)" == ${quote(`${release.version}-`)}* ]]
919
+ }
920
+ install_${name}() {
921
+ if ! verify_${name}; then
922
+ apt_network update
923
+ apt_install ${quote(release.package)}
924
+ verify_${name} || fail ${quote(`Installed ${tool} version differs from its pin`)}
925
+ fi
926
+ }
927
+ `
928
+ };
929
+ };
930
+ const artifactToolInstall = ({ tool, release }) => {
931
+ const directory = `/opt/stll-cloud/tools/${tool}/${release.version}`;
932
+ const command = tool === "wasm-bindgen-cli" ? "wasm-bindgen" : tool;
933
+ const marker = `${tool === "cargo-nextest" ? "cargo-nextest" : command} ${release.version}`;
934
+ return {
935
+ name: tool.replaceAll("-", "_"),
936
+ text: `verify_${tool.replaceAll("-", "_")}() {
883
937
  ${Object.keys(release.amd64.binaries).map((binary) => `[[ -x ${quote(`${directory}/${binary}`)} ]] || return 1`).join("\n ")}
884
938
  [[ "$( ${quote(`${directory}/${command}`)} --version)" == ${quote(marker)} || "$( ${quote(`${directory}/${command}`)} --version)" == ${quote(`${marker} `)}* ]]
885
939
  }
@@ -901,7 +955,15 @@ ${["amd64", "arm64"].map((arch) => ` ${arch})\n${Object.entries(release[arc
901
955
  ${Object.keys(release.amd64.binaries).map((binary) => ` ln -sfnT -- ${quote(`${directory}/${binary}`)} ${quote(`/usr/local/bin/${binary}`)}`).join("\n")}
902
956
  )
903
957
  `
904
- };
958
+ };
959
+ };
960
+ const renderCloudToolInstall = ({ rust, tools }) => {
961
+ const installs = tools.map((entry) => {
962
+ switch (entry.type) {
963
+ case "package": return packageToolInstall(entry);
964
+ case "artifact": return artifactToolInstall(entry);
965
+ default: throw new Error(`Unsupported cloud tool: ${String(entry)}`);
966
+ }
905
967
  });
906
968
  return `${helpers}${rust === void 0 ? "" : rustInstall(rust)}\n${installs.map(({ text }) => text).join("\n")}
907
969
  install_tools() {
@@ -952,6 +1014,14 @@ usage() { echo 'Usage: cloud-setup.sh install|start' >&2; exit 2; }
952
1014
  case "$1" in install|start) MODE="$1" ;; *) usage ;; esac
953
1015
  fail() { echo "cloud-setup: $*" >&2; exit 1; }
954
1016
  trap 'echo "cloud-setup: command failed at line $LINENO" >&2' ERR
1017
+ # Each phase logs its start and duration so a stalled phase is visible in the task log.
1018
+ step() {
1019
+ local name="$1" started="$SECONDS"
1020
+ shift
1021
+ echo "cloud-setup: $name started" >&2
1022
+ "$@"
1023
+ echo "cloud-setup: $name done in $((SECONDS - started))s" >&2
1024
+ }
955
1025
  [[ -f /etc/os-release ]] || fail 'Ubuntu 24.04 is required'
956
1026
  . /etc/os-release
957
1027
  [[ "$ID" == ubuntu && "$VERSION_ID" == 24.04 && "$VERSION_CODENAME" == noble ]] || fail 'Only Ubuntu 24.04 (noble) is supported'
@@ -995,7 +1065,7 @@ runtime_ready() {
995
1065
  }
996
1066
  install_runtimes() {
997
1067
  if ! command -v curl >/dev/null || ! command -v unzip >/dev/null || ! command -v xz >/dev/null || ! command -v openssl >/dev/null || ! command -v gpg >/dev/null || ! ldconfig -p | awk '$1 == "libatomic.so.1" { found=1 } END { exit !found }'; then
998
- apt_network update
1068
+ apt_refresh_for ca-certificates curl unzip xz-utils openssl gnupg util-linux libatomic1
999
1069
  DEBIAN_FRONTEND=noninteractive apt_install ca-certificates curl unzip xz-utils openssl gnupg util-linux libatomic1
1000
1070
  fi
1001
1071
  local scratch node_archive bun_archive bun_folder
@@ -1061,7 +1131,7 @@ for (const input of inputs) {
1061
1131
  process.stdout.write([...hosts].sort().join(","));
1062
1132
  ')"
1063
1133
  # Bun 1.4.3 socket inactivity timeout, in seconds; the outer bound includes scripts.
1064
- network_run "$download_hosts" 240 runuser -u "$OUTPUT_USER" -- env HOME="$OUTPUT_HOME" BUN_INSTALL_CACHE_DIR="$BUN_CACHE" PATH="$PATH" BUN_CONFIG_HTTP_IDLE_TIMEOUT=20 "$BUN_DIR/bun" install --frozen-lockfile
1134
+ network_run "$download_hosts" 240 runuser -u "$OUTPUT_USER" -- env HOME="$OUTPUT_HOME" BUN_INSTALL_CACHE_DIR="$BUN_CACHE" PATH="$PATH" BUN_CONFIG_HTTP_IDLE_TIMEOUT=20 "$BUN_DIR/bun" @INSTALL_ARGS@
1065
1135
  }
1066
1136
 
1067
1137
  @SERVICE_INSTALL@
@@ -1074,20 +1144,20 @@ process.stdout.write([...hosts].sort().join(","));
1074
1144
  @ENV_FUNCTIONS@
1075
1145
 
1076
1146
  if [[ "$MODE" == install ]]; then
1077
- install_runtimes
1078
- install_services
1079
- install_tools
1080
- install_shell_environment
1081
- dependency_install
1147
+ step runtimes install_runtimes
1148
+ step services install_services
1149
+ step tools install_tools
1150
+ step shell install_shell_environment
1151
+ step dependencies dependency_install
1082
1152
  exit 0
1083
1153
  fi
1084
- runtime_ready
1085
- verify_tools
1086
- install_shell_environment
1087
- verify_shell_environment
1154
+ step runtime-check runtime_ready
1155
+ step tool-check verify_tools
1156
+ step shell install_shell_environment
1157
+ step shell-check verify_shell_environment
1088
1158
  @ENV_VALIDATE@
1089
- prepare_state
1090
- start_services
1159
+ step state prepare_state
1160
+ step service-start start_services
1091
1161
  @ENV_WRITE@
1092
1162
  `;
1093
1163
  const environmentFunctions = String.raw`
@@ -1132,6 +1202,7 @@ const generateCloudSetup = ({ policy, cloud, nodeVersion, files = {}, rustExcept
1132
1202
  });
1133
1203
  const services = cloudServices.filter((service) => selected.services.includes(service));
1134
1204
  const replacements = /* @__PURE__ */ new Map([
1205
+ ["@INSTALL_ARGS@", selected.install.replace(/^bun /, "")],
1135
1206
  ["@NETWORK@", renderCloudNetwork()],
1136
1207
  ["@TOOL_INSTALL@", renderCloudToolInstall(toolchains) + renderCloudShellInstall({
1137
1208
  nodeVersion: exactNode,
@@ -1175,12 +1246,12 @@ const generateCloudSetup = ({ policy, cloud, nodeVersion, files = {}, rustExcept
1175
1246
  //#endregion
1176
1247
  //#region src/cloud-setup-claude.ts
1177
1248
  const cloudClaudeSettingsPath = ".claude/settings.json";
1178
- const cloudSessionStartHook = {
1249
+ const sessionStartHook = (script) => ({
1179
1250
  matcher: "",
1180
1251
  hooks: [{
1181
1252
  type: "command",
1182
1253
  command: `if [ "\${CLAUDE_CODE_REMOTE:-}" = "true" ]; then
1183
- bash ${cloudSetupPath} start || exit "$?"
1254
+ ${script} start || exit "$?"
1184
1255
  if [ -n "\${CLAUDE_ENV_FILE:-}" ]; then
1185
1256
  if [ ! -f ${cloudShellProfilePath} ]; then
1186
1257
  printf '%s\\n' 'cloud-setup: cloud shell profile is unavailable; run install' >&2
@@ -1191,6 +1262,12 @@ const cloudSessionStartHook = {
1191
1262
  fi
1192
1263
  fi`
1193
1264
  }]
1265
+ });
1266
+ const cloudSessionStartHook = sessionStartHook(`bash "$CLAUDE_PROJECT_DIR/${cloudSetupPath}"`);
1267
+ const previousCloudSessionStartHooks = [sessionStartHook(`bash ${cloudSetupPath}`)];
1268
+ const cloudAttribution = {
1269
+ commit: "",
1270
+ pr: ""
1194
1271
  };
1195
1272
  const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1196
1273
  const array = (value) => Array.isArray(value);
@@ -1222,33 +1299,69 @@ const readSettings = (text) => {
1222
1299
  };
1223
1300
  const cloudHookGroups = (sessionStart) => sessionStart.filter((entry) => record$1(entry) && array(entry["hooks"]) && entry["hooks"].some((hook) => record$1(hook) && typeof hook["command"] === "string" && hook["command"].includes(".agents/cloud-setup.sh")));
1224
1301
  const cloudSessionStartFix = `preserve unrelated settings in ${cloudClaudeSettingsPath}, repair its JSON/hooks structure and set disableAllHooks to false if needed, and ensure hooks.SessionStart contains exactly this cloud setup entry (replace conflicting cloud setup entries):\n` + JSON.stringify(cloudSessionStartHook, null, 2);
1225
- /** Append only when the existing settings have no conflicting cloud startup. */
1226
- const mergeCloudSessionStartHook = (text) => {
1302
+ const cloudAttributionFix = `preserve unrelated settings in ${cloudClaudeSettingsPath} and set its top-level attribution to exactly:\n` + JSON.stringify({ attribution: cloudAttribution }, null, 2);
1303
+ const sessionStartState = (sessionStart) => {
1304
+ const groups = cloudHookGroups(sessionStart);
1305
+ const group = groups.at(0);
1306
+ if (group === void 0) return { type: "missing" };
1307
+ if (groups.length === 1 && isDeepStrictEqual(group, cloudSessionStartHook)) return { type: "current" };
1308
+ if (groups.length === 1 && previousCloudSessionStartHooks.some((hook) => isDeepStrictEqual(group, hook))) return {
1309
+ type: "previous",
1310
+ group
1311
+ };
1312
+ return { type: "conflict" };
1313
+ };
1314
+ const attributionState = (settings) => {
1315
+ const attribution = settings["attribution"];
1316
+ if (attribution === void 0) return "missing";
1317
+ return isDeepStrictEqual(attribution, cloudAttribution) ? "current" : "conflict";
1318
+ };
1319
+ const mergeError = (reason, fix) => /* @__PURE__ */ new Error(`cannot safely merge ${cloudClaudeSettingsPath}: ${reason}; ${fix}`);
1320
+ const readMergeableSettings = (text) => {
1227
1321
  try {
1228
- const { settings, hooks, sessionStart } = readSettings(text);
1229
- const cloudGroups = cloudHookGroups(sessionStart);
1230
- if (cloudGroups.length !== 0) {
1231
- if (cloudGroups.length === 1 && isDeepStrictEqual(cloudGroups.at(0), cloudSessionStartHook) && text !== void 0) return text;
1232
- throw new Error("existing cloud setup SessionStart hook differs");
1233
- }
1234
- return `${JSON.stringify({
1235
- ...settings,
1236
- hooks: {
1237
- ...hooks,
1238
- SessionStart: [...sessionStart, cloudSessionStartHook]
1239
- }
1240
- }, null, 2)}\n`;
1322
+ return readSettings(text);
1241
1323
  } catch (error) {
1242
- throw new Error(`cannot safely merge ${cloudClaudeSettingsPath}: ${error instanceof Error ? error.message : String(error)}; ${cloudSessionStartFix}`);
1324
+ throw mergeError(error instanceof Error ? error.message : String(error), cloudSessionStartFix);
1243
1325
  }
1244
1326
  };
1327
+ /** Add the cloud hook and attribution; never overwrite conflicting values. */
1328
+ const mergeCloudClaudeSettings = (text) => {
1329
+ const { settings, hooks, sessionStart } = readMergeableSettings(text);
1330
+ const hook = sessionStartState(sessionStart);
1331
+ if (hook.type === "conflict") throw mergeError("existing cloud setup SessionStart hook differs", cloudSessionStartFix);
1332
+ const attribution = attributionState(settings);
1333
+ if (attribution === "conflict") throw mergeError("existing attribution differs", cloudAttributionFix);
1334
+ if (hook.type === "current" && attribution === "current" && text !== void 0) return text;
1335
+ const mergedSessionStart = (() => {
1336
+ switch (hook.type) {
1337
+ case "current": return sessionStart;
1338
+ case "missing": return [...sessionStart, cloudSessionStartHook];
1339
+ case "previous": return sessionStart.map((entry) => entry === hook.group ? cloudSessionStartHook : entry);
1340
+ default: throw new Error(`Unsupported hook state: ${String(hook)}`);
1341
+ }
1342
+ })();
1343
+ return `${JSON.stringify({
1344
+ ...settings,
1345
+ attribution: cloudAttribution,
1346
+ hooks: {
1347
+ ...hooks,
1348
+ SessionStart: mergedSessionStart
1349
+ }
1350
+ }, null, 2)}\n`;
1351
+ };
1245
1352
  /** Settings formatting and unrelated hooks do not affect the generated contract. */
1246
1353
  const hasCloudSessionStartHook = (text) => {
1247
1354
  if (text === void 0) return false;
1248
1355
  try {
1249
- const { sessionStart } = readSettings(text);
1250
- const cloudGroups = cloudHookGroups(sessionStart);
1251
- return cloudGroups.length === 1 && isDeepStrictEqual(cloudGroups.at(0), cloudSessionStartHook);
1356
+ return sessionStartState(readSettings(text).sessionStart).type === "current";
1357
+ } catch {
1358
+ return false;
1359
+ }
1360
+ };
1361
+ const hasCloudAttribution = (text) => {
1362
+ if (text === void 0) return false;
1363
+ try {
1364
+ return attributionState(readSettings(text).settings) === "current";
1252
1365
  } catch {
1253
1366
  return false;
1254
1367
  }
@@ -1427,6 +1540,6 @@ const toolchainInputKind = (file, text = "") => {
1427
1540
  if (githubAutomationFileKind(file) !== void 0 || isShellScript(file, text) || /(?:^|\/)ci-service-images\.json$/.test(file) || isMiseConfigPath(file) || isPythonDependencyManifest(file) || isDockerDefinitionPath(file) || isComposeDefinitionPath(file) || isKubernetesDefinitionPath(file) || /(?:^|\/)(?:package\.json|consumer-compat\.json|pnpm-workspace\.yaml|stll-toolchain\.json|\.bun-version|\.node-version|\.nvmrc|\.python-version|rust-toolchain(?:\.toml)?|\.tool-versions|\.?mise\.toml|uv\.toml|\.uv\.toml|\.github\/dependabot\.ya?ml|Cargo\.toml)$/.test(file)) return "config";
1428
1541
  };
1429
1542
  //#endregion
1430
- export { parseCloudSetup as C, nodeSupportRangeMatches as S, mergeCloudSessionStartHook as _, isPythonDependencyManifest as a, nodePolicyValid as b, pythonDependencyManifestKind as c, isComposeDefinitionPath as d, isKubernetesDefinitionPath as f, hasCloudSessionStartHook as g, cloudSessionStartFix as h, isMiseConfigPath as i, toolchainInputKind as l, cloudClaudeSettingsPath as m, isDependabotGithubActionsPath as n, isShellScript as o, workspaceContains as p, isDockerDefinitionPath as r, javascriptDependencyLockfiles as s, githubAutomationFileKind as t, containerDocumentImages as u, cloudSetupPath as v, nodeSelectorMatches as x, generateCloudSetup as y };
1543
+ export { nodeSelectorMatches as C, nodePolicyValid as S, parseCloudSetup as T, hasCloudAttribution as _, isPythonDependencyManifest as a, cloudSetupPath as b, pythonDependencyManifestKind as c, isComposeDefinitionPath as d, isKubernetesDefinitionPath as f, cloudSessionStartFix as g, cloudClaudeSettingsPath as h, isMiseConfigPath as i, toolchainInputKind as l, cloudAttributionFix as m, isDependabotGithubActionsPath as n, isShellScript as o, workspaceContains as p, isDockerDefinitionPath as r, javascriptDependencyLockfiles as s, githubAutomationFileKind as t, containerDocumentImages as u, hasCloudSessionStartHook as v, nodeSupportRangeMatches as w, generateCloudSetup as x, mergeCloudClaudeSettings as y };
1431
1544
 
1432
1545
  //# sourceMappingURL=toolchain-inputs.mjs.map