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