archprint 0.8.3 → 0.9.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 (83) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +81 -11
  3. package/dist/cli/adopted-rules.d.ts +49 -0
  4. package/dist/cli/adopted-rules.d.ts.map +1 -0
  5. package/dist/cli/adopted-rules.js +159 -0
  6. package/dist/cli/adopted-rules.js.map +1 -0
  7. package/dist/cli/check-diff.d.ts +19 -0
  8. package/dist/cli/check-diff.d.ts.map +1 -0
  9. package/dist/cli/check-diff.js +41 -0
  10. package/dist/cli/check-diff.js.map +1 -0
  11. package/dist/cli/check-evaluate.d.ts +10 -0
  12. package/dist/cli/check-evaluate.d.ts.map +1 -0
  13. package/dist/cli/check-evaluate.js +83 -0
  14. package/dist/cli/check-evaluate.js.map +1 -0
  15. package/dist/cli/check-git.d.ts +12 -0
  16. package/dist/cli/check-git.d.ts.map +1 -0
  17. package/dist/cli/check-git.js +87 -0
  18. package/dist/cli/check-git.js.map +1 -0
  19. package/dist/cli/check-locate.d.ts +3 -0
  20. package/dist/cli/check-locate.d.ts.map +1 -0
  21. package/dist/cli/check-locate.js +29 -0
  22. package/dist/cli/check-locate.js.map +1 -0
  23. package/dist/cli/check.d.ts +41 -0
  24. package/dist/cli/check.d.ts.map +1 -0
  25. package/dist/cli/check.js +230 -0
  26. package/dist/cli/check.js.map +1 -0
  27. package/dist/cli/generate.d.ts +5 -0
  28. package/dist/cli/generate.d.ts.map +1 -1
  29. package/dist/cli/generate.js +37 -11
  30. package/dist/cli/generate.js.map +1 -1
  31. package/dist/cli/git-env.d.ts +2 -0
  32. package/dist/cli/git-env.d.ts.map +1 -0
  33. package/dist/cli/git-env.js +14 -0
  34. package/dist/cli/git-env.js.map +1 -0
  35. package/dist/cli/layout.d.ts +2 -0
  36. package/dist/cli/layout.d.ts.map +1 -1
  37. package/dist/cli/layout.js +3 -1
  38. package/dist/cli/layout.js.map +1 -1
  39. package/dist/cli/migrate.d.ts.map +1 -1
  40. package/dist/cli/migrate.js +1 -0
  41. package/dist/cli/migrate.js.map +1 -1
  42. package/dist/cli/program.d.ts.map +1 -1
  43. package/dist/cli/program.js +52 -0
  44. package/dist/cli/program.js.map +1 -1
  45. package/dist/cli/report.d.ts.map +1 -1
  46. package/dist/cli/report.js +2 -1
  47. package/dist/cli/report.js.map +1 -1
  48. package/dist/cli/rule-guidance.js +3 -3
  49. package/dist/cli/rule-guidance.js.map +1 -1
  50. package/dist/cli/scan.d.ts.map +1 -1
  51. package/dist/cli/scan.js +6 -3
  52. package/dist/cli/scan.js.map +1 -1
  53. package/dist/detector/marker-inference.d.ts +3 -0
  54. package/dist/detector/marker-inference.d.ts.map +1 -1
  55. package/dist/detector/marker-inference.js +26 -18
  56. package/dist/detector/marker-inference.js.map +1 -1
  57. package/dist/detector/pattern-detector.d.ts +1 -0
  58. package/dist/detector/pattern-detector.d.ts.map +1 -1
  59. package/dist/detector/pattern-detector.js +7 -4
  60. package/dist/detector/pattern-detector.js.map +1 -1
  61. package/dist/generator/public-api-emitters.d.ts +1 -0
  62. package/dist/generator/public-api-emitters.d.ts.map +1 -1
  63. package/dist/generator/public-api-emitters.js +2 -1
  64. package/dist/generator/public-api-emitters.js.map +1 -1
  65. package/dist/generator/rule-generator.d.ts.map +1 -1
  66. package/dist/generator/rule-generator.js +51 -29
  67. package/dist/generator/rule-generator.js.map +1 -1
  68. package/dist/mcp/server.d.ts +3 -3
  69. package/dist/mcp/server.d.ts.map +1 -1
  70. package/dist/mcp/server.js +12 -3
  71. package/dist/mcp/server.js.map +1 -1
  72. package/dist/mcp/tools.d.ts.map +1 -1
  73. package/dist/mcp/tools.js +12 -1
  74. package/dist/mcp/tools.js.map +1 -1
  75. package/dist/scanner/file-walker.d.ts +5 -0
  76. package/dist/scanner/file-walker.d.ts.map +1 -1
  77. package/dist/scanner/file-walker.js +8 -2
  78. package/dist/scanner/file-walker.js.map +1 -1
  79. package/dist/scanner/import-graph.d.ts +2 -0
  80. package/dist/scanner/import-graph.d.ts.map +1 -1
  81. package/dist/scanner/import-graph.js +1 -1
  82. package/dist/scanner/import-graph.js.map +1 -1
  83. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # archprint
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add `archprint check`, which reports only the violations a change introduces, compared with a base branch or commit, for the rules your team adopted with `init` or `generate`. It works in any CI. On GitHub, `--format github` shows each finding inline on the pull request, with its evidence, plus a step summary that also counts the violations the change fixed. It warns by default; `--fail-on new` fails the job so you can make it a required status check. `--format json` and exit codes (`0` ok, `1` new violations, `2` the CI setup is wrong) cover other CI systems.
8
+
9
+ `init` and `generate` now also write `.archprint/rules.json`, the exact definition of each adopted mechanical rule, which `check` uses so it never re-infers a rule. If you set up archprint with 0.8.x or earlier, run `archprint generate` once; until then `check` reports that it did not run.
10
+
11
+ ### Patch Changes
12
+
13
+ - The same repository now gives the same output on every machine. archprint reads files in name order instead of the order the filesystem returns them, which differs between macOS and Linux, so an example exception or a generated fixture no longer changes between a laptop and CI.
14
+ - `generate --expand` now writes each forbidden-import rule's files from that rule. Before, the database rule (AP-001) got the UI rule's ESLint message, rule card text and fixtures, so its failing fixture imported a UI component that AP-001 never flags. Each rule's message now states the rule, its card lists what it forbids, and its failing fixture imports something the rule really reports: one of the repo's own exceptions when there is one, otherwise the database wrapper or component folder archprint found.
15
+ - The local MCP server now says plainly when it is given a repository URL instead of a directory, and points to `archprint mcp --http` for scanning a public repository by URL. Before, it reported a missing `tsconfig.json` under a path built from the URL. It also reads a `file://` URL as the local directory it names, and ignores spaces around a path.
16
+ - Plainer wording. The database and UI rules (AP-001, AP-002) now say "request handler" instead of "request-entry" or "server-entry" file, in scan output, ESLint messages, rule cards and fix guidance. `scan` prints each of these rules as a sentence followed by "N of M files it applies to follow it".
17
+
3
18
  ## 0.8.3
4
19
 
5
20
  ### Patch Changes
package/README.md CHANGED
@@ -5,11 +5,12 @@
5
5
  [![npm downloads](https://img.shields.io/npm/dm/archprint.svg)](https://www.npmjs.com/package/archprint)
6
6
  [![license](https://img.shields.io/npm/l/archprint.svg)](./LICENSE)
7
7
  [![docs](https://img.shields.io/badge/docs-live-blue)](https://tommkruix.github.io/archprint/)
8
+ [![Glama MCP server score](https://glama.ai/mcp/servers/Tommkruix/archprint/badges/score.svg)](https://glama.ai/mcp/servers/Tommkruix/archprint)
8
9
 
9
10
  **Mine the architecture rules your repo already enforces, with the evidence attached.**
10
11
 
11
12
  **[Try it in your browser](https://stackblitz.com/github/Tommkruix/archprint-demo)**, nothing to install ·
12
- [Quick start](#quick-start) · [Use with AI agents](#use-it-with-ai-coding-agents) · [Docs](https://tommkruix.github.io/archprint/)
13
+ [Quick start](#quick-start) · [Use in CI](#use-in-ci) · [Use with AI agents](#use-it-with-ai-coding-agents) · [Docs](https://tommkruix.github.io/archprint/)
13
14
 
14
15
  ## What it does, in plain words
15
16
 
@@ -27,6 +28,8 @@ instead of handing you a rulebook from somewhere else.
27
28
 
28
29
  - **For developers and tech leads:** inferred, evidence-backed lint rules for ESLint and dependency-cruiser,
29
30
  generated with `init`, connected with `wire`, and removed with `eject`.
31
+ - **For reviewers:** `archprint check` flags only the rule breaks a pull request introduces, inline on GitHub,
32
+ never the existing backlog.
30
33
  - **For teams using AI coding agents:** Claude Code, Cursor and other agents can ask Archprint for the project's
31
34
  rules, with the evidence, before they write code.
32
35
  - **For anyone evaluating a codebase:** a quick, honest picture of how a project is actually structured.
@@ -114,17 +117,27 @@ also accept the root and cover every app, while `init` and `generate` work on on
114
117
  # 1. See the rules your code already follows, with the evidence. Changes nothing.
115
118
  npx archprint scan .
116
119
 
117
- # 2. Set up enforcement for the rules your code already follows cleanly,
120
+ # 2. Ask why one rule is trusted (use a rule label from the scan)
121
+ npx archprint explain AP-001 .
122
+
123
+ # 3. Set up enforcement for the rules your code already follows cleanly,
118
124
  # and record what to review or adopt next in .archprint/config.json
119
125
  npx archprint init .
120
126
 
121
- # 3. Connect the generated rules to your ESLint / dependency-cruiser config (one managed line)
127
+ # 4. Connect the generated rules to your ESLint / dependency-cruiser config (one managed line)
122
128
  npx archprint wire
123
129
 
124
- # Then run your linter as usual. To undo everything, exactly:
130
+ # 5. Run your linter as usual. Your code passes today; a new import that breaks a rule fails.
131
+ npx eslint .
132
+
133
+ # To undo everything, exactly:
125
134
  npx archprint eject
126
135
  ```
127
136
 
137
+ **See it catch something.** After step 4, break a rule on purpose: for example, make a route file import your
138
+ database client directly, then run your linter. That is the whole loop the
139
+ [demo](https://stackblitz.com/github/Tommkruix/archprint-demo) walks through in the browser.
140
+
128
141
  To keep it in the project instead of using `npx`: `npm install --save-dev archprint`.
129
142
 
130
143
  More commands for a deliberate, step-by-step setup:
@@ -253,6 +266,9 @@ a tool you do not have. `--emit <eslint|dependency-cruiser|all>` forces the form
253
266
  mechanical boundaries (public-API deep-import, test-isolation); the review-held ones (layer, role-layering,
254
267
  feature-slice, app-isolation, entry-purity, dependency-internals, phantom deps) are added only with
255
268
  `--include-structural`, after you review them.
269
+ - **`.archprint/rules.json`**: the exact definition of each mechanical rule you adopted, with the evidence
270
+ recorded at adoption and the resolution mode it was generated in. `archprint check` reads it, so a check never
271
+ re-infers a rule.
256
272
  - **`.archprint/config.json`**: what is enforced, followed but only reported, held for review, and worth
257
273
  adopting, plus the list of managed outputs `eject` removes.
258
274
  - **A managed section in your `README.md`** summarizing what is enforced now, followed but only reported, held for
@@ -272,6 +288,45 @@ exactly. `generate --check` runs the generated ESLint rules against your repo an
272
288
  can confirm before wiring. Upgrading from 0.5.x? `archprint migrate` moves an older `archprint-rules/` setup to
273
289
  this layout and rewires your configs in place.
274
290
 
291
+ ## Use in CI
292
+
293
+ `archprint check` reports only the violations a change **introduces**, compared with a base branch, for the rules
294
+ your team adopted with `init` or `generate`. The existing backlog never shows up, and every finding carries its
295
+ evidence. It works in any CI, and on GitHub it shows each finding inline on the pull request.
296
+
297
+ ```yaml
298
+ # .github/workflows/archprint.yml
299
+ name: archprint
300
+ on: pull_request
301
+ permissions:
302
+ contents: read
303
+ jobs:
304
+ check:
305
+ runs-on: ubuntu-latest
306
+ steps:
307
+ - uses: actions/checkout@v5
308
+ with:
309
+ ref: ${{ github.event.pull_request.head.sha }}
310
+ fetch-depth: 0
311
+ - uses: actions/setup-node@v5
312
+ with:
313
+ node-version: 22
314
+ - run: npm ci
315
+ - run: npx archprint check --base ${{ github.event.pull_request.base.sha }} --format github
316
+ ```
317
+
318
+ - **Warning by default.** Add `--fail-on new` to fail the job on a new violation, then mark the job a required
319
+ status check in your branch protection so a pull request that adds one can't merge.
320
+ - **Only adopted rules.** `check` reads `.archprint/rules.json`, written by `init` and `generate`, and checks only
321
+ the mechanical rules recorded there, never structural ones. Rules adopted in the same pull request are listed
322
+ but never counted against it.
323
+ - **Upgrading from 0.8.x or earlier:** run `archprint generate` once to write `rules.json`. Until you do, `check`
324
+ posts a notice that it did not run and exits 0.
325
+ - **Other CI systems:** `--format json` gives version-keyed output, and the exit code is the contract: `0` ok, `1`
326
+ new violations with `--fail-on new`, `2` the CI setup is wrong (for example a shallow clone without the base
327
+ commit). Check out the full history (`fetch-depth: 0` or your CI's equivalent).
328
+ - **Safe on pull requests from forks.** It needs only read access, uses no secrets, and never runs your code.
329
+
275
330
  ## MCP setup
276
331
 
277
332
  `archprint mcp` runs Archprint as an [MCP](https://modelcontextprotocol.io) server over stdio, so an agent can ask
@@ -317,6 +372,15 @@ comes from nvm or Homebrew. A full path to `npx` alone is not enough, because `n
317
372
 
318
373
  Starting the editor from a terminal also works, because it then inherits your shell's `PATH`.
319
374
 
375
+ **One-line installs.** In Claude Code:
376
+
377
+ ```bash
378
+ claude mcp add archprint -- npx -y archprint mcp
379
+ ```
380
+
381
+ In Cursor, put the JSON above in `.cursor/mcp.json` in your project (or `~/.cursor/mcp.json` for every project),
382
+ then enable **archprint** under **Settings > MCP**.
383
+
320
384
  **Scanning a public repo by URL.** `archprint mcp --http` runs a remote server instead. It clones the repo shallow
321
385
  to a temp dir, runs the same read-only analysis, returns the result, and deletes the clone (only public
322
386
  `github.com`, `gitlab.com`, and `bitbucket.org` URLs; nothing is written or kept). The tools then take a `repo` URL
@@ -369,6 +433,9 @@ reachability) and knip (dead code); rather than compete, it writes the rules it
369
433
  `--only <family>` and `--rules <ids>` narrow the output, `--check` runs the generated rules against your repo,
370
434
  `--readme` adds the README section, `--expand` also writes the per-family files, cards, fixtures and graph, and
371
435
  `--rule <id>` emits one reviewed rule. Also `--include-structural`, `--no-graph`, `--out <dir>`, `--fast`.
436
+ - **`archprint check [path]`**: reports the violations of your adopted rules that a change introduces, compared
437
+ with `--base <branch or commit>`. Warning only unless `--fail-on new`. `--format text|json|github`,
438
+ `--out <dir>`. See [Use in CI](#use-in-ci).
372
439
  - **`archprint wire`**: references the generated rules from the enforcement tools your repo uses (flat eslint
373
440
  config, `.dependency-cruiser.json`) through a managed, reversible reference. `--out <dir>`, `--dry-run`.
374
441
  - **`archprint eject`**: removes Archprint's generated files, its config, the managed README section, and any
@@ -379,8 +446,9 @@ reachability) and knip (dead code); rather than compete, it writes the rules it
379
446
  `scan`, `recommend`, and `explain` tools. Serves over stdio by default; `--http` runs a remote server that scans
380
447
  a public repo by URL.
381
448
 
382
- `scan --json` and `recommend --json` emit stable, version-keyed JSON for scripting. Exit codes are the contract:
383
- `0` on success, `1` on error.
449
+ `scan --json`, `recommend --json` and `check --format json` emit stable, version-keyed JSON for scripting. Exit
450
+ codes are the contract: `0` on success, `1` on error (for `check`: new violations with `--fail-on new`), `2` for a
451
+ `check` that could not run because of the CI setup.
384
452
 
385
453
  ## Example on a real repo
386
454
 
@@ -392,7 +460,8 @@ Workspace aliases: 18 resolved
392
460
 
393
461
  GENERATED RULES
394
462
  AP-002 no-ui-layer-in-server-entry confidence 97%
395
- Evidence: 216/217 role files conform (99.5% observed)
463
+ A request handler must not import UI components.
464
+ Evidence: 216 of 217 files it applies to follow it (99.5%)
396
465
  Exceptions: 1
397
466
 
398
467
  LAYER BOUNDARIES (review before enforcing)
@@ -426,10 +495,11 @@ were inferred from.
426
495
  - **Validated at scale:** `scan` and `recommend` ran over a corpus of 92,861 public TypeScript repositories (61,690
427
496
  apps) with zero crashes; 91 repos (0.1%) could not be fetched or timed out. The full `init`/`wire`/`eject`
428
497
  round-trip ran clean on a 2,000-repo stratified sample.
429
- - **Production-ready today:** `scan` and `recommend`, and auto-enforcement of the mechanical families, with a
430
- self-consistency check at generate time, an `init` scaffolder for fresh repos, and framework coverage across
431
- React, Angular, Vue, and Svelte. The engine (twenty detectors, the confidence gate, and emitters for a
432
- self-contained ESLint file, dependency-cruiser, ts-arch, and the layer graph) is in place and tested.
498
+ - **Production-ready today:** `scan` and `recommend`, `check` for pull requests, and auto-enforcement of the
499
+ mechanical families, with a self-consistency check at generate time, an `init` scaffolder for fresh repos, and
500
+ framework coverage across React, Angular, Vue, and Svelte. The engine (twenty detectors, the confidence gate, and
501
+ emitters for a self-contained ESLint file, dependency-cruiser, ts-arch, and the layer graph) is in place and
502
+ tested.
433
503
  - **Still ahead:** hardening the structural families toward auto-enforcement (a real per-file role-confidence
434
504
  measure, layer cohesion, role-classifier ordering).
435
505
  - **Versioning is still 0.x,** so the CLI surface and rule format can refine between minor versions. That is a
@@ -0,0 +1,49 @@
1
+ import type { Role } from '../scanner/role-classifier.js';
2
+ import type { ScannedPattern, ScanResult } from './scan.js';
3
+ export declare const RULES_FILE = "rules.json";
4
+ export declare const RULES_FORMAT = 1;
5
+ export type ResolutionMode = 'deep' | 'fast';
6
+ export interface AdoptionEvidence {
7
+ conforming: number;
8
+ total: number;
9
+ floor: number;
10
+ }
11
+ interface RuleBase {
12
+ id: string;
13
+ statement: string;
14
+ mode: ResolutionMode;
15
+ evidence: AdoptionEvidence;
16
+ }
17
+ export interface ForbiddenImportRule extends RuleBase {
18
+ family: 'forbidden-imports';
19
+ roles: Role[];
20
+ forbidden: {
21
+ source: string;
22
+ flags: string;
23
+ }[];
24
+ }
25
+ export interface PublicApiRule extends RuleBase {
26
+ family: 'public-api';
27
+ dir: string;
28
+ }
29
+ export interface SingleFamilyRule extends RuleBase {
30
+ family: 'test-isolation' | 'console-isolation' | 'import-style';
31
+ }
32
+ export type AdoptedRule = ForbiddenImportRule | PublicApiRule | SingleFamilyRule;
33
+ export interface AdoptedRules {
34
+ format: typeof RULES_FORMAT;
35
+ archprintVersion: string;
36
+ rules: AdoptedRule[];
37
+ }
38
+ export declare function forbiddenImportRule(pattern: ScannedPattern, mode: ResolutionMode): AdoptedRule;
39
+ export declare function testIsolationRule(scan: ScanResult): AdoptedRule;
40
+ export declare function consoleIsolationRule(scan: ScanResult): AdoptedRule;
41
+ export declare function importStyleRule(scan: ScanResult): AdoptedRule;
42
+ export declare function publicApiRules(scan: ScanResult): AdoptedRule[];
43
+ export declare function writeAdoptedRules(outDir: string, rules: readonly AdoptedRule[], archprintVersion: string): string;
44
+ export declare class InvalidRulesError extends Error {
45
+ }
46
+ export declare function readAdoptedRules(outDir: string): AdoptedRules | null;
47
+ export declare function ruleDefinitionKey(rule: AdoptedRule): string;
48
+ export {};
49
+ //# sourceMappingURL=adopted-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adopted-rules.d.ts","sourceRoot":"","sources":["../../src/cli/adopted-rules.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,+BAA+B,CAAC;AAG1D,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5D,eAAO,MAAM,UAAU,eAAe,CAAC;AACvC,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC;AAE7C,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,cAAc,CAAC;IACrB,QAAQ,EAAE,gBAAgB,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAoB,SAAQ,QAAQ;IACnD,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAChD;AAED,MAAM,WAAW,aAAc,SAAQ,QAAQ;IAC7C,MAAM,EAAE,YAAY,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,gBAAiB,SAAQ,QAAQ;IAChD,MAAM,EAAE,gBAAgB,GAAG,mBAAmB,GAAG,cAAc,CAAC;CACjE;AAED,MAAM,MAAM,WAAW,GAAG,mBAAmB,GAAG,aAAa,GAAG,gBAAgB,CAAC;AAEjF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,YAAY,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAQD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,GAAG,WAAW,CAW9F;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,CAS/D;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,CASlE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,CAS7D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,EAAE,CAW9D;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,gBAAgB,EAAE,MAAM,GACvB,MAAM,CAOR;AAED,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAyD/C,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAmBpE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAc3D"}
@@ -0,0 +1,159 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { publicApiRuleName } from '../generator/public-api-emitters.js';
4
+ import { FAMILY_STATEMENTS } from './rule-statements.js';
5
+ export const RULES_FILE = 'rules.json';
6
+ export const RULES_FORMAT = 1;
7
+ const evidenceOf = (gate, total, violating) => ({
8
+ conforming: total - violating,
9
+ total,
10
+ floor: gate.conditions.confidence.value,
11
+ });
12
+ export function forbiddenImportRule(pattern, mode) {
13
+ const { config, result } = pattern;
14
+ return {
15
+ id: config.id,
16
+ family: 'forbidden-imports',
17
+ statement: config.description,
18
+ mode,
19
+ roles: [...config.roles],
20
+ forbidden: config.forbidden.map((marker) => ({ source: marker.source, flags: marker.flags })),
21
+ evidence: evidenceOf(result.gate, result.stats.roleFileCount, result.stats.violatingFileCount),
22
+ };
23
+ }
24
+ export function testIsolationRule(scan) {
25
+ const analysis = scan.testIsolation;
26
+ return {
27
+ id: 'test-isolation',
28
+ family: 'test-isolation',
29
+ statement: FAMILY_STATEMENTS['test-isolation'],
30
+ mode: 'fast',
31
+ evidence: evidenceOf(analysis.gate, analysis.productionFileCount, analysis.offenderCount),
32
+ };
33
+ }
34
+ export function consoleIsolationRule(scan) {
35
+ const analysis = scan.consoleIsolation;
36
+ return {
37
+ id: 'console-isolation',
38
+ family: 'console-isolation',
39
+ statement: FAMILY_STATEMENTS['console-isolation'],
40
+ mode: 'fast',
41
+ evidence: evidenceOf(analysis.gate, analysis.libraryFileCount, analysis.offenderCount),
42
+ };
43
+ }
44
+ export function importStyleRule(scan) {
45
+ const analysis = scan.deepRelative;
46
+ return {
47
+ id: 'import-style',
48
+ family: 'import-style',
49
+ statement: FAMILY_STATEMENTS['import-style'],
50
+ mode: 'fast',
51
+ evidence: evidenceOf(analysis.gate, analysis.relativeImporterCount, analysis.offenderCount),
52
+ };
53
+ }
54
+ export function publicApiRules(scan) {
55
+ return scan.publicApi.groups
56
+ .filter((group) => group.gate.status === 'AUTO')
57
+ .map((group) => ({
58
+ id: publicApiRuleName(group.dir),
59
+ family: 'public-api',
60
+ dir: group.dir,
61
+ statement: `import ${group.dir} through its public entry (barrel), not its internal files`,
62
+ mode: 'fast',
63
+ evidence: evidenceOf(group.gate, group.consumerCount, group.deepImporterCount),
64
+ }));
65
+ }
66
+ export function writeAdoptedRules(outDir, rules, archprintVersion) {
67
+ mkdirSync(outDir, { recursive: true });
68
+ const file = path.join(outDir, RULES_FILE);
69
+ const sorted = [...rules].sort((a, b) => a.id.localeCompare(b.id));
70
+ const content = { format: RULES_FORMAT, archprintVersion, rules: sorted };
71
+ writeFileSync(file, `${JSON.stringify(content, null, 2)}\n`);
72
+ return file;
73
+ }
74
+ export class InvalidRulesError extends Error {
75
+ }
76
+ const FAMILIES = new Set([
77
+ 'forbidden-imports',
78
+ 'public-api',
79
+ 'test-isolation',
80
+ 'console-isolation',
81
+ 'import-style',
82
+ ]);
83
+ const MAX_PATTERN_LENGTH = 200;
84
+ const PATTERN_FLAGS = /^[imsu]*$/;
85
+ function validateRule(rule, file) {
86
+ const invalid = (reason) => {
87
+ throw new InvalidRulesError(`${file}: ${reason}. Re-run archprint generate.`);
88
+ };
89
+ if (typeof rule !== 'object' || rule === null)
90
+ return invalid('a rule is not an object');
91
+ const candidate = rule;
92
+ if (typeof candidate.id !== 'string' || !FAMILIES.has(String(candidate.family))) {
93
+ return invalid('a rule has no valid id or family');
94
+ }
95
+ if (candidate.mode !== 'deep' && candidate.mode !== 'fast') {
96
+ return invalid(`rule ${candidate.id} has no valid resolution mode`);
97
+ }
98
+ if (candidate.family === 'forbidden-imports') {
99
+ const markers = candidate.forbidden;
100
+ if (!Array.isArray(markers) || !Array.isArray(candidate.roles)) {
101
+ return invalid(`rule ${candidate.id} has no roles or forbidden markers`);
102
+ }
103
+ for (const marker of markers) {
104
+ const { source, flags } = marker ?? {};
105
+ if (typeof source !== 'string' || source.length > MAX_PATTERN_LENGTH) {
106
+ return invalid(`rule ${candidate.id} has a missing or oversized pattern`);
107
+ }
108
+ if (typeof flags !== 'string' || !PATTERN_FLAGS.test(flags)) {
109
+ return invalid(`rule ${candidate.id} has unsupported pattern flags`);
110
+ }
111
+ try {
112
+ new RegExp(source, flags);
113
+ }
114
+ catch {
115
+ return invalid(`rule ${candidate.id} has an invalid pattern`);
116
+ }
117
+ }
118
+ }
119
+ if (candidate.family === 'public-api' && typeof candidate.dir !== 'string') {
120
+ return invalid(`rule ${candidate.id} has no directory`);
121
+ }
122
+ return rule;
123
+ }
124
+ export function readAdoptedRules(outDir) {
125
+ const file = path.join(outDir, RULES_FILE);
126
+ if (!existsSync(file))
127
+ return null;
128
+ let parsed;
129
+ try {
130
+ parsed = JSON.parse(readFileSync(file, 'utf8'));
131
+ }
132
+ catch {
133
+ throw new InvalidRulesError(`${file} is not valid JSON. Re-run archprint generate.`);
134
+ }
135
+ if (parsed.format !== RULES_FORMAT || !Array.isArray(parsed.rules)) {
136
+ throw new InvalidRulesError(`${file} was written by a different archprint version. Re-run archprint generate.`);
137
+ }
138
+ return {
139
+ format: RULES_FORMAT,
140
+ archprintVersion: String(parsed.archprintVersion),
141
+ rules: parsed.rules.map((rule) => validateRule(rule, file)),
142
+ };
143
+ }
144
+ export function ruleDefinitionKey(rule) {
145
+ const common = [rule.family, rule.id, rule.mode];
146
+ switch (rule.family) {
147
+ case 'forbidden-imports':
148
+ return JSON.stringify([
149
+ ...common,
150
+ [...rule.roles].sort(),
151
+ rule.forbidden.map((marker) => `${marker.source}/${marker.flags}`).sort(),
152
+ ]);
153
+ case 'public-api':
154
+ return JSON.stringify([...common, rule.dir]);
155
+ default:
156
+ return JSON.stringify(common);
157
+ }
158
+ }
159
+ //# sourceMappingURL=adopted-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adopted-rules.js","sourceRoot":"","sources":["../../src/cli/adopted-rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { type AdoptedRule } from './adopted-rules.js';
2
+ import type { Finding } from './check-evaluate.js';
3
+ export type RuleChange = 'new' | 'changed';
4
+ export interface RulePartition {
5
+ comparable: AdoptedRule[];
6
+ adoptedInChange: {
7
+ rule: AdoptedRule;
8
+ change: RuleChange;
9
+ }[];
10
+ removedInChange: AdoptedRule[];
11
+ }
12
+ export declare function partitionRules(head: readonly AdoptedRule[], base: readonly AdoptedRule[] | null): RulePartition;
13
+ export declare function renameFinding(finding: Finding, renames: ReadonlyMap<string, string>): Finding;
14
+ export interface FindingDiff {
15
+ introduced: Finding[];
16
+ fixed: Finding[];
17
+ }
18
+ export declare function diffFindings(base: readonly Finding[], head: readonly Finding[], renames: ReadonlyMap<string, string>): FindingDiff;
19
+ //# sourceMappingURL=check-diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-diff.d.ts","sourceRoot":"","sources":["../../src/cli/check-diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,SAAS,CAAC;AAE3C,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,WAAW,EAAE,CAAC;IAC1B,eAAe,EAAE;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,MAAM,EAAE,UAAU,CAAA;KAAE,EAAE,CAAC;IAC7D,eAAe,EAAE,WAAW,EAAE,CAAC;CAChC;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,SAAS,WAAW,EAAE,EAC5B,IAAI,EAAE,SAAS,WAAW,EAAE,GAAG,IAAI,GAClC,aAAa,CAgBf;AAKD,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAS7F;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,OAAO,EAAE,CAAC;IACtB,KAAK,EAAE,OAAO,EAAE,CAAC;CAClB;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,SAAS,OAAO,EAAE,EACxB,IAAI,EAAE,SAAS,OAAO,EAAE,EACxB,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GACnC,WAAW,CASb"}
@@ -0,0 +1,41 @@
1
+ import { ruleDefinitionKey } from './adopted-rules.js';
2
+ export function partitionRules(head, base) {
3
+ const baseById = new Map((base ?? []).map((rule) => [rule.id, rule]));
4
+ const headIds = new Set(head.map((rule) => rule.id));
5
+ const partition = {
6
+ comparable: [],
7
+ adoptedInChange: [],
8
+ removedInChange: (base ?? []).filter((rule) => !headIds.has(rule.id)),
9
+ };
10
+ for (const rule of head) {
11
+ const prior = baseById.get(rule.id);
12
+ if (prior === undefined)
13
+ partition.adoptedInChange.push({ rule, change: 'new' });
14
+ else if (ruleDefinitionKey(prior) !== ruleDefinitionKey(rule))
15
+ partition.adoptedInChange.push({ rule, change: 'changed' });
16
+ else
17
+ partition.comparable.push(rule);
18
+ }
19
+ return partition;
20
+ }
21
+ const identity = (finding) => [finding.ruleId, finding.file, finding.subject].join('\u0000');
22
+ export function renameFinding(finding, renames) {
23
+ return {
24
+ ...finding,
25
+ file: renames.get(finding.file) ?? finding.file,
26
+ subject: finding.kind === 'target'
27
+ ? (renames.get(finding.subject) ?? finding.subject)
28
+ : finding.subject,
29
+ };
30
+ }
31
+ export function diffFindings(base, head, renames) {
32
+ const renamedBase = base.map((finding) => renameFinding(finding, renames));
33
+ const baseKeys = new Set(renamedBase.map(identity));
34
+ const headKeys = new Set(head.map(identity));
35
+ const byIdentity = (a, b) => identity(a).localeCompare(identity(b));
36
+ return {
37
+ introduced: head.filter((finding) => !baseKeys.has(identity(finding))).sort(byIdentity),
38
+ fixed: renamedBase.filter((finding) => !headKeys.has(identity(finding))).sort(byIdentity),
39
+ };
40
+ }
41
+ //# sourceMappingURL=check-diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-diff.js","sourceRoot":"","sources":["../../src/cli/check-diff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { AdoptedRule } from './adopted-rules.js';
2
+ export type SubjectKind = 'specifier' | 'target' | 'file';
3
+ export interface Finding {
4
+ ruleId: string;
5
+ file: string;
6
+ subject: string;
7
+ kind: SubjectKind;
8
+ }
9
+ export declare function evaluateRules(appDir: string, rules: readonly AdoptedRule[]): Finding[];
10
+ //# sourceMappingURL=check-evaluate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-evaluate.d.ts","sourceRoot":"","sources":["../../src/cli/check-evaluate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAuC,MAAM,oBAAoB,CAAC;AAE3F,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC;CACnB;AAiCD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,WAAW,EAAE,GAAG,OAAO,EAAE,CAkDtF"}
@@ -0,0 +1,83 @@
1
+ import { detectConsoleIsolation } from '../detector/console-isolation-detector.js';
2
+ import { detectDeepRelativeImports } from '../detector/deep-relative-detector.js';
3
+ import { detectForbiddenImports } from '../detector/pattern-detector.js';
4
+ import { detectPublicApiBoundaries } from '../detector/public-api-detector.js';
5
+ import { detectTestIsolation } from '../detector/test-isolation-detector.js';
6
+ const toPatternConfig = (rule) => ({
7
+ id: rule.id,
8
+ name: rule.id,
9
+ description: rule.statement,
10
+ roles: rule.roles,
11
+ forbidden: rule.forbidden.map((marker) => new RegExp(marker.source, marker.flags)),
12
+ });
13
+ function forbiddenImportFindings(appDir, rules) {
14
+ const findings = [];
15
+ const modes = ['deep', 'fast'];
16
+ for (const mode of modes) {
17
+ const group = rules.filter((rule) => rule.mode === mode);
18
+ if (group.length === 0)
19
+ continue;
20
+ const results = detectForbiddenImports(appDir, group.map(toPatternConfig), {
21
+ resolve: mode === 'deep',
22
+ });
23
+ results.forEach((result, index) => {
24
+ for (const violation of result.violations) {
25
+ findings.push({
26
+ ruleId: group[index].id,
27
+ file: violation.file,
28
+ subject: violation.specifier,
29
+ kind: 'specifier',
30
+ });
31
+ }
32
+ });
33
+ }
34
+ return findings;
35
+ }
36
+ export function evaluateRules(appDir, rules) {
37
+ const findings = forbiddenImportFindings(appDir, rules.filter((rule) => rule.family === 'forbidden-imports'));
38
+ const byFamily = (family) => rules.filter((rule) => rule.family === family);
39
+ for (const rule of byFamily('test-isolation')) {
40
+ for (const violation of detectTestIsolation(appDir).violations) {
41
+ findings.push({
42
+ ruleId: rule.id,
43
+ file: violation.file,
44
+ subject: violation.target,
45
+ kind: 'target',
46
+ });
47
+ }
48
+ }
49
+ for (const rule of byFamily('console-isolation')) {
50
+ for (const violation of detectConsoleIsolation(appDir).violations) {
51
+ findings.push({ ruleId: rule.id, file: violation.file, subject: '', kind: 'file' });
52
+ }
53
+ }
54
+ for (const rule of byFamily('import-style')) {
55
+ for (const violation of detectDeepRelativeImports(appDir).violations) {
56
+ findings.push({
57
+ ruleId: rule.id,
58
+ file: violation.file,
59
+ subject: violation.specifier,
60
+ kind: 'specifier',
61
+ });
62
+ }
63
+ }
64
+ const publicApi = byFamily('public-api');
65
+ if (publicApi.length > 0) {
66
+ const groups = detectPublicApiBoundaries(appDir).groups;
67
+ for (const rule of publicApi) {
68
+ if (rule.family !== 'public-api')
69
+ continue;
70
+ const group = groups.find((candidate) => candidate.dir === rule.dir);
71
+ for (const violation of group?.violations ?? []) {
72
+ findings.push({
73
+ ruleId: rule.id,
74
+ file: violation.file,
75
+ subject: violation.target,
76
+ kind: 'target',
77
+ });
78
+ }
79
+ }
80
+ }
81
+ return findings;
82
+ }
83
+ //# sourceMappingURL=check-evaluate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-evaluate.js","sourceRoot":"","sources":["../../src/cli/check-evaluate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export declare class CheckSetupError extends Error {
2
+ }
3
+ export declare const repoRoot: (cwd: string) => string;
4
+ export declare function defaultBase(root: string): string;
5
+ export declare function mergeBase(root: string, base: string): string;
6
+ export declare function renamedPaths(root: string, commit: string): Map<string, string>;
7
+ export interface BaseTree {
8
+ root: string;
9
+ dispose: () => void;
10
+ }
11
+ export declare function checkoutBase(root: string, commit: string, appRelative: string): BaseTree;
12
+ //# sourceMappingURL=check-git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-git.d.ts","sourceRoot":"","sources":["../../src/cli/check-git.ts"],"names":[],"mappings":"AAMA,qBAAa,eAAgB,SAAQ,KAAK;CAAG;AAgB7C,eAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,KAAG,MAA2D,CAAC;AAEnG,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQhD;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAQ5D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAc9E;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAgBD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,QAAQ,CAoBxF"}