@trebired/code-discipline 4.8.0 → 4.9.3

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 (107) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +347 -324
  3. package/dist/checks/apply-fixes.d.ts +2 -2
  4. package/dist/checks/apply-fixes.d.ts.map +1 -1
  5. package/dist/checks/apply-fixes.js +6 -6
  6. package/dist/checks/apply-fixes.js.map +1 -1
  7. package/dist/checks/index.js +5 -5
  8. package/dist/checks/index.js.map +1 -1
  9. package/dist/checks/rule-slugs.d.ts +1 -1
  10. package/dist/checks/rule-slugs.js +3 -3
  11. package/dist/checks/rule-slugs.js.map +1 -1
  12. package/dist/checks/rules/min/file/lines/fix.d.ts +2 -2
  13. package/dist/checks/rules/min/file/lines/fix.d.ts.map +1 -1
  14. package/dist/checks/rules/min/file/lines/fix.js +4 -4
  15. package/dist/checks/rules/min/file/lines/fix.js.map +1 -1
  16. package/dist/checks/severity.js +1 -1
  17. package/dist/checks/severity.js.map +1 -1
  18. package/dist/checks/sync-options.d.ts +3 -2
  19. package/dist/checks/sync-options.d.ts.map +1 -1
  20. package/dist/checks/sync-options.js +3 -2
  21. package/dist/checks/sync-options.js.map +1 -1
  22. package/dist/checks/types.d.ts +7 -7
  23. package/dist/checks/types.d.ts.map +1 -1
  24. package/dist/cli/logging.js +1 -1
  25. package/dist/cli/logging.js.map +1 -1
  26. package/dist/config/normalize/check-options.js +3 -3
  27. package/dist/config/normalize/check-options.js.map +1 -1
  28. package/dist/config/normalize/imports-options.d.ts +4 -0
  29. package/dist/config/normalize/imports-options.d.ts.map +1 -0
  30. package/dist/config/normalize/{sync-imports-options.js → imports-options.js} +12 -11
  31. package/dist/config/normalize/imports-options.js.map +1 -0
  32. package/dist/config/normalize/rule-options.d.ts +6 -5
  33. package/dist/config/normalize/rule-options.d.ts.map +1 -1
  34. package/dist/config/normalize/rule-options.js +17 -16
  35. package/dist/config/normalize/rule-options.js.map +1 -1
  36. package/dist/imports/aliases.d.ts +4 -4
  37. package/dist/imports/aliases.d.ts.map +1 -1
  38. package/dist/imports/aliases.js +4 -4
  39. package/dist/imports/aliases.js.map +1 -1
  40. package/dist/imports/check-imports.d.ts +6 -0
  41. package/dist/imports/check-imports.d.ts.map +1 -0
  42. package/dist/imports/{check-sync-imports.js → check-imports.js} +24 -10
  43. package/dist/imports/check-imports.js.map +1 -0
  44. package/dist/imports/dead-imports.d.ts +9 -0
  45. package/dist/imports/dead-imports.d.ts.map +1 -0
  46. package/dist/imports/dead-imports.js +92 -0
  47. package/dist/imports/dead-imports.js.map +1 -0
  48. package/dist/imports/folder.d.ts +5 -5
  49. package/dist/imports/folder.d.ts.map +1 -1
  50. package/dist/imports/folder.js.map +1 -1
  51. package/dist/imports/imports.d.ts +4 -0
  52. package/dist/imports/imports.d.ts.map +1 -0
  53. package/dist/imports/{sync-imports.js → imports.js} +20 -20
  54. package/dist/imports/imports.js.map +1 -0
  55. package/dist/imports/module-specifiers.d.ts +5 -1
  56. package/dist/imports/module-specifiers.d.ts.map +1 -1
  57. package/dist/imports/module-specifiers.js +1 -1
  58. package/dist/imports/module-specifiers.js.map +1 -1
  59. package/dist/imports/resolve.d.ts +2 -2
  60. package/dist/imports/resolve.d.ts.map +1 -1
  61. package/dist/imports/rewrite.d.ts +3 -3
  62. package/dist/imports/rewrite.d.ts.map +1 -1
  63. package/dist/imports/rewrite.js +20 -4
  64. package/dist/imports/rewrite.js.map +1 -1
  65. package/dist/imports/strategies.d.ts +2 -2
  66. package/dist/imports/strategies.d.ts.map +1 -1
  67. package/dist/imports/strategies.js.map +1 -1
  68. package/dist/imports/types.d.ts +15 -13
  69. package/dist/imports/types.d.ts.map +1 -1
  70. package/dist/index.d.ts +6 -6
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +3 -3
  73. package/dist/index.js.map +1 -1
  74. package/dist/runtime/orchestrate.js +2 -2
  75. package/dist/runtime/orchestrate.js.map +1 -1
  76. package/dist/runtime/tsconfig-paths.d.ts +2 -2
  77. package/dist/runtime/tsconfig-paths.d.ts.map +1 -1
  78. package/dist/runtime/tsconfig-paths.js.map +1 -1
  79. package/dist/shared/constants.d.ts +2 -1
  80. package/dist/shared/constants.d.ts.map +1 -1
  81. package/dist/shared/constants.js +2 -1
  82. package/dist/shared/constants.js.map +1 -1
  83. package/dist/shared/discipline-types.d.ts +1 -1
  84. package/dist/shared/discipline-types.d.ts.map +1 -1
  85. package/dist/shared/errors.d.ts +17 -17
  86. package/dist/shared/errors.d.ts.map +1 -1
  87. package/dist/shared/errors.js +15 -15
  88. package/dist/shared/errors.js.map +1 -1
  89. package/dist/shared/languages.d.ts +2 -2
  90. package/dist/shared/languages.d.ts.map +1 -1
  91. package/dist/shared/languages.js +3 -3
  92. package/dist/shared/languages.js.map +1 -1
  93. package/dist/shared/logging-types.d.ts +4 -4
  94. package/dist/shared/logging-types.d.ts.map +1 -1
  95. package/dist/shared/package-metadata.d.ts.map +1 -1
  96. package/dist/shared/package-metadata.js +24 -4
  97. package/dist/shared/package-metadata.js.map +1 -1
  98. package/package.json +14 -15
  99. package/dist/config/normalize/sync-imports-options.d.ts +0 -4
  100. package/dist/config/normalize/sync-imports-options.d.ts.map +0 -1
  101. package/dist/config/normalize/sync-imports-options.js.map +0 -1
  102. package/dist/imports/check-sync-imports.d.ts +0 -6
  103. package/dist/imports/check-sync-imports.d.ts.map +0 -1
  104. package/dist/imports/check-sync-imports.js.map +0 -1
  105. package/dist/imports/sync-imports.d.ts +0 -4
  106. package/dist/imports/sync-imports.d.ts.map +0 -1
  107. package/dist/imports/sync-imports.js.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @trebired/code-discipline
2
2
 
3
- Configurable repository discipline checks and rule-driven fixes for Bun and Node.js projects.
3
+ Configurable repository discipline checks and rule-driven fixes for Bun projects.
4
4
 
5
5
  `@trebired/code-discipline` stays in one lane:
6
6
 
@@ -16,73 +16,288 @@ It is not a formatter, linter replacement, or build system.
16
16
 
17
17
  ## Install
18
18
 
19
- Runtime support:
20
-
21
- - Bun 1+
22
- - Node.js 18+
19
+ Runtime support: Bun 1+.
23
20
 
24
21
  ```sh
25
- npm install @trebired/code-discipline
22
+ bun i @trebired/code-discipline
26
23
  ```
27
24
 
28
- ## Commands
25
+ ## Quick Start
29
26
 
30
27
  ```sh
31
28
  code-discipline check
32
29
  code-discipline check save
33
30
  code-discipline check max-function-lines dry
34
31
  code-discipline fix
35
- code-discipline fix banned-files min-file-lines max-characters-per-line sync-imports remove-comments structural-blank-lines
32
+ code-discipline fix banned-files min-file-lines max-characters-per-line imports remove-comments structural-blank-lines
36
33
  code-discipline gate -- bun run dev
37
34
  ```
38
35
 
39
- ## Native Backend
36
+ ## Concepts
40
37
 
41
- `@trebired/code-discipline` can use a Rust native backend when a matching binary is available, with the TypeScript implementation as the fallback. This follows the same native-fast-path shape as `@trebired/logger`: package users keep the same CLI/API, while hot scanning and rewrite paths can move into Rust.
38
+ ### Rules
42
39
 
43
- The current native backend accelerates source scanning, `max-file-lines`, common `max-function-lines` paths, `folderize-compound-files` checks, and `remove-comments`. If no binary is present, the package automatically uses the TypeScript fallback.
40
+ #### `bannedPatterns`
44
41
 
45
- Useful native controls:
42
+ Reports case-insensitive substring matches found in source files.
46
43
 
47
- - `bun run build:native` builds the host native addon into `native/<platform>.node`
48
- - `bun run build:native:matrix` builds the release target matrix
49
- - `TB_CODE_DISCIPLINE_DISABLE_NATIVE=1` forces the TypeScript fallback
50
- - `TB_CODE_DISCIPLINE_NATIVE_BINARY=/path/to/addon.node` loads a specific native addon
44
+ - `"test"` matches `test`, `Test`, `contest`, and `"Test runner"`
45
+ - matching is content-based, not whole-word-only
46
+ - `allowedFiles` lets specific project-relative files bypass a specific banned pattern
47
+ - `severity` defaults to `"fail"`
48
+ - for `.ts`/`.tsx`/`.mts`/`.cts`/`.js`/`.jsx`/`.mjs`/`.cjs` files, patterns are also checked against expressions the compiler can constant-fold to a fixed string at zero runtime cost: `+` concatenation of literals, template literals with foldable interpolations, `[...literals].join(literalSeparator)`, and same-scope `const` aliases of those - so `["OPER", "LORN"].join("")` is caught the same as the literal `"OPERLORN"` would be
49
+ - expressions touching anything non-literal (a parameter, `process.env`, a function call, a name shadowed elsewhere in the file) are never folded - this closes the "split into literal chunks" evasion without guessing at genuinely computed values
51
50
 
52
- Top-level `sync` is gone.
51
+ Example:
52
+
53
+ ```ts
54
+ bannedPatterns: {
55
+ patterns: [
56
+ "test",
57
+ {
58
+ value: "mock",
59
+ allowedFiles: ["src/testing/mock-registry.ts"],
60
+ },
61
+ ],
62
+ }
63
+ ```
64
+
65
+ #### `bannedFiles`
66
+
67
+ Reports source files whose project-relative paths match banned glob patterns.
68
+
69
+ - `**/*.spec.ts` matches root and nested TypeScript spec files
70
+ - `*` matches within a single path segment
71
+ - `**` can cross directory boundaries
72
+ - `code-discipline fix banned-files` deletes matching files
73
+ - `severity` defaults to `"fail"`
74
+
75
+ Example:
76
+
77
+ ```ts
78
+ bannedFiles: {
79
+ patterns: [
80
+ { glob: "**/*.spec.ts" },
81
+ { glob: "**/*.spec.tsx" },
82
+ ],
83
+ }
84
+ ```
85
+
86
+ #### `maxFileLines`
87
+
88
+ Reports files whose total line count exceeds `max`.
89
+
90
+ #### `minFileLines`
91
+
92
+ Reports files whose code line count is at or below `min`, defaulting to `1` when the rule is configured. This catches tiny legacy compatibility shims that only re-export or re-import another module.
93
+
94
+ `code-discipline fix min-file-lines` can delete tiny redirect shims when the file is clearly a single JavaScript/TypeScript `export ... from "..."` statement or a single SCSS `@forward "..."` directive. Importers of that shim are rewritten to the forwarded target before the shim file is removed.
95
+
96
+ #### `minDeclarationName`
97
+
98
+ Reports JavaScript and TypeScript `function` declarations and simple `const` identifiers whose names are shorter than `min`, defaulting to `2` when the rule is configured.
99
+
100
+ #### `maxCharactersPerLine`
101
+
102
+ Reports physical lines whose character count exceeds `max`, defaulting to `150` when the rule is configured.
103
+
104
+ `code-discipline fix max-characters-per-line` can split safe JavaScript and TypeScript string literals into concatenated string segments without requiring Prettier. The fixer preserves the exact runtime string value, prefers whitespace split points, keeps the original quote style when practical, and handles common expression positions such as object property values, variable initializers, array elements, call arguments, and return statements.
105
+
106
+ Example:
107
+
108
+ ```ts
109
+ const messages = {
110
+ repositoryActionsDescription:
111
+ "Akce jsou workflow vlastněná repozitářem, nalezená v {{dir}}. Starší shellová workflow stále fungují, zatímco úlohy, artefakty a běhy workflow_dispatch jsou podporovány také zde.",
112
+ };
113
+ ```
114
+
115
+ Becomes:
116
+
117
+ ```ts
118
+ const messages = {
119
+ repositoryActionsDescription:
120
+ "Akce jsou workflow vlastněná repozitářem, nalezená v {{dir}}. " +
121
+ "Starší shellová workflow stále fungují, zatímco úlohy, artefakty " +
122
+ "a běhy workflow_dispatch jsou podporovány také zde.",
123
+ };
124
+ ```
125
+
126
+ Unsafe cases stay unchanged and remain reported after the fix pass. The fixer intentionally avoids template literals, escaped strings, strings with newlines, import/export specifiers, directive prologues, JSX text and attributes, URLs, long unbroken tokens, generated files, minified files, regex literals, comments, and syntax cases where semantic preservation is uncertain.
127
+
128
+ #### `maxFunctionLines`
129
+
130
+ Reports function-like declarations whose total span exceeds `max`.
131
+
132
+ #### `folderizeCompoundFiles`
133
+
134
+ Detects flat compound names such as `user_route.ts` and can move them into structural folders such as `user/route.ts`.
135
+
136
+ The rule config only describes separators now. Whether it mutates is decided by running `code-discipline fix`.
137
+
138
+ Folderization autofix stays intentionally conservative: move-aware relative import repair is implemented for the JavaScript and TypeScript module family, while Go and Rust files are scanned safely for other rules but are not folderized automatically.
139
+
140
+ #### `imports`
141
+
142
+ Validates and optionally fixes:
143
+
144
+ - `tsconfig.compilerOptions.paths`
145
+ - relative source imports that should become aliases
146
+ - `project-manifests` output drift in root `tsconfig.json` and `package.json#imports`
147
+ - `alias-map` output drift in `.code-discipline/imports/*.json` and the generated tsconfig projection
148
+
149
+ `imports` rewrites JavaScript, TypeScript, and SCSS module specifiers. Mixed-language repositories can still include Go and Rust; those files are ignored by alias syncing instead of causing parser failures.
150
+
151
+ When `imports` sees a relative import that resolves nowhere, check mode reports it. Fix mode removes safe line-isolated static import/export declarations and Sass `@use`, `@forward`, or single-specifier quoted `@import` directives. Dynamic `import(...)`, comments, strings, CSS `url(...)`, and arbitrary CSS values are left alone.
152
+
153
+ Setting `removeDeadImports: true` also detects and removes unused JavaScript/TypeScript import bindings (default, namespace, and named specifiers, including `import type`). Detection is syntactic: a binding is dead when its local name has no other identifier reference anywhere else in the file. Side-effect-only imports (`import "./x"`) are never touched, and only the unused portion of a multi-binding import is removed, keeping the rest intact. This option is off by default.
154
+
155
+ The default `output: { type: "project-manifests" }` writes aliases directly into root `tsconfig.json` and mirrors them into `package.json#imports`. `output: { type: "alias-map" }` uses `.code-discipline/imports/*.json` as the alias source of truth, writes `.code-discipline/generated/tsconfig.paths.json`, makes root `tsconfig.json` extend the generated file, and removes managed project-manifest alias state. `code-discipline check imports` reports missing or stale generated tsconfig wiring as a fixable violation, and `code-discipline fix imports` migrates both directions when the configured output model changes.
156
+
157
+ Alias-map output separates committed state from disposable package output:
158
+
159
+ - commit `.code-discipline/config.ts`
160
+ - commit `.code-discipline/imports/*.json` when stable or random aliases are part of the configured alias-map state
161
+ - do not commit `.code-discipline/generated/`
162
+ - `code-discipline fix imports` creates root `.gitignore` when missing and adds `.code-discipline/generated/` idempotently
163
+ - saved CLI reports are written under `.code-discipline/generated/reports/`
164
+
165
+ Top-level `ignore` groups shared scan and formatter exclusions in one place, so you can add explicit entries through `ignore.entries` and opt into root `.gitignore` entries through `ignore.use_gitignore`.
166
+
167
+ Example:
168
+
169
+ ```ts
170
+ ignore: {
171
+ entries: [
172
+ { type: "folder", pattern: "coverage" },
173
+ { type: "folder", pattern: "tmp" },
174
+ ],
175
+ use_gitignore: true,
176
+ },
177
+ ```
53
178
 
54
- `sync-imports` is now just another fixable rule, so targeted sync work is done through:
179
+ Example targeted CLI usage:
55
180
 
56
181
  ```sh
57
- code-discipline fix sync-imports
182
+ code-discipline fix imports
58
183
  ```
59
184
 
60
- If you want the terminal output written to a package-managed report file too, add `save`:
185
+ #### `removeComments`
186
+
187
+ Reports files that still contain removable comments and strips them when you run `code-discipline fix`.
188
+
189
+ The rule supports the same language families this package currently scans for discipline work:
190
+
191
+ - JavaScript and TypeScript
192
+ - Go
193
+ - Rust
194
+ - SCSS and CSS
195
+
196
+ It keeps string, regex, rune, char, byte-string, and raw-string content intact while removing actual source comments. When a removed comment occupied the whole line, that empty line is removed in the same file rewrite.
197
+
198
+ You can preserve specific comments by matching plain substrings inside the comment text itself, without hardcoding any comment syntax:
199
+
200
+ ```ts
201
+ removeComments: {
202
+ exclude: ["@ts-nocheck"],
203
+ }
204
+ ```
205
+
206
+ In that example, any comment containing `@ts-nocheck` is ignored by both `check` and `fix`.
207
+
208
+ Example targeted CLI usage:
61
209
 
62
210
  ```sh
63
- code-discipline check save
211
+ code-discipline fix remove-comments
64
212
  ```
65
213
 
66
- This writes a plain-text report to a timestamped file such as `.code-discipline/generated/reports/cd-report-2026-05-26-19-00-00.txt`.
214
+ #### `structuralBlankLines`
67
215
 
68
- Typical `package.json` scripts can stay simple:
216
+ Reports JavaScript and TypeScript files where the major structural sections aren't visually separated, and normalizes the blank lines between them when you run `code-discipline fix`.
69
217
 
70
- ```json
71
- {
72
- "scripts": {
73
- "discipline:check": "code-discipline check",
74
- "discipline:fix": "code-discipline fix",
75
- "start:app": "node dist/server.js",
76
- "start": "code-discipline gate -- npm run start:app"
77
- }
218
+ It only enforces blank lines at boundaries the AST clearly identifies as structural: after the file header, between imports and the first non-import statement, between declaration groups (variables, types, functions, classes, enums, namespaces), and between class fields/methods/constructors. Compact groups - consecutive imports, variables, type declarations, re-exports, top-level executable statements, class fields, directive prologues, function overload chains, and getter/setter pairs - allow zero or one blank line and only collapse two or more down to one.
219
+
220
+ It never touches statements inside function or method bodies, `if`/loop/`try` bodies, object literals, array elements, interface members, type literal members, enum members, or JSX children - spacing choices inside those remain up to the developer.
221
+
222
+ ```ts
223
+ structuralBlankLines: {}
224
+ ```
225
+
226
+ Example targeted CLI usage:
227
+
228
+ ```sh
229
+ code-discipline fix structural-blank-lines
230
+ ```
231
+
232
+ #### `dry`
233
+
234
+ Reports duplicate function groups across the configured source tree.
235
+
236
+ - exact normalized structure is reported with 100% confidence
237
+ - equivalent normalized behavior in simple pure functions is reported with 100% confidence
238
+ - matching function names are reported with 100% confidence
239
+ - highly similar normalized function structure is reported as a likely duplicate
240
+ - `minDuplicateCharacters` defaults to `0`; raise it if you only want larger duplicate functions
241
+ - whitespace, comments, function names, parameter names, and local identifier names do not matter
242
+ - expression bodies, single-return blocks, simple const-then-return blocks, nullish fallback forms, finite number guards, and object guard branches are normalized when their behavior matches
243
+ - reports are neutral groups, not "file A duplicates file B"
244
+ - `dry` is check-only
245
+
246
+ ```ts
247
+ dry: {
248
+ minDuplicateCharacters: 0,
78
249
  }
79
250
  ```
80
251
 
81
- `gate` runs the same repo config discovery as `check`. If violations are found, it exits non-zero and does not launch the child command. If the repo is clean, it starts the child command and forwards its exit status.
252
+ Example output:
82
253
 
83
- Long check and fix runs emit chunked rule progress in the CLI, including current violation counts and fix mutation counts where applicable.
254
+ ```txt
255
+ dry duplicate function group: 2 functions, confidence 1, signals: exact-normalized, normalized-behavior, similar-structure
256
+ - src/one.ts:1 buildUserLabel
257
+ - src/two.ts:1 formatUserLabel
258
+ ```
259
+
260
+ ### Lifecycle Hooks
261
+
262
+ Hooks remain package-owned and generic:
263
+
264
+ - `beforeRun(context)`
265
+ - `afterRun(context, result)`
266
+ - `beforeMode(context)`
267
+ - `afterMode(context, result)`
268
+
269
+ The hook context includes:
270
+
271
+ - `mode`
272
+ - `projectRoot`
273
+ - `configPath`
274
+ - `config`
275
+ - mutable `state`
276
+
277
+ ### Tsconfig Path Normalization
278
+
279
+ Use `rules.imports.runtime` when a run needs temporary `compilerOptions.paths` normalization:
280
+
281
+ ```ts
282
+ rules: {
283
+ imports: {
284
+ runtime: {
285
+ normalize: "relative-dot-prefix",
286
+ restoreAfterRun: true,
287
+ },
288
+ },
289
+ }
290
+ ```
84
291
 
85
- ## Config
292
+ Available modes:
293
+
294
+ - `"relative-dot-prefix"` turns `src/x.ts` into `./src/x.ts`
295
+ - `"strip-dot-prefix"` turns `./src/x.ts` into `src/x.ts`
296
+ - `"none"` leaves values unchanged
297
+
298
+ ## Configuration
299
+
300
+ ### Config
86
301
 
87
302
  The CLI auto-discovers config modules in this order:
88
303
 
@@ -183,7 +398,7 @@ export default defineCodeDisciplineConfig({
183
398
  { glob: "**/*.spec.tsx" },
184
399
  ],
185
400
  },
186
- syncImports: {
401
+ imports: {
187
402
  alias: {
188
403
  prefix: "#",
189
404
  strategy: "relative-path-slug",
@@ -238,9 +453,9 @@ export default defineCodeDisciplineConfig({
238
453
  });
239
454
  ```
240
455
 
241
- ## Presets
456
+ ### Presets
242
457
 
243
- ### `nodeProcessBoundary`
458
+ #### `nodeProcessBoundary`
244
459
 
245
460
  `nodeProcessBoundary` is opt-in. It expands into ordinary `bannedPatterns` entries so projects can keep direct Node `process` access inside explicit boundary files.
246
461
 
@@ -260,13 +475,13 @@ presets: {
260
475
 
261
476
  Manual `rules.bannedPatterns` entries still work normally. If both are configured, the preset patterns are appended to the manual list and share the same `bannedPatterns` severity and exclusions.
262
477
 
263
- ## Selectors
478
+ ### Selectors
264
479
 
265
480
  `check` and `fix` both accept positional selectors:
266
481
 
267
482
  ```sh
268
483
  code-discipline check max-file-lines max-function-lines
269
- code-discipline fix banned-files min-file-lines max-characters-per-line sync-imports remove-comments structural-blank-lines
484
+ code-discipline fix banned-files min-file-lines max-characters-per-line imports remove-comments structural-blank-lines
270
485
  code-discipline check prettier
271
486
  code-discipline fix prettier
272
487
  ```
@@ -281,7 +496,7 @@ Rules use kebab-case public slugs:
281
496
  - `max-characters-per-line`
282
497
  - `max-function-lines`
283
498
  - `folderize-compound-files`
284
- - `sync-imports`
499
+ - `imports`
285
500
  - `remove-comments`
286
501
  - `structural-blank-lines`
287
502
  - `dry`
@@ -290,19 +505,7 @@ Rules use kebab-case public slugs:
290
505
 
291
506
  Formatter selectors such as `prettier` are enabled by top-level `formatters` config, not by `rules`.
292
507
 
293
- ## Logging
294
-
295
- Code Discipline uses logger levels directly: passing summaries use `success`, blocking findings use `fail`, and warning findings use `warn`.
296
-
297
- ```ts
298
- logging: {
299
- warnings: false,
300
- }
301
- ```
302
-
303
- Set `logging.warnings: false` to hide warning-level CLI output and warning-only report rows. The default is `true`.
304
-
305
- ## Formatters
508
+ ### Formatters
306
509
 
307
510
  Formatters are configured at top level under `formatters`, not under `rules`. Presence enables a formatter; there is no `enabled: true` key.
308
511
 
@@ -327,22 +530,84 @@ formatters: {
327
530
  },
328
531
  ```
329
532
 
330
- ## Runtime API
533
+ ## Runtime
331
534
 
332
- The package-owned runtime dispatcher now has two modes only:
535
+ ### Native Backend
333
536
 
334
- - `check`
335
- - `fix`
537
+ `@trebired/code-discipline` can use a Rust native backend when a matching binary is available, with the TypeScript implementation as the fallback. This follows the same native-fast-path shape as `@trebired/logger`: package users keep the same CLI/API, while hot scanning and rewrite paths can move into Rust.
336
538
 
337
- ```ts
338
- import { codeDiscipline } from "@trebired/code-discipline";
539
+ The current native backend accelerates source scanning, `max-file-lines`, common `max-function-lines` paths, `folderize-compound-files` checks, and `remove-comments`. If no binary is present, the package automatically uses the TypeScript fallback.
540
+
541
+ Useful native controls:
542
+
543
+ - `bun run build:native` builds the host native addon into `native/<platform>.node`
544
+ - `bun run build:native:matrix` builds the release target matrix
545
+ - `TB_CODE_DISCIPLINE_DISABLE_NATIVE=1` forces the TypeScript fallback
546
+ - `TB_CODE_DISCIPLINE_NATIVE_BINARY=/path/to/addon.node` loads a specific native addon
547
+
548
+ Top-level `sync` is gone.
549
+
550
+ `imports` is now just another fixable rule, so targeted sync work is done through:
551
+
552
+ ```sh
553
+ code-discipline fix imports
554
+ ```
555
+
556
+ If you want the terminal output written to a package-managed report file too, add `save`:
557
+
558
+ ```sh
559
+ code-discipline check save
560
+ ```
561
+
562
+ This writes a plain-text report to a timestamped file such as `.code-discipline/generated/reports/cd-report-2026-05-26-19-00-00.txt`.
563
+
564
+ Typical `package.json` scripts can stay simple:
565
+
566
+ ```json
567
+ {
568
+ "scripts": {
569
+ "discipline:check": "code-discipline check",
570
+ "discipline:fix": "code-discipline fix",
571
+ "start:app": "bun dist/server.js",
572
+ "start": "code-discipline gate -- bun run start:app"
573
+ }
574
+ }
575
+ ```
576
+
577
+ `gate` runs the same repo config discovery as `check`. If violations are found, it exits non-zero and does not launch the child command. If the repo is clean, it starts the child command and forwards its exit status.
578
+
579
+ Long check and fix runs emit chunked rule progress in the CLI, including current violation counts and fix mutation counts where applicable.
580
+
581
+ ### Logging
582
+
583
+ Code Discipline uses logger levels directly: passing summaries use `success`, blocking findings use `fail`, and warning findings use `warn`.
584
+
585
+ ```ts
586
+ logging: {
587
+ warnings: false,
588
+ }
589
+ ```
590
+
591
+ Set `logging.warnings: false` to hide warning-level CLI output and warning-only report rows. The default is `true`.
592
+
593
+ ## Public API
594
+
595
+ ### Runtime API
596
+
597
+ The package-owned runtime dispatcher now has two modes only:
598
+
599
+ - `check`
600
+ - `fix`
601
+
602
+ ```ts
603
+ import { codeDiscipline } from "@trebired/code-discipline";
339
604
 
340
605
  const result = await codeDiscipline({
341
606
  mode: "fix",
342
607
  projectRoot: process.cwd(),
343
- onlyRules: ["sync-imports"],
608
+ onlyRules: ["imports"],
344
609
  rules: {
345
- syncImports: {
610
+ imports: {
346
611
  alias: {
347
612
  strategy: "relative-path-slug",
348
613
  },
@@ -373,7 +638,7 @@ const discipline = createCodeDiscipline({
373
638
  maxFunctionLines: {
374
639
  max: 80,
375
640
  },
376
- syncImports: {
641
+ imports: {
377
642
  alias: {
378
643
  strategy: "relative-path-slug",
379
644
  },
@@ -383,277 +648,19 @@ const discipline = createCodeDiscipline({
383
648
 
384
649
  await discipline.fix({
385
650
  projectRoot: process.cwd(),
386
- onlyRules: ["sync-imports"],
651
+ onlyRules: ["imports"],
387
652
  });
388
653
  ```
389
654
 
390
655
  Every violation is treated uniformly now. Results expose `ok`, `violationCount`, and `violations`, and the CLI prints concise rule/file/message lines instead of large JSON-style payloads.
391
656
 
392
- ## Rules
393
-
394
- ### `bannedPatterns`
395
-
396
- Reports case-insensitive substring matches found in source files.
397
-
398
- - `"test"` matches `test`, `Test`, `contest`, and `"Test runner"`
399
- - matching is content-based, not whole-word-only
400
- - `allowedFiles` lets specific project-relative files bypass a specific banned pattern
401
- - `severity` defaults to `"fail"`
402
- - for `.ts`/`.tsx`/`.mts`/`.cts`/`.js`/`.jsx`/`.mjs`/`.cjs` files, patterns are also checked against expressions the compiler can constant-fold to a fixed string at zero runtime cost: `+` concatenation of literals, template literals with foldable interpolations, `[...literals].join(literalSeparator)`, and same-scope `const` aliases of those - so `["OPER", "LORN"].join("")` is caught the same as the literal `"OPERLORN"` would be
403
- - expressions touching anything non-literal (a parameter, `process.env`, a function call, a name shadowed elsewhere in the file) are never folded - this closes the "split into literal chunks" evasion without guessing at genuinely computed values
404
-
405
- Example:
406
-
407
- ```ts
408
- bannedPatterns: {
409
- patterns: [
410
- "test",
411
- {
412
- value: "mock",
413
- allowedFiles: ["src/testing/mock-registry.ts"],
414
- },
415
- ],
416
- }
417
- ```
418
-
419
- ### `bannedFiles`
420
-
421
- Reports source files whose project-relative paths match banned glob patterns.
422
-
423
- - `**/*.spec.ts` matches root and nested TypeScript spec files
424
- - `*` matches within a single path segment
425
- - `**` can cross directory boundaries
426
- - `code-discipline fix banned-files` deletes matching files
427
- - `severity` defaults to `"fail"`
428
-
429
- Example:
430
-
431
- ```ts
432
- bannedFiles: {
433
- patterns: [
434
- { glob: "**/*.spec.ts" },
435
- { glob: "**/*.spec.tsx" },
436
- ],
437
- }
438
- ```
439
-
440
- ### `maxFileLines`
441
-
442
- Reports files whose total line count exceeds `max`.
443
-
444
- ### `minFileLines`
445
-
446
- Reports files whose code line count is at or below `min`, defaulting to `1` when the rule is configured. This catches tiny legacy compatibility shims that only re-export or re-import another module.
447
-
448
- `code-discipline fix min-file-lines` can delete tiny redirect shims when the file is clearly a single JavaScript/TypeScript `export ... from "..."` statement or a single SCSS `@forward "..."` directive. Importers of that shim are rewritten to the forwarded target before the shim file is removed.
449
-
450
- ### `minDeclarationName`
451
-
452
- Reports JavaScript and TypeScript `function` declarations and simple `const` identifiers whose names are shorter than `min`, defaulting to `2` when the rule is configured.
453
-
454
- ### `maxCharactersPerLine`
455
-
456
- Reports physical lines whose character count exceeds `max`, defaulting to `150` when the rule is configured.
457
-
458
- `code-discipline fix max-characters-per-line` can split safe JavaScript and TypeScript string literals into concatenated string segments without requiring Prettier. The fixer preserves the exact runtime string value, prefers whitespace split points, keeps the original quote style when practical, and handles common expression positions such as object property values, variable initializers, array elements, call arguments, and return statements.
459
-
460
- Example:
461
-
462
- ```ts
463
- const messages = {
464
- repositoryActionsDescription:
465
- "Akce jsou workflow vlastněná repozitářem, nalezená v {{dir}}. Starší shellová workflow stále fungují, zatímco úlohy, artefakty a běhy workflow_dispatch jsou podporovány také zde.",
466
- };
467
- ```
468
-
469
- Becomes:
470
-
471
- ```ts
472
- const messages = {
473
- repositoryActionsDescription:
474
- "Akce jsou workflow vlastněná repozitářem, nalezená v {{dir}}. " +
475
- "Starší shellová workflow stále fungují, zatímco úlohy, artefakty " +
476
- "a běhy workflow_dispatch jsou podporovány také zde.",
477
- };
478
- ```
479
-
480
- Unsafe cases stay unchanged and remain reported after the fix pass. The fixer intentionally avoids template literals, escaped strings, strings with newlines, import/export specifiers, directive prologues, JSX text and attributes, URLs, long unbroken tokens, generated files, minified files, regex literals, comments, and syntax cases where semantic preservation is uncertain.
481
-
482
- ### `maxFunctionLines`
483
-
484
- Reports function-like declarations whose total span exceeds `max`.
485
-
486
- ### `folderizeCompoundFiles`
487
-
488
- Detects flat compound names such as `user_route.ts` and can move them into structural folders such as `user/route.ts`.
489
-
490
- The rule config only describes separators now. Whether it mutates is decided by running `code-discipline fix`.
491
-
492
- Folderization autofix stays intentionally conservative: move-aware relative import repair is implemented for the JavaScript and TypeScript module family, while Go and Rust files are scanned safely for other rules but are not folderized automatically.
493
-
494
- ### `syncImports`
495
-
496
- Validates and optionally fixes:
497
-
498
- - `tsconfig.compilerOptions.paths`
499
- - relative source imports that should become aliases
500
- - `project-manifests` output drift in root `tsconfig.json` and `package.json#imports`
501
- - `alias-map` output drift in `.code-discipline/imports/*.json` and the generated tsconfig projection
502
-
503
- `syncImports` rewrites JavaScript, TypeScript, and SCSS module specifiers. Mixed-language repositories can still include Go and Rust; those files are ignored by alias syncing instead of causing parser failures.
504
-
505
- When `syncImports` sees a relative import that resolves nowhere, check mode reports it. Fix mode removes safe line-isolated static import/export declarations and Sass `@use`, `@forward`, or single-specifier quoted `@import` directives. Dynamic `import(...)`, comments, strings, CSS `url(...)`, and arbitrary CSS values are left alone.
506
-
507
- The default `output: { type: "project-manifests" }` writes aliases directly into root `tsconfig.json` and mirrors them into `package.json#imports`. `output: { type: "alias-map" }` uses `.code-discipline/imports/*.json` as the alias source of truth, writes `.code-discipline/generated/tsconfig.paths.json`, makes root `tsconfig.json` extend the generated file, and removes managed project-manifest alias state. `code-discipline check sync-imports` reports missing or stale generated tsconfig wiring as a fixable violation, and `code-discipline fix sync-imports` migrates both directions when the configured output model changes.
508
-
509
- Alias-map output separates committed state from disposable package output:
510
-
511
- - commit `.code-discipline/config.ts`
512
- - commit `.code-discipline/imports/*.json` when stable or random aliases are part of the configured alias-map state
513
- - do not commit `.code-discipline/generated/`
514
- - `code-discipline fix sync-imports` creates root `.gitignore` when missing and adds `.code-discipline/generated/` idempotently
515
- - saved CLI reports are written under `.code-discipline/generated/reports/`
516
-
517
- Top-level `ignore` groups shared scan and formatter exclusions in one place, so you can add explicit entries through `ignore.entries` and opt into root `.gitignore` entries through `ignore.use_gitignore`.
518
-
519
- Example:
520
-
521
- ```ts
522
- ignore: {
523
- entries: [
524
- { type: "folder", pattern: "coverage" },
525
- { type: "folder", pattern: "tmp" },
526
- ],
527
- use_gitignore: true,
528
- },
529
- ```
530
-
531
- Example targeted CLI usage:
532
-
533
- ```sh
534
- code-discipline fix sync-imports
535
- ```
536
-
537
- ### `removeComments`
538
-
539
- Reports files that still contain removable comments and strips them when you run `code-discipline fix`.
540
-
541
- The rule supports the same language families this package currently scans for discipline work:
542
-
543
- - JavaScript and TypeScript
544
- - Go
545
- - Rust
546
- - SCSS and CSS
547
-
548
- It keeps string, regex, rune, char, byte-string, and raw-string content intact while removing actual source comments. When a removed comment occupied the whole line, that empty line is removed in the same file rewrite.
549
-
550
- You can preserve specific comments by matching plain substrings inside the comment text itself, without hardcoding any comment syntax:
551
-
552
- ```ts
553
- removeComments: {
554
- exclude: ["@ts-nocheck"],
555
- }
556
- ```
557
-
558
- In that example, any comment containing `@ts-nocheck` is ignored by both `check` and `fix`.
559
-
560
- Example targeted CLI usage:
561
-
562
- ```sh
563
- code-discipline fix remove-comments
564
- ```
565
-
566
- ### `structuralBlankLines`
567
-
568
- Reports JavaScript and TypeScript files where the major structural sections aren't visually separated, and normalizes the blank lines between them when you run `code-discipline fix`.
569
-
570
- It only enforces blank lines at boundaries the AST clearly identifies as structural: after the file header, between imports and the first non-import statement, between declaration groups (variables, types, functions, classes, enums, namespaces), and between class fields/methods/constructors. Compact groups - consecutive imports, variables, type declarations, re-exports, top-level executable statements, class fields, directive prologues, function overload chains, and getter/setter pairs - allow zero or one blank line and only collapse two or more down to one.
571
-
572
- It never touches statements inside function or method bodies, `if`/loop/`try` bodies, object literals, array elements, interface members, type literal members, enum members, or JSX children - spacing choices inside those remain up to the developer.
573
-
574
- ```ts
575
- structuralBlankLines: {}
576
- ```
577
-
578
- Example targeted CLI usage:
579
-
580
- ```sh
581
- code-discipline fix structural-blank-lines
582
- ```
583
-
584
- ### `dry`
585
-
586
- Reports duplicate function groups across the configured source tree.
587
-
588
- - exact normalized structure is reported with 100% confidence
589
- - equivalent normalized behavior in simple pure functions is reported with 100% confidence
590
- - matching function names are reported with 100% confidence
591
- - highly similar normalized function structure is reported as a likely duplicate
592
- - `minDuplicateCharacters` defaults to `0`; raise it if you only want larger duplicate functions
593
- - whitespace, comments, function names, parameter names, and local identifier names do not matter
594
- - expression bodies, single-return blocks, simple const-then-return blocks, nullish fallback forms, finite number guards, and object guard branches are normalized when their behavior matches
595
- - reports are neutral groups, not "file A duplicates file B"
596
- - `dry` is check-only
597
-
598
- ```ts
599
- dry: {
600
- minDuplicateCharacters: 0,
601
- }
602
- ```
603
-
604
- Example output:
605
-
606
- ```txt
607
- dry duplicate function group: 2 functions, confidence 1, signals: exact-normalized, normalized-behavior, similar-structure
608
- - src/one.ts:1 buildUserLabel
609
- - src/two.ts:1 formatUserLabel
610
- ```
611
-
612
- ## Lifecycle Hooks
613
-
614
- Hooks remain package-owned and generic:
615
-
616
- - `beforeRun(context)`
617
- - `afterRun(context, result)`
618
- - `beforeMode(context)`
619
- - `afterMode(context, result)`
620
-
621
- The hook context includes:
622
-
623
- - `mode`
624
- - `projectRoot`
625
- - `configPath`
626
- - `config`
627
- - mutable `state`
628
-
629
- ## Tsconfig Path Normalization
630
-
631
- Use `rules.syncImports.runtime` when a run needs temporary `compilerOptions.paths` normalization:
632
-
633
- ```ts
634
- rules: {
635
- syncImports: {
636
- runtime: {
637
- normalize: "relative-dot-prefix",
638
- restoreAfterRun: true,
639
- },
640
- },
641
- }
642
- ```
643
-
644
- Available modes:
645
-
646
- - `"relative-dot-prefix"` turns `src/x.ts` into `./src/x.ts`
647
- - `"strip-dot-prefix"` turns `./src/x.ts` into `src/x.ts`
648
- - `"none"` leaves values unchanged
649
-
650
- ## Advanced Helpers
657
+ ### Advanced Helpers
651
658
 
652
659
  Low-level helpers are still exported for advanced tooling:
653
660
 
654
661
  - `checkCodeDiscipline()`
655
662
  - `fixCodeDiscipline()`
656
- - `syncImports()`
663
+ - `imports()`
657
664
  - `defineCodeDisciplineConfig()`
658
665
  - `findCodeDisciplineConfigModule()`
659
666
  - `loadResolvedCodeDisciplineConfig()`
@@ -661,4 +668,20 @@ Low-level helpers are still exported for advanced tooling:
661
668
  - `restoreTsconfigPaths()`
662
669
  - `syncPackageJsonImportsFromTsconfigPaths()`
663
670
 
664
- `syncImports()` remains available as a lower-level helper, but the package CLI no longer exposes a separate `sync` command.
671
+ `imports()` remains available as a lower-level helper, but the package CLI no longer exposes a separate `sync` command.
672
+
673
+ ## CLI
674
+
675
+ ### Command Reference
676
+
677
+ - `code-discipline` runs the package CLI.
678
+
679
+ The CLI exits with status 1 when it reports a failing violation or runtime error.
680
+
681
+ ## What It Does Not Do
682
+
683
+ This package does not:
684
+
685
+ - replace a formatter, linter, compiler, or build system
686
+ - own application architecture or product naming policy
687
+ - require generated output to be committed