code-foundry 1.4.0 → 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.
@@ -29,8 +29,10 @@ updates:
29
29
  applies-to: version-updates
30
30
  patterns: ['*']
31
31
  ignore:
32
- # TypeScript 7 cannot resolve node: builtins in this toolchain
33
- # (verified); revisit when the TS/oxlint toolchain supports TS 7.
32
+ # TypeScript 7 (the native compiler) is supported by the oxlint/oxfmt
33
+ # toolchain; the tsconfig must use NodeNext or bundler resolution since
34
+ # legacy node10 resolution was removed. Held with 5.9 until each
35
+ # repository's tsconfig is verified on 7.
34
36
  - dependency-name: 'typescript'
35
37
  versions: ['>=7.0.0']
36
38
  # Held with TypeScript 5.9 above.
@@ -0,0 +1,98 @@
1
+ name: Code Foundry Cloudflare Deploy
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ mode:
7
+ description: 'Deploy mode: preview (wrangler versions upload) or production (wrangler deploy).'
8
+ required: false
9
+ type: string
10
+ default: preview
11
+ working-directory:
12
+ description: 'Directory containing the wrangler configuration.'
13
+ required: false
14
+ type: string
15
+ default: '.'
16
+ runner:
17
+ required: false
18
+ type: string
19
+ default: ubuntu-latest
20
+ wrangler-version:
21
+ required: false
22
+ type: string
23
+ default: 'latest'
24
+ environment:
25
+ description: 'GitHub deployment environment. Defaults to Preview/Production based on the mode.'
26
+ required: false
27
+ type: string
28
+ default: ''
29
+ secrets:
30
+ CLOUDFLARE_API_TOKEN:
31
+ required: true
32
+ CLOUDFLARE_ACCOUNT_ID:
33
+ required: true
34
+
35
+ permissions:
36
+ contents: read
37
+ deployments: write
38
+
39
+ jobs:
40
+ deploy:
41
+ name: Cloudflare Deploy / ${{ inputs.mode }}
42
+ if: vars.CI_BILLING_PAUSED != 'true'
43
+ runs-on: ${{ inputs.runner }}
44
+ timeout-minutes: 20
45
+ steps:
46
+ - name: Checkout
47
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
48
+ with:
49
+ persist-credentials: false
50
+
51
+ - name: Deploy to Cloudflare
52
+ id: deploy
53
+ working-directory: ${{ inputs.working-directory }}
54
+ env:
55
+ CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
56
+ CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
57
+ WRANGLER_VERSION: ${{ inputs.wrangler-version }}
58
+ MODE: ${{ inputs.mode }}
59
+ run: |
60
+ set -euo pipefail
61
+ npx -y wrangler@"$WRANGLER_VERSION" --version
62
+ if [ "$MODE" = "production" ]; then
63
+ npx -y wrangler@"$WRANGLER_VERSION" deploy 2>&1 | tee deploy-output.txt
64
+ elif [ "$MODE" = "preview" ]; then
65
+ npx -y wrangler@"$WRANGLER_VERSION" versions upload --message "preview ${GITHUB_SHA::7}" 2>&1 | tee deploy-output.txt
66
+ else
67
+ echo "Unsupported mode: $MODE (use preview or production)" >&2
68
+ exit 1
69
+ fi
70
+ # Prefer version preview URLs; fall back to the first workers.dev URL.
71
+ url=$(grep -Eo 'https://[a-zA-Z0-9][a-zA-Z0-9.-]*\.workers\.dev' deploy-output.txt | tail -1 || true)
72
+ echo "url=$url" >> "$GITHUB_OUTPUT"
73
+
74
+ - name: Record GitHub deployment
75
+ env:
76
+ GH_TOKEN: ${{ github.token }}
77
+ MODE: ${{ inputs.mode }}
78
+ ENVIRONMENT_OVERRIDE: ${{ inputs.environment }}
79
+ DEPLOY_URL: ${{ steps.deploy.outputs.url }}
80
+ run: |
81
+ set -euo pipefail
82
+ if [ "$MODE" != "production" ] && [ "$MODE" != "preview" ]; then
83
+ echo "Unsupported mode: $MODE" >&2
84
+ exit 1
85
+ fi
86
+ environment="${ENVIRONMENT_OVERRIDE:-$([ "$MODE" = "production" ] && echo Production || echo Preview)}"
87
+ body=$(jq -n \
88
+ --arg ref "$GITHUB_SHA" \
89
+ --arg environment "$environment" \
90
+ --argjson production "$([ "$MODE" = "production" ] && echo true || echo false)" \
91
+ '{ref: $ref, environment: $environment, auto_merge: false, production_environment: $production, required_contexts: []}')
92
+ deployment=$(gh api "repos/$GITHUB_REPOSITORY/deployments" --input - <<< "$body" --jq .id)
93
+ status_args=(-f state=success -f description="Cloudflare $MODE deploy (${GITHUB_SHA::7})")
94
+ if [ -n "$DEPLOY_URL" ]; then
95
+ status_args+=(-f environment_url="$DEPLOY_URL" -f log_url="$DEPLOY_URL")
96
+ fi
97
+ gh api -X POST "repos/$GITHUB_REPOSITORY/deployments/$deployment/statuses" "${status_args[@]}" --jq .state
98
+ echo "Recorded $environment deployment for ${GITHUB_SHA::7}"
@@ -89,17 +89,21 @@ jobs:
89
89
  let releaseType = config.release_type || 'auto'
90
90
  let npmPublish = config.npm_publish || 'false'
91
91
  // The merge audit topology requires rebase for Release Please version
92
- // pull requests into main, matching the linear-history ruleset.
93
- // Release automation never defaults to a merge method: an unset or
94
- // non-rebase release_merge_strategy fails the release job before any
95
- // pull request is created or merged.
92
+ // pull requests into main, matching the linear-history ruleset. The
93
+ // direct topology may also opt into squash: a single-commit release
94
+ // PR squashes to the identical tree and release-please recommends
95
+ // squash. Release automation never defaults to a merge method: an
96
+ // unset or unsupported release_merge_strategy fails the release job
97
+ // before any pull request is created or merged.
98
+ const gitWorkflow = String(config.git_workflow || 'direct').trim().toLowerCase()
96
99
  const releaseMergeStrategy = String(config.release_merge_strategy || '').trim().toLowerCase()
97
100
  if (releaseType === 'auto') {
98
101
  releaseType = fs.existsSync('package.json') ? 'node' : fs.existsSync('pyproject.toml') ? 'python' : fs.existsSync('Cargo.toml') ? 'rust' : fs.existsSync('version.txt') ? 'simple' : 'none'
99
102
  }
100
103
  if (!['node', 'python', 'rust', 'simple', 'none'].includes(releaseType)) throw new Error(`Unsupported release_type: ${releaseType}`)
101
- if (releaseMergeStrategy !== 'rebase') {
102
- throw new Error(`release_merge_strategy must be "rebase" for automated release merges; got ${releaseMergeStrategy || '(unset; release automation never defaults to merge)'}`)
104
+ const allowedStrategies = gitWorkflow === 'staging-release' ? ['rebase'] : ['rebase', 'squash']
105
+ if (!allowedStrategies.includes(releaseMergeStrategy)) {
106
+ throw new Error(`release_merge_strategy must be "rebase"${gitWorkflow === 'staging-release' ? '' : ' or "squash" (direct topology)'} for automated release merges; got ${releaseMergeStrategy || '(unset; release automation never defaults to merge)'}`)
103
107
  }
104
108
  if (releaseType === 'none' || !fs.existsSync('package.json')) npmPublish = 'false'
105
109
  let legacyReleaseType = ''
@@ -109,7 +109,9 @@ jobs:
109
109
 
110
110
  codeql:
111
111
  name: CodeQL
112
- if: vars.CI_BILLING_PAUSED != 'true' && inputs.mode == 'audit'
112
+ # Release-please pull requests also analyze with CodeQL so repository
113
+ # rulesets requiring code scanning results never deadlock the release.
114
+ if: vars.CI_BILLING_PAUSED != 'true' && (inputs.mode == 'audit' || inputs.mode == 'release')
113
115
  uses: ./.github/workflows/codeql.yml
114
116
  with:
115
117
  runtime-repository: ${{ inputs.runtime-repository }}
package/.oxlintrc.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "$schema": "./node_modules/oxlint/configuration_schema.json",
3
- "categories": { "correctness": "error" },
3
+ "categories": { "correctness": "error", "suspicious": "warn" },
4
4
  "ignorePatterns": ["node_modules/**"]
5
5
  }
package/AGENTS.md CHANGED
@@ -77,6 +77,21 @@ This repository uses the `staging-release` workflow: topic branches **squash** i
77
77
 
78
78
  Merge only with the repository's canonical method. Never merge with `--admin`, never default or auto-select a merge method, and never use a method the branch ruleset does not allow. When in doubt, prefer the merge button's configured method and verify the ruleset after merging. Check `.github/CONTRIBUTING.md` for the complete flow and merge table.
79
79
 
80
+ ### Branch and commit policy
81
+
82
+ Branch rulesets enforce deletions, force-pushes, required status checks, pull
83
+ requests, conversation resolution, and linear history where the repository's
84
+ plan supports them. Mirror those rules even where the plan cannot enforce
85
+ them:
86
+
87
+ - Branch from the default branch using
88
+ `feat/*`, `fix/*`, `chore/*`, `refactor/*`, `docs/*`, or `test/*` names.
89
+ - Never push directly to protected branches; open a pull request.
90
+ - Use Conventional Commit subjects (`feat:`, `fix:`, `chore:`, …); Release
91
+ Please depends on them to version releases.
92
+ - Keep pull requests focused; merge with the canonical method only after
93
+ required checks pass.
94
+
80
95
  ## Toolchain and dependencies
81
96
 
82
97
  - Follow `toolchain: auto` in `.github/code-foundry.yml`; use native tools by
@@ -112,7 +127,7 @@ Run focused tests first, then the complete applicable set for release, security,
112
127
 
113
128
  At minimum:
114
129
 
115
- - TypeScript/JavaScript: Oxfmt formatting, Oxlint linting, type-check, build, and Bun's native test runner for unit/integration tests; use the project's native browser runner for E2E tests. Repositories still on Prettier/ESLint keep working through the runtime's fallback detection until they migrate.
130
+ - TypeScript/JavaScript: Oxfmt formatting, Oxlint linting, type-check, build, and Bun's native test runner for unit/integration tests; use the project's native browser runner for E2E tests. Repositories using a different linter or formatter keep full control through their own `lint`/`format` scripts, which the runtime honors.
116
131
  - Do not add Vitest. Preserve specialized native runners such as Matchstick for The Graph and Hardhat for smart contracts.
117
132
  - Rust: default rustfmt, Clippy with warnings treated as errors, check, unit/integration tests, and dependency audit
118
133
  - Python: Ruff formatting and linting, compile or type checks, pytest, coverage, and dependency audit
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.5.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.4.1...v1.5.0) (2026-09-07)
4
+
5
+
6
+ ### Features
7
+
8
+ * **release:** squash release merges, Cloudflare deploy workflow, tooling neutrality ([6a847eb](https://github.com/0xPlayerOne/code-foundry/commit/6a847eb0a3f78dbd343b7b189f32543d621b4b8a))
9
+
10
+ ## [1.4.1](https://github.com/0xPlayerOne/code-foundry/compare/v1.4.0...v1.4.1) (2026-09-07)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **validation:** run CodeQL on the release tier ([e93c36c](https://github.com/0xPlayerOne/code-foundry/commit/e93c36c4be11611117a13aa88212f16b6129f464))
16
+
3
17
  ## [1.4.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.3.2...v1.4.0) (2026-09-07)
4
18
 
5
19
 
package/README.md CHANGED
@@ -74,7 +74,8 @@ The standard workflow triggers are:
74
74
 
75
75
  Jobs are language-aware and skip irrelevant setup while remaining visible as
76
76
  successful required checks. TypeScript uses Oxlint, Oxfmt, and Bun's native
77
- test runner (Prettier/ESLint setups keep working through fallback detection).
77
+ test runner (repositories using another linter or formatter keep full control
78
+ through their own `lint`/`format` scripts, which the runtime honors).
78
79
  Rust uses `rustfmt`, Clippy with warnings as errors, and native
79
80
  Cargo tests. Python uses Ruff, uv/pip-compatible setup, and native Python
80
81
  tests. Solidity projects retain their native toolchain and test runner.
@@ -50,7 +50,7 @@ repository manifests and source
50
50
  | `license` | `gpl-3.0-or-later`, `agpl-3.0-or-later`, `apache-2.0`, `mit`, `preserve`, `none` | License policy; new repositories default to GPLv3 |
51
51
  | `git_workflow` | `direct` (default), `staging-release` | Branch/release model; `direct` opens feature branches into `main`, `staging-release` promotes `staging` into `main` |
52
52
  | `merge_strategy` | `rebase` | Promotion merge method for `staging` → `main`; only enforced by the `staging-release` topology |
53
- | `release_merge_strategy` | `rebase` | Merge method for Release Please version PRs into `main`; release automation fails closed unless rebase |
53
+ | `release_merge_strategy` | `rebase`, `squash` (direct topology only) | Merge method for Release Please version PRs into `main`; release automation fails closed on anything else |
54
54
  | `runner` fields | GitHub runner names | Per-workflow runner policy |
55
55
 
56
56
  Supported features are `ci`, `codeql`, `security`, `test`, `draft-pr`,
@@ -115,3 +115,14 @@ Each scoped shard must contain tracked Rust source. Code Foundry rejects
115
115
  absolute paths, parent traversal, duplicates, empty scopes, and more than eight
116
116
  shards. Do not split a single crate by arbitrary non-Rust directories: use
117
117
  `["all"]` when complete, non-overlapping source scopes are not available.
118
+
119
+ ## Cloudflare Workers deployments
120
+
121
+ Repositories that deploy to Cloudflare Workers can opt into GitHub-native
122
+ deployments (Preview/Production environments with deployment statuses, like
123
+ Vercel's integration) by adding a small caller for the runtime's reusable
124
+ `cloudflare-deploy.yml` workflow. The workflow runs `wrangler versions upload`
125
+ for pull-request previews and `wrangler deploy` for production, records a
126
+ GitHub deployment plus status with the workers.dev URL, and respects
127
+ `CI_BILLING_PAUSED`. It requires the `CLOUDFLARE_API_TOKEN` and
128
+ `CLOUDFLARE_ACCOUNT_ID` secrets in the consumer repository.
package/docs/WORKFLOWS.md CHANGED
@@ -16,7 +16,9 @@ workflow_dispatch:
16
16
 
17
17
  In the `staging-release` topology, pull requests into `staging` run the fast
18
18
  tier, ordinary pull requests into `main` run the full audit tier, and exact
19
- Release Please pull requests into `main` run only release policy. In the
19
+ Release Please pull requests into `main` run only release policy plus CodeQL
20
+ (the CodeQL analysis keeps repository rulesets that require code scanning
21
+ results satisfiable for the release commit). In the
20
22
  `direct` topology (the default) every pull request targets `main` and runs the
21
23
  full audit tier, because there is no integration branch for a fast pass.
22
24
  Scheduled and manual runs select the audit tier in both topologies. Draft PR
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "A fast, language-aware repository factory for agent-ready workflows, testing, security, and release automation.",
5
5
  "homepage": "https://github.com/0xPlayerOne/code-foundry#readme",
6
6
  "bugs": {
@@ -161,10 +161,18 @@ export function syncRepository(options) {
161
161
  )
162
162
  }
163
163
  const releaseMergeStrategy = configured(config.release_merge_strategy, '')
164
- if (includesValue(features, 'release') && releaseMergeStrategy !== 'rebase') {
165
- throw new Error(
166
- `Unsupported release_merge_strategy: ${releaseMergeStrategy || '(unset)'}; release automation requires rebase for Release Please version pull requests and never defaults to merge.`
167
- )
164
+ if (includesValue(features, 'release')) {
165
+ // Staging-release reconciliations depend on rebase promotions and rebase
166
+ // release commits; the direct topology has no reconciliation step, so it
167
+ // may also squash Release Please version PRs (a single-commit release PR
168
+ // squashes to the identical tree, and release-please recommends squash).
169
+ const allowedReleaseStrategies =
170
+ workflow === 'staging-release' ? ['rebase'] : ['rebase', 'squash']
171
+ if (!allowedReleaseStrategies.includes(releaseMergeStrategy)) {
172
+ throw new Error(
173
+ `Unsupported release_merge_strategy: ${releaseMergeStrategy || '(unset)'}; release automation requires rebase (or squash in the direct topology) for Release Please version pull requests and never defaults to merge.`
174
+ )
175
+ }
168
176
  }
169
177
  const changed = []
170
178
 
@@ -643,7 +651,7 @@ const DIRECT_DOC_REPLACEMENTS = {
643
651
  ],
644
652
  [
645
653
  'This repository uses the `staging-release` workflow: topic branches **squash** into `staging`, a promotion PR **rebases** validated changes into `main` (`merge_strategy: rebase`), and the Release Please version PR **rebases** into `main` (`release_merge_strategy: rebase`). Feature PRs land on `staging` with squash merges; promotion and release PRs land on `main` with rebase merges. Re-align `staging` with `main` after a release when needed.',
646
- 'This repository uses the `direct` workflow: topic branches **squash** directly into `main`, and the Release Please version PR **rebases** into `main` (`release_merge_strategy: rebase`). Feature PRs land on `main` with squash merges; release PRs land on `main` with rebase merges. No integration branch exists; all pull requests target `main`.',
654
+ 'This repository uses the `direct` workflow: topic branches **squash** directly into `main`, and the Release Please version PR lands on `main` with the configured `release_merge_strategy` (rebase by default, or squash when opted in). Feature PRs land on `main` with squash merges. No integration branch exists; all pull requests target `main`.',
647
655
  ],
648
656
  ],
649
657
  '.github/CONTRIBUTING.md': [
@@ -661,7 +669,7 @@ const DIRECT_DOC_REPLACEMENTS = {
661
669
  ],
662
670
  [
663
671
  'The Git workflow is `staging-release`: topic branches **squash** into `staging`, a promotion PR **rebases** validated changes into `main` (`merge_strategy: rebase`), and the Release Please version PR **rebases** into `main` (`release_merge_strategy: rebase`). Release automation never defaults to a merge method and never merges with `--admin`; `code-foundry doctor` and `code-foundry sync` fail closed on any other merge strategy. Re-align `staging` with `main` after a release when needed.',
664
- 'The Git workflow is `direct`: topic branches **squash** directly into `main`, and the Release Please version PR **rebases** into `main` (`release_merge_strategy: rebase`). Release automation never defaults to a merge method and never merges with `--admin`; `code-foundry doctor` and `code-foundry sync` fail closed on any other release merge strategy. Feature branches never touch `staging`; repositories with a preview/staging environment opt into `git_workflow: staging-release` explicitly.',
672
+ 'The Git workflow is `direct`: topic branches **squash** directly into `main`, and the Release Please version PR lands on `main` with the configured `release_merge_strategy` (rebase by default, or squash when opted in). Release automation never defaults to a merge method and never merges with `--admin`; `code-foundry doctor` and `code-foundry sync` fail closed on any other release merge strategy. Feature branches never touch `staging`; repositories with a preview/staging environment opt into `git_workflow: staging-release` explicitly.',
665
673
  ],
666
674
  [
667
675
  'git switch staging\ngit pull --ff-only origin staging',
@@ -687,7 +695,7 @@ const DIRECT_DOC_REPLACEMENTS = {
687
695
  ],
688
696
  [
689
697
  '| Change | Target | Merge method | Merge gate |\n| ---------------------------- | --------- | ----------------------------------------------- | --------------------------------------------------------- |\n| Working branch | `staging` | Squash | All applicable required checks pass |\n| `staging` → `main` promotion | `main` | Rebase (`merge_strategy`) | Current staging checks, release review, and rollout notes |\n| Release Please version PR | `main` | Rebase (`release_merge_strategy`, fails closed) | Validation gate and release policy pass |\n',
690
- '| Change | Target | Merge method | Merge gate |\n|----------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| Working branch | `main` | Squash | All applicable required checks pass |\n| Release Please version PR | `main` | Rebase (`release_merge_strategy`, fails closed) | Validation gate and release policy pass |',
698
+ '| Change | Target | Merge method | Merge gate |\n|----------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| Working branch | `main` | Squash | All applicable required checks pass |\n| Release Please version PR | `main` | Configured `release_merge_strategy` (rebase default, or squash in the direct topology) | Validation gate and release policy pass |',
691
699
  ],
692
700
  ['1. Create a focused branch from `staging`.', '1. Create a focused branch from `main`.'],
693
701
  ],
@@ -82,10 +82,12 @@ export function classifyValidationMode(input) {
82
82
  const REQUIRED_JOBS_BY_MODE = {
83
83
  fast: ['ci', 'test'],
84
84
  audit: ['ci', 'test', 'security', 'codeql'],
85
- // The release tier requires no suite jobs: the generated release diff
86
- // check runs as a conditional step inside the gate itself, so an empty
87
- // requirement list is complete and any suite result is irrelevant.
88
- release: [],
85
+ // The release tier skips the CI/test/security suites (the generated release
86
+ // diff check runs as a conditional step inside the gate itself) but still
87
+ // runs CodeQL: repository rulesets that require code scanning results need
88
+ // a CodeQL analysis of the release pull request's commit, and Release
89
+ // Please pull requests must not deadlock against that requirement.
90
+ release: ['codeql'],
89
91
  }
90
92
 
91
93
  /**
package/src/runtime.mjs CHANGED
@@ -327,34 +327,11 @@ function hasDependencyManifest(ecosystem) {
327
327
  return false
328
328
  }
329
329
 
330
- /**
331
- * Detect repository-owned ESLint setup: an eslint dependency (or script
332
- * reference), or a tracked flat/legacy config file. Repositories without any
333
- * of these have nothing for the fallback runner to enforce, so lint skips
334
- * instead of forcing a network fetch of ESLint in CI.
335
- * @returns {boolean}
336
- */
337
- function hasEslintSetup() {
338
- const pkg = readPackage() ?? {}
339
- const scripts = pkg.scripts ?? {}
340
- const dependencies = {
341
- ...pkg.dependencies,
342
- ...pkg.devDependencies,
343
- ...pkg.optionalDependencies,
344
- ...pkg.peerDependencies,
345
- }
346
- if (dependencies.eslint) return true
347
- if (Object.values(scripts).some((value) => /\beslint\b/.test(String(value)))) return true
348
- return repositoryTestFiles().some((file) =>
349
- /(^|\/)(\.eslintrc[^/]*|eslint\.config\.[^/]*)$/.test(file)
350
- )
351
- }
352
-
353
330
  /**
354
331
  * Detect repository-owned Oxlint setup: an oxlint dependency (or script
355
332
  * reference) or a tracked `.oxlintrc.json` / `oxlint.config.*` file. Oxlint
356
- * is the baseline's preferred JavaScript linter; the ESLint fallback only
357
- * runs when no Oxlint setup exists.
333
+ * is the baseline's preferred JavaScript linter; repositories without one
334
+ * fall back to their own `lint` script if defined.
358
335
  * @returns {boolean}
359
336
  */
360
337
  function hasOxlintSetup() {
@@ -376,8 +353,8 @@ function hasOxlintSetup() {
376
353
  /**
377
354
  * Detect repository-owned Oxfmt setup: an oxfmt dependency (or script
378
355
  * reference) or a tracked `.oxfmtrc.json` / `oxfmt.config.*` file. Oxfmt is
379
- * the baseline's preferred JavaScript formatter; the Prettier fallback only
380
- * runs when no Oxfmt setup exists.
356
+ * the baseline's formatter; repositories using a different formatter opt out
357
+ * through their own `format`/`fmt` scripts, which the script fallback honors.
381
358
  * @returns {boolean}
382
359
  */
383
360
  function hasOxfmtSetup() {
@@ -415,12 +392,13 @@ function ci(task) {
415
392
  if (
416
393
  !scripted &&
417
394
  (hasLanguage('typescript') || hasLanguage('javascript')) &&
418
- hasRootJavascriptProject()
395
+ hasRootJavascriptProject() &&
396
+ hasOxfmtSetup()
419
397
  ) {
420
- // Oxfmt is the preferred formatter; Prettier remains the fallback for
421
- // repositories that have not migrated.
422
- if (hasOxfmtSetup()) runTool('oxfmt', ['--check', '.'])
423
- else runTool('prettier', ['--check', '.'])
398
+ // Oxfmt is the baseline's formatter. Repositories that use a different
399
+ // formatter keep full control through their own `format`/`fmt` scripts,
400
+ // which the runScript fallback above already honors.
401
+ runTool('oxfmt', ['--check', '.'])
424
402
  }
425
403
  if (hasLanguage('python') && hasRootPythonProject()) runTool('ruff', ['format', '--check', '.'])
426
404
  if (hasLanguage('rust') && hasRootRustProject()) run('cargo', ['fmt', '--check'])
@@ -431,12 +409,13 @@ function ci(task) {
431
409
  if (
432
410
  !scripted &&
433
411
  (hasLanguage('typescript') || hasLanguage('javascript')) &&
434
- hasRootJavascriptProject()
412
+ hasRootJavascriptProject() &&
413
+ hasOxlintSetup()
435
414
  ) {
436
- // Oxlint is the preferred linter; ESLint remains the fallback for
437
- // repositories that have not migrated.
438
- if (hasOxlintSetup()) runTool('oxlint', [])
439
- else if (hasEslintSetup()) runTool('eslint', ['.'])
415
+ // Oxlint is the baseline's linter. Repositories that use a different
416
+ // linter keep full control through their own `lint` script, which the
417
+ // runScript fallback above already honors.
418
+ runTool('oxlint', [])
440
419
  }
441
420
  if (hasLanguage('python') && hasRootPythonProject()) runTool('ruff', ['check', '.'])
442
421
  if (hasLanguage('rust') && hasRootRustProject())