@stll/oxlint-config 1.3.1 → 1.5.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.
Files changed (95) hide show
  1. package/README.md +341 -28
  2. package/dist/cloud-setup-cli.cjs +20 -40
  3. package/dist/cloud-setup-cli.cjs.map +1 -1
  4. package/dist/cloud-setup-cli.mjs +21 -41
  5. package/dist/cloud-setup-cli.mjs.map +1 -1
  6. package/dist/consumer-compat-cli.cjs +52 -251
  7. package/dist/consumer-compat-cli.cjs.map +1 -1
  8. package/dist/consumer-compat-cli.mjs +7 -206
  9. package/dist/consumer-compat-cli.mjs.map +1 -1
  10. package/dist/consumer-node-support.cjs +497 -0
  11. package/dist/consumer-node-support.cjs.map +1 -0
  12. package/dist/consumer-node-support.mjs +381 -0
  13. package/dist/consumer-node-support.mjs.map +1 -0
  14. package/dist/dependabot-cli.cjs +91 -0
  15. package/dist/dependabot-cli.cjs.map +1 -0
  16. package/dist/dependabot-cli.d.cts +1 -0
  17. package/dist/dependabot-cli.d.mts +1 -0
  18. package/dist/dependabot-cli.mjs +91 -0
  19. package/dist/dependabot-cli.mjs.map +1 -0
  20. package/dist/dependabot.cjs +7 -0
  21. package/dist/dependabot.d.cts +118 -0
  22. package/dist/dependabot.d.mts +118 -0
  23. package/dist/dependabot.mjs +3 -0
  24. package/dist/generated-file.cjs +61 -0
  25. package/dist/generated-file.cjs.map +1 -0
  26. package/dist/generated-file.mjs +44 -0
  27. package/dist/generated-file.mjs.map +1 -0
  28. package/dist/git-tracked-output.cjs +2 -2
  29. package/dist/git-tracked-output.mjs +1 -1
  30. package/dist/jsonc.cjs +66 -0
  31. package/dist/jsonc.cjs.map +1 -0
  32. package/dist/jsonc.mjs +61 -0
  33. package/dist/jsonc.mjs.map +1 -0
  34. package/dist/publish-artifact-syntax.cjs +1309 -0
  35. package/dist/publish-artifact-syntax.cjs.map +1 -0
  36. package/dist/publish-artifact-syntax.mjs +1265 -0
  37. package/dist/publish-artifact-syntax.mjs.map +1 -0
  38. package/dist/publish-build-target-vite-dts-helper2.cjs +1 -1
  39. package/dist/publish-build-target-vite-dts-helper2.mjs +1 -1
  40. package/dist/publish-contract-cli.cjs +55 -14
  41. package/dist/publish-contract-cli.cjs.map +1 -1
  42. package/dist/publish-contract-cli.mjs +50 -9
  43. package/dist/publish-contract-cli.mjs.map +1 -1
  44. package/dist/quarantine-cli.cjs +66 -0
  45. package/dist/quarantine-cli.cjs.map +1 -0
  46. package/dist/quarantine-cli.d.cts +1 -0
  47. package/dist/quarantine-cli.d.mts +1 -0
  48. package/dist/quarantine-cli.mjs +66 -0
  49. package/dist/quarantine-cli.mjs.map +1 -0
  50. package/dist/quarantine-policy.cjs +10 -0
  51. package/dist/quarantine-policy.d.cts +27 -0
  52. package/dist/quarantine-policy.d.mts +27 -0
  53. package/dist/quarantine-policy.mjs +1 -0
  54. package/dist/quarantine-policy2.cjs +399 -0
  55. package/dist/quarantine-policy2.cjs.map +1 -0
  56. package/dist/quarantine-policy2.mjs +352 -0
  57. package/dist/quarantine-policy2.mjs.map +1 -0
  58. package/dist/toolchain-changed.cjs +16 -67
  59. package/dist/toolchain-changed.cjs.map +1 -1
  60. package/dist/toolchain-changed.mjs +19 -70
  61. package/dist/toolchain-changed.mjs.map +1 -1
  62. package/dist/toolchain-check-cli.cjs +1 -0
  63. package/dist/toolchain-check-cli.cjs.map +1 -1
  64. package/dist/toolchain-check-cli.mjs +1 -0
  65. package/dist/toolchain-check-cli.mjs.map +1 -1
  66. package/dist/toolchain-dependabot.cjs +529 -0
  67. package/dist/toolchain-dependabot.cjs.map +1 -0
  68. package/dist/toolchain-dependabot.mjs +485 -0
  69. package/dist/toolchain-dependabot.mjs.map +1 -0
  70. package/dist/toolchain-guard.cjs +1143 -432
  71. package/dist/toolchain-guard.cjs.map +1 -1
  72. package/dist/toolchain-guard.mjs +1132 -421
  73. package/dist/toolchain-guard.mjs.map +1 -1
  74. package/dist/toolchain-inputs.cjs +216 -38
  75. package/dist/toolchain-inputs.cjs.map +1 -1
  76. package/dist/toolchain-inputs.mjs +213 -41
  77. package/dist/toolchain-inputs.mjs.map +1 -1
  78. package/dist/toolchain-schema.cjs +0 -493
  79. package/dist/toolchain-schema.cjs.map +1 -1
  80. package/dist/toolchain-schema.mjs +3 -379
  81. package/dist/toolchain-schema.mjs.map +1 -1
  82. package/dist/typecheck-parity.cjs +4 -3
  83. package/dist/typecheck-parity.cjs.map +1 -1
  84. package/dist/typecheck-parity.mjs +4 -3
  85. package/dist/typecheck-parity.mjs.map +1 -1
  86. package/dist/typecheck-probe-cli.cjs +3 -3
  87. package/dist/typecheck-probe-cli.cjs.map +1 -1
  88. package/dist/typecheck-probe-cli.mjs +3 -3
  89. package/dist/typecheck-probe-cli.mjs.map +1 -1
  90. package/package.json +25 -1
  91. package/toolchain.json +11 -4
  92. package/dist/publish-contract.cjs +0 -431
  93. package/dist/publish-contract.cjs.map +0 -1
  94. package/dist/publish-contract.mjs +0 -396
  95. package/dist/publish-contract.mjs.map +0 -1
@@ -1,21 +1,25 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime.cjs");
2
+ const require_quarantine_policy = require("./quarantine-policy2.cjs");
2
3
  const require_toolchain_inputs = require("./toolchain-inputs.cjs");
3
4
  const require_git_tracked_output = require("./git-tracked-output.cjs");
4
- const require_toolchain_schema = require("./toolchain-schema.cjs");
5
- let node_fs = require("node:fs");
6
- let node_child_process = require("node:child_process");
5
+ const require_consumer_node_support = require("./consumer-node-support.cjs");
6
+ const require_toolchain_dependabot = require("./toolchain-dependabot.cjs");
7
+ require("./toolchain-schema.cjs");
7
8
  let node_path = require("node:path");
8
9
  node_path = require_rolldown_runtime.__toESM(node_path, 1);
9
- let semver = require("semver");
10
+ let node_fs = require("node:fs");
10
11
  let smol_toml = require("smol-toml");
12
+ let node_child_process = require("node:child_process");
13
+ let semver = require("semver");
11
14
  let yaml = require("yaml");
12
15
  let picomatch = require("picomatch");
13
16
  picomatch = require_rolldown_runtime.__toESM(picomatch, 1);
17
+ let node_module = require("node:module");
14
18
  //#endregion
15
19
  //#region src/toolchain-consumer-checks.ts
16
- const consumerRunnerVersion = "1.3.1";
20
+ const consumerRunnerVersion = "1.5.0";
17
21
  const consumerCheckWorkflow = "stella/.github/.github/workflows/package-consumer-compat.yml";
18
- const record$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22
+ const record$10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
19
23
  const repositoryDirectory = (value) => typeof value === "string" && value !== "" && !value.includes("\\") && !value.includes("${{") && !value.split("").some((character) => {
20
24
  const code = character.charCodeAt(0);
21
25
  return code < 32 || code === 127;
@@ -101,12 +105,12 @@ const validConsumerCron = (text) => {
101
105
  });
102
106
  };
103
107
  const nightlySchedule = (value) => {
104
- if (!record$7(value)) return false;
108
+ if (!record$10(value)) return false;
105
109
  const schedule = value["schedule"];
106
110
  if (!Array.isArray(schedule) || schedule.length === 0) return false;
107
111
  const coveredDays = /* @__PURE__ */ new Set();
108
112
  for (const entry of schedule) {
109
- if (!record$7(entry) || typeof entry["cron"] !== "string" || !validConsumerCron(entry["cron"])) return false;
113
+ if (!record$10(entry) || typeof entry["cron"] !== "string" || !validConsumerCron(entry["cron"])) return false;
110
114
  const fields = entry["cron"].trim().split(/\s+/);
111
115
  const selections = cronFields.map((field, index) => {
112
116
  const text = fields.at(index);
@@ -122,7 +126,7 @@ const parseConsumerChecks = (input) => {
122
126
  if (!Array.isArray(input)) throw new Error("consumerChecks must be an array");
123
127
  const targets = /* @__PURE__ */ new Set();
124
128
  return input.map((entry) => {
125
- if (!record$7(entry) || Object.keys(entry).some((key) => ![
129
+ if (!record$10(entry) || Object.keys(entry).some((key) => ![
126
130
  "workflow",
127
131
  "job",
128
132
  "packages",
@@ -160,9 +164,9 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
160
164
  });
161
165
  if (document.errors.length > 0) throw new Error("invalid workflow YAML");
162
166
  const source = document.toJS({ maxAliasCount: 100 });
163
- if (!record$7(source) || !record$7(source["jobs"])) continue;
167
+ if (!record$10(source) || !record$10(source["jobs"])) continue;
164
168
  for (const [jobName, job] of Object.entries(source["jobs"])) {
165
- if (!record$7(job) || typeof job["uses"] !== "string") continue;
169
+ if (!record$10(job) || typeof job["uses"] !== "string") continue;
166
170
  const [workflowPath, ref] = job["uses"].split("@");
167
171
  if (workflowPath?.toLowerCase() !== "stella/.github/.github/workflows/package-consumer-compat.yml") continue;
168
172
  const declaration = declarations.find((entry) => entry.workflow === workflow && entry.job === jobName);
@@ -176,7 +180,7 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
176
180
  const approved = Object.entries(policy.actions).find(([name]) => name.toLowerCase() === consumerCheckWorkflow)?.[1];
177
181
  if (job["uses"].split("@").length !== 2 || ref === void 0 || !/^[a-f0-9]{40}$/.test(ref) || approved === void 0 || ref !== approved.sha || "steps" in job || "runs-on" in job) throw new Error(`${workflow}:${jobName} must invoke the approved immutable consumer workflow as a reusable job`);
178
182
  const inputs = job["with"];
179
- if (!record$7(inputs)) throw new Error(`${workflow}:${jobName} requires consumer inputs`);
183
+ if (!record$10(inputs)) throw new Error(`${workflow}:${jobName} requires consumer inputs`);
180
184
  const names = /* @__PURE__ */ new Set();
181
185
  for (const key of Object.keys(inputs)) {
182
186
  const normalized = key.toLowerCase();
@@ -196,8 +200,8 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
196
200
  const fixtureSource = files[fixtureManifest];
197
201
  if (fixtureSource === void 0) throw new Error(`consumerChecks fixture configuration must be tracked: ${fixtureManifest}`);
198
202
  const fixtureConfiguration = JSON.parse(fixtureSource);
199
- const fixtures = require_toolchain_schema.parseConsumerFixtures(fixtureConfiguration);
200
- require_toolchain_schema.assertConsumerFixtureSelection({
203
+ const fixtures = require_consumer_node_support.parseConsumerFixtures(fixtureConfiguration);
204
+ require_consumer_node_support.assertConsumerFixtureSelection({
201
205
  selected: declaration.packages,
202
206
  fixtures
203
207
  });
@@ -205,7 +209,7 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
205
209
  if (typeof rawPackages !== "string") throw new Error(`${workflow}:${jobName} packages must be a static JSON array`);
206
210
  const selected = JSON.parse(rawPackages);
207
211
  if (!packageDirectories(selected) || selected.length !== declaration.packages.length || !selected.every((directory) => declaration.packages.includes(directory))) throw new Error(`${workflow}:${jobName} packages must exactly match consumerChecks`);
208
- const workspacePackages = require_toolchain_schema.discoverConsumerPackages(files);
212
+ const workspacePackages = require_consumer_node_support.discoverConsumerPackages(files);
209
213
  const packagesByDirectory = new Map([...workspacePackages.values()].map((entry) => [entry.directory, entry]));
210
214
  for (const directory of declaration.packages) {
211
215
  const manifestPath = node_path.default.posix.join(directory, "package.json");
@@ -214,18 +218,18 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
214
218
  if (selectedPackage === void 0) throw new Error(`consumerChecks package must be a discovered workspace member: ${manifestPath}`);
215
219
  const fixture = fixtures.find((candidate) => candidate.package === directory);
216
220
  if (fixture === void 0) throw new Error(`missing declared consumer fixture: ${directory}`);
217
- require_toolchain_schema.assertConsumerFixtureKind({
221
+ require_consumer_node_support.assertConsumerFixtureKind({
218
222
  fixture,
219
223
  pkg: selectedPackage
220
224
  });
221
225
  const manifest = selectedPackage.manifest;
222
226
  if (manifest["private"] === true || typeof manifest["name"] !== "string" || manifest["name"].trim() === "") throw new Error(`consumerChecks requires a named published package: ${manifestPath}`);
223
- const closure = require_toolchain_schema.consumerPackageClosure({
227
+ const closure = require_consumer_node_support.consumerPackageClosure({
224
228
  selected: selectedPackage,
225
229
  packages: workspacePackages,
226
230
  files
227
231
  });
228
- for (const member of closure.values()) require_toolchain_schema.assertConsumerNodeSupport({
232
+ for (const member of closure.values()) require_consumer_node_support.assertConsumerNodeSupport({
229
233
  manifest: member.manifest,
230
234
  node: policy.consumerNode,
231
235
  label: node_path.default.posix.join(member.directory, "package.json")
@@ -240,367 +244,13 @@ const checkConsumerChecks = ({ declarations, files, policy }) => {
240
244
  return diagnostics;
241
245
  };
242
246
  //#endregion
243
- //#region src/toolchain-images.ts
244
- /** Docker Hub spellings accepted by the shared runtime policy. */
245
- const dockerHubRegistries = [
246
- "docker.io",
247
- "index.docker.io",
248
- "registry-1.docker.io"
249
- ];
250
- /** Expand the full accepted repository alias class without parsing Dockerfile instructions. */
251
- const ownedDockerImageAliases = (images) => {
252
- const aliases = /* @__PURE__ */ new Set();
253
- for (const image of images) for (const repository of [image, `library/${image}`]) {
254
- aliases.add(repository);
255
- for (const registry of dockerHubRegistries) aliases.add(`${registry}/${repository}`);
256
- }
257
- return [...aliases].sort();
258
- };
259
- /** Preserve tag/digest spelling while normalizing registry and namespace aliases. */
260
- const canonicalDockerRuntime = (image) => {
261
- const registry = dockerHubRegistries.find((entry) => image.toLowerCase().startsWith(`${entry}/`));
262
- return (registry === void 0 ? image : image.slice(registry.length + 1)).replace(/^library\//i, "");
263
- };
264
- /** Preserve Rust release identity across official Debian slim and Alpine variants. */
265
- const rustImageVersion = (tag) => tag.replace(/-(?:alpine(?:\d+(?:\.\d+)*)?|(?:bookworm|bullseye|trixie|buster)(?:-slim)?|slim(?:-(?:bookworm|bullseye|trixie|buster))?)$/, "");
266
- //#endregion
267
- //#region src/toolchain-dependabot.ts
268
- const dependabotRules = ["dependabot-policy"];
269
- const record$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
270
- const strings = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
271
- const sameStrings = (value, expected) => {
272
- if (!strings(value) || value.length !== expected.length) return false;
273
- const sortedExpected = [...expected].sort();
274
- return [...value].sort().every((entry, index) => entry === sortedExpected[index]);
275
- };
276
- const pinnedVersionUpdateTypes = [
277
- "version-update:semver-major",
278
- "version-update:semver-minor",
279
- "version-update:semver-patch"
280
- ];
281
- const versionOnlyIgnore = (entry) => record$6(entry) && Object.keys(entry).length === 2 && typeof entry["dependency-name"] === "string" && sameStrings(entry["update-types"], pinnedVersionUpdateTypes);
282
- const directoryOf = (file) => {
283
- const directory = node_path.default.posix.dirname(file);
284
- return directory === "." ? "/" : `/${directory}`;
285
- };
286
- const workspaceManifests = {
287
- npm: "package.json",
288
- cargo: "Cargo.toml",
289
- uv: "pyproject.toml"
290
- };
291
- const workspaceTable = (parsed, ecosystem) => {
292
- switch (ecosystem) {
293
- case "npm": return parsed["workspaces"];
294
- case "cargo": return parsed["workspace"];
295
- case "uv": {
296
- const tool = parsed["tool"];
297
- const uv = record$6(tool) ? tool["uv"] : void 0;
298
- return record$6(uv) ? uv["workspace"] : void 0;
299
- }
300
- default: throw new Error("unknown workspace ecosystem", { cause: ecosystem });
301
- }
302
- };
303
- /** Installable workspace members share their update root; own-lock projects stay discoverable. */
304
- const workspaceRoots = (files, ecosystem) => {
305
- const manifests = Object.keys(files).filter((file) => node_path.default.posix.basename(file) === workspaceManifests[ecosystem] || ecosystem === "npm" && node_path.default.posix.basename(file) === "pnpm-workspace.yaml");
306
- const workspaces = /* @__PURE__ */ new Map();
307
- const npmWorkspaceErrors = /* @__PURE__ */ new Map();
308
- for (const file of manifests) {
309
- if (ecosystem === "npm") {
310
- const directory = node_path.default.posix.dirname(file);
311
- const workspaceFile = node_path.default.posix.join(directory, "pnpm-workspace.yaml");
312
- const text = files[workspaceFile];
313
- if (text !== void 0) {
314
- try {
315
- const document = (0, yaml.parseDocument)(text);
316
- if (document.errors.length > 0) throw new Error(`${workspaceFile}:1: invalid pnpm workspace YAML`);
317
- const parsed = document.toJS({ maxAliasCount: 100 });
318
- if (!record$6(parsed)) throw new Error(`${workspaceFile}:1: pnpm workspace must contain an object`);
319
- const patterns = parsed["packages"] ?? [];
320
- if (!strings(patterns)) throw new Error(`${workspaceFile}:1: pnpm packages must be a string array`);
321
- workspaces.set(directory, patterns.map((pattern) => pattern.startsWith("!") ? `!${node_path.default.posix.normalize(pattern.slice(1))}` : node_path.default.posix.normalize(pattern)));
322
- } catch (error) {
323
- npmWorkspaceErrors.set(directory, error instanceof Error ? error : new Error(String(error)));
324
- }
325
- continue;
326
- }
327
- }
328
- try {
329
- const parsed = ecosystem === "npm" ? JSON.parse(files[file] ?? "") : (0, smol_toml.parse)(files[file] ?? "");
330
- if (!record$6(parsed)) continue;
331
- if (ecosystem === "npm" && typeof parsed["packageManager"] === "string" && parsed["packageManager"].startsWith("pnpm@")) continue;
332
- const value = workspaceTable(parsed, ecosystem);
333
- const patterns = record$6(value) ? value[ecosystem === "npm" ? "packages" : "members"] : value;
334
- const members = ecosystem === "cargo" && record$6(value) && patterns === void 0 ? [] : patterns;
335
- if (strings(members)) {
336
- const excluded = ecosystem !== "npm" && record$6(value) && strings(value["exclude"]) ? value["exclude"].map((pattern) => `!${pattern}`) : [];
337
- workspaces.set(node_path.default.posix.dirname(file), [...members, ...excluded]);
338
- }
339
- } catch {}
340
- }
341
- const implicitCargoMembers = /* @__PURE__ */ new Map();
342
- const declaredMember = (root, patterns, directory) => {
343
- const relative = node_path.default.posix.relative(root, directory);
344
- if (relative === "" || relative === ".." || relative.startsWith("../")) return false;
345
- return require_toolchain_inputs.workspaceContains({
346
- directory: root,
347
- file: node_path.default.posix.join(directory, "package.json"),
348
- patterns,
349
- rootMembership: "patterns"
350
- });
351
- };
352
- const installable = /* @__PURE__ */ new Set(["."]);
353
- if (ecosystem === "npm") {
354
- for (const file of manifests) {
355
- const directory = node_path.default.posix.dirname(file);
356
- if (require_toolchain_inputs.javascriptDependencyLockfiles.some((name) => files[node_path.default.posix.join(directory, name)] !== void 0)) installable.add(directory);
357
- }
358
- let changed = true;
359
- while (changed) {
360
- changed = false;
361
- for (const [root, patterns] of workspaces) {
362
- if (!installable.has(root)) continue;
363
- for (const file of manifests) {
364
- const directory = node_path.default.posix.dirname(file);
365
- if (installable.has(directory) || !declaredMember(root, patterns, directory)) continue;
366
- installable.add(directory);
367
- changed = true;
368
- }
369
- }
370
- }
371
- }
372
- if (ecosystem === "npm") {
373
- for (const [directory, error] of npmWorkspaceErrors) if (installable.has(directory)) throw error;
374
- }
375
- if (ecosystem === "cargo") for (const [root, patterns] of workspaces) {
376
- const includes = patterns.filter((pattern) => !pattern.startsWith("!"));
377
- const excludes = patterns.filter((pattern) => pattern.startsWith("!"));
378
- const memberDirectories = /* @__PURE__ */ new Set([root]);
379
- for (const file of manifests) {
380
- const directory = node_path.default.posix.dirname(file);
381
- const relative = node_path.default.posix.relative(root, directory);
382
- if (includes.some((pattern) => picomatch.default.isMatch(relative, pattern)) && !excludes.some((pattern) => picomatch.default.isMatch(relative, pattern.slice(1)))) memberDirectories.add(directory);
383
- }
384
- for (const directory of memberDirectories) try {
385
- const parsed = (0, smol_toml.parse)(files[node_path.default.posix.join(directory, "Cargo.toml")] ?? "");
386
- const workspace = (0, smol_toml.parse)(files[node_path.default.posix.join(root, "Cargo.toml")] ?? "")["workspace"];
387
- const inherited = record$6(workspace) ? workspace["dependencies"] : void 0;
388
- const dependencyOwners = [parsed];
389
- const targets = parsed["target"];
390
- if (record$6(targets)) {
391
- for (const target of Object.values(targets)) if (record$6(target)) dependencyOwners.push(target);
392
- }
393
- for (const owner of dependencyOwners) for (const key of [
394
- "dependencies",
395
- "dev-dependencies",
396
- "build-dependencies"
397
- ]) {
398
- const dependencies = owner[key];
399
- if (!record$6(dependencies)) continue;
400
- for (const [name, dependency] of Object.entries(dependencies)) {
401
- if (!record$6(dependency)) continue;
402
- const isInherited = dependency["workspace"] === true;
403
- const resolved = isInherited && record$6(inherited) ? inherited[name] : dependency;
404
- if (!record$6(resolved) || typeof resolved["path"] !== "string") continue;
405
- if (node_path.default.posix.isAbsolute(resolved["path"]) || /\\|\$|^[A-Za-z]:/.test(resolved["path"])) continue;
406
- const target = node_path.default.posix.normalize(node_path.default.posix.join(isInherited ? root : directory, resolved["path"]));
407
- const relative = node_path.default.posix.relative(root, target);
408
- if (relative.startsWith("../") || relative === ".." || files[node_path.default.posix.join(target, "Cargo.toml")] === void 0 || excludes.some((pattern) => picomatch.default.isMatch(relative, pattern.slice(1)))) continue;
409
- memberDirectories.add(target);
410
- }
411
- }
412
- } catch {}
413
- implicitCargoMembers.set(root, memberDirectories);
414
- }
415
- return manifests.filter((file) => ecosystem !== "npm" || installable.has(node_path.default.posix.dirname(file))).filter((file) => {
416
- const directory = node_path.default.posix.dirname(file);
417
- return ![...workspaces].some(([root, patterns]) => {
418
- if (ecosystem === "npm" && !installable.has(root)) return false;
419
- const relative = node_path.default.posix.relative(root, directory);
420
- if (relative === "" || relative.startsWith("../")) return false;
421
- const includes = patterns.filter((pattern) => !pattern.startsWith("!"));
422
- const excludes = patterns.filter((pattern) => pattern.startsWith("!"));
423
- return (implicitCargoMembers.get(root)?.has(directory) === true || (ecosystem === "npm" ? declaredMember(root, patterns, directory) : includes.some((pattern) => picomatch.default.isMatch(relative, pattern)))) && !excludes.some((pattern) => picomatch.default.isMatch(relative, pattern.slice(1)));
424
- });
425
- }).map(directoryOf).filter((directory, index, values) => values.indexOf(directory) === index);
426
- };
427
- const javascriptEcosystem = (directory, files) => {
428
- const root = directory === "/" ? "." : directory.slice(1);
429
- if (files[node_path.default.posix.join(root, "bun.lock")] !== void 0) return "bun";
430
- try {
431
- const manifest = JSON.parse(files[node_path.default.posix.join(root, "package.json")] ?? "{}");
432
- if (record$6(manifest) && typeof manifest["packageManager"] === "string" && manifest["packageManager"].startsWith("bun@")) return "bun";
433
- } catch {}
434
- return "npm";
435
- };
436
- const pythonEcosystem = (file, files) => {
437
- const manifestKind = require_toolchain_inputs.pythonDependencyManifestKind(file);
438
- if (manifestKind === "pipfile" || manifestKind === "pipfile-lock" || manifestKind === "setup") return "pip";
439
- const root = node_path.default.posix.dirname(file);
440
- if (files[node_path.default.posix.join(root, "uv.lock")] !== void 0 || files[node_path.default.posix.join(root, "uv.toml")] !== void 0) return "uv";
441
- const pyproject = files[node_path.default.posix.join(root, "pyproject.toml")];
442
- if (pyproject !== void 0) try {
443
- const tool = (0, smol_toml.parse)(pyproject)["tool"];
444
- if (record$6(tool) && record$6(tool["uv"])) return "uv";
445
- } catch {}
446
- return "pip";
447
- };
448
- const ecosystemRoots = (files) => {
449
- const roots = /* @__PURE__ */ new Map();
450
- const requireRoot = (ecosystem, directory) => {
451
- const directories = roots.get(ecosystem) ?? /* @__PURE__ */ new Set();
452
- directories.add(directory);
453
- roots.set(ecosystem, directories);
454
- };
455
- for (const directory of workspaceRoots(files, "npm")) requireRoot(javascriptEcosystem(directory, files), directory);
456
- for (const directory of workspaceRoots(files, "cargo")) requireRoot("cargo", directory);
457
- for (const directory of workspaceRoots(files, "uv")) {
458
- const root = directory === "/" ? "." : directory.slice(1);
459
- requireRoot(pythonEcosystem(node_path.default.posix.join(root, "pyproject.toml"), files), directory);
460
- }
461
- for (const file of Object.keys(files)) {
462
- if (require_toolchain_inputs.isDependabotGithubActionsPath(file)) requireRoot("github-actions", "/");
463
- const name = node_path.default.posix.basename(file);
464
- if (require_toolchain_inputs.isDockerDefinitionPath(file)) requireRoot("docker", directoryOf(file));
465
- if (require_toolchain_inputs.isComposeDefinitionPath(file) || require_toolchain_inputs.isKubernetesDefinitionPath(file)) for (const document of (0, yaml.parseAllDocuments)(files[file] ?? "", { merge: true })) {
466
- if (document.errors.length > 0) continue;
467
- try {
468
- const classified = require_toolchain_inputs.containerDocumentImages(document.toJS({ maxAliasCount: 100 }), file);
469
- if (classified !== void 0) requireRoot(classified.ecosystem, directoryOf(file));
470
- } catch {}
471
- }
472
- if (name === "uv.lock" || name === "uv.toml" || name !== "pyproject.toml" && require_toolchain_inputs.isPythonDependencyManifest(file)) requireRoot(pythonEcosystem(file, files), directoryOf(file));
473
- }
474
- return roots;
475
- };
476
- const ignoredFor = (ecosystem, policy) => {
477
- if (ecosystem === "npm" || ecosystem === "bun") return policy.ignoredPackages;
478
- if (ecosystem === "github-actions") return policy.ignoredActions;
479
- if (ecosystem === "docker" || ecosystem === "docker-compose") return ownedDockerImageAliases(policy.ignoredImages);
480
- return [];
481
- };
482
- /** Validate the shared update policy against a hermetic tracked-file snapshot. */
483
- const checkDependabot = ({ files, policy }) => {
484
- const roots = ecosystemRoots(files);
485
- const paths = [".github/dependabot.yml", ".github/dependabot.yaml"].filter((file) => files[file] !== void 0);
486
- if (roots.size === 0 && paths.length === 0) return [];
487
- const file = paths.at(0) ?? ".github/dependabot.yml";
488
- const diagnostics = [];
489
- const add = (message, line = 1) => {
490
- diagnostics.push({
491
- rule: "dependabot-policy",
492
- path: file,
493
- line,
494
- message
495
- });
496
- };
497
- if (paths.length === 0) {
498
- add("add a Dependabot configuration for the repository's package ecosystems");
499
- return diagnostics;
500
- }
501
- if (paths.length > 1) add("keep one Dependabot configuration file");
502
- const lineCounter = new yaml.LineCounter();
503
- const document = (0, yaml.parseDocument)(files[file] ?? "", { lineCounter });
504
- if (document.errors.length > 0) {
505
- for (const error of document.errors) add(`invalid Dependabot YAML: ${error.message}`, lineCounter.linePos(error.pos[0]).line);
506
- return diagnostics;
507
- }
508
- let parsed;
509
- try {
510
- parsed = document.toJS({ maxAliasCount: 100 });
511
- } catch {
512
- add("cannot resolve Dependabot YAML aliases");
513
- return diagnostics;
514
- }
515
- if (!record$6(parsed) || parsed["version"] !== 2 || !Array.isArray(parsed["updates"])) {
516
- add("Dependabot must declare version: 2 and an updates array");
517
- return diagnostics;
518
- }
519
- const covered = /* @__PURE__ */ new Map();
520
- let legacy;
521
- try {
522
- const legacyDocument = (0, yaml.parseDocument)(files[file] ?? "", {
523
- version: "1.1",
524
- schema: "yaml-1.1"
525
- });
526
- if (legacyDocument.errors.length === 0) legacy = legacyDocument.toJS({ maxAliasCount: 100 });
527
- } catch {}
528
- const legacyUpdates = record$6(legacy) && Array.isArray(legacy["updates"]) ? legacy["updates"] : [];
529
- const rootClaims = /* @__PURE__ */ new Map();
530
- const directoryClaims = /* @__PURE__ */ new Map();
531
- for (const [index, update] of parsed["updates"].entries()) {
532
- const lineOf = (key) => {
533
- const node = document.getIn([
534
- "updates",
535
- index,
536
- key
537
- ], true);
538
- const offset = (0, yaml.isNode)(node) ? node.range?.[0] : void 0;
539
- return offset === void 0 ? 1 : lineCounter.linePos(offset).line;
540
- };
541
- const report = (key, message) => add(message, lineOf(key));
542
- if (!record$6(update) || typeof update["package-ecosystem"] !== "string") {
543
- report("package-ecosystem", "each Dependabot update must declare a package ecosystem");
544
- continue;
545
- }
546
- const ecosystem = update["package-ecosystem"];
547
- if (update["target-branch"] !== void 0) report("target-branch", `${ecosystem}: shared updates must target the default branch`);
548
- if (update["open-pull-requests-limit"] === 0) report("open-pull-requests-limit", `${ecosystem}: version updates must remain enabled`);
549
- const single = update["directory"];
550
- const multiple = update["directories"];
551
- let directories = [];
552
- if (typeof single === "string" && multiple === void 0) directories = [single];
553
- else if (single === void 0 && strings(multiple)) directories = multiple;
554
- const validDirectories = directories.filter((directory) => directory.startsWith("/") && !directory.split("/").includes(".."));
555
- if (directories.length === 0 || validDirectories.length !== directories.length) report("directory", `${ecosystem}: declare directory or nonempty directories with absolute repository paths`);
556
- const normalizedDirectories = Array.from(new Set(validDirectories.map((value) => node_path.default.posix.normalize(value).replace(/\/+$/, "") || "/")));
557
- const matches = covered.get(ecosystem) ?? /* @__PURE__ */ new Set();
558
- const claimedDirectories = directoryClaims.get(ecosystem) ?? /* @__PURE__ */ new Map();
559
- for (const directory of normalizedDirectories) {
560
- const previous = claimedDirectories.get(directory);
561
- if (previous !== void 0) report(multiple === void 0 ? "directory" : "directories", `${ecosystem}: directory ${directory} overlaps update entry ${previous + 1}`);
562
- else claimedDirectories.set(directory, index);
563
- }
564
- directoryClaims.set(ecosystem, claimedDirectories);
565
- const claimedRoots = rootClaims.get(ecosystem) ?? /* @__PURE__ */ new Map();
566
- for (const root of roots.get(ecosystem) ?? []) if (normalizedDirectories.some((directory) => directory === root || multiple !== void 0 && picomatch.default.isMatch(root, directory))) {
567
- const previous = claimedRoots.get(root);
568
- if (previous !== void 0) report(multiple === void 0 ? "directory" : "directories", `${ecosystem}: update root ${root} overlaps update entry ${previous + 1}`);
569
- else claimedRoots.set(root, index);
570
- matches.add(root);
571
- }
572
- rootClaims.set(ecosystem, claimedRoots);
573
- covered.set(ecosystem, matches);
574
- const schedule = update["schedule"];
575
- const legacyUpdate = legacyUpdates.at(index);
576
- const legacySchedule = record$6(legacyUpdate) ? legacyUpdate["schedule"] : void 0;
577
- if (record$6(schedule) && schedule["time"] === policy.schedule.time && (!record$6(legacySchedule) || legacySchedule["time"] !== policy.schedule.time)) report("schedule", `${ecosystem}: quote schedule time to preserve its string value in YAML 1.1`);
578
- if (!record$6(schedule) || Object.keys(schedule).length !== Object.keys(policy.schedule).length || Object.entries(policy.schedule).some(([key, value]) => schedule[key] !== value)) report("schedule", `${ecosystem}: schedule must match the shared policy`);
579
- const cooldown = update["cooldown"];
580
- if (!record$6(cooldown) || Object.keys(cooldown).length !== 1 || cooldown["default-days"] !== policy.cooldown.defaultDays) report("cooldown", `${ecosystem}: cooldown must contain only default-days: ${policy.cooldown.defaultDays}`);
581
- const groups = update["groups"];
582
- if (!record$6(groups) || Object.keys(groups).length !== Object.keys(policy.groups).length || Object.entries(policy.groups).some(([name, expected]) => {
583
- const group = groups[name];
584
- return !record$6(group) || Object.keys(group).length !== 2 || !sameStrings(group["patterns"], expected.patterns) || !sameStrings(group["update-types"], expected.updateTypes);
585
- })) report("groups", `${ecosystem}: groups must match the shared policy`);
586
- const ignored = ignoredFor(ecosystem, policy);
587
- const ignore = update["ignore"];
588
- if (ignore !== void 0 && (!Array.isArray(ignore) || ignore.length === 0)) report("ignore", `${ecosystem}: omit ignore or provide a nonempty array`);
589
- if (Array.isArray(ignore) && ignore.some((entry) => !record$6(entry) || typeof entry["dependency-name"] !== "string" || !ignored.includes(entry["dependency-name"]))) report("ignore", `${ecosystem}: ignore only packages owned by the shared policy`);
590
- if (Array.isArray(ignore) && ignore.some((entry) => !versionOnlyIgnore(entry))) report("ignore", `${ecosystem}: each shared-pin ignore must contain only dependency-name and update-types for semver-major, semver-minor, and semver-patch version updates; security updates must remain enabled`);
591
- for (const name of ignored) if (!Array.isArray(ignore) || !ignore.some((entry) => record$6(entry) && entry["dependency-name"] === name && versionOnlyIgnore(entry))) report("ignore", `${ecosystem}: ignore all version updates for shared pin ${name}`);
592
- }
593
- for (const [ecosystem, directories] of roots) for (const directory of directories) if (!covered.get(ecosystem)?.has(directory)) add(`add a ${ecosystem} update entry covering ${directory}`);
594
- return diagnostics;
595
- };
596
- //#endregion
597
247
  //#region src/toolchain-dynamic-selectors.ts
598
248
  const locatorValidators = {
599
249
  image: /^(?:jobs\.[\w-]+\.(?:container|services\.[\w-]+)|services\.[\w.-]+\.image)$/,
600
250
  "bun-source": /^(?:jobs\.[\w-]+|runs)\.steps\.[\w-]+$/
601
251
  };
602
252
  const knownKind = (value) => typeof value === "string" && Object.hasOwn(locatorValidators, value);
603
- const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
253
+ const record$9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
604
254
  /** One reviewed declaration identifies one unresolved selector, never a rule. */
605
255
  const parseDynamicSelectors = (input) => {
606
256
  if (input === void 0) return [];
@@ -608,7 +258,7 @@ const parseDynamicSelectors = (input) => {
608
258
  const result = [];
609
259
  const keys = /* @__PURE__ */ new Set();
610
260
  for (const entry of input) {
611
- if (!record$5(entry) || Object.keys(entry).some((key) => ![
261
+ if (!record$9(entry) || Object.keys(entry).some((key) => ![
612
262
  "path",
613
263
  "kind",
614
264
  "reason",
@@ -644,7 +294,7 @@ const parseDynamicSelectors = (input) => {
644
294
  };
645
295
  //#endregion
646
296
  //#region src/toolchain-engine-floors.ts
647
- const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
297
+ const record$8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
648
298
  const repositoryPath = (value) => typeof value === "string" && value !== "" && !/^[A-Za-z]:/.test(value) && !/[\\\s]/.test(value) && !Array.from(value).some((character) => character.charCodeAt(0) < 32) && value.split("/").every((part) => part !== "" && part !== "." && part !== "..");
649
299
  const parseEngineFloors = (input) => {
650
300
  if (input === void 0) return [];
@@ -652,7 +302,7 @@ const parseEngineFloors = (input) => {
652
302
  const result = [];
653
303
  const jobs = /* @__PURE__ */ new Set();
654
304
  for (const entry of input) {
655
- if (!record$4(entry) || Object.keys(entry).some((key) => ![
305
+ if (!record$8(entry) || Object.keys(entry).some((key) => ![
656
306
  "package",
657
307
  "workflow",
658
308
  "job"
@@ -673,16 +323,16 @@ const resolveEngineFloor = (entry, files) => {
673
323
  const workflowText = files[entry.workflow];
674
324
  if (manifestText === void 0 || workflowText === void 0) throw new Error("engine floor package and workflow must be tracked");
675
325
  const manifest = JSON.parse(manifestText);
676
- if (!record$4(manifest) || manifest["private"] === true || typeof manifest["name"] !== "string" || manifest["name"].trim() === "" || !record$4(manifest["engines"]) || typeof manifest["engines"]["node"] !== "string") throw new Error("engine floor package must be published and declare engines.node");
326
+ if (!record$8(manifest) || manifest["private"] === true || typeof manifest["name"] !== "string" || manifest["name"].trim() === "" || !record$8(manifest["engines"]) || typeof manifest["engines"]["node"] !== "string") throw new Error("engine floor package must be published and declare engines.node");
677
327
  const range = manifest["engines"]["node"];
678
328
  const minimum = (0, semver.validRange)(range) === null ? null : (0, semver.minVersion)(range);
679
329
  if (minimum === null || minimum.prerelease.length !== 0) throw new Error("engine floor engines.node must declare a stable minimum");
680
330
  const document = (0, yaml.parseDocument)(workflowText, { merge: true });
681
331
  if (document.errors.length !== 0) throw new Error("engine floor workflow must be valid YAML");
682
332
  const workflow = document.toJS({ maxAliasCount: 100 });
683
- if (!record$4(workflow) || !record$4(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
333
+ if (!record$8(workflow) || !record$8(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
684
334
  const job = workflow["jobs"][entry.job];
685
- if (!record$4(job)) throw new Error("engine floor job must exist in its tracked workflow");
335
+ if (!record$8(job)) throw new Error("engine floor job must exist in its tracked workflow");
686
336
  if (!Array.isArray(job["steps"]) || Object.hasOwn(job, "uses")) throw new Error("engine floor job must declare steps and cannot reuse a workflow");
687
337
  return {
688
338
  ...entry,
@@ -702,7 +352,7 @@ const packageRules = [
702
352
  "typescript-layout",
703
353
  "node-engine"
704
354
  ];
705
- const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
355
+ const record$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
706
356
  const shellSubstitutions = (command) => {
707
357
  const frames = [{
708
358
  kind: "root",
@@ -1262,10 +912,10 @@ const checkPackageFiles = ({ files, policy }) => {
1262
912
  const document = pnpm ? (0, yaml.parseDocument)(text, { lineCounter }) : void 0;
1263
913
  if (document !== void 0 && document.errors.length > 0) throw new Error("invalid YAML");
1264
914
  const json = document === void 0 ? JSON.parse(text) : document.toJS();
1265
- if (!record$3(json)) throw new Error("manifest must be an object");
915
+ if (!record$7(json)) throw new Error("manifest must be an object");
1266
916
  if (pnpm) {
1267
- if (json["catalog"] !== void 0 && !record$3(json["catalog"])) throw new Error("catalog must be a mapping");
1268
- if (json["catalogs"] !== void 0 && (!record$3(json["catalogs"]) || Object.values(json["catalogs"]).some((catalog) => !record$3(catalog)))) throw new Error("named catalogs must be mappings");
917
+ if (json["catalog"] !== void 0 && !record$7(json["catalog"])) throw new Error("catalog must be a mapping");
918
+ if (json["catalogs"] !== void 0 && (!record$7(json["catalogs"]) || Object.values(json["catalogs"]).some((catalog) => !record$7(catalog)))) throw new Error("named catalogs must be mappings");
1269
919
  if (json["packages"] !== void 0 && (!Array.isArray(json["packages"]) || !json["packages"].every((pattern) => typeof pattern === "string"))) throw new Error("workspace packages must be patterns");
1270
920
  }
1271
921
  manifests.set(file, json);
@@ -1279,13 +929,13 @@ const checkPackageFiles = ({ files, policy }) => {
1279
929
  catalog: json["overrides"],
1280
930
  prefix: ["overrides"]
1281
931
  },
1282
- ...record$3(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
932
+ ...record$7(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
1283
933
  catalog,
1284
934
  prefix: ["catalogs", name]
1285
935
  })) : []
1286
936
  ];
1287
937
  for (const { catalog, prefix } of catalogEntries) {
1288
- if (!record$3(catalog)) continue;
938
+ if (!record$7(catalog)) continue;
1289
939
  for (const [name, value] of Object.entries(catalog)) {
1290
940
  const node = document.getIn([...prefix, name], true);
1291
941
  if (!(0, yaml.isNode)(node) || node.range === void 0 || node.range === null) continue;
@@ -1304,7 +954,7 @@ const checkPackageFiles = ({ files, policy }) => {
1304
954
  });
1305
955
  }
1306
956
  }
1307
- const catalogSources = (json) => [json, ...record$3(json["workspaces"]) ? [json["workspaces"]] : []];
957
+ const catalogSources = (json) => [json, ...record$7(json["workspaces"]) ? [json["workspaces"]] : []];
1308
958
  const pnpmWorkspaceFor = (file) => {
1309
959
  let directory = node_path.default.posix.dirname(file);
1310
960
  while (true) {
@@ -1327,8 +977,8 @@ const checkPackageFiles = ({ files, policy }) => {
1327
977
  patterns: workspace["packages"],
1328
978
  rootMembership: "implicit"
1329
979
  })) return void 0;
1330
- if (name === "" || name === "default") return workspace["catalog"] ?? (record$3(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
1331
- return record$3(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
980
+ if (name === "" || name === "default") return workspace["catalog"] ?? (record$7(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
981
+ return record$7(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
1332
982
  }
1333
983
  let directory = node_path.default.posix.dirname(file);
1334
984
  while (true) {
@@ -1336,7 +986,7 @@ const checkPackageFiles = ({ files, policy }) => {
1336
986
  if (manifest !== void 0) {
1337
987
  for (const source of catalogSources(manifest)) {
1338
988
  if (name === "" && source["catalog"] !== void 0) return source["catalog"];
1339
- if (record$3(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
989
+ if (record$7(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
1340
990
  }
1341
991
  if (manifest["workspaces"] !== void 0) return void 0;
1342
992
  }
@@ -1350,7 +1000,7 @@ const checkPackageFiles = ({ files, policy }) => {
1350
1000
  if (visited.has(value)) return void 0;
1351
1001
  visited.add(value);
1352
1002
  const catalog = catalogFor(file, value.slice(8));
1353
- value = record$3(catalog) ? catalog[name] : void 0;
1003
+ value = record$7(catalog) ? catalog[name] : void 0;
1354
1004
  }
1355
1005
  return value;
1356
1006
  };
@@ -1366,7 +1016,7 @@ const checkPackageFiles = ({ files, policy }) => {
1366
1016
  const workspaces = manifests.get(node_path.default.posix.join(directory, "package.json"))?.["workspaces"];
1367
1017
  if (workspaces !== void 0) return {
1368
1018
  directory,
1369
- patterns: record$3(workspaces) ? workspaces["packages"] : workspaces,
1019
+ patterns: record$7(workspaces) ? workspaces["packages"] : workspaces,
1370
1020
  source: "json"
1371
1021
  };
1372
1022
  if (directory === ".") return void 0;
@@ -1422,7 +1072,7 @@ const checkPackageFiles = ({ files, policy }) => {
1422
1072
  "peerDependencies"
1423
1073
  ]) {
1424
1074
  const dependencies = json[section];
1425
- if (!record$3(dependencies)) continue;
1075
+ if (!record$7(dependencies)) continue;
1426
1076
  for (const [name, value] of Object.entries(dependencies)) {
1427
1077
  if (!tsSpecifiers.has(name)) continue;
1428
1078
  const resolved = resolve(file, name, value);
@@ -1484,7 +1134,7 @@ const checkPackageFiles = ({ files, policy }) => {
1484
1134
  message: `packageManager Bun version must be ${policy.bun}, found ${JSON.stringify(manager)}`
1485
1135
  });
1486
1136
  const checkEntries = (entries, location) => {
1487
- if (!record$3(entries)) return;
1137
+ if (!record$7(entries)) return;
1488
1138
  for (const [name, raw] of Object.entries(entries)) {
1489
1139
  const value = resolve(file, name, raw);
1490
1140
  const tsAllowed = tsSpecifiers.get(name);
@@ -1521,12 +1171,12 @@ const checkPackageFiles = ({ files, policy }) => {
1521
1171
  "devDependencies",
1522
1172
  "optionalDependencies"
1523
1173
  ]) checkEntries(json[key]);
1524
- if (record$3(json["peerDependencies"])) {
1174
+ if (record$7(json["peerDependencies"])) {
1525
1175
  for (const [name, value] of Object.entries(json["peerDependencies"])) if (tsSpecifiers.has(name) && !peerRange(resolve(file, name, value))) checkEntries({ [name]: value });
1526
1176
  }
1527
1177
  for (const source of catalogSources(json)) {
1528
1178
  checkEntries(source["catalog"]);
1529
- if (!record$3(source["catalogs"])) continue;
1179
+ if (!record$7(source["catalogs"])) continue;
1530
1180
  for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog);
1531
1181
  }
1532
1182
  const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...tsSpecifiers.keys()]);
@@ -1538,7 +1188,7 @@ const checkPackageFiles = ({ files, policy }) => {
1538
1188
  "optionalDependencies"
1539
1189
  ]) {
1540
1190
  const entries = resolutionRoot[section];
1541
- if (!record$3(entries)) continue;
1191
+ if (!record$7(entries)) continue;
1542
1192
  for (const [name, value] of Object.entries(entries)) resolutionDependencies[name] = resolve(file, name, value);
1543
1193
  }
1544
1194
  const affectedSpecifiers = /* @__PURE__ */ new Map();
@@ -1558,7 +1208,7 @@ const checkPackageFiles = ({ files, policy }) => {
1558
1208
  "optionalDependencies"
1559
1209
  ]) {
1560
1210
  const entries = member[section];
1561
- if (!record$3(entries)) continue;
1211
+ if (!record$7(entries)) continue;
1562
1212
  for (const [name, value] of Object.entries(entries)) {
1563
1213
  if (!tsSpecifiers.has(name)) continue;
1564
1214
  const values = affectedSpecifiers.get(name) ?? /* @__PURE__ */ new Set();
@@ -1568,7 +1218,7 @@ const checkPackageFiles = ({ files, policy }) => {
1568
1218
  }
1569
1219
  }
1570
1220
  const checkResolutionMap = (entries, parent) => {
1571
- if (!record$3(entries)) return;
1221
+ if (!record$7(entries)) return;
1572
1222
  for (const [selector, replacement] of Object.entries(entries)) {
1573
1223
  const target = selector === "." ? parent : selector;
1574
1224
  if (target === void 0) continue;
@@ -1591,7 +1241,7 @@ const checkPackageFiles = ({ files, policy }) => {
1591
1241
  });
1592
1242
  continue;
1593
1243
  }
1594
- if (record$3(replacement)) {
1244
+ if (record$7(replacement)) {
1595
1245
  checkResolutionMap(replacement, target);
1596
1246
  continue;
1597
1247
  }
@@ -1626,7 +1276,7 @@ const checkPackageFiles = ({ files, policy }) => {
1626
1276
  };
1627
1277
  checkResolutionMap(json["overrides"]);
1628
1278
  checkResolutionMap(json["resolutions"]);
1629
- if (record$3(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
1279
+ if (record$7(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
1630
1280
  const localEntries = toolchainEntries(file, json);
1631
1281
  const usesCompiler = policy.typescriptInstallLayouts.some((layout) => localEntries.some(({ name }) => name === layout.compilerPackage));
1632
1282
  let selectedLayout = layoutFor(specifierMap(localEntries));
@@ -1650,7 +1300,7 @@ const checkPackageFiles = ({ files, policy }) => {
1650
1300
  key: "dependencies",
1651
1301
  message: "TypeScript compiler and compatibility packages must match one complete declared install layout"
1652
1302
  });
1653
- const command = record$3(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
1303
+ const command = record$7(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
1654
1304
  if (selectedLayout !== void 0 && typeof command === "string") {
1655
1305
  const normalizedExpected = selectedLayout.typecheckCommand.replace(/\s+/g, " ").trim();
1656
1306
  for (const words of compilerCommandSegments(command)) {
@@ -1668,7 +1318,7 @@ const checkPackageFiles = ({ files, policy }) => {
1668
1318
  }
1669
1319
  }
1670
1320
  }
1671
- if (record$3(json["peerDependencies"])) {
1321
+ if (record$7(json["peerDependencies"])) {
1672
1322
  const peerLayout = selectedLayout ?? workspaceLayoutFor(file)?.layout;
1673
1323
  for (const [name, raw] of Object.entries(json["peerDependencies"])) {
1674
1324
  if (!tsSpecifiers.has(name)) continue;
@@ -1692,7 +1342,7 @@ const checkPackageFiles = ({ files, policy }) => {
1692
1342
  });
1693
1343
  }
1694
1344
  }
1695
- if (record$3(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !require_toolchain_inputs.nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
1345
+ if (record$7(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !require_toolchain_inputs.nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
1696
1346
  file,
1697
1347
  rule: "node-engine",
1698
1348
  key: "node",
@@ -1702,16 +1352,633 @@ const checkPackageFiles = ({ files, policy }) => {
1702
1352
  }
1703
1353
  return diagnostics;
1704
1354
  };
1355
+ const extensionKinds = new Map(Object.entries({
1356
+ ".js": "javascript",
1357
+ ".jsx": "javascript",
1358
+ ".mjs": "javascript",
1359
+ ".cjs": "javascript",
1360
+ ".ts": "typescript",
1361
+ ".tsx": "typescript",
1362
+ ".mts": "typescript",
1363
+ ".cts": "typescript"
1364
+ }));
1365
+ /** Share source classification between lint ownership and script command discovery. */
1366
+ const scriptSourceKind = (file) => extensionKinds.get((0, node_path.extname)(file));
1367
+ //#endregion
1368
+ //#region src/toolchain-script-lifecycle.ts
1369
+ const record$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1370
+ const declaredManager = (value) => typeof value === "string" ? /^(bun|npm|pnpm|yarn)@(\d+\.\d+\.\d+)(?:\+\S+)?$/u.exec(value) : null;
1371
+ const rootScriptManager = (manifest, context) => declaredManager(context.packageManager ?? (record$6(manifest) ? manifest["packageManager"] : void 0))?.at(1);
1372
+ const npmHooksEnabled = (file) => {
1373
+ if (file?.type === "unknown") throw new Error("npm lifecycle configuration must be a tracked regular file");
1374
+ if (file?.type !== "tracked") return true;
1375
+ let enabled = true;
1376
+ let section = false;
1377
+ for (const raw of file.source.split(/\r?\n/u)) {
1378
+ const line = raw.trim();
1379
+ if (line.startsWith("[")) {
1380
+ section = true;
1381
+ continue;
1382
+ }
1383
+ if (section || !/^ignore-scripts(?:\s*=|\s*$)/iu.test(line)) continue;
1384
+ if (!/^ignore-scripts(?:\s*=|\s*$)/u.test(line)) throw new Error("npm ignore-scripts must use its canonical lowercase key");
1385
+ const value = line.slice(line.indexOf("=") + 1).trim();
1386
+ if (value !== "true" && value !== "false") throw new Error("npm ignore-scripts must be a literal boolean for lifecycle discovery");
1387
+ enabled = value === "false";
1388
+ }
1389
+ return enabled;
1390
+ };
1391
+ const yamlSettings = (file, manager) => {
1392
+ if (file?.type === "unknown") throw new Error(`${manager} lifecycle configuration requires tracked regular root files without nested workspace overrides`);
1393
+ if (file?.type !== "tracked") return {};
1394
+ const document = (0, yaml.parseDocument)(file.source);
1395
+ if (document.errors.length > 0) throw new Error(`Cannot parse ${manager} lifecycle configuration`);
1396
+ let settings;
1397
+ try {
1398
+ settings = document.toJS();
1399
+ } catch {
1400
+ throw new Error(`Cannot parse ${manager} lifecycle configuration`);
1401
+ }
1402
+ if (!record$6(settings)) throw new Error(`${manager} lifecycle configuration must be a mapping`);
1403
+ return settings;
1404
+ };
1405
+ const pnpmHooksEnabled = (manifest, context) => {
1406
+ const declared = declaredManager(context.packageManager ?? manifest["packageManager"]);
1407
+ const dependency = [manifest["dependencies"], manifest["devDependencies"]].flatMap((field) => record$6(field) ? [field["pnpm"]] : []).find((value) => typeof value === "string");
1408
+ if ((declared?.at(1) === "pnpm" ? declared.at(2) : dependency) !== "12.9.1") throw new Error("pnpm lifecycle discovery requires declared pnpm@12.9.1");
1409
+ const enabled = yamlSettings(context.pnpmWorkspace, "pnpm")["enablePrePostScripts"];
1410
+ if (enabled === void 0) return true;
1411
+ if (typeof enabled !== "boolean") throw new Error("pnpm enablePrePostScripts must be a literal boolean");
1412
+ return enabled;
1413
+ };
1414
+ /** Automatic pre/post stages run their bodies directly, without pre-pre recursion. */
1415
+ const scriptLifecycleStages = ({ name, manager, manifest, scripts, context }) => {
1416
+ const present = [`pre${name}`, `post${name}`].filter((hook) => scripts[hook] !== void 0 && scripts[hook] !== "");
1417
+ if (present.length === 0) return [name];
1418
+ if (present.some((hook) => typeof scripts[hook] !== "string")) throw new Error(`Lifecycle hooks for ${name} must be literal package scripts`);
1419
+ let enabled;
1420
+ switch (manager) {
1421
+ case "npm":
1422
+ enabled = npmHooksEnabled(context.npmrc);
1423
+ break;
1424
+ case "bun":
1425
+ enabled = true;
1426
+ break;
1427
+ case "pnpm":
1428
+ enabled = pnpmHooksEnabled(manifest, context);
1429
+ break;
1430
+ case "yarn": {
1431
+ const declaration = declaredManager(context.packageManager ?? manifest["packageManager"]);
1432
+ if (declaration?.at(1) !== "yarn" || !/^[234]\./u.test(declaration.at(2) ?? "")) throw new Error("Yarn lifecycle discovery requires a declared Berry version (2, 3 or 4)");
1433
+ const settings = yamlSettings(context.yarnrc, "Yarn");
1434
+ if (settings["yarnPath"] !== void 0 && settings["yarnPath"] !== null || settings["plugins"] !== void 0 && (!Array.isArray(settings["plugins"]) || settings["plugins"].length > 0)) throw new Error("Custom Yarn executables or plugins require explicit lifecycle discovery support");
1435
+ enabled = false;
1436
+ break;
1437
+ }
1438
+ default: throw new Error(`Cannot determine the package manager for lifecycle hooks of ${name}`);
1439
+ }
1440
+ if (!enabled) return [name];
1441
+ const source = scripts[name];
1442
+ const include = (hook) => present.includes(hook) && !(manager === "pnpm" && typeof source === "string" && source.includes(hook));
1443
+ return [
1444
+ ...include(`pre${name}`) ? [`pre${name}`] : [],
1445
+ name,
1446
+ ...include(`post${name}`) ? [`post${name}`] : []
1447
+ ];
1448
+ };
1449
+ //#endregion
1450
+ //#region src/toolchain-lint-command.ts
1451
+ const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1452
+ const scriptManagers = /* @__PURE__ */ new Set([
1453
+ "bun",
1454
+ "npm",
1455
+ "pnpm",
1456
+ "yarn"
1457
+ ]);
1458
+ const valueFlags = /* @__PURE__ */ new Set([
1459
+ "-c",
1460
+ "--config",
1461
+ "--tsconfig",
1462
+ "--ignore-path",
1463
+ "--ignore-pattern",
1464
+ "-A",
1465
+ "--allow",
1466
+ "-W",
1467
+ "--warn",
1468
+ "-D",
1469
+ "--deny",
1470
+ "-f",
1471
+ "--format",
1472
+ "--max-warnings",
1473
+ "--threads",
1474
+ "--report-unused-disable-directives-severity"
1475
+ ]);
1476
+ const booleanFlags = /* @__PURE__ */ new Set([
1477
+ "--no-ignore",
1478
+ "--quiet",
1479
+ "--deny-warnings",
1480
+ "--silent",
1481
+ "--no-error-on-unmatched-pattern",
1482
+ "--disable-nested-config",
1483
+ "--report-unused-disable-directives",
1484
+ "--disable-unicorn-plugin",
1485
+ "--disable-oxc-plugin",
1486
+ "--disable-typescript-plugin",
1487
+ "--import-plugin",
1488
+ "--react-plugin",
1489
+ "--jsdoc-plugin",
1490
+ "--jest-plugin",
1491
+ "--vitest-plugin",
1492
+ "--jsx-a11y-plugin",
1493
+ "--nextjs-plugin",
1494
+ "--react-perf-plugin",
1495
+ "--promise-plugin",
1496
+ "--node-plugin",
1497
+ "--vue-plugin"
1498
+ ]);
1499
+ const literalWord = (raw) => {
1500
+ let quote;
1501
+ for (let index = 0; index < raw.length; index++) {
1502
+ const character = raw.at(index);
1503
+ if (character === "\\" && quote !== "'") {
1504
+ index++;
1505
+ continue;
1506
+ }
1507
+ if (character === quote) {
1508
+ quote = void 0;
1509
+ continue;
1510
+ }
1511
+ if (quote === void 0 && (character === "'" || character === "\"")) {
1512
+ quote = character;
1513
+ continue;
1514
+ }
1515
+ if (quote !== "'" && (character === "$" || character === "`")) return false;
1516
+ if (quote === void 0 && character !== void 0 && /[*?[\]{}~]/.test(character)) return false;
1517
+ }
1518
+ return quote === void 0;
1519
+ };
1520
+ const inventoryArguments = (raw) => {
1521
+ const args = [];
1522
+ let threadsSeen = false;
1523
+ for (let index = 0; index < raw.length; index++) {
1524
+ const word = raw.at(index);
1525
+ if (word === void 0) break;
1526
+ if (!literalWord(word)) return { error: "oxlint arguments must be literal shell words" };
1527
+ const value = decodeShellWord(word);
1528
+ if (!value.startsWith("-")) {
1529
+ args.push(value);
1530
+ continue;
1531
+ }
1532
+ if (value === "--") return { error: "oxlint argument terminators are unsupported for inventory" };
1533
+ if (value === "--type-aware" || value === "--type-check") continue;
1534
+ const equals = value.indexOf("=");
1535
+ const flag = equals < 0 ? value : value.slice(0, equals);
1536
+ if (booleanFlags.has(flag)) {
1537
+ if (equals >= 0) return { error: `unexpected value for oxlint flag ${flag}` };
1538
+ args.push(value);
1539
+ continue;
1540
+ }
1541
+ if (!valueFlags.has(flag)) return { error: `unsupported oxlint flag ${flag}` };
1542
+ let argument;
1543
+ if (equals >= 0) {
1544
+ argument = value.slice(equals + 1);
1545
+ if (argument === "") return { error: `missing value for oxlint flag ${flag}` };
1546
+ } else {
1547
+ const next = raw.at(++index);
1548
+ if (next === void 0 || !literalWord(next) || decodeShellWord(next).startsWith("-")) return { error: `missing or nonliteral value for oxlint flag ${flag}` };
1549
+ argument = decodeShellWord(next);
1550
+ }
1551
+ if (flag === "--threads") {
1552
+ if (threadsSeen) return { error: "duplicate oxlint flag --threads" };
1553
+ if (!/^\+?[0-9]+$/u.test(argument) || BigInt(argument) > 18446744073709551615n) return { error: "invalid integer value for oxlint flag --threads" };
1554
+ threadsSeen = true;
1555
+ continue;
1556
+ }
1557
+ if (equals >= 0) args.push(value);
1558
+ else args.push(value, argument);
1559
+ }
1560
+ return { args };
1561
+ };
1562
+ /** Extract runtime and arguments for the pinned inventory binary; never execute package-script shell text. */
1563
+ const lintInventoryCommands = (manifest, forceEnabled = false, context = {}) => {
1564
+ const commands = [];
1565
+ const errors = [];
1566
+ const workspaceTasks = /* @__PURE__ */ new Set();
1567
+ const filteredBuilds = /* @__PURE__ */ new Set();
1568
+ const declaresEngine = record$5(manifest) && [manifest["dependencies"], manifest["devDependencies"]].some((dependencies) => record$5(dependencies) && "oxlint-tsgolint" in dependencies);
1569
+ const enabled = forceEnabled || declaresEngine;
1570
+ if (!enabled) return {
1571
+ commands,
1572
+ errors,
1573
+ enabled,
1574
+ workspaceTask: false
1575
+ };
1576
+ if (!record$5(manifest)) return {
1577
+ commands,
1578
+ errors: ["lint inventory requires an object package manifest"],
1579
+ enabled,
1580
+ workspaceTask: false
1581
+ };
1582
+ const scripts = manifest["scripts"];
1583
+ if (!record$5(scripts)) return {
1584
+ commands,
1585
+ errors: ["type-aware linting requires a root lint script"],
1586
+ enabled,
1587
+ workspaceTask: false
1588
+ };
1589
+ const active = /* @__PURE__ */ new Set();
1590
+ const visited = /* @__PURE__ */ new Set();
1591
+ const visit = (name) => {
1592
+ if (active.has(name)) {
1593
+ errors.push(`cyclic lint script alias: ${name}`);
1594
+ return;
1595
+ }
1596
+ if (visited.has(name)) return;
1597
+ const source = scripts[name];
1598
+ if (typeof source !== "string") {
1599
+ errors.push(`missing literal package script: ${name}`);
1600
+ return;
1601
+ }
1602
+ active.add(name);
1603
+ for (const raw of shellCommandSegments(source)) {
1604
+ if (raw.length === 0) continue;
1605
+ const words = raw.map(decodeShellWord);
1606
+ const first = words.at(0);
1607
+ if (first === "cd" || first === "pushd" || first === "popd") {
1608
+ errors.push(`${name}: changing directories is unsupported for lint inventory`);
1609
+ continue;
1610
+ }
1611
+ if (first === "echo" || first === "printf") continue;
1612
+ if (first === "turbo") {
1613
+ if (words.length === 3 && words.at(1) === "run" && words.at(2) === "lint" && raw.every(literalWord)) workspaceTasks.add(name);
1614
+ else if (words.includes("lint") || !raw.every(literalWord)) errors.push(`${name}: workspace lint delegation requires literal turbo run lint without options`);
1615
+ continue;
1616
+ }
1617
+ if (first === "bun" && words.length === 4 && words.at(1) === "--filter" && words.at(2) !== "" && !words.at(2)?.startsWith("-") && words.at(3) === "build" && raw.every(literalWord)) {
1618
+ const selector = words.at(2);
1619
+ try {
1620
+ if (selector === void 0 || context.assertFilteredBunBuild === void 0) throw new Error("Filtered Bun builds require verified workspace lifecycle context");
1621
+ context.assertFilteredBunBuild(selector);
1622
+ filteredBuilds.add(selector);
1623
+ } catch (error) {
1624
+ errors.push(error instanceof Error ? error.message : "Cannot determine filtered build lifecycle stages");
1625
+ }
1626
+ continue;
1627
+ }
1628
+ if (first !== void 0 && scriptManagers.has(first) && words.at(1) === "run") {
1629
+ const target = words.at(2);
1630
+ if (target !== void 0 && first === "bun" && scriptSourceKind(target) !== void 0 && typeof scripts[target] !== "string") {
1631
+ if (!raw.every(literalWord)) errors.push(`${name}: validation file commands require literal arguments`);
1632
+ continue;
1633
+ }
1634
+ if (raw.length !== 3 || !raw.every(literalWord)) {
1635
+ errors.push(`${name}: package script aliases must have one literal name`);
1636
+ continue;
1637
+ }
1638
+ const alias = words.at(2);
1639
+ if (alias !== void 0) invoke({
1640
+ name: alias,
1641
+ manager: first
1642
+ });
1643
+ continue;
1644
+ }
1645
+ let offset;
1646
+ if (first === "oxlint" || first === "./node_modules/.bin/oxlint" || first === "node_modules/.bin/oxlint") offset = 1;
1647
+ else if (first === "bun" && words.at(1) === "--bun" && words.at(2) === "oxlint") offset = 3;
1648
+ else if (first === "bunx" && words.at(1) === "oxlint") offset = 2;
1649
+ else if (first === "bunx" && words.at(1) === "--no-install" && words.at(2) === "oxlint") offset = 3;
1650
+ if (offset === void 0) {
1651
+ if (words.some((word) => /(?:^|\/)oxlint(?:@[^/]+)?$/.test(word)) || words.some((word, index) => scriptManagers.has(word) && words.slice(index + 1).includes("run")) || words.includes("turbo") && words.includes("lint") || first === "bun" && words.includes("--filter") || first !== void 0 && scriptManagers.has(first) && !(first === "bun" && [
1652
+ "test",
1653
+ "build",
1654
+ "check",
1655
+ "install",
1656
+ "x"
1657
+ ].includes(words.at(1) ?? "")) && (words.slice(1).some((word) => typeof scripts[word] === "string") || !raw.every(literalWord)) || raw.at(0) !== void 0 && !literalWord(raw.at(0) ?? "")) errors.push(`${name}: unsupported or dynamic oxlint launcher`);
1658
+ continue;
1659
+ }
1660
+ if (!raw.slice(0, offset).every(literalWord)) {
1661
+ errors.push(`${name}: oxlint launcher must be literal`);
1662
+ continue;
1663
+ }
1664
+ const inventory = inventoryArguments(raw.slice(offset));
1665
+ if (inventory.error !== void 0) errors.push(`${name}: ${inventory.error}`);
1666
+ else commands.push({
1667
+ runtime: first === "bun" ? "bun" : "node",
1668
+ args: inventory.args
1669
+ });
1670
+ }
1671
+ active.delete(name);
1672
+ visited.add(name);
1673
+ };
1674
+ const activeInvocations = /* @__PURE__ */ new Set();
1675
+ const invoke = ({ name, manager }) => {
1676
+ const key = `${manager ?? "unknown"}:${name}`;
1677
+ if (activeInvocations.has(key)) {
1678
+ errors.push(`cyclic lint script alias: ${name}`);
1679
+ return;
1680
+ }
1681
+ activeInvocations.add(key);
1682
+ try {
1683
+ const stages = scriptLifecycleStages({
1684
+ name,
1685
+ manager,
1686
+ manifest,
1687
+ scripts,
1688
+ context
1689
+ });
1690
+ for (const stage of stages) visit(stage);
1691
+ } catch (error) {
1692
+ errors.push(error instanceof Error ? error.message : "Cannot determine script lifecycle stages");
1693
+ } finally {
1694
+ activeInvocations.delete(key);
1695
+ }
1696
+ };
1697
+ invoke({
1698
+ name: context.entryScript ?? "lint",
1699
+ manager: rootScriptManager(manifest, context)
1700
+ });
1701
+ const workspaceTask = workspaceTasks.size > 0;
1702
+ if (!declaresEngine && commands.length === 0 && !workspaceTask && filteredBuilds.size === 0 && errors.length === 0) return {
1703
+ commands,
1704
+ errors,
1705
+ enabled: false,
1706
+ workspaceTask
1707
+ };
1708
+ if (commands.length === 0 && !workspaceTask && filteredBuilds.size === 0 && context.entryScript === void 0) errors.push("root lint scripts contain no supported oxlint invocation");
1709
+ return {
1710
+ commands: errors.length === 0 ? commands : [],
1711
+ errors,
1712
+ enabled,
1713
+ workspaceTask: errors.length === 0 && workspaceTask
1714
+ };
1715
+ };
1716
+ //#endregion
1717
+ //#region src/toolchain-node-projects.ts
1718
+ const nodeProjectRule = "type-aware-projects";
1719
+ const checkProjectOwnership = ({ trackedFiles, lintedFiles, projectFiles }) => [...new Set(lintedFiles)].filter((file) => scriptSourceKind(file) !== void 0 && trackedFiles.has(file) && !projectFiles.has(file)).sort((left, right) => left.localeCompare(right)).map((file) => ({
1720
+ rule: nodeProjectRule,
1721
+ path: file,
1722
+ line: 1,
1723
+ message: "type-aware lint would use an inferred project; include this file in a declared tsconfig project (for Node scripts, add scripts/tsconfig.json extending @stll/typescript-config/node.json; see the Node scripts recipe in stella/tooling README)"
1724
+ }));
1725
+ const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1726
+ const stringList = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
1727
+ const readCommand = ({ root, executable, args, runtime }) => {
1728
+ const result = (0, node_child_process.spawnSync)(runtime, [
1729
+ ...runtime === "bun" ? ["--bun"] : [],
1730
+ executable,
1731
+ ...args
1732
+ ], {
1733
+ cwd: root,
1734
+ encoding: "utf8",
1735
+ timeout: 3e4,
1736
+ maxBuffer: 33554432
1737
+ });
1738
+ if (result.error !== void 0) throw result.error;
1739
+ if (result.status !== 0) throw new Error(`configuration discovery failed: ${(result.stderr + result.stdout).trim()}`);
1740
+ return result.stdout;
1741
+ };
1742
+ const relativePath = (root, file) => node_path.default.relative(root, node_path.default.resolve(root, file)).split(node_path.default.sep).join("/");
1743
+ const lifecycleFile = (root, file, tracked) => {
1744
+ try {
1745
+ const location = node_path.default.join(root, file);
1746
+ const metadata = (0, node_fs.lstatSync)(location);
1747
+ if (!tracked.has(file) || !metadata.isFile()) return { type: "unknown" };
1748
+ return {
1749
+ type: "tracked",
1750
+ source: (0, node_fs.readFileSync)(location, "utf8")
1751
+ };
1752
+ } catch (error) {
1753
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return { type: "absent" };
1754
+ return { type: "unknown" };
1755
+ }
1756
+ };
1757
+ /** Inventory and config expansion only: neither subprocess lints or typechecks. */
1758
+ const checkNodeProjects = ({ root, manifest, trackedFiles }) => {
1759
+ const packageManager = record$4(manifest) ? manifest["packageManager"] : void 0;
1760
+ const managerUsesPnpmWorkspace = packageManager === void 0 || typeof packageManager === "string" && packageManager.startsWith("pnpm@");
1761
+ const discovered = /* @__PURE__ */ new Map();
1762
+ const workspaceMembers = (pnpmMembership) => {
1763
+ const cached = discovered.get(pnpmMembership);
1764
+ if (cached !== void 0) return cached;
1765
+ const files = { "package.json": JSON.stringify(manifest) };
1766
+ for (const file of trackedFiles) {
1767
+ const name = node_path.default.posix.basename(file);
1768
+ if (file !== "package.json" && (name === "package.json" || pnpmMembership && name === "pnpm-workspace.yaml") && !file.split("/").includes("node_modules")) files[file] = (0, node_fs.readFileSync)(node_path.default.join(root, file), "utf8");
1769
+ }
1770
+ const result = require_consumer_node_support.discoverConsumerManifests(files);
1771
+ discovered.set(pnpmMembership, result);
1772
+ return result;
1773
+ };
1774
+ const filteredCommands = [];
1775
+ const activeBuilds = /* @__PURE__ */ new Set();
1776
+ const workspaceLifecycle = (directory) => {
1777
+ const nestedLifecycle = { ...lifecycle };
1778
+ for (const [key, name] of [
1779
+ ["npmrc", ".npmrc"],
1780
+ ["pnpmWorkspace", "pnpm-workspace.yaml"],
1781
+ ["yarnrc", ".yarnrc.yml"]
1782
+ ]) {
1783
+ let ancestor = directory;
1784
+ while (ancestor !== ".") {
1785
+ if (lifecycleFile(root, node_path.default.posix.join(ancestor, name), trackedFiles).type !== "absent") {
1786
+ nestedLifecycle[key] = { type: "unknown" };
1787
+ break;
1788
+ }
1789
+ ancestor = node_path.default.posix.dirname(ancestor);
1790
+ }
1791
+ }
1792
+ return nestedLifecycle;
1793
+ };
1794
+ const lifecycle = {
1795
+ packageManager: record$4(manifest) ? manifest["packageManager"] : void 0,
1796
+ npmrc: lifecycleFile(root, ".npmrc", trackedFiles),
1797
+ pnpmWorkspace: lifecycleFile(root, "pnpm-workspace.yaml", trackedFiles),
1798
+ yarnrc: lifecycleFile(root, ".yarnrc.yml", trackedFiles),
1799
+ assertFilteredBunBuild: (selector) => {
1800
+ const selected = [...workspaceMembers(false).entries()].filter(([, pkg]) => pkg["name"] === selector);
1801
+ if (selected.length !== 1) throw new Error("Filtered Bun build requires one declared workspace package");
1802
+ const member = selected.at(0);
1803
+ if (member === void 0) throw new Error("Missing selected workspace");
1804
+ const [directory, pkg] = member;
1805
+ const scripts = pkg["scripts"];
1806
+ if (!record$4(scripts) || typeof scripts["build"] !== "string") throw new Error("Filtered Bun build requires a literal build script");
1807
+ if (["prebuild", "postbuild"].some((name) => scripts[name] !== void 0 && scripts[name] !== "")) throw new Error("Filtered Bun build lifecycle hooks are unsupported for lint inventory; invoke lint scopes explicitly");
1808
+ if (activeBuilds.has(directory)) throw new Error("Cyclic filtered Bun build delegation");
1809
+ activeBuilds.add(directory);
1810
+ try {
1811
+ const nested = lintInventoryCommands(pkg, true, {
1812
+ ...workspaceLifecycle(directory),
1813
+ entryScript: "build"
1814
+ });
1815
+ if (nested.workspaceTask) throw new Error("Nested workspace lint delegation is unsupported in filtered builds");
1816
+ if (nested.errors.length > 0) throw new Error(`${directory}/package.json: ${nested.errors.join("; ")}`);
1817
+ for (const command of nested.commands) filteredCommands.push({
1818
+ cwd: node_path.default.join(root, directory),
1819
+ ...command
1820
+ });
1821
+ } finally {
1822
+ activeBuilds.delete(directory);
1823
+ }
1824
+ }
1825
+ };
1826
+ const inventory = lintInventoryCommands(manifest, false, lifecycle);
1827
+ if (!inventory.enabled) return [];
1828
+ const diagnostics = [];
1829
+ const addConfiguration = (file, message) => diagnostics.push({
1830
+ rule: nodeProjectRule,
1831
+ path: file,
1832
+ line: 1,
1833
+ message
1834
+ });
1835
+ for (const error of inventory.errors) addConfiguration("package.json", error);
1836
+ if (inventory.errors.length !== 0) return diagnostics;
1837
+ try {
1838
+ const require$1 = (0, node_module.createRequire)(node_path.default.join(root, "package.json"));
1839
+ const oxlint = node_path.default.join(node_path.default.dirname(require$1.resolve("oxlint/package.json")), "bin/oxlint");
1840
+ const compiler = node_path.default.join(node_path.default.dirname(require$1.resolve("typescript/package.json")), "bin/tsc");
1841
+ const commands = inventory.commands.map((command) => ({
1842
+ cwd: root,
1843
+ ...command
1844
+ }));
1845
+ if (inventory.workspaceTask) {
1846
+ for (const [directory, workspace] of workspaceMembers(managerUsesPnpmWorkspace)) {
1847
+ if (directory === ".") continue;
1848
+ const file = `${directory}/package.json`;
1849
+ if (!record$4(workspace["scripts"]) || workspace["scripts"]["lint"] === void 0) continue;
1850
+ const nestedLifecycle = workspaceLifecycle(directory);
1851
+ const nested = lintInventoryCommands(workspace, true, nestedLifecycle);
1852
+ if (nested.workspaceTask) throw new Error(`nested workspace lint delegation is unsupported: ${file}`);
1853
+ if (nested.errors.length > 0) throw new Error(`${file}: ${nested.errors.join("; ")}`);
1854
+ for (const command of nested.commands) commands.push({
1855
+ cwd: node_path.default.join(root, directory),
1856
+ ...command
1857
+ });
1858
+ }
1859
+ if (commands.length === 0 && filteredCommands.length === 0) throw new Error("workspace lint task has no declared oxlint commands");
1860
+ }
1861
+ commands.push(...filteredCommands);
1862
+ if (commands.length === 0) throw new Error("root lint scripts contain no supported oxlint invocation");
1863
+ const lintedFiles = commands.flatMap(({ cwd, args, runtime }) => readCommand({
1864
+ root: cwd,
1865
+ executable: oxlint,
1866
+ args: [
1867
+ ...args,
1868
+ "--debug",
1869
+ "files",
1870
+ "--threads",
1871
+ "1"
1872
+ ],
1873
+ runtime
1874
+ }).split(/\r?\n/).filter((file) => file !== "").map((file) => relativePath(root, node_path.default.resolve(cwd, file))));
1875
+ const nearestProject = (start) => {
1876
+ let directory = start;
1877
+ while (true) {
1878
+ const config = node_path.default.posix.join(directory, "tsconfig.json");
1879
+ if (trackedFiles.has(config)) return config;
1880
+ if (directory === ".") return void 0;
1881
+ directory = node_path.default.posix.dirname(directory);
1882
+ }
1883
+ };
1884
+ const groups = /* @__PURE__ */ new Map();
1885
+ for (const file of new Set(lintedFiles)) {
1886
+ if (!trackedFiles.has(file) || scriptSourceKind(file) === void 0) continue;
1887
+ const config = nearestProject(node_path.default.posix.dirname(file));
1888
+ if (config === void 0) continue;
1889
+ const group = groups.get(config);
1890
+ if (group === void 0) groups.set(config, [file]);
1891
+ else group.push(file);
1892
+ }
1893
+ const projects = /* @__PURE__ */ new Map();
1894
+ const readProject = (config) => {
1895
+ const cached = projects.get(config);
1896
+ if (cached !== void 0) return cached;
1897
+ const result = {
1898
+ files: /* @__PURE__ */ new Set(),
1899
+ references: []
1900
+ };
1901
+ try {
1902
+ const project = JSON.parse(readCommand({
1903
+ root,
1904
+ executable: compiler,
1905
+ args: [
1906
+ "--showConfig",
1907
+ "--project",
1908
+ config
1909
+ ],
1910
+ runtime: "node"
1911
+ }));
1912
+ if (!record$4(project)) throw new Error("compiler configuration must be an object");
1913
+ const references = project["references"] ?? [];
1914
+ if (!Array.isArray(references)) throw new Error("compiler configuration references must be an array");
1915
+ for (const reference of references) {
1916
+ if (!record$4(reference) || typeof reference["path"] !== "string") throw new Error("compiler project reference requires a path");
1917
+ const target = relativePath(root, node_path.default.resolve(root, node_path.default.dirname(config), reference["path"]));
1918
+ const referencedConfig = target.endsWith(".json") ? target : node_path.default.posix.join(target, "tsconfig.json");
1919
+ if (!trackedFiles.has(referencedConfig)) throw new Error(`project reference is not tracked: ${referencedConfig}`);
1920
+ result.references.push(referencedConfig);
1921
+ }
1922
+ const sources = project["files"] ?? [];
1923
+ if (!stringList(sources)) throw new Error("compiler configuration did not return a files array");
1924
+ const options = project["compilerOptions"];
1925
+ for (const file of sources) {
1926
+ if (scriptSourceKind(file) === "javascript" && (!record$4(options) || options["allowJs"] !== true && options["checkJs"] !== true)) continue;
1927
+ result.files.add(relativePath(root, node_path.default.resolve(root, node_path.default.dirname(config), file)));
1928
+ }
1929
+ } catch (error) {
1930
+ result.files.clear();
1931
+ result.references.length = 0;
1932
+ addConfiguration(config, error instanceof Error ? error.message : String(error));
1933
+ }
1934
+ projects.set(config, result);
1935
+ return result;
1936
+ };
1937
+ const projectFiles = /* @__PURE__ */ new Set();
1938
+ for (const [nearest, sources] of groups) {
1939
+ const remaining = new Set(sources);
1940
+ const visited = /* @__PURE__ */ new Set();
1941
+ let ancestor = nearest;
1942
+ while (ancestor !== void 0 && remaining.size > 0) {
1943
+ const pending = [ancestor];
1944
+ let config = pending.pop();
1945
+ while (config !== void 0 && remaining.size > 0) {
1946
+ if (!visited.has(config)) {
1947
+ visited.add(config);
1948
+ const project = readProject(config);
1949
+ for (const source of remaining) {
1950
+ if (!project.files.has(source)) continue;
1951
+ projectFiles.add(source);
1952
+ remaining.delete(source);
1953
+ }
1954
+ pending.push(...project.references);
1955
+ }
1956
+ config = pending.pop();
1957
+ }
1958
+ const directory = node_path.default.posix.dirname(ancestor);
1959
+ ancestor = directory === "." ? void 0 : nearestProject(node_path.default.posix.dirname(directory));
1960
+ }
1961
+ }
1962
+ diagnostics.push(...checkProjectOwnership({
1963
+ trackedFiles,
1964
+ lintedFiles,
1965
+ projectFiles
1966
+ }));
1967
+ } catch (error) {
1968
+ addConfiguration("package.json", error instanceof Error ? error.message : String(error));
1969
+ }
1970
+ return diagnostics;
1971
+ };
1705
1972
  //#endregion
1706
1973
  //#region src/toolchain-rust.ts
1707
- const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1974
+ const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1708
1975
  /** Exceptions authorize one immutable release, never a channel or an entire rule. */
1709
1976
  const parseRustExceptions = (input, today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)) => {
1710
1977
  if (input === void 0) return [];
1711
1978
  if (!Array.isArray(input)) throw new Error("rustExceptions must be an array");
1712
1979
  const pins = /* @__PURE__ */ new Set();
1713
1980
  return input.map((entry) => {
1714
- if (!record$2(entry) || Object.keys(entry).length !== 4 || Object.keys(entry).some((key) => ![
1981
+ if (!record$3(entry) || Object.keys(entry).length !== 4 || Object.keys(entry).some((key) => ![
1715
1982
  "pin",
1716
1983
  "reason",
1717
1984
  "tracking",
@@ -1865,18 +2132,18 @@ const checkRustReferences = (options) => {
1865
2132
  if (require_toolchain_inputs.isShellScript(file, text)) shell(text);
1866
2133
  if (/(?:^|\/)package\.json$/.test(file)) try {
1867
2134
  const object = JSON.parse(text);
1868
- if (record$2(object) && record$2(object["scripts"])) {
2135
+ if (record$3(object) && record$3(object["scripts"])) {
1869
2136
  for (const [name, script] of Object.entries(object["scripts"])) if (typeof script === "string") shell(script, lineOf(`"${name}"`));
1870
2137
  }
1871
2138
  } catch {}
1872
2139
  if (/(?:^|\/)ci-service-images\.json$/.test(file)) {
1873
2140
  const images = (value) => {
1874
- if (typeof value === "string" && /^rust(?=:|@|$)/.test(canonicalDockerRuntime(value))) {
1875
- const tag = canonicalDockerRuntime(value).replace(/^rust:?/, "").split("@").at(0);
1876
- const version = tag === void 0 ? void 0 : rustImageVersion(tag);
2141
+ if (typeof value === "string" && /^rust(?=:|@|$)/.test(require_toolchain_dependabot.canonicalDockerRuntime(value))) {
2142
+ const tag = require_toolchain_dependabot.canonicalDockerRuntime(value).replace(/^rust:?/, "").split("@").at(0);
2143
+ const version = tag === void 0 ? void 0 : require_toolchain_dependabot.rustImageVersion(tag);
1877
2144
  select(version, "Rust service image", lineOf(value));
1878
2145
  } else if (Array.isArray(value)) value.forEach(images);
1879
- else if (record$2(value)) {
2146
+ else if (record$3(value)) {
1880
2147
  for (const [key, entry] of Object.entries(value)) if (key !== "name") images(entry);
1881
2148
  }
1882
2149
  };
@@ -1900,13 +2167,13 @@ const checkRustReferences = (options) => {
1900
2167
  value.forEach(visit);
1901
2168
  return;
1902
2169
  }
1903
- if (!record$2(value)) return;
1904
- if (record$2(value["env"]) && value["env"]["RUSTUP_TOOLCHAIN"] !== void 0) select(value["env"]["RUSTUP_TOOLCHAIN"], "RUSTUP_TOOLCHAIN", lineOf("RUSTUP_TOOLCHAIN"));
2170
+ if (!record$3(value)) return;
2171
+ if (record$3(value["env"]) && value["env"]["RUSTUP_TOOLCHAIN"] !== void 0) select(value["env"]["RUSTUP_TOOLCHAIN"], "RUSTUP_TOOLCHAIN", lineOf("RUSTUP_TOOLCHAIN"));
1905
2172
  if (typeof value["run"] === "string") shell(value["run"], lineOf("run:"));
1906
2173
  const uses = value["uses"];
1907
2174
  if (typeof uses === "string") {
1908
2175
  const action = uses.split("@").at(0)?.toLowerCase();
1909
- const inputs = record$2(value["with"]) ? value["with"] : {};
2176
+ const inputs = record$3(value["with"]) ? value["with"] : {};
1910
2177
  if (action === "dtolnay/rust-toolchain" || action === "actions-rust-lang/setup-rust-toolchain") {
1911
2178
  const entries = Object.entries(inputs).filter(([key]) => key.toLowerCase() === "toolchain");
1912
2179
  if (entries.length !== 1) select(void 0, `${action} requires an explicit exact toolchain input`, lineOf(uses));
@@ -1920,8 +2187,8 @@ const checkRustReferences = (options) => {
1920
2187
  }
1921
2188
  }
1922
2189
  for (const [key, child] of Object.entries(value)) {
1923
- if (key === "workflow_call" && record$2(child) && record$2(child["inputs"])) {
1924
- for (const [input, declaration] of Object.entries(child["inputs"])) if (["toolchain", "rust-toolchain"].includes(input) && record$2(declaration) && declaration["default"] !== void 0) select(declaration["default"], `reusable workflow default ${input}`, lineOf("default:"));
2190
+ if (key === "workflow_call" && record$3(child) && record$3(child["inputs"])) {
2191
+ for (const [input, declaration] of Object.entries(child["inputs"])) if (["toolchain", "rust-toolchain"].includes(input) && record$3(declaration) && declaration["default"] !== void 0) select(declaration["default"], `reusable workflow default ${input}`, lineOf("default:"));
1925
2192
  }
1926
2193
  visit(child);
1927
2194
  }
@@ -1931,6 +2198,374 @@ const checkRustReferences = (options) => {
1931
2198
  return diagnostics;
1932
2199
  };
1933
2200
  //#endregion
2201
+ //#region src/toolchain-workflow-trust.ts
2202
+ const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2203
+ const booleanParts = (source, operator) => {
2204
+ const parts = [];
2205
+ let depth = 0;
2206
+ let quoted = false;
2207
+ let start = 0;
2208
+ for (let index = 0; index < source.length; index++) {
2209
+ const character = source[index];
2210
+ if (character === "'") {
2211
+ if (quoted && source[index + 1] === "'") index++;
2212
+ else quoted = !quoted;
2213
+ }
2214
+ if (quoted) continue;
2215
+ if (character === "(") depth++;
2216
+ if (character === ")") depth--;
2217
+ if (depth === 0 && source.slice(index, index + 2) === operator) {
2218
+ parts.push(source.slice(start, index));
2219
+ start = index + 2;
2220
+ index++;
2221
+ }
2222
+ }
2223
+ parts.push(source.slice(start));
2224
+ return parts;
2225
+ };
2226
+ const withoutOuterParentheses = (source) => {
2227
+ if (!source.startsWith("(") || !source.endsWith(")")) return source;
2228
+ let depth = 0;
2229
+ let quoted = false;
2230
+ for (let index = 0; index < source.length; index++) {
2231
+ const character = source[index];
2232
+ if (character === "'") {
2233
+ if (quoted && source[index + 1] === "'") index++;
2234
+ else quoted = !quoted;
2235
+ }
2236
+ if (quoted) continue;
2237
+ if (character === "(") depth++;
2238
+ if (character === ")") depth--;
2239
+ if (depth === 0 && index !== source.length - 1) return source;
2240
+ }
2241
+ return withoutOuterParentheses(source.slice(1, -1).trim());
2242
+ };
2243
+ const excludesUntrusted = ({ condition, event, allowTrust = true }) => {
2244
+ if (condition === false) return true;
2245
+ if (typeof condition !== "string") return false;
2246
+ const source = withoutOuterParentheses(condition.trim().replace(/^\$\{\{([\s\S]*)\}\}$/u, "$1").trim());
2247
+ const alternatives = booleanParts(source, "||");
2248
+ if (alternatives.length > 1) return alternatives.every((part) => excludesUntrusted({
2249
+ condition: part,
2250
+ event,
2251
+ allowTrust
2252
+ }));
2253
+ const conjuncts = booleanParts(source, "&&");
2254
+ if (conjuncts.length > 1) return conjuncts.some((part) => excludesUntrusted({
2255
+ condition: part,
2256
+ event,
2257
+ allowTrust
2258
+ }));
2259
+ const trustComparison = /^needs\.trust-check\.outputs\.trusted\s*==\s*'([^']*)'$/u.exec(source);
2260
+ if (allowTrust && trustComparison?.at(1)?.toLowerCase() === "true") return true;
2261
+ if (source === "false") return true;
2262
+ const comparison = /^github\.event_name\s*(==|!=)\s*'([^']+)'$/u.exec(source);
2263
+ if (comparison === null) return false;
2264
+ const equal = comparison.at(2)?.toLowerCase() === event.toLowerCase();
2265
+ return comparison.at(1) === "==" ? !equal : equal;
2266
+ };
2267
+ const staticData = (word) => !/[`$\\]/u.test(word) && !/[<>]/u.test(word);
2268
+ const metadataScript = (command) => {
2269
+ const interpolation = /\$\{\{([\s\S]*?)\}\}/gu;
2270
+ for (const match of command.matchAll(interpolation)) {
2271
+ const expression = match.at(1);
2272
+ if (expression === void 0 || ![
2273
+ "github.sha",
2274
+ "github.run_id",
2275
+ "github.run_attempt",
2276
+ "github.repository"
2277
+ ].includes(expression.trim())) return void 0;
2278
+ }
2279
+ const source = command.replace(interpolation, "stll-metadata");
2280
+ return source.includes("${{") ? void 0 : source;
2281
+ };
2282
+ const metadataWord = (raw) => {
2283
+ if (/^'[^']*'$/u.test(raw)) return true;
2284
+ if ([
2285
+ "[",
2286
+ "]",
2287
+ "[[",
2288
+ "]]"
2289
+ ].includes(raw)) return true;
2290
+ let quote;
2291
+ for (const character of raw) {
2292
+ if (quote === void 0 && "*?~[{$".includes(character)) return false;
2293
+ if (character === quote) quote = void 0;
2294
+ else if (quote === void 0 && (character === "'" || character === "\"")) quote = character;
2295
+ }
2296
+ if (quote !== void 0) return false;
2297
+ const withoutKnownExpressions = raw.replace(/\$\{[A-Za-z_][A-Za-z_0-9]*\}|\$[A-Za-z_][A-Za-z_0-9]*/gu, "value");
2298
+ return !/[`$\\]/u.test(withoutKnownExpressions);
2299
+ };
2300
+ const safeComparison = (words) => {
2301
+ const first = words.at(0);
2302
+ if (![
2303
+ "test",
2304
+ "[",
2305
+ "[["
2306
+ ].includes(first ?? "")) return false;
2307
+ let end;
2308
+ if (first === "[[") end = "]]";
2309
+ else if (first === "[") end = "]";
2310
+ if (end !== void 0 && words.at(-1) !== end) return false;
2311
+ const operands = words.slice(1, end === void 0 ? void 0 : -1);
2312
+ if (operands.length === 1) return true;
2313
+ if (operands.length === 2) return ["-n", "-z"].includes(operands.at(0) ?? "");
2314
+ return operands.length === 3 && [
2315
+ "=",
2316
+ "==",
2317
+ "!="
2318
+ ].includes(operands.at(1) ?? "");
2319
+ };
2320
+ const objectReference = (value) => /^(?:HEAD|[a-f0-9]{7,64}|(?:refs\/(?:heads|tags|remotes)\/|origin\/)[A-Za-z0-9][A-Za-z0-9._/-]*)(?:[~^][0-9]*)*$/u.test(value) && !value.includes("..") && !value.includes("//") && !value.endsWith("/");
2321
+ const workflowTrustBootstrapCatalog = {
2322
+ commands: {
2323
+ test: "comparison",
2324
+ "[": "comparison",
2325
+ "[[": "comparison",
2326
+ true: "status",
2327
+ false: "status",
2328
+ exit: "exit",
2329
+ echo: "output",
2330
+ printf: "format",
2331
+ git: "git",
2332
+ gh: "github"
2333
+ },
2334
+ git: {
2335
+ "rev-parse": {
2336
+ flags: ["--short", "--verify"],
2337
+ references: 1
2338
+ },
2339
+ "merge-base": {
2340
+ flags: ["--all", "--is-ancestor"],
2341
+ references: 2
2342
+ }
2343
+ },
2344
+ github: ["api"],
2345
+ actions: {
2346
+ "actions/checkout": [
2347
+ "fetch-depth",
2348
+ "persist-credentials",
2349
+ "ref",
2350
+ "repository",
2351
+ "submodules"
2352
+ ],
2353
+ "oven-sh/setup-bun": ["bun-version", "no-cache"]
2354
+ }
2355
+ };
2356
+ const safeGit = (words) => {
2357
+ const selected = Object.entries(workflowTrustBootstrapCatalog.git).find(([name]) => name === words.at(1));
2358
+ if (selected === void 0) return false;
2359
+ const [, policy] = selected;
2360
+ const references = [];
2361
+ for (const argument of words.slice(2)) {
2362
+ if (!staticData(argument)) return false;
2363
+ if (!argument.startsWith("-")) {
2364
+ references.push(argument);
2365
+ continue;
2366
+ }
2367
+ if (!policy.flags.some((flag) => flag === argument)) return false;
2368
+ }
2369
+ return references.length === policy.references && references.every(objectReference);
2370
+ };
2371
+ const safeGh = (words) => {
2372
+ if (!words.every(staticData)) return false;
2373
+ if (workflowTrustBootstrapCatalog.github.some((name) => name === words.at(1))) {
2374
+ const arguments_ = words.slice(2);
2375
+ if (["--method", "-X"].includes(arguments_.at(0) ?? "") && arguments_.at(1) === "GET") arguments_.splice(0, 2);
2376
+ if (!arguments_.at(0) || arguments_.at(0)?.startsWith("-")) return false;
2377
+ for (let index = 1; index < arguments_.length; index++) {
2378
+ const argument = arguments_.at(index);
2379
+ if ((argument === "--method" || argument === "-X") && arguments_.at(index + 1) === "GET") index++;
2380
+ else if (["--jq", "-q"].includes(argument ?? "") && /^\.[A-Za-z0-9_.[\]" -]*$/u.test(arguments_.at(index + 1) ?? "")) index++;
2381
+ else return false;
2382
+ }
2383
+ return /^\/?repos\/[A-Za-z0-9_./-]+$/u.test(arguments_.at(0) ?? "");
2384
+ }
2385
+ return false;
2386
+ };
2387
+ const safeCommand = (raw) => {
2388
+ if (raw.length === 0) return true;
2389
+ if (!raw.every(metadataWord)) return false;
2390
+ const words = raw.map(decodeShellWord);
2391
+ const name = words.at(0);
2392
+ if (raw.at(0) !== name) return false;
2393
+ if (name === "if" || name === "elif") return safeComparison(words.slice(1));
2394
+ if (["then", "else"].includes(name ?? "")) return safeCommand(raw.slice(1));
2395
+ if (name === "fi") return words.length === 1;
2396
+ const kind = Object.entries(workflowTrustBootstrapCatalog.commands).find(([command]) => command === name)?.[1];
2397
+ if (kind === "comparison") return safeComparison(words);
2398
+ if (kind === "status") return words.length === 1;
2399
+ if (kind === "exit") return words.length === 1 || words.length === 2 && /^[0-9]+$/u.test(words.at(1) ?? "");
2400
+ if (kind === "output") return true;
2401
+ if (kind === "format") {
2402
+ const format = words.at(1);
2403
+ return format !== void 0 && !format.startsWith("-") && /^(?:[^%$`]|%%|%s)*$/u.test(format);
2404
+ }
2405
+ if (kind === "git") return safeGit(words);
2406
+ if (kind === "github") return safeGh(words);
2407
+ return false;
2408
+ };
2409
+ const invokesRepositoryCode = (command) => {
2410
+ const source = metadataScript(command);
2411
+ if (source === void 0) return true;
2412
+ const tokens = shellTokens(source);
2413
+ for (const [index, token] of tokens.entries()) {
2414
+ if (token.kind !== "redirect") continue;
2415
+ const target = tokens.at(index + 1);
2416
+ if (![">", ">>"].includes(token.value) || target?.kind !== "word" || ![
2417
+ "\"$GITHUB_OUTPUT\"",
2418
+ "\"${GITHUB_OUTPUT}\"",
2419
+ "/dev/null"
2420
+ ].includes(target.value)) return true;
2421
+ }
2422
+ const branches = [];
2423
+ for (let words of shellCommandSegments(source, "tokens")) {
2424
+ if (!safeCommand(words)) return true;
2425
+ while (["then", "else"].includes(words.at(0) ?? "")) {
2426
+ const keyword = words.at(0);
2427
+ const previous = branches.pop();
2428
+ if (keyword === "then" && previous === "condition") branches.push("then");
2429
+ else if (keyword === "else" && previous === "then") branches.push("else");
2430
+ else return true;
2431
+ words = words.slice(1);
2432
+ }
2433
+ const keyword = words.at(0);
2434
+ if (keyword === "if") branches.push("condition");
2435
+ if (keyword === "elif") {
2436
+ if (branches.pop() !== "then") return true;
2437
+ branches.push("condition");
2438
+ }
2439
+ if (keyword === "fi") {
2440
+ const previous = branches.pop();
2441
+ if (previous !== "then" && previous !== "else") return true;
2442
+ }
2443
+ }
2444
+ return branches.length !== 0;
2445
+ };
2446
+ const safeAction = (uses, inputs) => {
2447
+ if (typeof uses !== "string" || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_./-]+@[a-f0-9]{40}$/u.test(uses)) return false;
2448
+ const repository = uses.split("@").at(0);
2449
+ const approved = Object.entries(workflowTrustBootstrapCatalog.actions).find(([name]) => name === repository);
2450
+ if (approved === void 0) return false;
2451
+ if (inputs === void 0) return repository === "actions/checkout";
2452
+ if (!record$2(inputs)) return false;
2453
+ if (repository !== "actions/checkout") {
2454
+ const version = inputs["bun-version"];
2455
+ if (typeof version !== "string" || !/^v?[0-9]+(?:\.(?:[0-9]+|x)){0,2}$/u.test(version)) return false;
2456
+ }
2457
+ const [, allowedInputs] = approved;
2458
+ return Object.entries(inputs).every(([name, value]) => allowedInputs.some((allowed) => allowed === name) && [
2459
+ "string",
2460
+ "boolean",
2461
+ "number"
2462
+ ].includes(typeof value) && (name !== "submodules" || value === false || value === "false"));
2463
+ };
2464
+ const safeShell = (shell) => shell === void 0 || shell === "bash" || shell === "sh";
2465
+ const safeEnvironment = (value) => value === void 0 || record$2(value) && Object.keys(value).every((name) => [
2466
+ "HEAD_REPO",
2467
+ "BASE_REPO",
2468
+ "HAS_RUN_CI_LABEL",
2469
+ "PR_TITLE",
2470
+ "HEAD_REF",
2471
+ "ISSUE_BODY",
2472
+ "COMMENT_BODY",
2473
+ "GH_TOKEN",
2474
+ "GITHUB_TOKEN",
2475
+ "HAWK_RUST_TOOLCHAIN",
2476
+ "HAWK_VERSION",
2477
+ "HAWK_X86_64_UNKNOWN_LINUX_GNU_SHA256"
2478
+ ].includes(name));
2479
+ const configuredShell = (value) => record$2(value) && record$2(value["run"]) ? value["run"]["shell"] : void 0;
2480
+ const workflowTrustRules = ["workflow-trust"];
2481
+ const workflowEvents = (triggers) => {
2482
+ if (typeof triggers === "string") return [triggers];
2483
+ if (Array.isArray(triggers)) return triggers;
2484
+ if (record$2(triggers)) return Object.keys(triggers);
2485
+ return [];
2486
+ };
2487
+ /** Enforce a workflow's declared trust boundary before repository dependency execution. */
2488
+ const checkWorkflowTrust = (workflow) => {
2489
+ const findings = [];
2490
+ if (!record$2(workflow) || !record$2(workflow["jobs"])) return findings;
2491
+ const jobs = workflow["jobs"];
2492
+ if (!record$2(jobs["trust-check"])) return findings;
2493
+ const triggers = workflow["on"];
2494
+ const pullRequests = workflowEvents(triggers).filter((event) => event === "pull_request" || event === "pull_request_target");
2495
+ if (pullRequests.length === 0) return findings;
2496
+ for (const [name, job] of Object.entries(jobs)) {
2497
+ if (!record$2(job)) continue;
2498
+ const needs = job["needs"];
2499
+ const ordered = name !== "trust-check" && (needs === "trust-check" || Array.isArray(needs) && needs.includes("trust-check"));
2500
+ const permitsExecution = (stepCondition) => {
2501
+ const guarded = pullRequests.every((event) => excludesUntrusted({
2502
+ condition: job["if"],
2503
+ event
2504
+ }) || excludesUntrusted({
2505
+ condition: stepCondition,
2506
+ event
2507
+ }));
2508
+ if (ordered && guarded) return true;
2509
+ return pullRequests.every((event) => excludesUntrusted({
2510
+ condition: job["if"],
2511
+ event,
2512
+ allowTrust: false
2513
+ }) || excludesUntrusted({
2514
+ condition: stepCondition,
2515
+ event,
2516
+ allowTrust: false
2517
+ }));
2518
+ };
2519
+ const message = `jobs.${name} repository commands require needs: trust-check and a condition requiring its trusted output on pull requests`;
2520
+ if (job["uses"] !== void 0 && !permitsExecution(void 0)) findings.push({
2521
+ type: "job",
2522
+ property: "uses",
2523
+ job: name,
2524
+ message
2525
+ });
2526
+ if (!permitsExecution(void 0)) {
2527
+ if (job["container"] !== void 0) findings.push({
2528
+ type: "job",
2529
+ property: "container",
2530
+ job: name,
2531
+ message
2532
+ });
2533
+ const services = job["services"];
2534
+ if (services !== void 0 && (!record$2(services) || Object.keys(services).length > 0)) findings.push({
2535
+ type: "job",
2536
+ property: "services",
2537
+ job: name,
2538
+ message
2539
+ });
2540
+ }
2541
+ if (!Array.isArray(job["steps"])) continue;
2542
+ for (const [index, step] of job["steps"].entries()) {
2543
+ if (!record$2(step)) continue;
2544
+ const run = step["run"];
2545
+ const uses = step["uses"];
2546
+ const unsafeRun = typeof run === "string" && (invokesRepositoryCode(run) || !safeShell(step["shell"] ?? configuredShell(job["defaults"]) ?? configuredShell(workflow["defaults"])) || ![
2547
+ workflow["env"],
2548
+ job["env"],
2549
+ step["env"]
2550
+ ].every(safeEnvironment));
2551
+ const unsafeAction = uses !== void 0 && (!safeAction(uses, step["with"]) || ![
2552
+ workflow["env"],
2553
+ job["env"],
2554
+ step["env"]
2555
+ ].every(safeEnvironment));
2556
+ if (!unsafeRun && !unsafeAction) continue;
2557
+ if (permitsExecution(unsafeAction ? void 0 : step["if"])) continue;
2558
+ findings.push({
2559
+ type: "step",
2560
+ job: name,
2561
+ step: index,
2562
+ message
2563
+ });
2564
+ }
2565
+ }
2566
+ return findings;
2567
+ };
2568
+ //#endregion
1934
2569
  //#region src/toolchain-runtime.ts
1935
2570
  const runtimeRules = [
1936
2571
  "bun-pins",
@@ -1940,7 +2575,8 @@ const runtimeRules = [
1940
2575
  "runtime-manager",
1941
2576
  "runtime-docker",
1942
2577
  "runtime-workflow",
1943
- "action-pins"
2578
+ "action-pins",
2579
+ ...workflowTrustRules
1944
2580
  ];
1945
2581
  const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1946
2582
  const dockerEnvironmentAssignments = ({ source, escape }) => {
@@ -2306,7 +2942,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2306
2942
  });
2307
2943
  const checkRuntimeImage = ({ image, line, label, at }) => {
2308
2944
  const unknownRuntime = () => {
2309
- const rustImage = typeof image === "string" && /^rust(?=:|@|$)/i.test(canonicalDockerRuntime(image));
2945
+ const rustImage = typeof image === "string" && /^rust(?=:|@|$)/i.test(require_toolchain_dependabot.canonicalDockerRuntime(image));
2310
2946
  if (!rustImage && dynamic(image) && authorizeDynamic("image", line, at)) return;
2311
2947
  add({
2312
2948
  rule: rustImage ? "rust-version" : "runtime-docker",
@@ -2318,7 +2954,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2318
2954
  unknownRuntime();
2319
2955
  return;
2320
2956
  }
2321
- const canonical = canonicalDockerRuntime(image);
2957
+ const canonical = require_toolchain_dependabot.canonicalDockerRuntime(image);
2322
2958
  const structuralImage = canonical.replace(/\$\{[^}]*\}|\$[A-Za-z_][A-Za-z_\d]*/g, (variable) => "x".repeat(variable.length));
2323
2959
  const digestSeparator = structuralImage.indexOf("@");
2324
2960
  const reference = digestSeparator < 0 ? canonical : canonical.slice(0, digestSeparator);
@@ -2351,7 +2987,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
2351
2987
  }
2352
2988
  const tag = hasTag ? reference.slice(tagSeparator + 1) : void 0;
2353
2989
  const variantSuffix = tool === "oven/bun" ? /-(?:alpine|slim|debian)$/ : /-(?:alpine(?:\d+(?:\.\d+)*)?|(?:bookworm|bullseye|trixie|buster)(?:-slim)?|slim)$/;
2354
- const version = tool === "rust" && tag !== void 0 ? rustImageVersion(tag) : tag?.replace(variantSuffix, "");
2990
+ const version = tool === "rust" && tag !== void 0 ? require_toolchain_dependabot.rustImageVersion(tag) : tag?.replace(variantSuffix, "");
2355
2991
  let expected = policy.bun;
2356
2992
  if (tool === "node") expected = policy.node;
2357
2993
  else if (tool === "python") expected = policy.python;
@@ -3174,8 +3810,29 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
3174
3810
  }
3175
3811
  };
3176
3812
  try {
3177
- document.toJS({ maxAliasCount: 100 });
3813
+ const workflow = document.toJS({ maxAliasCount: 100 });
3178
3814
  if (automationKind === "workflow") {
3815
+ for (const finding of checkWorkflowTrust(workflow)) {
3816
+ const jobNode = getNode(getNode(document.contents, "jobs"), finding.job);
3817
+ let source;
3818
+ switch (finding.type) {
3819
+ case "job":
3820
+ source = getNode(jobNode, finding.property);
3821
+ break;
3822
+ case "step": {
3823
+ const steps = resolveNode(getNode(jobNode, "steps"));
3824
+ const step = (0, yaml.isSeq)(steps) ? steps.items.at(finding.step) : void 0;
3825
+ source = getNode(step, "run") ?? getNode(step, "uses");
3826
+ break;
3827
+ }
3828
+ default: throw new Error("unknown workflow finding", { cause: finding });
3829
+ }
3830
+ add({
3831
+ rule: "workflow-trust",
3832
+ line: nodeLine(source ?? jobNode),
3833
+ message: finding.message
3834
+ });
3835
+ }
3179
3836
  const jobs = getNode(document.contents, "jobs");
3180
3837
  if ((0, yaml.isMap)(jobs)) for (const key of keysOf(jobs)) {
3181
3838
  checkoutBindings = [];
@@ -3228,8 +3885,10 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
3228
3885
  const toolchainRules = [.../* @__PURE__ */ new Set([
3229
3886
  ...packageRules,
3230
3887
  ...runtimeRules,
3231
- ...dependabotRules,
3232
- "cloud-setup-drift"
3888
+ ...require_toolchain_dependabot.dependabotRules,
3889
+ nodeProjectRule,
3890
+ "cloud-setup-drift",
3891
+ "quarantine-policy"
3233
3892
  ])];
3234
3893
  const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3235
3894
  const array = (value) => Array.isArray(value);
@@ -3241,9 +3900,12 @@ const parseToolchainConfiguration = (input) => {
3241
3900
  if (!array(optOuts)) throw new Error("optOuts must be an array");
3242
3901
  for (const entry of optOuts) {
3243
3902
  if (!record(entry) || Object.keys(entry).some((key) => key !== "rule" && key !== "reason") || typeof entry["rule"] !== "string" || !toolchainRules.some((rule) => rule === entry["rule"]) || typeof entry["reason"] !== "string" || entry["reason"].trim() === "" || disabled.has(entry["rule"])) throw new Error("each opt-out requires a unique known rule and a nonempty reason");
3903
+ if (entry["rule"] === "quarantine-policy") throw new Error("quarantine-policy cannot be opted out; temporary third-party exclusions require expiry");
3244
3904
  if (entry["rule"] === "rust-version") throw new Error("rust-version requires expiring exact-pin rustExceptions, not an opt-out");
3245
3905
  if (entry["rule"] === "node-engine") throw new Error("node-engine cannot be opted out; engines.node must support the shared Node series");
3906
+ if (entry["rule"] === "workflow-trust") throw new Error("workflow-trust cannot be opted out; repository commands must follow the declared trust decision");
3246
3907
  if (entry["rule"] === "cloud-setup-drift") throw new Error("cloud-setup-drift cannot be opted out; generated setup must match shared policy");
3908
+ if (entry["rule"] === "type-aware-projects") throw new Error("type-aware-projects cannot be opted out; linted files require declared TypeScript projects");
3247
3909
  disabled.add(entry["rule"]);
3248
3910
  }
3249
3911
  return {
@@ -3255,8 +3917,8 @@ const parseToolchainConfiguration = (input) => {
3255
3917
  consumerChecks: parseConsumerChecks(input["consumerChecks"])
3256
3918
  };
3257
3919
  };
3258
- /** Read one tracked configuration snapshot for guards and policy generation. */
3259
- const readToolchainInputs = (root) => {
3920
+ /** Dependency graphs stay opaque unless a caller explicitly needs registry identities. */
3921
+ const readToolchainInputs = (root, { bunLock = "presence" } = {}) => {
3260
3922
  const diagnostics = [];
3261
3923
  const tracked = require_git_tracked_output.readGitTrackedOutput(root, "staged").split("\0").filter((entry) => entry !== "" && !entry.startsWith("160000 ")).map((entry) => entry.slice(entry.indexOf(" ") + 1));
3262
3924
  const files = {};
@@ -3266,12 +3928,13 @@ const readToolchainInputs = (root) => {
3266
3928
  const kind = require_toolchain_inputs.toolchainInputKind(file);
3267
3929
  const shebangCandidate = kind === void 0 && node_path.default.posix.extname(file) === "" && !file.split("/").some((part) => part === "vendor" || part === "node_modules");
3268
3930
  if (kind === void 0 && !shebangCandidate) continue;
3269
- if (kind === "presence") {
3270
- files[file] = "";
3271
- continue;
3272
- }
3273
3931
  try {
3274
3932
  const inputPath = node_path.default.join(resolvedRoot, file);
3933
+ if (kind === "presence" && !(file === "bun.lock" && bunLock === "content")) {
3934
+ (0, node_fs.lstatSync)(inputPath);
3935
+ files[file] = "";
3936
+ continue;
3937
+ }
3275
3938
  if (shebangCandidate) {
3276
3939
  const input = (0, node_fs.lstatSync)(inputPath);
3277
3940
  if (!input.isFile() && !input.isSymbolicLink()) continue;
@@ -3292,7 +3955,8 @@ const readToolchainInputs = (root) => {
3292
3955
  if (require_toolchain_inputs.toolchainInputKind(file, prefix.toString("utf8", 0, count)) === void 0) continue;
3293
3956
  }
3294
3957
  files[file] = (0, node_fs.readFileSync)(resolvedFile, "utf8");
3295
- } catch {
3958
+ } catch (error) {
3959
+ if (kind === "presence" && error instanceof Error && "code" in error && error.code === "ENOENT") continue;
3296
3960
  if (shebangCandidate) continue;
3297
3961
  diagnostics.push({
3298
3962
  rule: "configuration",
@@ -3308,8 +3972,8 @@ const readToolchainInputs = (root) => {
3308
3972
  diagnostics
3309
3973
  };
3310
3974
  };
3311
- const checkToolchain = ({ root, policy, onDelegated }) => {
3312
- const { files, trackedFiles, diagnostics } = readToolchainInputs(root);
3975
+ const checkToolchain = ({ root, policy, onDelegated, onWarning, now }) => {
3976
+ const { files, trackedFiles, diagnostics } = readToolchainInputs(root, { bunLock: "content" });
3313
3977
  let repository;
3314
3978
  try {
3315
3979
  const origin = (0, node_child_process.execFileSync)("git", [
@@ -3399,6 +4063,53 @@ const checkToolchain = ({ root, policy, onDelegated }) => {
3399
4063
  files,
3400
4064
  policy
3401
4065
  }));
4066
+ if (Object.entries(files).some(([file, text]) => {
4067
+ if (node_path.default.posix.basename(file) === "bun.lock") return true;
4068
+ if (node_path.default.posix.basename(file) !== "package.json") return false;
4069
+ try {
4070
+ const manifest = JSON.parse(text);
4071
+ return record(manifest) && typeof manifest["packageManager"] === "string" && /^bun(?:@|$)/u.test(manifest["packageManager"]);
4072
+ } catch {
4073
+ return false;
4074
+ }
4075
+ }) || files["bunfig.toml"] !== void 0) try {
4076
+ if (files["bunfig.toml"] === void 0) throw new Error("Bun repositories require a tracked root bunfig.toml with the five-day quarantine policy; use the documented pinned bunx stll-quarantine --sync bootstrap, then commit bunfig.toml");
4077
+ const result = require_quarantine_policy.checkRepositoryQuarantine({
4078
+ files,
4079
+ now,
4080
+ npmrcPresent: trackedFiles.has(".npmrc")
4081
+ });
4082
+ for (const message of result.errors) diagnostics.push({
4083
+ rule: "quarantine-policy",
4084
+ path: "bunfig.toml",
4085
+ line: 1,
4086
+ message
4087
+ });
4088
+ for (const message of result.warnings) onWarning?.({
4089
+ rule: "quarantine-policy",
4090
+ path: "bunfig.toml",
4091
+ line: 1,
4092
+ message
4093
+ });
4094
+ } catch (error) {
4095
+ diagnostics.push({
4096
+ rule: "quarantine-policy",
4097
+ path: "bunfig.toml",
4098
+ line: 1,
4099
+ message: error instanceof Error ? error.message : String(error)
4100
+ });
4101
+ }
4102
+ if (files["package.json"] !== void 0) {
4103
+ let manifest;
4104
+ try {
4105
+ manifest = JSON.parse(files["package.json"]);
4106
+ } catch {}
4107
+ diagnostics.push(...checkNodeProjects({
4108
+ root,
4109
+ manifest,
4110
+ trackedFiles
4111
+ }));
4112
+ }
3402
4113
  for (const [file, text] of Object.entries(files)) diagnostics.push(...checkRuntimeFile({
3403
4114
  file,
3404
4115
  text,
@@ -3434,7 +4145,7 @@ const checkToolchain = ({ root, policy, onDelegated }) => {
3434
4145
  line: 1,
3435
4146
  message: `dynamic selector ${entry.path}:${entry.at ?? entry.line} must match exactly one unresolved ${entry.kind} selector`
3436
4147
  });
3437
- diagnostics.push(...checkDependabot({
4148
+ diagnostics.push(...require_toolchain_dependabot.checkDependabot({
3438
4149
  files,
3439
4150
  policy: policy.dependabot
3440
4151
  }));