create-warlock 5.33.0 → 5.34.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 (35) hide show
  1. package/CHANGELOG.md +16 -47
  2. package/README.md +69 -75
  3. package/esm/commands/create-new-app/index.mjs +10 -9
  4. package/esm/commands/create-new-app/index.mjs.map +1 -1
  5. package/esm/commands/create-new-app/types.d.mts +1 -1
  6. package/esm/commands/create-warlock-app/index.mjs +1 -1
  7. package/esm/commands/create-warlock-app/index.mjs.map +1 -1
  8. package/esm/flags/definitions.mjs +2 -2
  9. package/esm/flags/definitions.mjs.map +1 -1
  10. package/esm/flags/seed-from-flags.mjs +1 -1
  11. package/esm/flags/seed-from-flags.mjs.map +1 -1
  12. package/esm/helpers/app.mjs +4 -28
  13. package/esm/helpers/app.mjs.map +1 -1
  14. package/esm/helpers/insert-import.mjs +2 -2
  15. package/esm/helpers/insert-import.mjs.map +1 -1
  16. package/llms-full.txt +55 -84
  17. package/package.json +2 -2
  18. package/skills/create-a-warlock-project/SKILL.md +55 -84
  19. package/templates/toolchains/oxc/.oxlintrc.json +1 -1
  20. package/templates/warlock/{.prettierignore → .oxfmtignore} +0 -2
  21. package/templates/warlock/.oxfmtrc.json +15 -0
  22. package/templates/warlock/.oxlintrc.json +24 -0
  23. package/templates/warlock/.vscode/extensions.json +3 -0
  24. package/templates/warlock/.vscode/settings.json +11 -17
  25. package/templates/warlock/README.md +18 -20
  26. package/templates/warlock/package.json +7 -14
  27. package/templates/warlock/skills/code-standards/SKILL.md +53 -36
  28. package/templates/warlock/skills/git-workflow/SKILL.md +25 -20
  29. package/templates/warlock/skills/module-boundaries/SKILL.md +33 -24
  30. package/templates/warlock/skills/web-code-standards/SKILL.md +1 -1
  31. package/templates/warlock/src/web/home/components/home-footer.tsx +3 -1
  32. package/esm/prompts/ask-lint-toolchain.mjs +0 -21
  33. package/esm/prompts/ask-lint-toolchain.mjs.map +0 -1
  34. package/templates/warlock/.prettierrc.json +0 -12
  35. package/templates/warlock/eslint.config.js +0 -98
@@ -46,7 +46,7 @@ that is just the project name, the one required answer with no default:
46
46
  ```
47
47
  A project name is required and no terminal is available to ask for one. Pass it as
48
48
  the first argument or --name=<name>. Non-interactive flags: --yes, --pm=<npm|yarn|pnpm>,
49
- --db=<driver>|--no-db, --features=<list>, --ai=<list>, --lint=<oxc|eslint>, --git|--no-git, --jwt|--no-jwt.
49
+ --db=<driver>|--no-db, --features=<list>, --ai=<list>, --lint=<oxc>, --git|--no-git, --jwt|--no-jwt.
50
50
  ```
51
51
 
52
52
  ## After scaffolding: the decisions summary
@@ -72,7 +72,7 @@ Features: none
72
72
  | `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
73
73
  | `--pm` | yes | inferred (`npm_config_user_agent` / system) | Package manager — must be one of `npm` / `yarn` / `pnpm` / `bun`; anything else is rejected before scaffolding starts. |
74
74
  | `--features` | yes (CSV) | none, or `["web"]` when `--stack=web` and `--features` is not given | Comma-separated optional feature keys. An explicit `--features` always wins over the `--stack` default. |
75
- | `--lint` | yes | `oxc` | `oxc` (Oxlint/Oxfmt) or `eslint` (ESLint/Prettier, deprecated); unknown or missing values fail before writes. |
75
+ | `--lint` | yes | `oxc` | `oxc` (Oxlint/Oxfmt); removed or invalid values fail before writes. |
76
76
  | `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
77
77
  | `--agents` | yes (CSV) | `claude` | Comma-separated `agent-kit` targets — see below. |
78
78
  | `--git` / `--no-git` | no | off | Initialize a Git repo (`git init` + `main` branch + initial commit). |
@@ -199,101 +199,72 @@ Flags preselect their existing feature keys. Both menus and checklists retain
199
199
  terminal paging. The decisions overview previews up to six feature/AI keys with
200
200
  a total count; the success screen shows the complete list.
201
201
 
202
- ## Choose the linter and formatter
202
+ ## Oxlint and Oxfmt
203
203
 
204
- `--lint=oxc` is the default. It also accepts `--lint oxc` and installs exact
205
- `oxlint@1.88.0` and `oxfmt@0.73.0` versions, emits `.oxlintrc.json` and
206
- `.oxfmtrc.json`, and removes the template's ESLint/Prettier configs and dependencies.
207
- Pass `--lint=eslint` to opt in to ESLint/Prettier. The custom wizard prompts for
208
- the same choice; a flag preselects it.
204
+ Oxlint **1.88.0** and Oxfmt **0.73.0** are the only supported tools. New apps
205
+ pin TypeScript **7.0.2** and include `.oxlintrc.json`, `.oxfmtrc.json`,
206
+ `.oxfmtignore`, and VS Code settings for `oxc.oxc-vscode`.
207
+ `--lint=oxc` is optional; `--lint=eslint` fails with the 5.34.0 removal message.
208
+ The wizard has no toolchain choice.
209
209
 
210
210
  ```bash
211
- # Default: Oxlint + Oxfmt, TypeScript 7.0.2
212
211
  npm create warlock@latest my-app -- --yes
213
-
214
- # Explicit opt-in: ESLint + Prettier, TypeScript 6.0.3
215
- npm create warlock@latest my-eslint-app -- --lint=eslint --yes
216
- ```
217
-
218
- Both choices provide `lint`/`format` and non-mutating `lint:check`/`format:check`
219
- scripts. Oxc projects also configure VS Code format-on-save with the official
220
- `oxc.oxc-vscode` formatter, use `source.fixAll.oxc` for lint fixes and recommend
221
- that extension. The ESLint toolchain retains its Prettier/ESLint editor settings.
222
- Oxc config enables `devServer.healthCheckers: { eslint: false, oxlint: true }` and
223
- leaves TypeScript health checks enabled. Style matches the template's 100-column,
224
- double-quote, semicolon and trailing-comma settings. Import sorting is on in
225
- `.oxfmtrc.json`, kept to one block like the ESLint toolchain's
226
- `@ianvs/prettier-plugin-sort-imports`:
227
-
228
- ```json
229
- { "sortImports": { "newlinesBetween": false, "internalPattern": ["app/", "web/", "@shared/"] } }
230
- ```
231
-
232
- `format` (`npm run format`) orders built-ins, packages, internal aliases,
233
- then relative imports. Neither formatter deletes unused imports. ESLint reports
234
- them through `unused-imports/no-unused-imports`; Oxlint uses `no-unused-vars`.
235
- Scripts in both toolchains call local binaries
236
- (`eslint --fix ./src --max-warnings=0`, `tsc --noEmit`), never `npx`.
237
-
238
- ## TypeScript 7 in 5.32
239
-
240
- Default Oxlint/Oxfmt projects pin `typescript` to `7.0.2`; `tsc` is the native Go compiler.
241
- The template uses `module: "ESNext"`, `moduleResolution: "bundler"`, explicit
242
- Node types, and `paths` relative to its tsconfig, without `baseUrl`.
243
- Existing apps must remove `baseUrl`, rebase alias targets, and replace
244
- `moduleResolution: "node"` / `"node10"` before typechecking.
245
-
246
- Explicit `--lint=eslint` projects pin `typescript` to **6.0.3**, the newest version
247
- supported by typescript-eslint. Its parser requires the JavaScript compiler API
248
- removed in TypeScript 7, and its declared peer range is `>=4.8.4 <6.1.0`
249
- ([parser package metadata](https://registry.npmjs.org/@typescript-eslint/parser/8.71.2)).
250
- Both modes use the same tsconfig settings above, which are valid in TypeScript 6.
251
- ESLint mode prints a deprecation warning naming removal in 5.34.0 and the Oxc migration command. Both modes install with every supported package manager.
252
-
253
- ### Prettier import order in ESLint mode
254
-
255
- The ESLint scaffold’s `@ianvs/prettier-plugin-sort-imports` uses Babel instead of
256
- the compiler API. Keep these options in `.prettierrc.json`:
257
-
258
- ```json
259
- {
260
- "plugins": ["@ianvs/prettier-plugin-sort-imports"],
261
- "importOrder": ["<BUILTIN_MODULES>", "<THIRD_PARTY_MODULES>", "^(app|web|@shared)/", "^[.]"],
262
- "importOrderParserPlugins": ["typescript", "jsx", "decorators"]
263
- }
212
+ cd my-app
213
+ npm run lint:check
214
+ npm run format:check
264
215
  ```
265
216
 
266
- Leave `importOrderTypeScriptVersion` at its default to keep type and value imports
267
- separate. Run `npm run format`, then `npm run lint:check`; formatting preserves
268
- unused imports, and `npm run lint` can apply ESLint’s unused-import fixes.
217
+ Oxlint enables `"categories": { "correctness": "error" }`, unused-variable/import
218
+ checks, explicit class accessibility and type-import style. Oxfmt sorts imports
219
+ with `"sortImports": { "newlinesBetween": false, "internalPattern": ["app/", "web/", "@shared/"] }`.
220
+ It keeps built-ins, packages, internal aliases and relative imports in one block.
221
+ Formatting retains unused imports; lint reports them. Local package scripts use
222
+ `oxlint`, `oxfmt` and `tsc` directly. The non-mutating scripts are `lint:check`
223
+ and `format:check`. Background checks run TypeScript and configured Oxlint.
269
224
 
270
- ## Migrate to Oxlint and Oxfmt in 5.33
225
+ ## Migration
271
226
 
272
- ESLint + Prettier are deprecated in 5.33.0 and will be removed in **5.34.0**.
273
- New apps should use `--lint=oxc`. To migrate an existing app, install the new
274
- core release and `ts-morph`, then run:
227
+ ESLint and Prettier support was removed in **5.34.0**. Oxlint/Oxfmt is the only
228
+ supported toolchain. `--lint eslint` and `healthCheckers.eslint` are removed.
229
+ Install the new core release and `ts-morph`, then migrate an existing app:
275
230
 
276
231
  ```bash
277
- npx warlock upgrade --from 5.32 --to 5.33.0 --dry-run
278
- npx warlock upgrade --from 5.32 --to 5.33.0
232
+ npm install --save-dev ts-morph
233
+ npx warlock upgrade --from 5.33 --to 5.34.0 --dry-run
234
+ npx warlock upgrade --from 5.33 --to 5.34.0
279
235
  npm install
280
236
  npm run format
281
237
  npm run lint:check
282
238
  npx warlock check
283
239
  ```
284
240
 
285
- Review every reported `file:line`: customized configs are preserved and need
286
- manual translation before removal. The codemod installs the template's Oxlint
287
- and Oxfmt versions and pins TypeScript to 7.0.2. It replaces standard lint/format
288
- scripts, removes legacy dependencies, and deletes only recognized generated
289
- configs. A generated `.prettierignore` becomes `.oxfmtignore`. Existing Oxc
290
- configs are preserved. `--dry-run --json` reports the same planned changes
291
- without writing files.
292
-
293
- Explicit deprecated ESLint scaffolds set `devServer.healthCheckers.eslint: true`;
294
- existing apps must set this themselves to retain background ESLint checking.
295
-
296
- Oxc formatting scripts explicitly read `.gitignore` and `.oxfmtignore`. Oxfmt
297
- 0.73 otherwise defaults to `.prettierignore` (`oxfmt --help`); the explicit
298
- paths make migrated ignore rules effective. The package template format gate
299
- also defaults to Oxfmt; `--lint=eslint` is its deprecated compatibility mode.
241
+ The `oxc-toolchain-removal` step reapplies the preserved 5.33 `oxc-toolchain`
242
+ migration when legacy configs, scripts or dependencies remain. Apps upgrading
243
+ from 5.32 can still use `npx warlock upgrade --from 5.32 --to 5.33.0` first.
244
+ It replaces standard scripts/dependencies, pins TypeScript 7.0.2, updates generated
245
+ editor settings, removes `healthCheckers.eslint` and selects Oxlint. A generated
246
+ `.prettierignore` becomes `.oxfmtignore`; Oxfmt scripts read it explicitly.
247
+
248
+ Literal custom rules are translated without executing the config. Per-folder
249
+ `no-restricted-imports` and `no-restricted-exports` become Oxlint overrides with
250
+ file/exclusion patterns. Supported `@typescript-eslint/*` rules become native
251
+ `typescript/*` rules (or native base rules). Options are checked against the
252
+ pinned Oxlint 1.88.0 schema. Type-aware equivalents enable `options.typeAware`
253
+ and add `oxlint-tsgolint@7.0.2004` to the app's dev dependencies.
254
+ `unused-imports/no-unused-imports` uses `no-unused-vars` with safe import fixes;
255
+ Oxlint shares the severity for imports and variables, so differing active
256
+ severities are reported for review.
257
+
258
+ Only unsupported rules/options, computed or imported settings, selectors such
259
+ as `no-restricted-syntax`, and conflicting existing Oxc rules need manual review.
260
+ Each finding names its source `file:line`, rule and affected file patterns.
261
+ Fully ported lint configs are removed; configs with findings remain for reference
262
+ until those findings are addressed. Existing Oxc rules are kept on conflict.
263
+ `--dry-run --json` reports the same plan without writes. Customized formatting,
264
+ editor settings and nonstandard scripts can also require review.
265
+
266
+ **Upgrading:** new and migrated template configs enable
267
+ `"categories": { "correctness": "error" }`. Address the additional diagnostics;
268
+ existing Oxc configs retain their categories. A real lockfile beside a workspace
269
+ marker determines the installer: `package-lock.json` plus `pnpm-workspace.yaml`
270
+ without `pnpm-lock.yaml` now produces `npm install`.
@@ -2,7 +2,7 @@
2
2
  "$schema": "./node_modules/oxlint/configuration_schema.json",
3
3
  "plugins": ["typescript", "import", "react"],
4
4
  "env": { "node": true, "es2021": true },
5
- "categories": { "correctness": "off" },
5
+ "categories": { "correctness": "error" },
6
6
  "rules": {
7
7
  "typescript/no-explicit-any": "off",
8
8
  "no-unused-vars": [
@@ -1,4 +1,2 @@
1
1
  node_modules
2
2
  dist
3
-
4
-
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
3
+ "semi": true,
4
+ "tabWidth": 2,
5
+ "printWidth": 100,
6
+ "singleQuote": false,
7
+ "arrowParens": "always",
8
+ "trailingComma": "all",
9
+ "endOfLine": "lf",
10
+ "sortImports": {
11
+ "newlinesBetween": false,
12
+ "internalPattern": ["app/", "web/", "@shared/"]
13
+ },
14
+ "ignorePatterns": ["node_modules/", "dist/"]
15
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "plugins": ["typescript", "import", "react"],
4
+ "env": { "node": true, "es2021": true },
5
+ "categories": { "correctness": "error" },
6
+ "rules": {
7
+ "typescript/no-explicit-any": "off",
8
+ "no-unused-vars": [
9
+ "warn",
10
+ {
11
+ "vars": "all",
12
+ "varsIgnorePattern": "^_",
13
+ "args": "after-used",
14
+ "argsIgnorePattern": "^_"
15
+ }
16
+ ],
17
+ "typescript/explicit-member-accessibility": ["error", { "accessibility": "explicit" }],
18
+ "typescript/consistent-type-imports": [
19
+ "error",
20
+ { "prefer": "type-imports", "disallowTypeAnnotations": false }
21
+ ]
22
+ },
23
+ "ignorePatterns": ["node_modules/", "dist/"]
24
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "recommendations": ["oxc.oxc-vscode"]
3
+ }
@@ -1,41 +1,35 @@
1
1
  {
2
- "editor.defaultFormatter": "esbenp.prettier-vscode",
2
+ "editor.defaultFormatter": "oxc.oxc-vscode",
3
3
  "editor.formatOnSave": true,
4
4
  "editor.codeActionsOnSave": {
5
- "source.fixAll.eslint": "explicit",
5
+ "source.fixAll.oxc": "explicit",
6
6
  "source.organizeImports": "never"
7
7
  },
8
8
  "[typescript]": {
9
- "editor.defaultFormatter": "esbenp.prettier-vscode",
9
+ "editor.defaultFormatter": "oxc.oxc-vscode",
10
10
  "editor.formatOnSave": true
11
11
  },
12
12
  "[typescriptreact]": {
13
- "editor.defaultFormatter": "esbenp.prettier-vscode",
13
+ "editor.defaultFormatter": "oxc.oxc-vscode",
14
14
  "editor.formatOnSave": true
15
15
  },
16
16
  "[javascript]": {
17
- "editor.defaultFormatter": "esbenp.prettier-vscode",
17
+ "editor.defaultFormatter": "oxc.oxc-vscode",
18
18
  "editor.formatOnSave": true
19
19
  },
20
20
  "[javascriptreact]": {
21
- "editor.defaultFormatter": "esbenp.prettier-vscode",
21
+ "editor.defaultFormatter": "oxc.oxc-vscode",
22
22
  "editor.formatOnSave": true
23
23
  },
24
24
  "[json]": {
25
- "editor.defaultFormatter": "esbenp.prettier-vscode",
25
+ "editor.defaultFormatter": "oxc.oxc-vscode",
26
26
  "editor.formatOnSave": true
27
27
  },
28
28
  "[css]": {
29
- "editor.defaultFormatter": "esbenp.prettier-vscode",
29
+ "editor.defaultFormatter": "oxc.oxc-vscode",
30
30
  "editor.formatOnSave": true
31
31
  },
32
- "prettier.configPath": ".prettierrc.json",
33
- "prettier.requireConfig": true,
34
- "prettier.ignorePath": ".prettierignore",
35
- "prettier.documentSelectors": ["**/*.{ts,tsx,js,jsx,json,css,md}"],
36
- "prettier.enable": true,
37
- "prettier.useEditorConfig": false,
38
- "prettier.resolveGlobalModules": false,
39
- "eslint.format.enable": false,
40
- "eslint.codeActionsOnSave.mode": "all"
32
+ "oxc.enable.oxlint": true,
33
+ "oxc.enable.oxfmt": true,
34
+ "oxc.requireConfig": true
41
35
  }
@@ -29,22 +29,22 @@ It restarts on file changes.
29
29
 
30
30
  ## Scripts
31
31
 
32
- | Command | What it does |
33
- | --- | --- |
34
- | `npm run dev` | Development server with live reload |
35
- | `npm run build` | Type-check and build for production |
36
- | `npm run start` | Run the production build (run `build` first) |
37
- | `npm run migrate` | Run pending database migrations |
38
- | `npm run migrate.fresh` | Drop everything and re-run all migrations |
39
- | `npm run migrate.list` | List migrations and their status |
40
- | `npm run seed` | Run the database seeders |
41
- | `npm run jwt` | Generate a JWT secret into `.env` |
42
- | `npm run tsc` | Type-check without building |
43
- | `npm run lint` | The configured linter, auto-fixing what it can |
44
- | `npm run format` | The configured formatter over `src/` |
45
- | `npm run lint:check` | Lint without changing files |
46
- | `npm run format:check` | Check formatting without changing files |
47
- | `npm run skills:sync` | Refresh the AI agent skills (see below) |
32
+ | Command | What it does |
33
+ | ----------------------- | ---------------------------------------------- |
34
+ | `npm run dev` | Development server with live reload |
35
+ | `npm run build` | Type-check and build for production |
36
+ | `npm run start` | Run the production build (run `build` first) |
37
+ | `npm run migrate` | Run pending database migrations |
38
+ | `npm run migrate.fresh` | Drop everything and re-run all migrations |
39
+ | `npm run migrate.list` | List migrations and their status |
40
+ | `npm run seed` | Run the database seeders |
41
+ | `npm run jwt` | Generate a JWT secret into `.env` |
42
+ | `npm run tsc` | Type-check without building |
43
+ | `npm run lint` | The configured linter, auto-fixing what it can |
44
+ | `npm run format` | The configured formatter over `src/` |
45
+ | `npm run lint:check` | Lint without changing files |
46
+ | `npm run format:check` | Check formatting without changing files |
47
+ | `npm run skills:sync` | Refresh the AI agent skills (see below) |
48
48
 
49
49
  The `warlock` CLI has more than the scripts expose — run `npx warlock --help`
50
50
  for the full list. Two worth knowing:
@@ -157,7 +157,5 @@ Node types, and `paths` relative to its tsconfig, without `baseUrl`.
157
157
  Existing apps must remove `baseUrl`, rebase alias targets, and replace
158
158
  `moduleResolution: "node"` / `"node10"` before typechecking.
159
159
 
160
- Use the Oxlint/Oxfmt lint mode with TS7. The optional ESLint scaffold is retained,
161
- but typescript-eslint's parser requires the removed JavaScript compiler API and
162
- does not support TS7. ESLint mode is scheduled for deprecation in 5.33 and removal
163
- in 5.34. A parser's bundled older TypeScript is separate from the app's native `tsc`.
160
+ Oxlint/Oxfmt is the only toolchain in 5.34.0. Correctness rules report errors;
161
+ use `npm run lint:check` and `npm run format:check` before committing.
@@ -19,14 +19,14 @@
19
19
  "gen.mig": "warlock generate.migration",
20
20
  "gen.r": "warlock generate.repository",
21
21
  "gen.rs": "warlock generate.resource",
22
- "lint": "eslint --fix ./src --max-warnings=0",
23
- "format": "node -e \"console.warn('Deprecated: Prettier support is removed in 5.34.0; migrate with npx warlock upgrade --from 5.32 --to 5.33.0.')\" && prettier --write \"./src/**/*.{js,jsx,ts,tsx,css,md,json}\" --config ./.prettierrc.json",
22
+ "lint": "oxlint --fix ./src --max-warnings=0",
23
+ "format": "oxfmt --ignore-path .gitignore --ignore-path .oxfmtignore --write ./src",
24
24
  "tsc": "warlock generate.typings && tsc --noEmit",
25
25
  "postinstall": "agent-kit init && agent-kit sync || echo \"warning: AI agent skills were NOT synced, so coding agents will not see the @warlock.js skills. Run the skills:sync script.\"",
26
26
  "skills:sync": "agent-kit sync",
27
27
  "skills:watch": "agent-kit watch",
28
- "lint:check": "eslint ./src --max-warnings=0",
29
- "format:check": "node -e \"console.warn('Deprecated: Prettier support is removed in 5.34.0; migrate with npx warlock upgrade --from 5.32 --to 5.33.0.')\" && prettier --check \"./src/**/*.{js,jsx,ts,tsx,css,md,json}\" --config ./.prettierrc.json"
28
+ "lint:check": "oxlint ./src --max-warnings=0",
29
+ "format:check": "oxfmt --ignore-path .gitignore --ignore-path .oxfmtignore --check ./src"
30
30
  },
31
31
  "dependencies": {
32
32
  "@mongez/reinforcements": "^4.0.2",
@@ -43,18 +43,11 @@
43
43
  "dayjs": "^1.11.19"
44
44
  },
45
45
  "devDependencies": {
46
- "@ianvs/prettier-plugin-sort-imports": "^4.7.1",
47
46
  "@mongez/agent-kit": "^1.3.1",
48
47
  "@types/node": "^25.0.3",
49
- "@types/prettier": "^3.0.0",
50
- "@typescript-eslint/eslint-plugin": "^8.50.0",
51
- "@typescript-eslint/parser": "^8.50.0",
52
- "eslint": "^9.39.2",
53
- "eslint-config-prettier": "^10.1.8",
54
- "eslint-plugin-prettier": "^5.5.4",
55
- "eslint-plugin-unused-imports": "^4.3.0",
56
- "prettier": "^3.7.4",
57
48
  "typescript": "7.0.2",
58
- "vitest": "4.0.5"
49
+ "vitest": "4.0.5",
50
+ "oxlint": "1.88.0",
51
+ "oxfmt": "0.73.0"
59
52
  }
60
53
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: code-standards
3
- description: 'Project-level TypeScript code standards for apps built on Warlock.js — `interface` vs `type`, access modifiers, no `any`, no inline-duplicated unions, file-naming suffixes (`.contract.ts` / `.type.ts` / `.service.ts` / `.model.ts` / `.resource.ts` / `.spec.ts`), single-responsibility files, FP-factory + internal-class pattern, brace-every-control-block readability with blank lines between consecutive blocks, JSDoc on public surface and non-obvious logic, error classes extending framework errors, async/await + `Promise.all`, `undefined` over `null`, Vitest co-located, ESLint/Prettier or Oxlint/Oxfmt. Triggers: writing/editing any `.ts` / `.tsx` file in this project; user asks "what are the code standards / style rules / conventions"; spawning a sub-agent that will write code; reviewing a diff for style issues; deciding `interface` vs `type`, when to use a class, where helpers live, how to name a file, or whether something needs JSDoc. Skip: pure-Markdown / JSON / YAML edits with no code change; runtime / behavior questions about a specific framework primitive — load the relevant `@warlock.js/<pkg>` skill instead; package-publishing or CI workflow questions.'
3
+ description: 'Project-level TypeScript code standards for apps built on Warlock.js — `interface` vs `type`, access modifiers, no `any`, no inline-duplicated unions, file-naming suffixes (`.contract.ts` / `.type.ts` / `.service.ts` / `.model.ts` / `.resource.ts` / `.spec.ts`), single-responsibility files, FP-factory + internal-class pattern, brace-every-control-block readability with blank lines between consecutive blocks, JSDoc on public surface and non-obvious logic, error classes extending framework errors, async/await + `Promise.all`, `undefined` over `null`, Vitest co-located, Oxlint/Oxfmt. Triggers: writing/editing any `.ts` / `.tsx` file in this project; user asks "what are the code standards / style rules / conventions"; spawning a sub-agent that will write code; reviewing a diff for style issues; deciding `interface` vs `type`, when to use a class, where helpers live, how to name a file, or whether something needs JSDoc. Skip: pure-Markdown / JSON / YAML edits with no code change; runtime / behavior questions about a specific framework primitive — load the relevant `@warlock.js/<pkg>` skill instead; package-publishing or CI workflow questions.'
4
4
  ---
5
5
 
6
6
  # Code Standards
@@ -52,14 +52,16 @@ Never omit `public` / `private` / `protected`. Explicit is faster to read than i
52
52
  export class CartService {
53
53
  public readonly currency: string;
54
54
  private items: CartItem[] = [];
55
- public add(item: CartItem) { /* ... */ }
55
+ public add(item: CartItem) {
56
+ /* ... */
57
+ }
56
58
  }
57
59
 
58
60
  // ❌
59
61
  export class CartService {
60
- readonly currency: string; // missing public
61
- items: CartItem[] = []; // missing private
62
- add(item: CartItem) {} // missing public
62
+ readonly currency: string; // missing public
63
+ items: CartItem[] = []; // missing private
64
+ add(item: CartItem) {} // missing public
63
65
  }
64
66
  ```
65
67
 
@@ -75,12 +77,12 @@ If a union appears in more than one place, extract it to a named exported type o
75
77
  // ✅
76
78
  export type OrderStatus = "pending" | "paid" | "shipped" | "cancelled";
77
79
 
78
- export type Order = { status: OrderStatus; /* ... */ };
79
- export type OrderEvent = { status: OrderStatus; /* ... */ };
80
+ export type Order = { status: OrderStatus /* ... */ };
81
+ export type OrderEvent = { status: OrderStatus /* ... */ };
80
82
 
81
83
  // ❌ inline duplication — changing the set means N edits and N chances to miss one
82
- export type Order = { status: "pending" | "paid" | "shipped" | "cancelled"; /* ... */ };
83
- export type OrderEvent = { status: "pending" | "paid" | "shipped" | "cancelled"; /* ... */ };
84
+ export type Order = { status: "pending" | "paid" | "shipped" | "cancelled" /* ... */ };
85
+ export type OrderEvent = { status: "pending" | "paid" | "shipped" | "cancelled" /* ... */ };
84
86
  ```
85
87
 
86
88
  ### 1.5 Constrain generics
@@ -89,10 +91,14 @@ Open generics are almost always wrong. If `T` has a contract, declare it.
89
91
 
90
92
  ```typescript
91
93
  // ✅
92
- export function publish<T extends DomainEvent>(event: T): void { /* ... */ }
94
+ export function publish<T extends DomainEvent>(event: T): void {
95
+ /* ... */
96
+ }
93
97
 
94
98
  // ❌
95
- export function publish<T>(event: T): void { /* ... */ }
99
+ export function publish<T>(event: T): void {
100
+ /* ... */
101
+ }
96
102
  ```
97
103
 
98
104
  ### 1.6 Prefer `undefined` for "absent"
@@ -102,7 +108,9 @@ Use `undefined` everywhere — optional properties, optional parameters, "not fo
102
108
  ```typescript
103
109
  // ✅
104
110
  export type User = { id: string; phone?: string };
105
- function find(id: string): User | undefined { /* ... */ }
111
+ function find(id: string): User | undefined {
112
+ /* ... */
113
+ }
106
114
 
107
115
  // ❌ mixed
108
116
  export type User = { id: string; phone: string | null };
@@ -162,14 +170,14 @@ Always bare: `from "./order.service"`, never `from "./order.service.ts"` or `.js
162
170
 
163
171
  ## 3. File naming
164
172
 
165
- | Suffix | Purpose | Example |
166
- | -------------- | ------------------------------------ | ---------------------- |
167
- | `.contract.ts` | Interface only (blueprint) | `payment.contract.ts` |
168
- | `.type.ts` | Types only (data shapes) | `order.type.ts` |
169
- | `.service.ts` | Service class | `cart.service.ts` |
170
- | `.model.ts` | Domain model class | `user.model.ts` |
171
- | `.resource.ts` | Output mapper (model → wire format) | `order.resource.ts` |
172
- | `.spec.ts` | Co-located test for a sibling `.ts` | `cart.service.spec.ts` |
173
+ | Suffix | Purpose | Example |
174
+ | -------------- | ----------------------------------- | ---------------------- |
175
+ | `.contract.ts` | Interface only (blueprint) | `payment.contract.ts` |
176
+ | `.type.ts` | Types only (data shapes) | `order.type.ts` |
177
+ | `.service.ts` | Service class | `cart.service.ts` |
178
+ | `.model.ts` | Domain model class | `user.model.ts` |
179
+ | `.resource.ts` | Output mapper (model → wire format) | `order.resource.ts` |
180
+ | `.spec.ts` | Co-located test for a sibling `.ts` | `cart.service.spec.ts` |
173
181
 
174
182
  **One primary export per file.** If a `.contract.ts` also defines closely-related data types used only by that contract, that's fine — but the primary export is the interface.
175
183
 
@@ -216,9 +224,15 @@ export function placeOrder(config: OrderConfig) {
216
224
  class PlacementRun {
217
225
  private items: ResolvedItem[] = [];
218
226
  private total = 0;
219
- public async run(): Promise<OrderResult> { /* ... */ }
220
- private async validate(): Promise<void> { /* ... */ }
221
- private async charge(): Promise<void> { /* ... */ }
227
+ public async run(): Promise<OrderResult> {
228
+ /* ... */
229
+ }
230
+ private async validate(): Promise<void> {
231
+ /* ... */
232
+ }
233
+ private async charge(): Promise<void> {
234
+ /* ... */
235
+ }
222
236
  }
223
237
  ```
224
238
 
@@ -283,7 +297,9 @@ Identifiers carry the meaning. Add a comment only when the **why** is non-obviou
283
297
  ```typescript
284
298
  // ❌ noise
285
299
  // Loop over items
286
- for (const item of items) { /* ... */ }
300
+ for (const item of items) {
301
+ /* ... */
302
+ }
287
303
 
288
304
  // ✅ the WHY isn't obvious from the code
289
305
  // Stripe returns idempotency_key on retry but not on first call —
@@ -362,7 +378,7 @@ Every promise is either awaited, returned, or explicitly `.catch()`'d. A "fire a
362
378
 
363
379
  ```typescript
364
380
  // ❌
365
- sendWelcomeEmail(user); // floating — if it rejects, you'll never know
381
+ sendWelcomeEmail(user); // floating — if it rejects, you'll never know
366
382
 
367
383
  // ✅
368
384
  await sendWelcomeEmail(user);
@@ -481,7 +497,7 @@ Validation runs once at the route boundary; everything inward consumes the typed
481
497
  type Order = { totalCents: number; placedAtUtc: Date };
482
498
 
483
499
  // ❌
484
- type Order = { total: number; placedAt: string }; // float money, ambiguous tz
500
+ type Order = { total: number; placedAt: string }; // float money, ambiguous tz
485
501
  ```
486
502
 
487
503
  See `skills/data-and-persistence/SKILL.md`.
@@ -551,12 +567,11 @@ A secret with no safe default (a payment key, a signing secret) should fail loud
551
567
 
552
568
  ## 11. Tooling
553
569
 
554
- Use the project's selected toolchain: `eslint.config.js` + `.prettierrc.json`, or
555
- `.oxlintrc.json` + `.oxfmtrc.json`. Run `npm run lint:check` and
570
+ Use the project's toolchain: `.oxlintrc.json` + `.oxfmtrc.json`. Run `npm run lint:check` and
556
571
  `npm run format:check` for non-mutating checks. The Oxc scaffold enables the
557
572
  Oxlint development health checker and retains TypeScript checking.
558
573
 
559
- `npx warlock check` is the one verify command, in either toolchain: it
574
+ `npx warlock check` is the one verify command, with Oxlint: it
560
575
  typechecks, runs the configured linter and the affected tests, and checks the
561
576
  framework's conventions — `request.validated()` instead of `request.all()`,
562
577
  guarded or `public: true` mutating routes, safe redirects, redacted logs,
@@ -564,17 +579,17 @@ bounded regexes on input, one controller per file, `type` (not `kind`) as a
564
579
  union's discriminator. Each problem has a `WL` code and its fix
565
580
  (`npx warlock explain <CODE>`).
566
581
 
567
- For ESLint, consider enabling these additional rules:
582
+ For Oxlint, consider enabling these additional rules:
568
583
 
569
- - `@typescript-eslint/explicit-member-accessibility` — enforces § 1.2
570
- - `@typescript-eslint/no-explicit-any` — enforces § 1.3
571
- - `@typescript-eslint/consistent-type-definitions` — supports § 1.1
572
- - `@typescript-eslint/no-floating-promises` — enforces § 6.3
573
- - `@typescript-eslint/await-thenable`
584
+ - `typescript/explicit-member-accessibility` — enforces § 1.2
585
+ - `typescript/no-explicit-any` — enforces § 1.3
586
+ - `typescript/consistent-type-definitions` — supports § 1.1
587
+ - `typescript/no-floating-promises` — enforces § 6.3 (requires `oxlint-tsgolint` and `options.typeAware: true`)
588
+ - `typescript/await-thenable` (same type-aware engine requirement)
574
589
  - `curly` — enforces braces (§ 4.4)
575
590
  - `no-console` — enforces § 8
576
591
 
577
- Match the selected formatter config: `.prettierrc.json` or `.oxfmtrc.json`. Both use 100 columns, double quotes, semicolons and trailing commas. Prettier uses `@ianvs/prettier-plugin-sort-imports` (Babel, independent of TypeScript); it sorts built-ins, packages, aliases (`app/*`, `web/*`, `@shared/*`) and relatives in one block while preserving separate type imports. Oxfmt sorts imports through its own configuration. Both retain unused imports: ESLint reports them through `unused-imports/no-unused-imports`, and Oxlint through `no-unused-vars`. Run the formatter, then the linter.
592
+ Match `.oxfmtrc.json`: 100 columns, double quotes, semicolons, trailing commas and built-in import sorting.
578
593
 
579
594
  ---
580
595
 
@@ -583,6 +598,7 @@ Match the selected formatter config: `.prettierrc.json` or `.oxfmtrc.json`. Both
583
598
  Before any change ships:
584
599
 
585
600
  **Craft**
601
+
586
602
  - [ ] All class members have access modifiers
587
603
  - [ ] No `any`
588
604
  - [ ] No inline-duplicated unions or literal sets
@@ -600,6 +616,7 @@ Before any change ships:
600
616
  - [ ] `npx warlock check` exits 0
601
617
 
602
618
  **Non-negotiable defaults**
619
+
603
620
  - [ ] Every untrusted input has a `@warlock.js/seal` schema at the boundary
604
621
  - [ ] Money fields are integer minor units, time stored as UTC
605
622
  - [ ] IDs exposed in URLs are opaque (UUID / nanoid)