@agjs/tsforge 0.1.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 (216) hide show
  1. package/bin/tsforge.js +2 -0
  2. package/package.json +35 -0
  3. package/src/agent/agent.constants.ts +382 -0
  4. package/src/agent/agent.types.ts +34 -0
  5. package/src/agent/index.ts +4 -0
  6. package/src/agent/model-agent.ts +297 -0
  7. package/src/agent/tool-repair.ts +194 -0
  8. package/src/agent/tools.ts +190 -0
  9. package/src/browser/checks.ts +96 -0
  10. package/src/browser/index.ts +8 -0
  11. package/src/browser/oracle.ts +303 -0
  12. package/src/classify.ts +48 -0
  13. package/src/cli.ts +1333 -0
  14. package/src/config/config.constants.ts +9 -0
  15. package/src/config/flags.ts +32 -0
  16. package/src/config/index.ts +8 -0
  17. package/src/config/tsforge-config.ts +301 -0
  18. package/src/constitution/baseline.ts +257 -0
  19. package/src/detect-gate.ts +498 -0
  20. package/src/eval/eval.types.ts +36 -0
  21. package/src/eval/index.ts +3 -0
  22. package/src/eval/judge.ts +62 -0
  23. package/src/eval/score.ts +39 -0
  24. package/src/files/create.ts +22 -0
  25. package/src/files/edit.ts +193 -0
  26. package/src/files/files.constants.ts +11 -0
  27. package/src/files/files.types.ts +81 -0
  28. package/src/files/hashline-format.ts +110 -0
  29. package/src/files/hashline.ts +689 -0
  30. package/src/files/index.ts +19 -0
  31. package/src/index.ts +8 -0
  32. package/src/inference/index.ts +6 -0
  33. package/src/inference/inference.constants.ts +34 -0
  34. package/src/inference/inference.types.ts +123 -0
  35. package/src/inference/openai-compatible.ts +113 -0
  36. package/src/inference/stream-guard.ts +161 -0
  37. package/src/inference/stream.ts +370 -0
  38. package/src/inference/transport.ts +78 -0
  39. package/src/inference/wire.ts +0 -0
  40. package/src/lib/fs/fs.ts +126 -0
  41. package/src/lib/fs/fs.types.ts +5 -0
  42. package/src/lib/fs/index.ts +3 -0
  43. package/src/lib/fs/process.ts +146 -0
  44. package/src/lib/guards/guards.ts +9 -0
  45. package/src/lib/guards/index.ts +1 -0
  46. package/src/lib/json/index.ts +1 -0
  47. package/src/lib/json/json.ts +12 -0
  48. package/src/lib/scope/index.ts +2 -0
  49. package/src/lib/scope/scope.constants.ts +3 -0
  50. package/src/lib/scope/scope.ts +40 -0
  51. package/src/loop/astgrep-fix.ts +228 -0
  52. package/src/loop/feedback/feedback.ts +138 -0
  53. package/src/loop/feedback/index.ts +8 -0
  54. package/src/loop/feedback/meta-rule-docs.ts +41 -0
  55. package/src/loop/feedback/meta-rule-feedback.ts +61 -0
  56. package/src/loop/feedback/rule-docs.generated.json +112 -0
  57. package/src/loop/feedback/rule-docs.ts +342 -0
  58. package/src/loop/index.ts +19 -0
  59. package/src/loop/loop.constants.ts +68 -0
  60. package/src/loop/loop.types.ts +99 -0
  61. package/src/loop/prompt/index.ts +2 -0
  62. package/src/loop/prompt/project-map.ts +69 -0
  63. package/src/loop/prompt/prompt.ts +107 -0
  64. package/src/loop/quality.ts +174 -0
  65. package/src/loop/rule-docs.generated.json +367 -0
  66. package/src/loop/run-spec.ts +88 -0
  67. package/src/loop/run.ts +400 -0
  68. package/src/loop/session.ts +1410 -0
  69. package/src/loop/tools/add-dependency.ts +71 -0
  70. package/src/loop/tools/condense.ts +498 -0
  71. package/src/loop/tools/edit-hashline.ts +80 -0
  72. package/src/loop/tools/execute-tool.ts +80 -0
  73. package/src/loop/tools/file-ops.ts +323 -0
  74. package/src/loop/tools/index.ts +2 -0
  75. package/src/loop/tools/lsp-ops.ts +222 -0
  76. package/src/loop/tools/scaffold-routes.ts +68 -0
  77. package/src/loop/tools/scaffold-ui.ts +62 -0
  78. package/src/loop/tools/scaffold-web.ts +35 -0
  79. package/src/loop/tools/tool-context.ts +126 -0
  80. package/src/loop/ttsr-defaults.ts +53 -0
  81. package/src/loop/ttsr.ts +322 -0
  82. package/src/loop/turn.ts +856 -0
  83. package/src/lsp/index.ts +2 -0
  84. package/src/lsp/lsp.types.ts +56 -0
  85. package/src/lsp/service.ts +500 -0
  86. package/src/meta-rules/context.ts +195 -0
  87. package/src/meta-rules/index.ts +9 -0
  88. package/src/meta-rules/meta-rules.types.ts +47 -0
  89. package/src/meta-rules/parsers/package-json-parser.ts +51 -0
  90. package/src/meta-rules/registry.ts +37 -0
  91. package/src/meta-rules/rules/ci/workflow-actions-pinned.ts +59 -0
  92. package/src/meta-rules/rules/ci/workflow-runner-pinned.ts +57 -0
  93. package/src/meta-rules/rules/ci/workflow-timeout-required.ts +114 -0
  94. package/src/meta-rules/rules/config/tsconfig-paths-exist.ts +117 -0
  95. package/src/meta-rules/rules/config/tsconfig-strict.ts +91 -0
  96. package/src/meta-rules/rules/source-text/no-eslint-disable-comments.ts +34 -0
  97. package/src/meta-rules/rules/source-text/no-ts-suppressions.ts +38 -0
  98. package/src/meta-rules/rules/supply-chain/no-overlapping-libs.ts +57 -0
  99. package/src/meta-rules/rules/supply-chain/package-exact-deps.ts +55 -0
  100. package/src/meta-rules/rules/testing/test-sibling-required.ts +110 -0
  101. package/src/meta-rules/runner.ts +64 -0
  102. package/src/models-config.ts +196 -0
  103. package/src/render/ansi.ts +289 -0
  104. package/src/render/banner.ts +113 -0
  105. package/src/render/box.ts +134 -0
  106. package/src/render/index.ts +7 -0
  107. package/src/render/markdown.ts +123 -0
  108. package/src/render/render.types.ts +21 -0
  109. package/src/render/stream-markdown.ts +128 -0
  110. package/src/render/style.ts +26 -0
  111. package/src/rule-packs/bullmq/index.ts +39 -0
  112. package/src/rule-packs/bullmq/rules/index.ts +7 -0
  113. package/src/rule-packs/bullmq/rules/job-name-must-be-constant.ts +141 -0
  114. package/src/rule-packs/bullmq/rules/job-options-must-set-attempts.ts +174 -0
  115. package/src/rule-packs/bullmq/rules/no-blocking-concurrency-zero.ts +103 -0
  116. package/src/rule-packs/bullmq/rules/queue-options-must-set-removeoncomplete.ts +130 -0
  117. package/src/rule-packs/bullmq/rules/queue-options-must-set-removeonfail.ts +130 -0
  118. package/src/rule-packs/bullmq/rules/worker-must-implement-close.ts +182 -0
  119. package/src/rule-packs/bullmq/rules/worker-must-listen-failed.ts +140 -0
  120. package/src/rule-packs/bullmq/utils.ts +334 -0
  121. package/src/rule-packs/code-flow/index.ts +25 -0
  122. package/src/rule-packs/code-flow/rules/index.ts +3 -0
  123. package/src/rule-packs/code-flow/rules/no-bare-date-now.ts +138 -0
  124. package/src/rule-packs/code-flow/rules/no-template-trim-empty-ternary.ts +87 -0
  125. package/src/rule-packs/code-flow/rules/prefer-early-return.ts +80 -0
  126. package/src/rule-packs/code-flow/utils/prefer-early-return.ts +132 -0
  127. package/src/rule-packs/comment-hygiene/index.ts +25 -0
  128. package/src/rule-packs/comment-hygiene/rules/index.ts +3 -0
  129. package/src/rule-packs/comment-hygiene/rules/no-historical-comments.ts +102 -0
  130. package/src/rule-packs/comment-hygiene/rules/no-narration-comments.ts +83 -0
  131. package/src/rule-packs/comment-hygiene/rules/no-pr-reference-comments.ts +90 -0
  132. package/src/rule-packs/create-rule.ts +9 -0
  133. package/src/rule-packs/drizzle/index.ts +41 -0
  134. package/src/rule-packs/drizzle/rules/account-scoped-tables-require-where.ts +371 -0
  135. package/src/rule-packs/drizzle/rules/index.ts +8 -0
  136. package/src/rule-packs/drizzle/rules/no-nested-db-transaction.ts +127 -0
  137. package/src/rule-packs/drizzle/rules/no-raw-sql-outside-allowlist.ts +100 -0
  138. package/src/rule-packs/drizzle/rules/relations-must-cover-fks.ts +209 -0
  139. package/src/rule-packs/drizzle/rules/schema-files-must-not-import-driver.ts +127 -0
  140. package/src/rule-packs/drizzle/rules/schema-files-must-only-export-schema.ts +149 -0
  141. package/src/rule-packs/drizzle/rules/tables-must-have-timestamps.ts +312 -0
  142. package/src/rule-packs/drizzle/rules/timestamp-must-specify-mode.ts +166 -0
  143. package/src/rule-packs/drizzle/utils.ts +115 -0
  144. package/src/rule-packs/elysia/index.ts +43 -0
  145. package/src/rule-packs/elysia/rules/consistent-status-via-set.ts +69 -0
  146. package/src/rule-packs/elysia/rules/no-decorate-state-collision.ts +276 -0
  147. package/src/rule-packs/elysia/rules/no-separate-model-interfaces.ts +144 -0
  148. package/src/rule-packs/elysia/rules/prefer-destructured-context.ts +155 -0
  149. package/src/rule-packs/elysia/rules/prefer-direct-return.ts +176 -0
  150. package/src/rule-packs/elysia/rules/prefer-static-services.ts +159 -0
  151. package/src/rule-packs/elysia/rules/prefer-throw-status.ts +151 -0
  152. package/src/rule-packs/elysia/rules/require-hooks-before-routes.ts +209 -0
  153. package/src/rule-packs/elysia/rules/require-plugin-name.ts +107 -0
  154. package/src/rule-packs/elysia/utils/elysiaChain.ts +306 -0
  155. package/src/rule-packs/env-access/index.ts +23 -0
  156. package/src/rule-packs/env-access/rules/index.ts +2 -0
  157. package/src/rule-packs/env-access/rules/no-direct-process-env.ts +133 -0
  158. package/src/rule-packs/env-access/rules/no-process-exit.ts +95 -0
  159. package/src/rule-packs/i18n-keys/index.ts +19 -0
  160. package/src/rule-packs/i18n-keys/rules/static-translation-key-exists.ts +173 -0
  161. package/src/rule-packs/index.ts +139 -0
  162. package/src/rule-packs/jwt-cookies/index.ts +25 -0
  163. package/src/rule-packs/jwt-cookies/rules/auth-cookie-must-be-httponly.ts +150 -0
  164. package/src/rule-packs/jwt-cookies/rules/auth-cookie-must-be-secure-in-prod.ts +149 -0
  165. package/src/rule-packs/jwt-cookies/rules/bcrypt-rounds-min.ts +195 -0
  166. package/src/rule-packs/jwt-cookies/utils.ts +188 -0
  167. package/src/rule-packs/oauth-security/index.ts +25 -0
  168. package/src/rule-packs/oauth-security/rules/pkce-required-for-oidc.ts +296 -0
  169. package/src/rule-packs/oauth-security/rules/state-must-be-redis-backed.ts +193 -0
  170. package/src/rule-packs/oauth-security/rules/state-ttl-bounded.ts +219 -0
  171. package/src/rule-packs/oauth-security/utils.ts +127 -0
  172. package/src/rule-packs/react-component-architecture/index.ts +35 -0
  173. package/src/rule-packs/react-component-architecture/rules/component-folder-structure.ts +123 -0
  174. package/src/rule-packs/react-component-architecture/rules/forwardref-display-name.ts +93 -0
  175. package/src/rule-packs/react-component-architecture/rules/index-must-reexport-default.ts +123 -0
  176. package/src/rule-packs/react-component-architecture/rules/max-hooks-per-file.ts +122 -0
  177. package/src/rule-packs/react-component-architecture/rules/no-cross-feature-imports.ts +170 -0
  178. package/src/rule-packs/react-component-architecture/rules/no-inline-jsx-functions.ts +66 -0
  179. package/src/rule-packs/react-component-architecture/utils.ts +47 -0
  180. package/src/rule-packs/rule-packs.types.ts +18 -0
  181. package/src/rule-packs/structured-logging/index.ts +26 -0
  182. package/src/rule-packs/structured-logging/rules/mask-pii-fields.ts +221 -0
  183. package/src/rule-packs/structured-logging/rules/no-error-stringify.ts +217 -0
  184. package/src/rule-packs/structured-logging/rules/require-event-field.ts +136 -0
  185. package/src/rule-packs/structured-logging/utils/logger.ts +104 -0
  186. package/src/rule-packs/tanstack-query/index.ts +20 -0
  187. package/src/rule-packs/tanstack-query/rules/prefix-query-key-must-use-set-queries-data.ts +321 -0
  188. package/src/rule-packs/test-conventions/index.ts +23 -0
  189. package/src/rule-packs/test-conventions/rules/index.ts +2 -0
  190. package/src/rule-packs/test-conventions/rules/no-focused-tests.ts +170 -0
  191. package/src/rule-packs/test-conventions/rules/test-file-mirrors-source.ts +127 -0
  192. package/src/rule-packs/utils.ts +142 -0
  193. package/src/session-store.ts +359 -0
  194. package/src/spec/generate-tests.ts +213 -0
  195. package/src/spec/index.ts +5 -0
  196. package/src/spec/parse.ts +152 -0
  197. package/src/spec/review-tests.ts +162 -0
  198. package/src/spec/spec.constants.ts +13 -0
  199. package/src/spec/spec.types.ts +79 -0
  200. package/src/stack-detection/detect.ts +246 -0
  201. package/src/stack-detection/index.ts +3 -0
  202. package/src/stack-detection/packs.ts +174 -0
  203. package/src/stack-detection/stack-detection.types.ts +47 -0
  204. package/src/validate/accept.ts +49 -0
  205. package/src/validate/errors.ts +35 -0
  206. package/src/validate/index.ts +12 -0
  207. package/src/validate/parse.ts +148 -0
  208. package/src/validate/run-tests.ts +59 -0
  209. package/src/validate/validate.ts +40 -0
  210. package/src/validate/validate.types.ts +52 -0
  211. package/src/web-components.ts +638 -0
  212. package/src/web-coverage.ts +89 -0
  213. package/src/web-routes.ts +151 -0
  214. package/src/web-templates.ts +1011 -0
  215. package/strict.eslint.config.mjs +84 -0
  216. package/strict.web.eslint.config.mjs +185 -0
@@ -0,0 +1,84 @@
1
+ // tsforge's BUNDLED strict-TypeScript config — the toolchain it brings to ANY
2
+ // project to enforce idiomatic strict TS on the model's output, regardless of
3
+ // what (if anything) the target repo has configured. Deliberately SYNTACTIC-ONLY
4
+ // rules (no type-aware rules / no `parserOptions.project`) so it runs on any .ts
5
+ // without the target's deps or a compiling tsconfig. The type-aware floor (tsc
6
+ // strict + noUncheckedIndexedAccess, no-unsafe-*) is layered separately when a
7
+ // tsconfig is available. This is what lifts the local model: the casts / `any` /
8
+ // `!` / over-annotation it habitually emits become errors it must fix.
9
+ //
10
+ // Stack-aware rule packs are loaded via TSFORGE_PACKS env var (comma-separated
11
+ // pack IDs), allowing the gate to inject stack-specific rules dynamically.
12
+ // Rule overrides are loaded via TSFORGE_RULE_OVERRIDES env var (JSON-encoded
13
+ // map of bare rule names to "error" | "warn" | "off").
14
+ import tseslint from "typescript-eslint";
15
+
16
+ // Load stack-aware packs if TSFORGE_PACKS env var is set
17
+ let packConfig = [];
18
+ const packIds = (process.env.TSFORGE_PACKS ?? "").split(",").filter(Boolean);
19
+ let ruleOverrides = {};
20
+
21
+ if (process.env.TSFORGE_RULE_OVERRIDES !== undefined) {
22
+ try {
23
+ ruleOverrides = JSON.parse(process.env.TSFORGE_RULE_OVERRIDES);
24
+ if (typeof ruleOverrides !== "object" || ruleOverrides === null) {
25
+ ruleOverrides = {};
26
+ }
27
+ } catch {
28
+ // If parsing fails, silently ignore overrides
29
+ }
30
+ }
31
+
32
+ if (packIds.length > 0) {
33
+ try {
34
+ const { buildPackEslintConfig } = await import(
35
+ "./src/rule-packs/index.ts"
36
+ );
37
+ const { plugin, rules } = buildPackEslintConfig(packIds, ruleOverrides);
38
+ packConfig = [
39
+ {
40
+ files: ["**/*.ts", "**/*.tsx"],
41
+ plugins: { tsforge: plugin },
42
+ rules,
43
+ },
44
+ ];
45
+ } catch {
46
+ // If pack loading fails, silently continue without them
47
+ }
48
+ }
49
+
50
+ export default tseslint.config(
51
+ { ignores: ["**/node_modules/**", "**/dist/**", "**/build/**"] },
52
+ {
53
+ files: ["**/*.ts", "**/*.tsx"],
54
+ languageOptions: { parser: tseslint.parser },
55
+ plugins: { "@typescript-eslint": tseslint.plugin },
56
+ rules: {
57
+ // The idioms the model habitually violates — all caught WITHOUT type info.
58
+ "@typescript-eslint/consistent-type-assertions": [
59
+ "error",
60
+ { assertionStyle: "never" },
61
+ ],
62
+ "@typescript-eslint/no-explicit-any": "error",
63
+ "@typescript-eslint/no-non-null-assertion": "error",
64
+ "@typescript-eslint/no-inferrable-types": "error",
65
+ "@typescript-eslint/naming-convention": [
66
+ "error",
67
+ { selector: "interface", format: ["PascalCase"], prefix: ["I"] },
68
+ ],
69
+ "prefer-const": "error",
70
+ "prefer-template": "error",
71
+ "no-var": "error",
72
+ eqeqeq: ["error", "always"],
73
+ curly: ["error", "all"],
74
+ "no-restricted-syntax": [
75
+ "error",
76
+ {
77
+ selector: "TSEnumDeclaration",
78
+ message: "Use 'as const' object literals instead of enums.",
79
+ },
80
+ ],
81
+ },
82
+ },
83
+ ...packConfig
84
+ );
@@ -0,0 +1,185 @@
1
+ // tsforge's bundled strict config for WEB stacks (React/Vue/Svelte via Vite).
2
+ // Like strict.eslint.config.mjs, it ENFORCES `I`-prefixed interfaces (project
3
+ // house style — `IIssue`, `IButtonProps`), with ONE exemption: library module-
4
+ // augmentation interfaces whose name the library dictates and you cannot rename
5
+ // (e.g. TanStack Router's `interface Register`). Differs from the core config in
6
+ // one other way: it allows `as const` (banning only value-changing `as`/`<Foo>`
7
+ // via AST selectors), since `as const` is idiomatic for typed literal registries.
8
+ //
9
+ // Stack-aware rule packs are loaded via TSFORGE_PACKS env var (comma-separated
10
+ // pack IDs), allowing the gate to inject stack-specific rules dynamically.
11
+ import tseslint from "typescript-eslint";
12
+ import stylistic from "@stylistic/eslint-plugin";
13
+
14
+ // Load stack-aware packs if TSFORGE_PACKS env var is set
15
+ let packConfig = [];
16
+ const packIds = (process.env.TSFORGE_PACKS ?? "").split(",").filter(Boolean);
17
+ if (packIds.length > 0) {
18
+ try {
19
+ const { buildPackEslintConfig } = await import(
20
+ "./src/rule-packs/index.ts"
21
+ );
22
+ const { plugin, rules } = buildPackEslintConfig(packIds);
23
+ packConfig = [
24
+ {
25
+ files: ["**/*.ts", "**/*.tsx"],
26
+ plugins: { tsforge: plugin },
27
+ rules,
28
+ },
29
+ ];
30
+ } catch {
31
+ // If pack loading fails, silently continue without them
32
+ }
33
+ }
34
+
35
+ // Custom rule: ONE React component per .tsx file (boringstack). The classic
36
+ // eslint-plugin-react/no-multi-comp crashes on ESLint 10 and @eslint-react has no
37
+ // equivalent, so we enforce it directly. A "component" = a TOP-LEVEL PascalCase
38
+ // `function`, or a `const PascalCase = (…) => …` whose init is a function. This
39
+ // excludes TanStack's `const Route = createFileRoute(...)(...)` (init is a call,
40
+ // not a function), so a route file's `Route` + its page component is fine.
41
+ const oneComponentPerFile = {
42
+ meta: {
43
+ type: "suggestion",
44
+ messages: {
45
+ multi:
46
+ "One component per file (boringstack): '{{name}}' is a second component — move it to its own file.",
47
+ },
48
+ },
49
+ create(context) {
50
+ if (!context.filename.endsWith(".tsx")) {
51
+ return {};
52
+ }
53
+
54
+ const isComponentName = (name) => /^[A-Z]/.test(name);
55
+ const isFn = (node) =>
56
+ node?.type === "ArrowFunctionExpression" ||
57
+ node?.type === "FunctionExpression";
58
+
59
+ return {
60
+ Program(program) {
61
+ const components = [];
62
+
63
+ for (const statement of program.body) {
64
+ const decl =
65
+ statement.type === "ExportNamedDeclaration" &&
66
+ statement.declaration !== null
67
+ ? statement.declaration
68
+ : statement;
69
+
70
+ if (
71
+ decl.type === "FunctionDeclaration" &&
72
+ decl.id !== null &&
73
+ isComponentName(decl.id.name)
74
+ ) {
75
+ components.push(decl.id);
76
+ } else if (decl.type === "VariableDeclaration") {
77
+ for (const d of decl.declarations) {
78
+ if (
79
+ d.id.type === "Identifier" &&
80
+ isComponentName(d.id.name) &&
81
+ isFn(d.init)
82
+ ) {
83
+ components.push(d.id);
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ for (const id of components.slice(1)) {
90
+ context.report({
91
+ node: id,
92
+ messageId: "multi",
93
+ data: { name: id.name },
94
+ });
95
+ }
96
+ },
97
+ };
98
+ },
99
+ };
100
+
101
+ export default tseslint.config(
102
+ { ignores: ["**/node_modules/**", "**/dist/**", "**/build/**"] },
103
+ {
104
+ files: ["**/*.ts", "**/*.tsx"],
105
+ languageOptions: { parser: tseslint.parser },
106
+ plugins: {
107
+ "@typescript-eslint": tseslint.plugin,
108
+ "@stylistic": stylistic,
109
+ boringstack: { rules: { "one-component-per-file": oneComponentPerFile } },
110
+ ...packConfig
111
+ .filter(
112
+ (cfg) => cfg.plugins !== undefined && cfg.plugins.tsforge !== undefined
113
+ )
114
+ .reduce((acc, cfg) => ({ ...acc, ...cfg.plugins }), {}),
115
+ },
116
+ rules: {
117
+ // NOTE: we do NOT use `consistent-type-assertions: never` here — that also
118
+ // bans `as const`, which is idiomatic and the cleanest escape for typed
119
+ // literal/tuple data (and it makes a fixed array a tuple, so literal-index
120
+ // access is defined, not `T | undefined`). Instead we ban only the
121
+ // value-changing forms (`x as Foo`, `<Foo>x`) via AST selectors below.
122
+ "@typescript-eslint/no-explicit-any": "error",
123
+ "@typescript-eslint/no-non-null-assertion": "error",
124
+ "@typescript-eslint/no-inferrable-types": "error",
125
+ // I-prefixed interfaces (house style), EXCEPT library-mandated augmentation
126
+ // names you cannot rename (TanStack Router's `interface Register`).
127
+ "@typescript-eslint/naming-convention": [
128
+ "error",
129
+ {
130
+ selector: "interface",
131
+ format: ["PascalCase"],
132
+ prefix: ["I"],
133
+ filter: { regex: "^(Register)$", match: false },
134
+ },
135
+ ],
136
+ // ONE React component per .tsx file (boringstack). Enforced by the custom
137
+ // rule defined above — eslint-plugin-react/no-multi-comp crashes on ESLint 10
138
+ // and @eslint-react has no equivalent, so we ship our own.
139
+ "boringstack/one-component-per-file": "error",
140
+ "prefer-const": "error",
141
+ "prefer-template": "error",
142
+ "no-var": "error",
143
+ // Blank-line discipline (mirrors the core config) — the model rarely gets
144
+ // spacing right, so prettier --write + this rule's --fix make it free. Uses
145
+ // @stylistic (the rule's maintained home; the core rule is deprecated and
146
+ // spams `usedDeprecatedRules` into eslint's --format json gate output).
147
+ "@stylistic/padding-line-between-statements": [
148
+ "error",
149
+ { blankLine: "always", prev: "import", next: "*" },
150
+ { blankLine: "any", prev: "import", next: "import" },
151
+ { blankLine: "always", prev: "*", next: "return" },
152
+ { blankLine: "always", prev: "*", next: "throw" },
153
+ { blankLine: "always", prev: ["const", "let", "var"], next: "*" },
154
+ {
155
+ blankLine: "any",
156
+ prev: ["const", "let", "var"],
157
+ next: ["const", "let", "var"],
158
+ },
159
+ { blankLine: "always", prev: "block-like", next: "*" },
160
+ { blankLine: "always", prev: "*", next: "block-like" },
161
+ ],
162
+ eqeqeq: ["error", "always"],
163
+ curly: ["error", "all"],
164
+ "no-restricted-syntax": [
165
+ "error",
166
+ {
167
+ selector: "TSEnumDeclaration",
168
+ message: "Use 'as const' object literals instead of enums.",
169
+ },
170
+ {
171
+ selector: "TSAsExpression[typeAnnotation.typeName.name!='const']",
172
+ message:
173
+ "No `as` type casts — type it properly (annotate, narrow, or guard). `as const` is allowed.",
174
+ },
175
+ {
176
+ selector: "TSTypeAssertion",
177
+ message:
178
+ "No angle-bracket type assertions — type it properly. `as const` is allowed.",
179
+ },
180
+ ],
181
+ ...packConfig.reduce((acc, cfg) => ({ ...acc, ...(cfg.rules ?? {}) }), {}),
182
+ },
183
+ },
184
+ ...packConfig
185
+ );