@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.
- package/.github/actions/drift-review/README.md +4 -2
- package/.github/actions/drift-review/action.yml +22 -5
- package/.github/actions/drift-scan/README.md +3 -3
- package/.github/actions/drift-scan/action.yml +1 -1
- package/.github/workflows/publish-vscode.yml +1 -3
- package/.github/workflows/publish.yml +8 -0
- package/.github/workflows/quality.yml +15 -0
- package/.github/workflows/reusable-quality-checks.yml +95 -0
- package/.github/workflows/review-pr.yml +0 -1
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +14 -1
- package/README.md +30 -3
- package/benchmarks/fixtures/critical/drift.config.ts +21 -0
- package/benchmarks/fixtures/critical/src/app/user-service.ts +30 -0
- package/benchmarks/fixtures/critical/src/domain/entities.ts +19 -0
- package/benchmarks/fixtures/critical/src/domain/policies.ts +22 -0
- package/benchmarks/fixtures/critical/src/index.ts +10 -0
- package/benchmarks/fixtures/critical/src/infra/memory-user-repo.ts +14 -0
- package/benchmarks/perf-budget.v1.json +27 -0
- package/dist/benchmark.js +12 -0
- package/dist/cli.js +2 -2
- package/dist/doctor.d.ts +21 -0
- package/dist/doctor.js +10 -3
- package/dist/guard-baseline.d.ts +12 -0
- package/dist/guard-baseline.js +57 -0
- package/dist/guard-metrics.d.ts +6 -0
- package/dist/guard-metrics.js +39 -0
- package/dist/guard-types.d.ts +2 -1
- package/dist/guard.d.ts +3 -1
- package/dist/guard.js +9 -70
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/init.js +1 -1
- package/dist/output-metadata.d.ts +2 -0
- package/dist/output-metadata.js +2 -0
- package/dist/trust.d.ts +2 -1
- package/dist/trust.js +1 -1
- package/dist/types.d.ts +1 -1
- package/docs/AGENTS.md +1 -1
- package/package.json +10 -4
- package/schemas/drift-doctor.v1.json +57 -0
- package/schemas/drift-guard.v1.json +298 -0
- package/scripts/check-docs-drift.mjs +154 -0
- package/scripts/check-performance-budget.mjs +360 -0
- package/scripts/check-runtime-policy.mjs +66 -0
- package/src/benchmark.ts +17 -0
- package/src/cli.ts +2 -2
- package/src/doctor.ts +15 -3
- package/src/guard-baseline.ts +74 -0
- package/src/guard-metrics.ts +52 -0
- package/src/guard-types.ts +3 -1
- package/src/guard.ts +14 -90
- package/src/index.ts +1 -0
- package/src/init.ts +1 -1
- package/src/output-metadata.ts +2 -0
- package/src/trust.ts +1 -1
- package/src/types.ts +1 -0
- package/tests/ci-quality-matrix.test.ts +37 -0
- package/tests/ci-smoke-gate.test.ts +26 -0
- package/tests/ci-version-alignment.test.ts +93 -0
- package/tests/docs-drift-check.test.ts +115 -0
- package/tests/new-features.test.ts +2 -2
- package/tests/perf-budget-check.test.ts +146 -0
- package/tests/phase1-init-doctor-guard.test.ts +104 -2
- package/tests/runtime-policy-alignment.test.ts +46 -0
- 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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
84
|
-
npm
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
33
|
+
| `version` | drift version for `npm exec` execution | `1.5.0` |
|
|
34
34
|
|
|
35
35
|
## Outputs
|
|
36
36
|
|
|
@@ -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,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
|
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.
|
|
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
|
|
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
|
-
|
|
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 (`>=
|
|
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.
|
|
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
|
|
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(
|
|
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
|