@codefast/cli 0.12.0 → 0.14.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 (66) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +120 -16
  3. package/dist/arrange/domain/ast/translator.js +42 -28
  4. package/dist/arrange/simplify/process-file.d.ts +1 -1
  5. package/dist/audit/assertions/cli-result.d.ts +13 -0
  6. package/dist/audit/assertions/cli-result.js +22 -0
  7. package/dist/audit/assertions/cli-schema.d.ts +18 -0
  8. package/dist/audit/assertions/cli-schema.js +12 -0
  9. package/dist/audit/assertions/domain/double-assertion.d.ts +20 -0
  10. package/dist/audit/assertions/domain/double-assertion.js +122 -0
  11. package/dist/audit/assertions/output.d.ts +7 -0
  12. package/dist/audit/assertions/output.js +21 -0
  13. package/dist/audit/assertions/prepare.d.ts +16 -0
  14. package/dist/audit/assertions/prepare.js +12 -0
  15. package/dist/audit/assertions/run.d.ts +14 -0
  16. package/dist/audit/assertions/run.js +40 -0
  17. package/dist/audit/command.js +46 -1
  18. package/dist/audit/comments/domain/comment-content.d.ts +12 -0
  19. package/dist/audit/comments/domain/comment-content.js +17 -4
  20. package/dist/audit/display-names/domain/display-names.js +1 -9
  21. package/dist/audit/domain/types.d.ts +86 -0
  22. package/dist/audit/imports/domain/import-policy.js +4 -18
  23. package/dist/audit/layers/cli-result.d.ts +13 -0
  24. package/dist/audit/layers/cli-result.js +22 -0
  25. package/dist/audit/layers/cli-schema.d.ts +29 -0
  26. package/dist/audit/layers/cli-schema.js +17 -0
  27. package/dist/audit/layers/domain/layering.d.ts +46 -0
  28. package/dist/audit/layers/domain/layering.js +191 -0
  29. package/dist/audit/layers/output.d.ts +7 -0
  30. package/dist/audit/layers/output.js +21 -0
  31. package/dist/audit/layers/prepare.d.ts +25 -0
  32. package/dist/audit/layers/prepare.js +66 -0
  33. package/dist/audit/layers/run.d.ts +19 -0
  34. package/dist/audit/layers/run.js +62 -0
  35. package/dist/audit/prepare.d.ts +13 -1
  36. package/dist/audit/prepare.js +16 -1
  37. package/dist/audit/publish/cli-result.d.ts +1 -1
  38. package/dist/audit/publish/cli-result.js +6 -3
  39. package/dist/audit/publish/domain/stylesheet-sources.d.ts +22 -0
  40. package/dist/audit/publish/domain/stylesheet-sources.js +64 -0
  41. package/dist/audit/publish/output.js +12 -3
  42. package/dist/audit/publish/run.d.ts +2 -1
  43. package/dist/audit/publish/run.js +30 -1
  44. package/dist/audit/publish/shipped-files.d.ts +21 -0
  45. package/dist/audit/publish/shipped-files.js +36 -0
  46. package/dist/core/config/schema.d.ts +13 -0
  47. package/dist/core/config/schema.js +14 -0
  48. package/dist/core/filesystem/filesystem.d.ts +3 -4
  49. package/dist/core/filesystem/node.js +1 -7
  50. package/dist/core/oxc-node.d.ts +32 -0
  51. package/dist/core/oxc-node.js +25 -0
  52. package/dist/core/source-position.d.ts +15 -0
  53. package/dist/core/source-position.js +26 -0
  54. package/dist/mirror/dist-filesystem-node.js +4 -14
  55. package/dist/pack-slim/run.js +1 -1
  56. package/dist/tag/cli-result.d.ts +3 -0
  57. package/dist/tag/cli-result.js +8 -2
  58. package/dist/tag/domain/types.d.ts +15 -0
  59. package/dist/tag/domain/version-summary.d.ts +5 -2
  60. package/dist/tag/output.js +13 -6
  61. package/dist/tag/run.js +2 -0
  62. package/dist/tag/writer/since-writer.d.ts +5 -0
  63. package/dist/tag/writer/since-writer.js +43 -13
  64. package/package.json +4 -4
  65. package/dist/mirror/domain/dirent-guard.d.ts +0 -10
  66. package/dist/mirror/domain/dirent-guard.js +0 -15
package/CHANGELOG.md CHANGED
@@ -1,5 +1,70 @@
1
1
  # @codefast/cli
2
2
 
3
+ ## 0.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1007](https://github.com/codefastlabs/codefast/pull/1007) Add `codefast audit layers`, which holds a package's `src/` to the layering its architecture states.
8
+ `audit.layers.packages` lists each package's layers bottom to top, every entry a family directly under the root — a
9
+ directory, or a lone module sitting flat — and the audit reports every value import or re-export that points up the
10
+ list, plus every module no layer places. Type-only imports pass whichever way they point; a dynamic `import()` counts as
11
+ a value import. A configured name the workspace does not hold, an entry nested below the root, and a family placed twice
12
+ are refused before anything is scanned. `audit.layers.allowlist` takes the offending import as written, or
13
+ `path:<import>`.
14
+
15
+ - [#990](https://github.com/codefastlabs/codefast/pull/990) Remove `codefast audit constants`, the audit that required every upper-case numeric `const` to name its kind in the
16
+ comment above it.
17
+
18
+ Breaking: the `audit constants` subcommand is gone, and `audit.constants` is no longer a config key, so a
19
+ `codefast.config` that still sets it fails validation as an unknown key; delete that section.
20
+
21
+ - [#992](https://github.com/codefastlabs/codefast/pull/992) `codefast tag` stamps every overload signature again, each in its own doc block. Since the move to `oxc-parser` it
22
+ stamped only an overloaded function's implementation, the one signature a `.d.ts` drops, so a released overload reached
23
+ consumers with no `@since` on any signature they can see.
24
+
25
+ A declaration with no doc block and a `//` comment on the line above it now fails the run instead of getting a fresh
26
+ block there, where the block would stack under a note (which `audit comments` rejects) or split a directive from the
27
+ code it governs. The run names each such declaration by file and line, lists it under `blockedDeclarations` in `--json`,
28
+ leaves its file untouched, and exits `1`: once a release ships the declaration unstamped, a later run can only stamp a
29
+ version that did not introduce it. The `--json` `ok` field now follows the exit code, so a failed target also reports
30
+ `ok: false`.
31
+
32
+ ## 0.13.0
33
+
34
+ ### Minor Changes
35
+
36
+ - [#951](https://github.com/codefastlabs/codefast/pull/951) Add `codefast audit assertions`, which reports every double type assertion through `unknown` or `any` —
37
+ `x as unknown as T`, `(x as unknown) as T`, `<T><unknown>x` and the `any` spellings — in `.ts`/`.tsx` files, tests
38
+ included. Where the erasure is the point, keep one with `// codefast-allow-double-assertion: <reason>` on its line or
39
+ the line above; a directive with no reason, or one that keeps no assertion, is reported too. Exceptions can also go in
40
+ `audit.assertions.allowlist`.
41
+
42
+ `Filesystem.readdir` is replaced by `readdirEntries(path, { recursive })`, which always returns directory entries: every
43
+ caller asked for them, and the old `string[] | DirectoryEntry[]` union forced each one to cast or guard.
44
+
45
+ - [#894](https://github.com/codefastlabs/codefast/pull/894) `codefast audit constants` reports every upper-case `const` bound to a number in a library's sources whose comment names
46
+ none of the three kinds a number may be — a constant of the machine, a value the contract fixes, or one derived from
47
+ bind-time data. Sentinel values (`0`, `1`, `-1`) are exempt; `audit.constants.target` and `audit.constants.allowlist` in
48
+ `codefast.config` scope and except it.
49
+
50
+ - [#980](https://github.com/codefastlabs/codefast/pull/980) `audit publish` also reports a shipped stylesheet whose Tailwind `@source` paths reach none of the files the slimmed
51
+ tarball ships, naming any `files` entry missing on disk so an unbuilt `dist` reads as such. A workspace resolves those
52
+ paths against `src`, so only the published layout used to show the failure.
53
+
54
+ - [#976](https://github.com/codefastlabs/codefast/pull/976) `engines.node` is now `>=24.0.0`, up from `>=22.12.0`, and Node 22 is no longer supported. Node 24.0.0 is the first
55
+ release with explicit resource management built in (`using`, `await using`, `DisposableStack`, `AsyncDisposableStack`,
56
+ `SuppressedError`) and all of ES2025, so the packages use both as the platform ships them instead of shimming them for
57
+ an older line, and the CI matrix runs the unit suite on 24.0.0 itself. Move to Node 24, or stay on the current minor
58
+ while a deployment still runs Node 22.
59
+
60
+ ### Patch Changes
61
+
62
+ - [#965](https://github.com/codefastlabs/codefast/pull/965) The optional `typescript` peer is now `>=7.0.0`. It was published as the repository's own `^7.0.2` pin, which excluded
63
+ TypeScript 8 and would have risen with each pin bump instead of staying at the supported floor.
64
+
65
+ - [#965](https://github.com/codefastlabs/codefast/pull/965) `README.md` now states TypeScript 7 or later as the floor for the package's types — the one compiler every `@codefast/*`
66
+ package is built and checked with. No code or declaration changed.
67
+
3
68
  ## 0.12.0
4
69
 
5
70
  ### Minor Changes
package/README.md CHANGED
@@ -22,7 +22,9 @@ encode an opinionated house style (called out below) that you can adopt, ignore,
22
22
 
23
23
  ## Requirements
24
24
 
25
- - **Node.js ≥ 22.12** (the CLI is published as ESM).
25
+ - **Node.js ≥ 24** (the CLI is published as ESM).
26
+ - **TypeScript ≥ 7** to type-check a config written with `defineConfig`, and for
27
+ `arrange simplify --fold-variant-classname`, the one command that loads `typescript` (an optional peer).
26
28
  - **A project root — workspace or single package.** Commands resolve their root by walking up from the current
27
29
  directory: the nearest `pnpm-workspace.yaml` marks a **workspace** (every package under it is in scope), and with no
28
30
  workspace file the nearest `package.json` marks a **single package** (that one package is the whole scope). Only
@@ -108,7 +110,10 @@ codefast # Codefast monorepo developer CLI
108
110
  │ ├─ rtl [target] # physical-direction Tailwind classes to make logical / rtl:-paired
109
111
  │ ├─ links [target] # markdown links pointing at a missing path/anchor
110
112
  │ ├─ imports [target] # banned import forms (React by-name, Zod namespace in front-end, …)
113
+ │ ├─ assertions [target] # double type assertions through unknown/any (x as unknown as T)
111
114
  │ ├─ display-names [target] # token()/tag()/module names breaking the <namespace>:<Name> convention
115
+ │ ├─ publish [target] # what breaks a consumer's install: #/ imports, unshipped targets, @source paths
116
+ │ ├─ layers [target] # value imports pointing up a package's configured layers
112
117
  │ └─ comments [target] # section dividers not in the one allowed form
113
118
  │ └─ --fix # rewrite every fixable divider in place (the only audit that writes)
114
119
  │ (each audit also takes [target] + --json)
@@ -139,14 +144,17 @@ Every command also responds to `--help`; each command's section below explains w
139
144
  | `audit rtl` | Report physical-direction Tailwind classes that should be logical | no |
140
145
  | `audit links` | Report markdown cross-references that resolve to nothing | no |
141
146
  | `audit imports` | Enforce the import policy (React by-name, Zod namespace in front-end) | no (report only) |
147
+ | `audit assertions` | Report double type assertions through `unknown` / `any`, tests included | no |
142
148
  | `audit display-names` | Enforce the `namespace:Name` display-name convention | no |
149
+ | `audit publish` | Report what would break a consumer's install of a published package | no |
150
+ | `audit layers` | Report value imports that point up a package's configured layers | no |
143
151
  | `audit comments` | Check doc-comment conventions; repair section dividers | `--fix` only |
144
152
 
145
- **Which of these are for you?** `arrange`, `mirror`, `pack-slim`, `tag`, and `audit links` are general-purpose — they
146
- work for any pnpm workspace or single package that builds with `tsc`. The other four audits encode codefast's own house
147
- style (logical Tailwind directions, named React imports, a specific comment/divider grammar, a `namespace:Name` scheme
148
- for `@codefast/di` tokens). Adopt them if they fit your project; otherwise skip them, or use an allowlist to narrow
149
- their scope.
153
+ **Which of these are for you?** `arrange`, `mirror`, `pack-slim`, `tag`, `audit links`, `audit assertions`,
154
+ `audit layers`, and `audit publish` are general-purpose — they work for any pnpm workspace or single package that builds
155
+ with `tsc`. The other four audits encode codefast's own house style (logical Tailwind directions, named React imports, a
156
+ specific comment/divider grammar, a `namespace:Name` scheme for `@codefast/di` tokens). Adopt them if they fit your
157
+ project; otherwise skip them, or use an allowlist to narrow their scope.
150
158
 
151
159
  ## `arrange`
152
160
 
@@ -244,12 +252,13 @@ apply (see the [Configuration](#configuration) example for their shape):
244
252
  ## `pack-slim`
245
253
 
246
254
  Slims a published package down to what a consumer's `tsc` and Node actually read, so the npm tarball ships `dist`
247
- runtime and types only. Where `mirror` writes the full exports — including the `source` condition — for local
248
- development, `pack-slim` removes that development lane for publish: it drops `src` from `files`, every `source`
249
- condition from `exports`/`imports`, every `imports` entry left pointing outside `files`, every script that is not an
250
- install or publish lifecycle hook, `devDependencies`, and the `dist` source maps plus their dangling `sourceMappingURL`
251
- directives. Private packages are skipped. It is meant to run on an ephemeral CI checkout right before publish, so its
252
- result is **never committed**.
255
+ runtime and types, plus any `src` subtree an export still points into (a raw stylesheet export such as `./css/*`). Where
256
+ `mirror` writes the full exports — including the `source` condition — for local development, `pack-slim` removes that
257
+ development lane for publish: it drops the rest of `src` from `files`, every `source` condition from
258
+ `exports`/`imports`, every `imports` entry left pointing outside `files`, every script that is not an install or publish
259
+ lifecycle hook, `devDependencies`, and the `dist` source maps plus their dangling `sourceMappingURL` directives. Private
260
+ packages are skipped. It is meant to run on an ephemeral CI checkout right before publish, so its result is **never
261
+ committed**.
253
262
 
254
263
  Because that result must never be committed, `pack-slim` refuses to write when the git working tree has uncommitted
255
264
  tracked changes — a guard against an accidental local run landing on real work. `--dry-run` is exempt (it writes
@@ -267,9 +276,15 @@ Exits `1` when any package fails, `0` otherwise.
267
276
  ## `tag`
268
277
 
269
278
  Adds `@since <version>` tags to the doc comments of exported declarations that lack one, creating the doc block when
270
- there is none. The version comes from the nearest `package.json` above each target file, and declarations that already
271
- carry `@since` are left alone. Run it at release time so published APIs carry accurate version metadata — never
272
- hand-write `@since`.
279
+ there is none. Overload signatures are declarations too, each stamped on its own, because the `.d.ts` keeps every
280
+ overload's doc block and drops the implementation's. The version comes from the nearest `package.json` above each target
281
+ file, and declarations that already carry `@since` are left alone. Run it at release time so published APIs carry
282
+ accurate version metadata — never hand-write `@since`.
283
+
284
+ A declaration with no doc block and a `//` comment on the line above it is reported instead: a block written there would
285
+ stack under a note, which `audit comments` rejects, or split a directive from the code it governs. Its file is left as
286
+ it is, so every reported line stays accurate. Write the doc block by hand before the release that ships the declaration,
287
+ since a later run can only stamp a version that did not introduce it.
273
288
 
274
289
  ```bash
275
290
  codefast tag # auto-discover packages from cwd (or the single package)
@@ -277,7 +292,8 @@ codefast tag packages/ui/src # tag one directory or file
277
292
  codefast tag --dry-run # summary only, no writes
278
293
  ```
279
294
 
280
- Exits `1` when no target is selected, when any target fails, or when the `tag.onAfterWrite` hook fails.
295
+ Exits `1` when no target is selected, when any target fails, when a declaration is left unstamped, or when the
296
+ `tag.onAfterWrite` hook fails.
281
297
 
282
298
  ## `audit`
283
299
 
@@ -332,6 +348,85 @@ codefast audit imports --json # machine-readable summary
332
348
  Configure exceptions via `audit.imports.allowlist` — each entry is the offending source text as written or
333
349
  `repo/relative/path.tsx:<text>`.
334
350
 
351
+ ### `audit assertions`
352
+
353
+ Reports every double type assertion through `unknown` or `any` in `.ts`/`.tsx` files, tests included —
354
+ `x as unknown as T`, `(x as unknown) as T`, `<T><unknown>x` and the `any` spellings. The pair tells the compiler two
355
+ types are unrelated and silences it anyway; no oxlint rule targets it (`typescript/no-unnecessary-type-assertion`
356
+ catches the redundant ones only). Make the types agree, narrow with a type guard, or — where the erasure is the point —
357
+ keep it with a directive that states why, on the assertion's line or the line above:
358
+
359
+ ```ts
360
+ // codefast-allow-double-assertion: the host stores each plugin's config untyped; the plugin owns its shape
361
+ const config = host.configFor(plugin.id) as unknown as PluginConfig;
362
+ ```
363
+
364
+ A directive with no reason, or one that keeps no assertion, is itself reported, so a kept assertion cannot outlive its
365
+ cause.
366
+
367
+ ```bash
368
+ codefast audit assertions # whole repo
369
+ codefast audit assertions packages/di # explicit target
370
+ codefast audit assertions --json # machine-readable summary
371
+ ```
372
+
373
+ Configure exceptions via `audit.assertions.allowlist` — each entry is the assertion as written or
374
+ `repo/relative/path.ts:<assertion>` — though the inline directive keeps the reason beside the code it excuses.
375
+
376
+ ### `audit publish`
377
+
378
+ Reports what would break a consumer's install of a published package, while it can still be fixed:
379
+
380
+ - a `#/`-prefixed internal import, which Node's ESM resolver rejects on Node 24 before 24.14 while every in-repo runner
381
+ accepts it;
382
+ - an `exports`/`imports` target the slimmed manifest does not ship, found by applying the same slim as `pack-slim`;
383
+ - a shipped stylesheet whose Tailwind `@source` paths reach none of the files the tarball ships. A workspace still
384
+ resolves them against `src`, so only the published layout shows it: the consumer's Tailwind registers no class names
385
+ and the components render unstyled.
386
+
387
+ It reads the built output, so run it after the build; a `files` entry missing on disk, such as an unbuilt `dist`, is
388
+ named in the report. Private packages are skipped, and it takes no allowlist.
389
+
390
+ ```bash
391
+ codefast audit publish # every published package in the workspace
392
+ codefast audit publish --json # machine-readable summary
393
+ ```
394
+
395
+ ### `audit layers`
396
+
397
+ _General-purpose._ Holds a package's `src/` to the layering its architecture states. You list the layers in
398
+ configuration, bottom to top, each naming the families directly under the root — a directory, or a lone module sitting
399
+ flat (`errors.ts`) — and the audit reports every value import or re-export that points up the list, plus every module no
400
+ layer places. Type-only imports erase at build time and couple nothing, so they may point anywhere; a dynamic `import()`
401
+ counts as a value import wherever it sits.
402
+
403
+ ```js
404
+ // codefast.config.js
405
+ export default {
406
+ audit: {
407
+ layers: {
408
+ packages: {
409
+ "@acme/di": {
410
+ root: "src", // where the layers sit, relative to the package (default)
411
+ layers: [["core", "errors.ts"], ["engine"], ["container"], ["index.ts"]],
412
+ },
413
+ },
414
+ allowlist: [],
415
+ },
416
+ },
417
+ };
418
+ ```
419
+
420
+ ```bash
421
+ codefast audit layers # every package audit.layers.packages names
422
+ codefast audit layers packages/di # one package, or a directory beneath its root
423
+ codefast audit layers --json # machine-readable summary
424
+ ```
425
+
426
+ A configured package name the workspace does not hold, a layer entry nested below the root, and a family placed twice
427
+ are refused before anything is scanned. Configure exceptions via `audit.layers.allowlist` — each entry is the offending
428
+ import as written or `repo/relative/path.ts:<import>`.
429
+
335
430
  ### `audit comments`
336
431
 
337
432
  _House style._ Checks doc-comment conventions. Section dividers not in the one allowed form are mechanical, so `--fix`
@@ -500,7 +595,13 @@ export default {
500
595
  links: { allowlist: [] }, // bare link target, or `repo/relative/doc.md:target`
501
596
  comments: { allowlist: [] }, // divider as written, or `repo/relative/path.ts:<divider>`
502
597
  imports: { allowlist: [] }, // offending import text as written, or `repo/relative/path.tsx:<text>`
598
+ assertions: { allowlist: [] }, // assertion as written, or `repo/relative/path.ts:<assertion>`
503
599
  displayNames: { allowlist: [] }, // call as written, or `repo/relative/path.ts:<call>`
600
+ layers: {
601
+ // per package: its layers bottom to top, each entry a directory or module file under `root` (default `src`)
602
+ packages: { "@acme/di": { layers: [["core", "errors.ts"], ["engine"], ["index.ts"]] } },
603
+ allowlist: [], // import as written, or `repo/relative/path.ts:<import>`
604
+ },
504
605
  },
505
606
  };
506
607
  ```
@@ -551,7 +652,10 @@ pnpm run cli:audit:links # codefast audit links
551
652
  pnpm run cli:audit:rtl # codefast audit rtl
552
653
  pnpm run cli:audit:comments # codefast audit comments
553
654
  pnpm run cli:audit:imports # codefast audit imports
655
+ pnpm run cli:audit:assertions # codefast audit assertions
554
656
  pnpm run cli:audit:display-names # codefast audit display-names
657
+ pnpm run cli:audit:layers # codefast audit layers
658
+ pnpm run cli:audit:publish # codefast audit publish
555
659
  ```
556
660
 
557
661
  `pnpm run version-packages` runs `changeset version` and then `codefast tag`, so published APIs are stamped at release,
@@ -4,9 +4,26 @@
4
4
  */
5
5
  import { parseSync } from "oxc-parser";
6
6
  import { DomainBinaryOperator, DomainSyntaxKind } from "#arrange/domain/ast/ast-node";
7
- function isOxcNode(value) {
8
- return typeof value === "object" && value !== null && typeof value.type === "string";
9
- }
7
+ import { isOxcNode, programStatements } from "#core/oxc-node";
8
+ /**
9
+ * Stands in for a child while its parent is built first, so the child has a parent to point at; the
10
+ * build replaces it before the node leaves the translator.
11
+ */
12
+ const PENDING_NODE = Object.freeze({
13
+ kind: DomainSyntaxKind.Unknown,
14
+ pos: -1,
15
+ end: -1,
16
+ parent: null,
17
+ children: [],
18
+ });
19
+ /** The same stand-in for a child typed as an identifier. */
20
+ const PENDING_IDENTIFIER = Object.freeze({
21
+ kind: DomainSyntaxKind.Identifier,
22
+ pos: -1,
23
+ end: -1,
24
+ parent: null,
25
+ text: "",
26
+ });
10
27
  function nodeName(node) {
11
28
  return typeof node.name === "string" ? node.name : "";
12
29
  }
@@ -87,15 +104,13 @@ export class TypeScriptAstTranslator {
87
104
  end: node.end,
88
105
  parent,
89
106
  importClause: undefined,
90
- moduleSpecifier: undefined,
107
+ moduleSpecifier: PENDING_NODE,
91
108
  };
92
109
  if (specifiers.length > 0) {
93
110
  self.importClause = this.buildImportClause(node, specifiers, self);
94
111
  }
95
112
  const source = isOxcNode(node.source) ? node.source : undefined;
96
- self.moduleSpecifier = source
97
- ? this.translateNode(source, self)
98
- : this.translateUnknown(node, self);
113
+ self.moduleSpecifier = source ? this.translateNode(source, self) : this.translateUnknown(node, self);
99
114
  return self;
100
115
  }
101
116
  buildImportClause(declaration, specifiers, parent) {
@@ -131,7 +146,7 @@ export class TypeScriptAstTranslator {
131
146
  pos: specifier.start,
132
147
  end: specifier.end,
133
148
  parent,
134
- name: undefined,
149
+ name: PENDING_IDENTIFIER,
135
150
  };
136
151
  self.name = this.translateIdentifier(local, self);
137
152
  return self;
@@ -158,7 +173,7 @@ export class TypeScriptAstTranslator {
158
173
  end: specifier.end,
159
174
  parent,
160
175
  propertyName: undefined,
161
- name: undefined,
176
+ name: PENDING_IDENTIFIER,
162
177
  };
163
178
  if (imported && imported.type === "Identifier" && nodeName(imported) !== nodeName(local)) {
164
179
  self.propertyName = this.translateIdentifier(imported, self);
@@ -220,7 +235,7 @@ export class TypeScriptAstTranslator {
220
235
  pos,
221
236
  end,
222
237
  parent,
223
- expression: undefined,
238
+ expression: PENDING_NODE,
224
239
  arguments: [],
225
240
  };
226
241
  self.expression = this.translateNode(callee, self);
@@ -238,8 +253,8 @@ export class TypeScriptAstTranslator {
238
253
  pos,
239
254
  end,
240
255
  parent,
241
- expression: undefined,
242
- name: undefined,
256
+ expression: PENDING_NODE,
257
+ name: PENDING_IDENTIFIER,
243
258
  };
244
259
  self.expression = this.translateNode(object, self);
245
260
  self.name = this.translateNode(property, self);
@@ -273,8 +288,8 @@ export class TypeScriptAstTranslator {
273
288
  pos,
274
289
  end,
275
290
  parent,
276
- name: undefined,
277
- initializer: undefined,
291
+ name: PENDING_NODE,
292
+ initializer: PENDING_NODE,
278
293
  };
279
294
  self.name = this.translateNode(key, self);
280
295
  self.initializer = this.translateNode(value, self);
@@ -305,7 +320,7 @@ export class TypeScriptAstTranslator {
305
320
  pos,
306
321
  end,
307
322
  parent,
308
- expression: undefined,
323
+ expression: PENDING_NODE,
309
324
  };
310
325
  self.expression = this.translateNode(argument, self);
311
326
  return self;
@@ -317,7 +332,7 @@ export class TypeScriptAstTranslator {
317
332
  pos,
318
333
  end,
319
334
  parent,
320
- expression: undefined,
335
+ expression: PENDING_NODE,
321
336
  };
322
337
  self.expression = this.translateNode(expression, self);
323
338
  return self;
@@ -329,7 +344,7 @@ export class TypeScriptAstTranslator {
329
344
  pos,
330
345
  end,
331
346
  parent,
332
- expression: undefined,
347
+ expression: PENDING_NODE,
333
348
  };
334
349
  self.expression = this.translateNode(expression, self);
335
350
  return self;
@@ -341,7 +356,7 @@ export class TypeScriptAstTranslator {
341
356
  pos,
342
357
  end,
343
358
  parent,
344
- expression: undefined,
359
+ expression: PENDING_NODE,
345
360
  };
346
361
  self.expression = this.translateNode(expression, self);
347
362
  return self;
@@ -353,7 +368,7 @@ export class TypeScriptAstTranslator {
353
368
  pos,
354
369
  end,
355
370
  parent,
356
- expression: undefined,
371
+ expression: PENDING_NODE,
357
372
  };
358
373
  self.expression = this.translateNode(expression, self);
359
374
  return self;
@@ -367,9 +382,9 @@ export class TypeScriptAstTranslator {
367
382
  pos,
368
383
  end,
369
384
  parent,
370
- condition: undefined,
371
- whenTrue: undefined,
372
- whenFalse: undefined,
385
+ condition: PENDING_NODE,
386
+ whenTrue: PENDING_NODE,
387
+ whenFalse: PENDING_NODE,
373
388
  };
374
389
  self.condition = this.translateNode(test, self);
375
390
  self.whenTrue = this.translateNode(consequent, self);
@@ -384,9 +399,9 @@ export class TypeScriptAstTranslator {
384
399
  pos,
385
400
  end,
386
401
  parent,
387
- left: undefined,
402
+ left: PENDING_NODE,
388
403
  operator: this.mapBinaryOperator(node.operator),
389
- right: undefined,
404
+ right: PENDING_NODE,
390
405
  };
391
406
  self.left = this.translateNode(left, self);
392
407
  self.right = this.translateNode(right, self);
@@ -399,7 +414,7 @@ export class TypeScriptAstTranslator {
399
414
  pos,
400
415
  end,
401
416
  parent,
402
- expression: undefined,
417
+ expression: PENDING_NODE,
403
418
  };
404
419
  self.expression = this.translateNode(expression, self);
405
420
  return self;
@@ -412,7 +427,7 @@ export class TypeScriptAstTranslator {
412
427
  pos,
413
428
  end,
414
429
  parent,
415
- name: undefined,
430
+ name: PENDING_NODE,
416
431
  initializer: undefined,
417
432
  };
418
433
  self.name = this.translateNode(name, self);
@@ -442,8 +457,7 @@ export class TypeScriptAstTranslator {
442
457
  parseDomainSourceFile(filePath, sourceText) {
443
458
  // Best-effort parse to mirror `ts.createSourceFile` leniency — recoverable syntax
444
459
  // errors still yield a usable tree, so `parseSync` errors are intentionally ignored.
445
- const program = parseSync(filePath, sourceText).program;
446
- const statements = program.body.map((statement) => this.translateNode(statement, null));
460
+ const statements = programStatements(parseSync(filePath, sourceText).program).map((statement) => this.translateNode(statement, null));
447
461
  return {
448
462
  fileName: filePath,
449
463
  text: sourceText,
@@ -6,7 +6,7 @@ import type { Filesystem } from "#core/filesystem/filesystem";
6
6
  *
7
7
  * @since 0.3.16-canary.0
8
8
  */
9
- export declare function processArrangeSimplifyFile(fs: Filesystem, args: {
9
+ export declare function processArrangeSimplifyFile(fs: Pick<Filesystem, "readFileSync" | "writeFileSync">, args: {
10
10
  readonly filePath: string;
11
11
  readonly write: boolean;
12
12
  readonly probe?: VariantClassNameProbe | null;
@@ -0,0 +1,13 @@
1
+ import type { AssertionAuditResult } from "#audit/domain/types";
2
+ /**
3
+ * Exit `1` when any non-allowlisted type-assertion violation remains.
4
+ *
5
+ * @since 0.13.0
6
+ */
7
+ export declare function exitCodeForAssertionAuditResult(result: AssertionAuditResult): number;
8
+ /**
9
+ * Machine-readable type-assertion summary for `--json`.
10
+ *
11
+ * @since 0.13.0
12
+ */
13
+ export declare function formatAssertionAuditJsonOutput(result: AssertionAuditResult, rootDir: string): string;
@@ -0,0 +1,22 @@
1
+ import { CLI_EXIT_GENERAL_ERROR, CLI_EXIT_SUCCESS } from "#core/exit-codes";
2
+ /**
3
+ * Exit `1` when any non-allowlisted type-assertion violation remains.
4
+ *
5
+ * @since 0.13.0
6
+ */
7
+ export function exitCodeForAssertionAuditResult(result) {
8
+ return result.violationCount > 0 ? CLI_EXIT_GENERAL_ERROR : CLI_EXIT_SUCCESS;
9
+ }
10
+ /**
11
+ * Machine-readable type-assertion summary for `--json`.
12
+ *
13
+ * @since 0.13.0
14
+ */
15
+ export function formatAssertionAuditJsonOutput(result, rootDir) {
16
+ return JSON.stringify({
17
+ schemaVersion: 1,
18
+ ok: result.violationCount === 0,
19
+ cwd: rootDir,
20
+ result,
21
+ });
22
+ }
@@ -0,0 +1,18 @@
1
+ import * as z from "zod";
2
+ /**
3
+ * Resolved request for a single type-assertion audit run.
4
+ *
5
+ * @since 0.13.0
6
+ */
7
+ export type AssertionAuditRunRequest = {
8
+ readonly rootDir: string;
9
+ readonly targetPath: string;
10
+ readonly allowlist?: ReadonlyArray<string> | undefined;
11
+ readonly json: boolean;
12
+ };
13
+ /**
14
+ * Zod schema for {@link AssertionAuditRunRequest}.
15
+ *
16
+ * @since 0.13.0
17
+ */
18
+ export declare const assertionAuditRunRequestSchema: z.ZodType<AssertionAuditRunRequest>;
@@ -0,0 +1,12 @@
1
+ import * as z from "zod";
2
+ /**
3
+ * Zod schema for {@link AssertionAuditRunRequest}.
4
+ *
5
+ * @since 0.13.0
6
+ */
7
+ export const assertionAuditRunRequestSchema = z.object({
8
+ rootDir: z.string().min(1),
9
+ targetPath: z.string().min(1),
10
+ allowlist: z.array(z.string()).optional(),
11
+ json: z.boolean(),
12
+ });
@@ -0,0 +1,20 @@
1
+ import type { AssertionViolation } from "#audit/domain/types";
2
+ /**
3
+ * The comment that keeps one double assertion, with the reason it has to stay.
4
+ *
5
+ * @remarks It covers an assertion on its own line or the line below it, and must carry a reason
6
+ * after the colon; one that covers nothing is reported, so a kept assertion cannot outlive its cause.
7
+ *
8
+ * @since 0.13.0
9
+ */
10
+ export declare const DOUBLE_ASSERTION_DIRECTIVE = "codefast-allow-double-assertion";
11
+ /**
12
+ * Scans one TypeScript source for double assertions through `unknown` or `any`, and for directives
13
+ * that keep none or give no reason.
14
+ *
15
+ * @remarks `x as unknown as T`, `(x as unknown) as T` and `<T><unknown>x` are one shape: the
16
+ * compiler found the two types unrelated, and the pair silences it instead of reconciling them.
17
+ *
18
+ * @since 0.13.0
19
+ */
20
+ export declare function auditDoubleAssertionSource(filePath: string, sourceText: string): Array<AssertionViolation>;