@eduardbar/drift 1.4.0 → 1.5.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/.github/actions/drift-review/README.md +4 -2
  2. package/.github/actions/drift-review/action.yml +22 -5
  3. package/.github/actions/drift-scan/README.md +3 -3
  4. package/.github/actions/drift-scan/action.yml +1 -1
  5. package/.github/workflows/publish-vscode.yml +1 -3
  6. package/.github/workflows/publish.yml +8 -0
  7. package/.github/workflows/quality.yml +15 -0
  8. package/.github/workflows/reusable-quality-checks.yml +95 -0
  9. package/.github/workflows/review-pr.yml +0 -1
  10. package/AGENTS.md +2 -2
  11. package/CHANGELOG.md +14 -1
  12. package/README.md +30 -3
  13. package/benchmarks/fixtures/critical/drift.config.ts +21 -0
  14. package/benchmarks/fixtures/critical/src/app/user-service.ts +30 -0
  15. package/benchmarks/fixtures/critical/src/domain/entities.ts +19 -0
  16. package/benchmarks/fixtures/critical/src/domain/policies.ts +22 -0
  17. package/benchmarks/fixtures/critical/src/index.ts +10 -0
  18. package/benchmarks/fixtures/critical/src/infra/memory-user-repo.ts +14 -0
  19. package/benchmarks/perf-budget.v1.json +27 -0
  20. package/dist/benchmark.js +12 -0
  21. package/dist/cli.js +2 -2
  22. package/dist/doctor.d.ts +21 -0
  23. package/dist/doctor.js +10 -3
  24. package/dist/guard-baseline.d.ts +12 -0
  25. package/dist/guard-baseline.js +57 -0
  26. package/dist/guard-metrics.d.ts +6 -0
  27. package/dist/guard-metrics.js +39 -0
  28. package/dist/guard-types.d.ts +2 -1
  29. package/dist/guard.d.ts +3 -1
  30. package/dist/guard.js +9 -70
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/init.js +1 -1
  34. package/dist/output-metadata.d.ts +2 -0
  35. package/dist/output-metadata.js +2 -0
  36. package/dist/trust.d.ts +2 -1
  37. package/dist/trust.js +1 -1
  38. package/dist/types.d.ts +1 -1
  39. package/docs/AGENTS.md +1 -1
  40. package/package.json +10 -4
  41. package/schemas/drift-doctor.v1.json +57 -0
  42. package/schemas/drift-guard.v1.json +298 -0
  43. package/scripts/check-docs-drift.mjs +154 -0
  44. package/scripts/check-performance-budget.mjs +360 -0
  45. package/scripts/check-runtime-policy.mjs +66 -0
  46. package/src/benchmark.ts +17 -0
  47. package/src/cli.ts +2 -2
  48. package/src/doctor.ts +15 -3
  49. package/src/guard-baseline.ts +74 -0
  50. package/src/guard-metrics.ts +52 -0
  51. package/src/guard-types.ts +3 -1
  52. package/src/guard.ts +14 -90
  53. package/src/index.ts +1 -0
  54. package/src/init.ts +1 -1
  55. package/src/output-metadata.ts +2 -0
  56. package/src/trust.ts +1 -1
  57. package/src/types.ts +1 -0
  58. package/tests/ci-quality-matrix.test.ts +37 -0
  59. package/tests/ci-smoke-gate.test.ts +26 -0
  60. package/tests/ci-version-alignment.test.ts +93 -0
  61. package/tests/docs-drift-check.test.ts +115 -0
  62. package/tests/new-features.test.ts +2 -2
  63. package/tests/perf-budget-check.test.ts +146 -0
  64. package/tests/phase1-init-doctor-guard.test.ts +104 -2
  65. package/tests/runtime-policy-alignment.test.ts +46 -0
  66. package/vitest.config.ts +2 -0
@@ -4,6 +4,8 @@ Composite action for PR workflows that wraps `drift trust`, optional `drift revi
4
4
 
5
5
  The action runs drift via `npm exec` with an isolated prefix under `$RUNNER_TEMP/drift-cli` to avoid local bin resolution conflicts when workflows execute inside the `@eduardbar/drift` repository.
6
6
 
7
+ If the requested `version` is not published on npm yet (for example, release-prep PRs), the action falls back to the local repository CLI (`npx --no-install tsx ./src/cli.ts`) so PR checks can still run.
8
+
7
9
  ## Why this action exists
8
10
 
9
11
  The repository workflow (`.github/workflows/review-pr.yml`) uses trust as the merge gate and review markdown as complementary context. This action packages that flow as a reusable contract:
@@ -22,7 +24,7 @@ The repository workflow (`.github/workflows/review-pr.yml`) uses trust as the me
22
24
  with:
23
25
  path: .
24
26
  base-ref: origin/${{ github.base_ref }}
25
- version: 1.3.0
27
+ version: 1.5.0
26
28
  min-trust: 40
27
29
  max-risk: HIGH
28
30
  fail-on-gate: true
@@ -35,7 +37,7 @@ The repository workflow (`.github/workflows/review-pr.yml`) uses trust as the me
35
37
  |-------|-------------|---------|
36
38
  | `path` | Path to analyze | `.` |
37
39
  | `base-ref` | Base git ref for diff-aware trust/review | `origin/main` |
38
- | `version` | drift version for `npm exec` execution | `1.3.0` |
40
+ | `version` | drift version for `npm exec` execution | `1.5.0` |
39
41
  | `min-trust` | Failing threshold for trust score | `45` |
40
42
  | `max-risk` | Failing threshold for merge risk | `HIGH` |
41
43
  | `fail-on-gate` | Enforce trust gate failure | `true` |
@@ -18,7 +18,7 @@ inputs:
18
18
  version:
19
19
  description: 'Version of @eduardbar/drift to use'
20
20
  required: false
21
- default: '1.3.0'
21
+ default: '1.5.0'
22
22
  min-trust:
23
23
  description: 'Fail gate if trust score is below this threshold'
24
24
  required: false
@@ -80,8 +80,15 @@ runs:
80
80
  set -euo pipefail
81
81
  EXEC_PREFIX="$RUNNER_TEMP/drift-cli"
82
82
  mkdir -p "$EXEC_PREFIX"
83
- npm exec --yes --prefix "$EXEC_PREFIX" --package=@eduardbar/drift@${{ inputs.version }} -- drift --version
84
- npm exec --yes --prefix "$EXEC_PREFIX" --package=@eduardbar/drift@${{ inputs.version }} -- drift trust "${{ inputs.path }}" --base "${{ inputs.base-ref }}" --markdown --json-output "${{ steps.files.outputs.trust_json }}" > "${{ steps.files.outputs.trust_md }}"
83
+ VERSION="${{ inputs.version }}"
84
+ if npm view "@eduardbar/drift@$VERSION" version >/dev/null 2>&1; then
85
+ npm exec --yes --prefix "$EXEC_PREFIX" --package="@eduardbar/drift@$VERSION" -- drift --version
86
+ npm exec --yes --prefix "$EXEC_PREFIX" --package="@eduardbar/drift@$VERSION" -- drift trust "${{ inputs.path }}" --base "${{ inputs.base-ref }}" --markdown --json-output "${{ steps.files.outputs.trust_json }}" > "${{ steps.files.outputs.trust_md }}"
87
+ else
88
+ echo "::notice::@eduardbar/drift@$VERSION is not published yet; using local CLI fallback"
89
+ npx --no-install tsx ./src/cli.ts --version
90
+ npx --no-install tsx ./src/cli.ts trust "${{ inputs.path }}" --base "${{ inputs.base-ref }}" --markdown --json-output "${{ steps.files.outputs.trust_json }}" > "${{ steps.files.outputs.trust_md }}"
91
+ fi
85
92
 
86
93
  - name: Generate review markdown
87
94
  if: inputs.include-review == 'true'
@@ -90,7 +97,12 @@ runs:
90
97
  set -euo pipefail
91
98
  EXEC_PREFIX="$RUNNER_TEMP/drift-cli"
92
99
  mkdir -p "$EXEC_PREFIX"
93
- npm exec --yes --prefix "$EXEC_PREFIX" --package=@eduardbar/drift@${{ inputs.version }} -- drift review --base "${{ inputs.base-ref }}" --comment > "${{ steps.files.outputs.review_md }}"
100
+ VERSION="${{ inputs.version }}"
101
+ if npm view "@eduardbar/drift@$VERSION" version >/dev/null 2>&1; then
102
+ npm exec --yes --prefix "$EXEC_PREFIX" --package="@eduardbar/drift@$VERSION" -- drift review --base "${{ inputs.base-ref }}" --comment > "${{ steps.files.outputs.review_md }}"
103
+ else
104
+ npx --no-install tsx ./src/cli.ts review --base "${{ inputs.base-ref }}" --comment > "${{ steps.files.outputs.review_md }}"
105
+ fi
94
106
 
95
107
  - name: Extract trust outputs
96
108
  id: extract
@@ -128,4 +140,9 @@ runs:
128
140
  set -euo pipefail
129
141
  EXEC_PREFIX="$RUNNER_TEMP/drift-cli"
130
142
  mkdir -p "$EXEC_PREFIX"
131
- npm exec --yes --prefix "$EXEC_PREFIX" --package=@eduardbar/drift@${{ inputs.version }} -- drift trust-gate "${{ steps.files.outputs.trust_json }}" --min-trust "${{ inputs.min-trust }}" --max-risk "${{ inputs.max-risk }}"
143
+ VERSION="${{ inputs.version }}"
144
+ if npm view "@eduardbar/drift@$VERSION" version >/dev/null 2>&1; then
145
+ npm exec --yes --prefix "$EXEC_PREFIX" --package="@eduardbar/drift@$VERSION" -- drift trust-gate "${{ steps.files.outputs.trust_json }}" --min-trust "${{ inputs.min-trust }}" --max-risk "${{ inputs.max-risk }}"
146
+ else
147
+ npx --no-install tsx ./src/cli.ts trust-gate "${{ steps.files.outputs.trust_json }}" --min-trust "${{ inputs.min-trust }}" --max-risk "${{ inputs.max-risk }}"
148
+ fi
@@ -6,7 +6,7 @@ Composite action to run `drift scan` in CI without global installs.
6
6
 
7
7
  - Runtime strategy: `npm exec --yes --prefix "$RUNNER_TEMP/drift-cli" --package=@eduardbar/drift@<version> -- drift ...` (no `npm install -g`)
8
8
  - Uses isolated `--prefix` under `$RUNNER_TEMP` to avoid bin resolution conflicts in self-hosting repository workflows
9
- - Default drift version is pinned (`1.3.0`) for deterministic runs
9
+ - Default drift version is pinned (`1.5.0`) for deterministic runs
10
10
  - Uses `drift scan --json` and extracts typed outputs
11
11
  - Critical command/parse failures are not silenced (step fails immediately)
12
12
 
@@ -20,7 +20,7 @@ Composite action to run `drift scan` in CI without global installs.
20
20
  path: ./src
21
21
  min-score: 60
22
22
  fail-on-threshold: true
23
- version: 1.3.0
23
+ version: 1.5.0
24
24
  ```
25
25
 
26
26
  ## Inputs
@@ -30,7 +30,7 @@ Composite action to run `drift scan` in CI without global installs.
30
30
  | `path` | Path to scan | `.` |
31
31
  | `min-score` | Fail if score exceeds this | `80` |
32
32
  | `fail-on-threshold` | Whether to fail on threshold | `true` |
33
- | `version` | drift version for `npm exec` execution | `1.3.0` |
33
+ | `version` | drift version for `npm exec` execution | `1.5.0` |
34
34
 
35
35
  ## Outputs
36
36
 
@@ -22,7 +22,7 @@ inputs:
22
22
  version:
23
23
  description: 'Version of @eduardbar/drift to use'
24
24
  required: false
25
- default: '1.3.0'
25
+ default: '1.5.0'
26
26
 
27
27
  outputs:
28
28
  score:
@@ -3,8 +3,6 @@ name: Publish VS Code Extension
3
3
  on:
4
4
  release:
5
5
  types: [published]
6
- tags:
7
- - 'vscode-v*'
8
6
  workflow_dispatch:
9
7
  inputs:
10
8
  version:
@@ -17,6 +15,7 @@ permissions:
17
15
 
18
16
  jobs:
19
17
  publish:
18
+ if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/vscode-v') }}
20
19
  runs-on: ubuntu-latest
21
20
  defaults:
22
21
  run:
@@ -36,7 +35,6 @@ jobs:
36
35
  cache-dependency-path: packages/vscode-drift/package-lock.json
37
36
 
38
37
  - name: Verify version matches tag
39
- if: github.event_name == 'release'
40
38
  run: |
41
39
  if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
42
40
  TAG_VERSION="${{ inputs.version }}"
@@ -14,7 +14,15 @@ permissions:
14
14
  contents: read
15
15
 
16
16
  jobs:
17
+ release-verify:
18
+ name: Release Verify
19
+ uses: ./.github/workflows/reusable-quality-checks.yml
20
+ with:
21
+ ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/v{0}', inputs.tag) || github.ref }}
22
+ coverage_artifact_prefix: release-coverage
23
+
17
24
  publish:
25
+ needs: release-verify
18
26
  runs-on: ubuntu-latest
19
27
  steps:
20
28
  - name: Checkout
@@ -0,0 +1,15 @@
1
+ name: Quality Gate
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main, master]
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ quality:
13
+ uses: ./.github/workflows/reusable-quality-checks.yml
14
+ with:
15
+ node_versions: '["20", "22"]'
@@ -0,0 +1,95 @@
1
+ name: Reusable Quality Checks
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ ref:
7
+ description: Git ref to checkout before running checks
8
+ required: false
9
+ type: string
10
+ default: ''
11
+ node_versions:
12
+ description: JSON array of Node.js versions for matrix execution
13
+ required: false
14
+ type: string
15
+ default: '["20", "22"]'
16
+ coverage_artifact_prefix:
17
+ description: Prefix used for uploaded coverage artifacts
18
+ required: false
19
+ type: string
20
+ default: coverage
21
+
22
+ permissions:
23
+ contents: read
24
+
25
+ jobs:
26
+ quality:
27
+ name: Quality (Node ${{ matrix.node }})
28
+ runs-on: ubuntu-latest
29
+ strategy:
30
+ fail-fast: false
31
+ matrix:
32
+ node: ${{ fromJson(inputs.node_versions) }}
33
+
34
+ steps:
35
+ - name: Checkout
36
+ uses: actions/checkout@v5
37
+ with:
38
+ ref: ${{ inputs.ref != '' && inputs.ref || github.ref }}
39
+
40
+ - name: Setup Node.js
41
+ uses: actions/setup-node@v5
42
+ with:
43
+ node-version: ${{ matrix.node }}
44
+ cache: npm
45
+
46
+ - name: Install dependencies
47
+ run: npm ci
48
+
49
+ - name: Validate runtime policy contract
50
+ run: npm run check:runtime-policy
51
+
52
+ - name: Validate docs/source-of-truth drift contract
53
+ run: npm run check:docs-drift
54
+
55
+ - name: Run tests
56
+ run: npm test
57
+
58
+ - name: Run coverage
59
+ run: npm run test:coverage -- --testTimeout=45000
60
+
61
+ - name: Build
62
+ run: npm run build
63
+
64
+ - name: Run CLI smoke E2E
65
+ run: npm run smoke:repo -- --base HEAD --out .drift-smoke/ci-node-${{ matrix.node }}
66
+
67
+ - name: Run performance regression gate
68
+ if: matrix.node == '20'
69
+ run: npm run check:perf-budget -- --out .drift-perf/ci-node-${{ matrix.node }}/benchmark-latest.json
70
+
71
+ - name: Upload coverage artifact
72
+ uses: actions/upload-artifact@v4
73
+ with:
74
+ name: ${{ inputs.coverage_artifact_prefix }}-node-${{ matrix.node }}-run-${{ github.run_id }}-${{ github.run_attempt }}
75
+ path: coverage/
76
+ if-no-files-found: error
77
+ retention-days: 14
78
+
79
+ - name: Upload smoke E2E artifacts
80
+ if: always()
81
+ uses: actions/upload-artifact@v4
82
+ with:
83
+ name: smoke-e2e-node-${{ matrix.node }}-run-${{ github.run_id }}-${{ github.run_attempt }}
84
+ path: .drift-smoke/ci-node-${{ matrix.node }}/
85
+ if-no-files-found: warn
86
+ retention-days: 14
87
+
88
+ - name: Upload perf gate artifacts
89
+ if: always() && matrix.node == '20'
90
+ uses: actions/upload-artifact@v4
91
+ with:
92
+ name: perf-gate-node-${{ matrix.node }}-run-${{ github.run_id }}-${{ github.run_attempt }}
93
+ path: .drift-perf/ci-node-${{ matrix.node }}/
94
+ if-no-files-found: warn
95
+ retention-days: 14
@@ -33,7 +33,6 @@ jobs:
33
33
  with:
34
34
  path: .
35
35
  base-ref: origin/${{ github.base_ref }}
36
- version: 1.3.0
37
36
  min-trust: 45
38
37
  max-risk: HIGH
39
38
  fail-on-gate: true
package/AGENTS.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  - Publicado en npm como `@eduardbar/drift`
8
8
  - Licencia MIT
9
- - Versión del paquete: `1.3.0` (`package.json`)
9
+ - Versión del paquete: `1.5.0` (`package.json`)
10
10
 
11
11
  ---
12
12
 
@@ -20,7 +20,7 @@
20
20
  | `typescript ^5.9` | compilación |
21
21
  | `vitest ^4` | testing |
22
22
 
23
- Runtime: Node.js 18+, ES Modules (`"type": "module"`).
23
+ Runtime: Node.js 20.x and 22.x (LTS), ES Modules (`"type": "module"`).
24
24
 
25
25
  ---
26
26
 
package/CHANGELOG.md CHANGED
@@ -18,7 +18,20 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
18
18
 
19
19
  ## [Unreleased]
20
20
 
21
- - No unreleased changes yet.
21
+ ---
22
+
23
+ ## [1.5.0] - 2026-03-26
24
+
25
+ ### Changed
26
+
27
+ - CI drift version policy now uses `package.json` as source of truth, aligns action defaults/docs to `1.5.0`, and adds a test guard that fails if action/workflow references drift semver values that diverge.
28
+ - Reusable quality checks now include a required CLI smoke E2E gate (`npm run smoke:repo`) for merge/release verification and upload smoke artifacts (`.drift-smoke/...`) for CI failure triage.
29
+ - Runtime support policy is now enforced as Node.js `20.x` and `22.x` (LTS): `package.json` now declares `engines.node`, CI matrix moved to `20/22`, and docs/doctor output were aligned to avoid advertising unsupported Node versions.
30
+ - Added runtime policy gate (`npm run check:runtime-policy`) in required CI checks to fail fast when `engines.node`, workflow matrix, README runtime line, or lockfile dependency constraints diverge.
31
+ - Tradeoff: dropped Node 18 from required support because `commander@14` (runtime dependency) requires Node `>=20`; preserving `18/20/22` would create false support claims and non-deterministic install/runtime behavior.
32
+ - `drift doctor --json` and `drift guard --json` now emit schema metadata (`$schema`, `toolVersion`) and are covered by v1 schema contract tests (`schemas/drift-doctor.v1.json`, `schemas/drift-guard.v1.json`).
33
+ - Added performance regression gate (`npm run check:perf-budget`) with versioned budgets in `benchmarks/perf-budget.v1.json`, benchmark memory/runtime contract checks, and CI artifact upload under `.drift-perf/` (gated on Node 20 to reduce matrix flakiness).
34
+ - Stabilized local/CI test reliability by setting global Vitest default timeouts (15s) and tuning the scan runtime perf budget threshold for lower false-positive noise.
22
35
 
23
36
  ---
24
37
 
package/README.md CHANGED
@@ -91,7 +91,9 @@ drift doctor --json
91
91
  |------|-------------|
92
92
  | `--json` | Output structured doctor report JSON |
93
93
 
94
- Checks include Node major version support (`>=18`), `package.json`, ESM mode, `tsconfig.json`, source file count, optional `--low-memory` recommendation, and `drift.config.*` detection.
94
+ Checks include Node major version support (`>=20`), `package.json`, ESM mode, `tsconfig.json`, source file count, optional `--low-memory` recommendation, and `drift.config.*` detection.
95
+
96
+ When `--json` is enabled, output includes `$schema` and `toolVersion` metadata and follows [`schemas/drift-doctor.v1.json`](./schemas/drift-doctor.v1.json).
95
97
 
96
98
  ---
97
99
 
@@ -190,6 +192,8 @@ Behavior:
190
192
  - Without `--base`, guard requires a baseline (inline or file) and compares only against available baseline anchors.
191
193
  - Exit code is `1` when any guard check fails.
192
194
 
195
+ When `--json` is enabled, output includes `$schema` and `toolVersion` metadata and follows [`schemas/drift-guard.v1.json`](./schemas/drift-guard.v1.json).
196
+
193
197
  ---
194
198
 
195
199
  ### `drift diff [ref]`
@@ -255,7 +259,7 @@ drift trust ./src --markdown --output trust.md
255
259
  drift trust ./src --min-trust 45
256
260
  drift trust ./src --max-risk HIGH
257
261
  drift trust ./src --branch main
258
- drift trust ./src --branch release/v1.4.0 --policy-pack strict --explain-policy
262
+ drift trust ./src --branch release/v1.5.0 --policy-pack strict --explain-policy
259
263
  drift trust ./src --advanced-trust
260
264
  drift trust ./src --advanced-trust --previous-trust ./artifacts/prev-trust.json
261
265
  drift trust ./src --advanced-trust --history-file ./drift-history.json --markdown
@@ -659,6 +663,29 @@ jobs:
659
663
 
660
664
  `drift ci` emits `::error` and `::warning` annotations that appear inline in the PR diff and writes a formatted summary to `$GITHUB_STEP_SUMMARY`. Use this when you want visibility beyond a pass/fail exit code.
661
665
 
666
+ ### Required quality checks for merge and release
667
+
668
+ This repository enforces `.github/workflows/quality.yml` on `pull_request` and `push` to `main`/`master`.
669
+
670
+ The workflow runs a required Node.js matrix (`20`, `22`) and executes these checks in each matrix job:
671
+ - `npm ci`
672
+ - `npm run check:runtime-policy`
673
+ - `npm run check:docs-drift`
674
+ - `npm test`
675
+ - `npm run test:coverage`
676
+ - `npm run build`
677
+ - `npm run smoke:repo -- --base HEAD --out .drift-smoke/ci-node-<node>`
678
+
679
+ Additionally, Node 20 runs a deterministic performance regression gate:
680
+ - `npm run check:perf-budget -- --out .drift-perf/ci-node-20/benchmark-latest.json`
681
+ - Budget source of truth: [`benchmarks/perf-budget.v1.json`](./benchmarks/perf-budget.v1.json)
682
+
683
+ `check:docs-drift` enforces documentation source-of-truth contracts: package version claims come from `package.json` and rule catalog/count claims come from `RULE_WEIGHTS` in `src/analyzer.ts`.
684
+
685
+ Coverage artifacts and smoke E2E artifacts are uploaded from each matrix run to support traceability and CI debugging. The perf gate also uploads `.drift-perf/...` artifacts on Node 20.
686
+
687
+ `publish.yml` now runs `release-verify` before publish, reusing the same quality workflow contract. `publish` only runs after `release-verify` passes.
688
+
662
689
  ### Auto PR comment with `drift review`
663
690
 
664
691
  The repository includes `.github/workflows/review-pr.yml`, which:
@@ -780,7 +807,7 @@ See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) before participating.
780
807
  | [`commander`](https://github.com/tj/commander.js) | CLI commands and flags |
781
808
  | [`kleur`](https://github.com/lukeed/kleur) | Terminal colors (zero dependencies) |
782
809
 
783
- **Runtime:** Node.js 18+ · TypeScript 5.x · ES Modules · Supports TypeScript (`.ts`, `.tsx`) and JavaScript (`.js`, `.jsx`) files
810
+ **Runtime:** Node.js 20.x and 22.x (LTS) · TypeScript 5.x · ES Modules · Supports TypeScript (`.ts`, `.tsx`) and JavaScript (`.js`, `.jsx`) files
784
811
 
785
812
  ---
786
813
 
@@ -0,0 +1,21 @@
1
+ const config = {
2
+ layers: [
3
+ { name: 'domain', patterns: ['src/domain/**'], canImportFrom: [] },
4
+ { name: 'app', patterns: ['src/app/**'], canImportFrom: ['domain'] },
5
+ { name: 'infra', patterns: ['src/infra/**'], canImportFrom: ['domain', 'app'] },
6
+ ],
7
+ architectureRules: {
8
+ controllerNoDb: true,
9
+ serviceNoHttp: true,
10
+ maxFunctionLines: 80,
11
+ },
12
+ performance: {
13
+ lowMemory: false,
14
+ chunkSize: 40,
15
+ maxFiles: 200,
16
+ maxFileSizeKb: 512,
17
+ includeSemanticDuplication: false,
18
+ },
19
+ }
20
+
21
+ export default config
@@ -0,0 +1,30 @@
1
+ import { canAccessBilling, normalizeEmail, type User } from '../domain/entities.js'
2
+
3
+ export interface UserRepository {
4
+ findByEmail(email: string): Promise<User | undefined>
5
+ save(user: User): Promise<void>
6
+ }
7
+
8
+ export class UserService {
9
+ constructor(private readonly repo: UserRepository) {}
10
+
11
+ async ensureUser(email: string): Promise<User> {
12
+ const normalizedEmail = normalizeEmail(email)
13
+ const existing = await this.repo.findByEmail(normalizedEmail)
14
+ if (existing) return existing
15
+
16
+ const created: User = {
17
+ id: `u-${normalizedEmail}`,
18
+ email: normalizedEmail,
19
+ active: true,
20
+ }
21
+
22
+ await this.repo.save(created)
23
+ return created
24
+ }
25
+
26
+ async canAccessFeature(email: string): Promise<boolean> {
27
+ const user = await this.ensureUser(email)
28
+ return canAccessBilling(user)
29
+ }
30
+ }
@@ -0,0 +1,19 @@
1
+ export type UserId = string
2
+
3
+ export interface User {
4
+ id: UserId
5
+ email: string
6
+ active: boolean
7
+ }
8
+
9
+ export function normalizeEmail(email: string): string {
10
+ return email.trim().toLowerCase()
11
+ }
12
+
13
+ export function isCompanyEmail(email: string): boolean {
14
+ return normalizeEmail(email).endsWith('@example.com')
15
+ }
16
+
17
+ export function canAccessBilling(user: User): boolean {
18
+ return user.active && isCompanyEmail(user.email)
19
+ }
@@ -0,0 +1,22 @@
1
+ export interface AccessPolicyInput {
2
+ score: number
3
+ isInternal: boolean
4
+ isTrial: boolean
5
+ hasPaymentIssue: boolean
6
+ }
7
+
8
+ export function computeTrustTier(input: AccessPolicyInput): 'low' | 'medium' | 'high' {
9
+ if (input.hasPaymentIssue) return 'low'
10
+ if (input.score >= 80 && input.isInternal) return 'high'
11
+ if (input.score >= 60 && !input.isTrial) return 'medium'
12
+ return 'low'
13
+ }
14
+
15
+ export function canRunExpensiveChecks(input: AccessPolicyInput): boolean {
16
+ const tier = computeTrustTier(input)
17
+ return tier === 'high' || (tier === 'medium' && input.score >= 70)
18
+ }
19
+
20
+ export function shouldNotifyOps(input: AccessPolicyInput): boolean {
21
+ return input.hasPaymentIssue || (!input.isInternal && input.score < 45)
22
+ }
@@ -0,0 +1,10 @@
1
+ import { UserService } from './app/user-service.js'
2
+ import { MemoryUserRepository } from './infra/memory-user-repo.js'
3
+
4
+ const service = new UserService(new MemoryUserRepository())
5
+
6
+ export async function runFixtureFlow(): Promise<boolean> {
7
+ return service.canAccessFeature('Demo.User@example.com')
8
+ }
9
+
10
+ void runFixtureFlow()
@@ -0,0 +1,14 @@
1
+ import type { User } from '../domain/entities.js'
2
+ import type { UserRepository } from '../app/user-service.js'
3
+
4
+ export class MemoryUserRepository implements UserRepository {
5
+ private readonly users = new Map<string, User>()
6
+
7
+ async findByEmail(email: string): Promise<User | undefined> {
8
+ return this.users.get(email)
9
+ }
10
+
11
+ async save(user: User): Promise<void> {
12
+ this.users.set(user.email, user)
13
+ }
14
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "schemaVersion": "drift-perf-budget/v1",
3
+ "budgetVersion": "2026-03-26",
4
+ "benchmark": {
5
+ "fixturePath": "benchmarks/fixtures/critical",
6
+ "warmupRuns": 1,
7
+ "measuredRuns": 3
8
+ },
9
+ "tolerance": {
10
+ "runtimePct": 30,
11
+ "memoryPct": 25
12
+ },
13
+ "tasks": {
14
+ "scan": {
15
+ "maxMedianMs": 3800,
16
+ "maxRssMb": 700
17
+ },
18
+ "review": {
19
+ "maxMedianMs": 6500,
20
+ "maxRssMb": 1300
21
+ },
22
+ "trust": {
23
+ "maxMedianMs": 6500,
24
+ "maxRssMb": 1300
25
+ }
26
+ }
27
+ }
package/dist/benchmark.js CHANGED
@@ -86,26 +86,38 @@ function median(values) {
86
86
  function formatMs(ms) {
87
87
  return ms.toFixed(2);
88
88
  }
89
+ function bytesToMb(bytes) {
90
+ return bytes / (1024 * 1024);
91
+ }
89
92
  async function runTask(name, warmupRuns, measuredRuns, task) {
90
93
  for (let i = 0; i < warmupRuns; i += 1) {
91
94
  await task();
92
95
  }
93
96
  const samplesMs = [];
97
+ const samplesRssMb = [];
94
98
  for (let i = 0; i < measuredRuns; i += 1) {
99
+ const rssBefore = process.memoryUsage().rss;
95
100
  const started = performance.now();
96
101
  await task();
97
102
  samplesMs.push(performance.now() - started);
103
+ const rssAfter = process.memoryUsage().rss;
104
+ samplesRssMb.push(bytesToMb(Math.max(rssBefore, rssAfter)));
98
105
  }
99
106
  const total = samplesMs.reduce((sum, sample) => sum + sample, 0);
107
+ const totalRss = samplesRssMb.reduce((sum, sample) => sum + sample, 0);
100
108
  return {
101
109
  name,
102
110
  warmupRuns,
103
111
  measuredRuns,
104
112
  samplesMs,
113
+ samplesRssMb,
105
114
  medianMs: median(samplesMs),
106
115
  meanMs: total / samplesMs.length,
107
116
  minMs: Math.min(...samplesMs),
108
117
  maxMs: Math.max(...samplesMs),
118
+ medianRssMb: median(samplesRssMb),
119
+ meanRssMb: totalRss / samplesRssMb.length,
120
+ maxRssMb: Math.max(...samplesRssMb),
109
121
  };
110
122
  }
111
123
  function printTable(results) {
package/dist/cli.js CHANGED
@@ -14,7 +14,7 @@ import { printConsole, printDiff } from './printer.js';
14
14
  import { loadConfig } from './config.js';
15
15
  import { extractFilesAtRef, cleanupTempDir } from './git.js';
16
16
  import { computeDiff } from './diff.js';
17
- import { runGuard } from './guard.js';
17
+ import { formatGuardJson, runGuard } from './guard.js';
18
18
  import { generateHtmlReport } from './report.js';
19
19
  import { generateBadge } from './badge.js';
20
20
  import { emitCIAnnotations, printCISummary } from './ci.js';
@@ -323,7 +323,7 @@ addResourceOptions(program
323
323
  analysis: resolveAnalysisOptions(options),
324
324
  });
325
325
  if (options.json) {
326
- process.stdout.write(JSON.stringify(result, null, 2) + '\n');
326
+ process.stdout.write(`${formatGuardJson(result)}\n`);
327
327
  }
328
328
  else {
329
329
  printGuardSummary(result);
package/dist/doctor.d.ts CHANGED
@@ -1,5 +1,26 @@
1
+ import type { DriftOutputMetadata } from './types.js';
1
2
  export interface DoctorOptions {
2
3
  json?: boolean;
3
4
  }
5
+ interface DoctorReport {
6
+ targetPath: string;
7
+ node: {
8
+ version: string;
9
+ major: number;
10
+ supported: boolean;
11
+ };
12
+ project: {
13
+ packageJsonFound: boolean;
14
+ esm: boolean;
15
+ tsconfigFound: boolean;
16
+ sourceFilesCount: number;
17
+ lowMemorySuggested: boolean;
18
+ driftConfigFile: string | null;
19
+ };
20
+ }
21
+ export type DoctorReportJson = DoctorReport & DriftOutputMetadata;
22
+ export declare function formatDoctorJsonObject(report: DoctorReport): DoctorReportJson;
23
+ export declare function formatDoctorJson(report: DoctorReport): string;
4
24
  export declare function runDoctor(projectPath: string, options?: DoctorOptions): Promise<number>;
25
+ export {};
5
26
  //# sourceMappingURL=doctor.d.ts.map