@eduardbar/drift 1.2.0 → 1.4.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/.gga +50 -0
- package/.github/actions/drift-review/README.md +60 -0
- package/.github/actions/drift-review/action.yml +131 -0
- package/.github/actions/drift-scan/README.md +28 -32
- package/.github/actions/drift-scan/action.yml +78 -14
- package/.github/workflows/publish-vscode.yml +3 -3
- package/.github/workflows/publish.yml +3 -3
- package/.github/workflows/review-pr.yml +94 -9
- package/AGENTS.md +75 -245
- package/CHANGELOG.md +28 -0
- package/README.md +308 -51
- package/ROADMAP.md +6 -5
- package/dist/analyzer.d.ts +2 -2
- package/dist/analyzer.js +420 -159
- package/dist/benchmark.d.ts +2 -0
- package/dist/benchmark.js +204 -0
- package/dist/cli.js +693 -67
- package/dist/config.js +16 -2
- package/dist/diff.js +66 -10
- package/dist/doctor.d.ts +5 -0
- package/dist/doctor.js +133 -0
- package/dist/format.d.ts +17 -0
- package/dist/format.js +45 -0
- package/dist/git.js +12 -0
- package/dist/guard-types.d.ts +57 -0
- package/dist/guard-types.js +2 -0
- package/dist/guard.d.ts +14 -0
- package/dist/guard.js +239 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.js +6 -1
- package/dist/init.d.ts +15 -0
- package/dist/init.js +273 -0
- package/dist/map-cycles.d.ts +2 -0
- package/dist/map-cycles.js +34 -0
- package/dist/map-svg.d.ts +19 -0
- package/dist/map-svg.js +97 -0
- package/dist/map.js +78 -138
- package/dist/metrics.js +70 -55
- package/dist/output-metadata.d.ts +13 -0
- package/dist/output-metadata.js +17 -0
- package/dist/plugins-capabilities.d.ts +4 -0
- package/dist/plugins-capabilities.js +21 -0
- package/dist/plugins-messages.d.ts +10 -0
- package/dist/plugins-messages.js +16 -0
- package/dist/plugins-rules.d.ts +9 -0
- package/dist/plugins-rules.js +137 -0
- package/dist/plugins.d.ts +2 -1
- package/dist/plugins.js +80 -28
- package/dist/printer.js +4 -0
- package/dist/reporter-constants.d.ts +16 -0
- package/dist/reporter-constants.js +39 -0
- package/dist/reporter.d.ts +3 -3
- package/dist/reporter.js +35 -55
- package/dist/review.d.ts +2 -1
- package/dist/review.js +4 -3
- package/dist/rules/comments.js +2 -2
- package/dist/rules/complexity.js +2 -7
- package/dist/rules/nesting.js +3 -13
- package/dist/rules/phase0-basic.js +10 -10
- package/dist/rules/phase3-configurable.js +23 -15
- package/dist/rules/shared.d.ts +2 -0
- package/dist/rules/shared.js +27 -3
- package/dist/saas/constants.d.ts +15 -0
- package/dist/saas/constants.js +48 -0
- package/dist/saas/dashboard.d.ts +8 -0
- package/dist/saas/dashboard.js +132 -0
- package/dist/saas/errors.d.ts +19 -0
- package/dist/saas/errors.js +37 -0
- package/dist/saas/helpers.d.ts +21 -0
- package/dist/saas/helpers.js +110 -0
- package/dist/saas/ingest.d.ts +3 -0
- package/dist/saas/ingest.js +249 -0
- package/dist/saas/organization.d.ts +5 -0
- package/dist/saas/organization.js +82 -0
- package/dist/saas/plan-change.d.ts +10 -0
- package/dist/saas/plan-change.js +15 -0
- package/dist/saas/store.d.ts +21 -0
- package/dist/saas/store.js +159 -0
- package/dist/saas/types.d.ts +191 -0
- package/dist/saas/types.js +2 -0
- package/dist/saas.d.ts +8 -82
- package/dist/saas.js +7 -320
- package/dist/sarif.d.ts +74 -0
- package/dist/sarif.js +122 -0
- package/dist/trust-advanced.d.ts +14 -0
- package/dist/trust-advanced.js +65 -0
- package/dist/trust-kpi-fs.d.ts +3 -0
- package/dist/trust-kpi-fs.js +141 -0
- package/dist/trust-kpi-parse.d.ts +7 -0
- package/dist/trust-kpi-parse.js +186 -0
- package/dist/trust-kpi-types.d.ts +16 -0
- package/dist/trust-kpi-types.js +2 -0
- package/dist/trust-kpi.d.ts +7 -0
- package/dist/trust-kpi.js +185 -0
- package/dist/trust-policy.d.ts +32 -0
- package/dist/trust-policy.js +160 -0
- package/dist/trust-render.d.ts +9 -0
- package/dist/trust-render.js +54 -0
- package/dist/trust-scoring.d.ts +9 -0
- package/dist/trust-scoring.js +208 -0
- package/dist/trust.d.ts +37 -0
- package/dist/trust.js +168 -0
- package/dist/types/app.d.ts +30 -0
- package/dist/types/app.js +2 -0
- package/dist/types/config.d.ts +25 -0
- package/dist/types/config.js +2 -0
- package/dist/types/core.d.ts +100 -0
- package/dist/types/core.js +2 -0
- package/dist/types/diff.d.ts +55 -0
- package/dist/types/diff.js +2 -0
- package/dist/types/plugin.d.ts +41 -0
- package/dist/types/plugin.js +2 -0
- package/dist/types/trust.d.ts +120 -0
- package/dist/types/trust.js +2 -0
- package/dist/types.d.ts +8 -211
- package/docs/PRD.md +187 -109
- package/docs/plugin-contract.md +61 -0
- package/docs/release-notes-draft.md +40 -0
- package/docs/rules-catalog.md +49 -0
- package/docs/trust-core-release-checklist.md +87 -0
- package/package.json +6 -3
- package/packages/vscode-drift/src/code-actions.ts +1 -1
- package/schemas/drift-ai-output.v1.json +162 -0
- package/schemas/drift-report.v1.json +151 -0
- package/schemas/drift-trust.v1.json +131 -0
- package/scripts/smoke-repo.mjs +394 -0
- package/src/analyzer.ts +484 -155
- package/src/benchmark.ts +266 -0
- package/src/cli.ts +840 -85
- package/src/config.ts +19 -2
- package/src/diff.ts +84 -10
- package/src/doctor.ts +173 -0
- package/src/format.ts +81 -0
- package/src/git.ts +16 -0
- package/src/guard-types.ts +64 -0
- package/src/guard.ts +324 -0
- package/src/index.ts +83 -0
- package/src/init.ts +298 -0
- package/src/map-cycles.ts +38 -0
- package/src/map-svg.ts +124 -0
- package/src/map.ts +111 -142
- package/src/metrics.ts +78 -59
- package/src/output-metadata.ts +30 -0
- package/src/plugins-capabilities.ts +36 -0
- package/src/plugins-messages.ts +35 -0
- package/src/plugins-rules.ts +296 -0
- package/src/plugins.ts +148 -27
- package/src/printer.ts +4 -0
- package/src/reporter-constants.ts +46 -0
- package/src/reporter.ts +64 -65
- package/src/review.ts +6 -4
- package/src/rules/comments.ts +2 -2
- package/src/rules/complexity.ts +2 -7
- package/src/rules/nesting.ts +3 -13
- package/src/rules/phase0-basic.ts +11 -12
- package/src/rules/phase3-configurable.ts +39 -26
- package/src/rules/shared.ts +31 -3
- package/src/saas/constants.ts +56 -0
- package/src/saas/dashboard.ts +172 -0
- package/src/saas/errors.ts +45 -0
- package/src/saas/helpers.ts +140 -0
- package/src/saas/ingest.ts +278 -0
- package/src/saas/organization.ts +99 -0
- package/src/saas/plan-change.ts +19 -0
- package/src/saas/store.ts +172 -0
- package/src/saas/types.ts +216 -0
- package/src/saas.ts +49 -433
- package/src/sarif.ts +232 -0
- package/src/trust-advanced.ts +99 -0
- package/src/trust-kpi-fs.ts +169 -0
- package/src/trust-kpi-parse.ts +219 -0
- package/src/trust-kpi-types.ts +19 -0
- package/src/trust-kpi.ts +210 -0
- package/src/trust-policy.ts +246 -0
- package/src/trust-render.ts +61 -0
- package/src/trust-scoring.ts +231 -0
- package/src/trust.ts +260 -0
- package/src/types/app.ts +30 -0
- package/src/types/config.ts +27 -0
- package/src/types/core.ts +105 -0
- package/src/types/diff.ts +61 -0
- package/src/types/plugin.ts +46 -0
- package/src/types/trust.ts +134 -0
- package/src/types.ts +78 -238
- package/tests/cli-sarif.test.ts +92 -0
- package/tests/diff.test.ts +124 -0
- package/tests/format.test.ts +157 -0
- package/tests/new-features.test.ts +80 -1
- package/tests/phase1-init-doctor-guard.test.ts +199 -0
- package/tests/plugins.test.ts +219 -0
- package/tests/rules.test.ts +23 -1
- package/tests/saas-foundation.test.ts +358 -1
- package/tests/sarif.test.ts +160 -0
- package/tests/trust-kpi.test.ts +147 -0
- package/tests/trust.test.ts +602 -0
package/.gga
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Gentleman Guardian Angel Configuration
|
|
2
|
+
# https://github.com/your-org/gga
|
|
3
|
+
|
|
4
|
+
# AI Provider (required)
|
|
5
|
+
# Options: claude, gemini, codex, opencode, ollama:<model>, lmstudio[:model], github:<model>
|
|
6
|
+
# Examples:
|
|
7
|
+
# PROVIDER="claude"
|
|
8
|
+
# PROVIDER="gemini"
|
|
9
|
+
# PROVIDER="codex"
|
|
10
|
+
# PROVIDER="opencode"
|
|
11
|
+
# PROVIDER="opencode:anthropic/claude-opus-4-5"
|
|
12
|
+
# PROVIDER="ollama:llama3.2"
|
|
13
|
+
# PROVIDER="ollama:codellama"
|
|
14
|
+
# PROVIDER="lmstudio"
|
|
15
|
+
# PROVIDER="lmstudio:qwen2.5-coder-7b-instruct"
|
|
16
|
+
# PROVIDER="github:gpt-4o"
|
|
17
|
+
# PROVIDER="github:deepseek-r1"
|
|
18
|
+
PROVIDER="claude"
|
|
19
|
+
|
|
20
|
+
# File patterns to include in review (comma-separated)
|
|
21
|
+
# Default: * (all files)
|
|
22
|
+
# Examples:
|
|
23
|
+
# FILE_PATTERNS="*.ts,*.tsx"
|
|
24
|
+
# FILE_PATTERNS="*.py"
|
|
25
|
+
# FILE_PATTERNS="*.go,*.mod"
|
|
26
|
+
FILE_PATTERNS="*.ts,*.tsx,*.js,*.jsx"
|
|
27
|
+
|
|
28
|
+
# File patterns to exclude from review (comma-separated)
|
|
29
|
+
# Default: none
|
|
30
|
+
# Examples:
|
|
31
|
+
# EXCLUDE_PATTERNS="*.test.ts,*.spec.ts"
|
|
32
|
+
# EXCLUDE_PATTERNS="*_test.go,*.mock.ts"
|
|
33
|
+
EXCLUDE_PATTERNS="*.test.ts,*.spec.ts,*.test.tsx,*.spec.tsx,*.d.ts"
|
|
34
|
+
|
|
35
|
+
# File containing code review rules
|
|
36
|
+
# Default: AGENTS.md
|
|
37
|
+
RULES_FILE="AGENTS.md"
|
|
38
|
+
|
|
39
|
+
# Strict mode: fail if AI response is ambiguous
|
|
40
|
+
# Default: true
|
|
41
|
+
STRICT_MODE="true"
|
|
42
|
+
|
|
43
|
+
# Timeout in seconds for AI provider response
|
|
44
|
+
# Default: 300 (5 minutes)
|
|
45
|
+
# Increase for large changesets or slow connections
|
|
46
|
+
TIMEOUT="300"
|
|
47
|
+
|
|
48
|
+
# Base branch for --pr-mode (auto-detects main/master/develop if empty)
|
|
49
|
+
# Default: auto-detect
|
|
50
|
+
# PR_BASE_BRANCH="main"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# drift-review Action
|
|
2
|
+
|
|
3
|
+
Composite action for PR workflows that wraps `drift trust`, optional `drift review`, and `drift trust-gate`.
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
## Why this action exists
|
|
8
|
+
|
|
9
|
+
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:
|
|
10
|
+
|
|
11
|
+
1. Generate trust markdown + JSON
|
|
12
|
+
2. Optionally generate review markdown
|
|
13
|
+
3. Extract stable trust outputs for downstream jobs/comments
|
|
14
|
+
4. Enforce trust gate when configured
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
- name: Drift trust/review
|
|
20
|
+
id: drift_review
|
|
21
|
+
uses: ./.github/actions/drift-review
|
|
22
|
+
with:
|
|
23
|
+
path: .
|
|
24
|
+
base-ref: origin/${{ github.base_ref }}
|
|
25
|
+
version: 1.3.0
|
|
26
|
+
min-trust: 40
|
|
27
|
+
max-risk: HIGH
|
|
28
|
+
fail-on-gate: true
|
|
29
|
+
include-review: true
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Inputs
|
|
33
|
+
|
|
34
|
+
| Input | Description | Default |
|
|
35
|
+
|-------|-------------|---------|
|
|
36
|
+
| `path` | Path to analyze | `.` |
|
|
37
|
+
| `base-ref` | Base git ref for diff-aware trust/review | `origin/main` |
|
|
38
|
+
| `version` | drift version for `npm exec` execution | `1.3.0` |
|
|
39
|
+
| `min-trust` | Failing threshold for trust score | `45` |
|
|
40
|
+
| `max-risk` | Failing threshold for merge risk | `HIGH` |
|
|
41
|
+
| `fail-on-gate` | Enforce trust gate failure | `true` |
|
|
42
|
+
| `include-review` | Generate review markdown file | `true` |
|
|
43
|
+
|
|
44
|
+
## Outputs
|
|
45
|
+
|
|
46
|
+
| Output | Description |
|
|
47
|
+
|--------|-------------|
|
|
48
|
+
| `trust-score` | `trust_score` from trust JSON |
|
|
49
|
+
| `merge-risk` | `merge_risk` from trust JSON |
|
|
50
|
+
| `new-issues` | New issue count from `diff_context` or `n/a` |
|
|
51
|
+
| `resolved-issues` | Resolved issue count from `diff_context` or `n/a` |
|
|
52
|
+
| `trust-json` | Trust JSON file path |
|
|
53
|
+
| `trust-markdown` | Trust markdown file path |
|
|
54
|
+
| `review-markdown` | Review markdown file path |
|
|
55
|
+
|
|
56
|
+
## Failure behavior
|
|
57
|
+
|
|
58
|
+
- Parsing failures on trust JSON fail the step.
|
|
59
|
+
- Gate violations fail when `fail-on-gate=true`.
|
|
60
|
+
- No `|| true` paths are used for critical scan/trust/gate commands.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
name: 'Drift — Trust + Review for PRs'
|
|
2
|
+
description: 'Generate trust/review artifacts and enforce trust gate for pull requests'
|
|
3
|
+
author: 'eduardbar'
|
|
4
|
+
|
|
5
|
+
branding:
|
|
6
|
+
icon: 'shield'
|
|
7
|
+
color: 'blue'
|
|
8
|
+
|
|
9
|
+
inputs:
|
|
10
|
+
path:
|
|
11
|
+
description: 'Path to analyze'
|
|
12
|
+
required: false
|
|
13
|
+
default: '.'
|
|
14
|
+
base-ref:
|
|
15
|
+
description: 'Git base ref used by trust/review diff context'
|
|
16
|
+
required: false
|
|
17
|
+
default: 'origin/main'
|
|
18
|
+
version:
|
|
19
|
+
description: 'Version of @eduardbar/drift to use'
|
|
20
|
+
required: false
|
|
21
|
+
default: '1.3.0'
|
|
22
|
+
min-trust:
|
|
23
|
+
description: 'Fail gate if trust score is below this threshold'
|
|
24
|
+
required: false
|
|
25
|
+
default: '45'
|
|
26
|
+
max-risk:
|
|
27
|
+
description: 'Fail gate if merge risk exceeds this level (LOW|MEDIUM|HIGH|CRITICAL)'
|
|
28
|
+
required: false
|
|
29
|
+
default: 'HIGH'
|
|
30
|
+
fail-on-gate:
|
|
31
|
+
description: 'Whether to enforce trust-gate failure'
|
|
32
|
+
required: false
|
|
33
|
+
default: 'true'
|
|
34
|
+
include-review:
|
|
35
|
+
description: 'Whether to generate drift review markdown'
|
|
36
|
+
required: false
|
|
37
|
+
default: 'true'
|
|
38
|
+
|
|
39
|
+
outputs:
|
|
40
|
+
trust-score:
|
|
41
|
+
description: 'Trust score from drift trust JSON'
|
|
42
|
+
value: ${{ steps.extract.outputs.trust_score }}
|
|
43
|
+
merge-risk:
|
|
44
|
+
description: 'Merge risk from drift trust JSON'
|
|
45
|
+
value: ${{ steps.extract.outputs.merge_risk }}
|
|
46
|
+
new-issues:
|
|
47
|
+
description: 'New issues compared to base ref when available'
|
|
48
|
+
value: ${{ steps.extract.outputs.new_issues }}
|
|
49
|
+
resolved-issues:
|
|
50
|
+
description: 'Resolved issues compared to base ref when available'
|
|
51
|
+
value: ${{ steps.extract.outputs.resolved_issues }}
|
|
52
|
+
trust-json:
|
|
53
|
+
description: 'Generated trust JSON file path'
|
|
54
|
+
value: ${{ steps.files.outputs.trust_json }}
|
|
55
|
+
trust-markdown:
|
|
56
|
+
description: 'Generated trust markdown file path'
|
|
57
|
+
value: ${{ steps.files.outputs.trust_md }}
|
|
58
|
+
review-markdown:
|
|
59
|
+
description: 'Generated review markdown file path (if include-review=true)'
|
|
60
|
+
value: ${{ steps.files.outputs.review_md }}
|
|
61
|
+
|
|
62
|
+
runs:
|
|
63
|
+
using: 'composite'
|
|
64
|
+
steps:
|
|
65
|
+
- name: Prepare artifact file paths
|
|
66
|
+
id: files
|
|
67
|
+
shell: bash
|
|
68
|
+
run: |
|
|
69
|
+
set -euo pipefail
|
|
70
|
+
trust_json="$RUNNER_TEMP/drift-trust.json"
|
|
71
|
+
trust_md="$RUNNER_TEMP/drift-trust.md"
|
|
72
|
+
review_md="$RUNNER_TEMP/drift-review.md"
|
|
73
|
+
echo "trust_json=$trust_json" >> "$GITHUB_OUTPUT"
|
|
74
|
+
echo "trust_md=$trust_md" >> "$GITHUB_OUTPUT"
|
|
75
|
+
echo "review_md=$review_md" >> "$GITHUB_OUTPUT"
|
|
76
|
+
|
|
77
|
+
- name: Generate trust artifacts
|
|
78
|
+
shell: bash
|
|
79
|
+
run: |
|
|
80
|
+
set -euo pipefail
|
|
81
|
+
EXEC_PREFIX="$RUNNER_TEMP/drift-cli"
|
|
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 }}"
|
|
85
|
+
|
|
86
|
+
- name: Generate review markdown
|
|
87
|
+
if: inputs.include-review == 'true'
|
|
88
|
+
shell: bash
|
|
89
|
+
run: |
|
|
90
|
+
set -euo pipefail
|
|
91
|
+
EXEC_PREFIX="$RUNNER_TEMP/drift-cli"
|
|
92
|
+
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 }}"
|
|
94
|
+
|
|
95
|
+
- name: Extract trust outputs
|
|
96
|
+
id: extract
|
|
97
|
+
shell: bash
|
|
98
|
+
run: |
|
|
99
|
+
set -euo pipefail
|
|
100
|
+
TRUST_JSON_PATH="${{ steps.files.outputs.trust_json }}" GITHUB_OUTPUT_PATH="$GITHUB_OUTPUT" node --input-type=module <<'NODE'
|
|
101
|
+
import fs from 'node:fs'
|
|
102
|
+
|
|
103
|
+
const payload = JSON.parse(fs.readFileSync(process.env.TRUST_JSON_PATH, 'utf8'))
|
|
104
|
+
const trust = payload.trust_score
|
|
105
|
+
const risk = payload.merge_risk
|
|
106
|
+
const diff = payload.diff_context ?? {}
|
|
107
|
+
|
|
108
|
+
if (typeof trust !== 'number') {
|
|
109
|
+
throw new Error('drift trust JSON missing numeric trust_score')
|
|
110
|
+
}
|
|
111
|
+
if (typeof risk !== 'string') {
|
|
112
|
+
throw new Error('drift trust JSON missing merge_risk')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const lines = [
|
|
116
|
+
`trust_score=${trust}`,
|
|
117
|
+
`merge_risk=${risk}`,
|
|
118
|
+
`new_issues=${diff.newIssues ?? 'n/a'}`,
|
|
119
|
+
`resolved_issues=${diff.resolvedIssues ?? 'n/a'}`,
|
|
120
|
+
]
|
|
121
|
+
fs.appendFileSync(process.env.GITHUB_OUTPUT_PATH, lines.join('\n') + '\n')
|
|
122
|
+
NODE
|
|
123
|
+
|
|
124
|
+
- name: Enforce trust gate
|
|
125
|
+
if: inputs.fail-on-gate == 'true'
|
|
126
|
+
shell: bash
|
|
127
|
+
run: |
|
|
128
|
+
set -euo pipefail
|
|
129
|
+
EXEC_PREFIX="$RUNNER_TEMP/drift-cli"
|
|
130
|
+
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 }}"
|
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
# drift-scan Action
|
|
1
|
+
# drift-scan Action (v2 contract)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Composite action to run `drift scan` in CI without global installs.
|
|
4
|
+
|
|
5
|
+
## Contract highlights
|
|
6
|
+
|
|
7
|
+
- Runtime strategy: `npm exec --yes --prefix "$RUNNER_TEMP/drift-cli" --package=@eduardbar/drift@<version> -- drift ...` (no `npm install -g`)
|
|
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
|
|
10
|
+
- Uses `drift scan --json` and extracts typed outputs
|
|
11
|
+
- Critical command/parse failures are not silenced (step fails immediately)
|
|
4
12
|
|
|
5
13
|
## Usage
|
|
6
14
|
|
|
7
15
|
```yaml
|
|
8
16
|
- name: Check drift score
|
|
9
|
-
|
|
17
|
+
id: drift_scan
|
|
18
|
+
uses: ./.github/actions/drift-scan
|
|
10
19
|
with:
|
|
11
20
|
path: ./src
|
|
12
21
|
min-score: 60
|
|
22
|
+
fail-on-threshold: true
|
|
23
|
+
version: 1.3.0
|
|
13
24
|
```
|
|
14
25
|
|
|
15
26
|
## Inputs
|
|
@@ -19,7 +30,7 @@ Scan your TypeScript project for AI-generated technical debt in CI.
|
|
|
19
30
|
| `path` | Path to scan | `.` |
|
|
20
31
|
| `min-score` | Fail if score exceeds this | `80` |
|
|
21
32
|
| `fail-on-threshold` | Whether to fail on threshold | `true` |
|
|
22
|
-
| `version` | drift version
|
|
33
|
+
| `version` | drift version for `npm exec` execution | `1.3.0` |
|
|
23
34
|
|
|
24
35
|
## Outputs
|
|
25
36
|
|
|
@@ -27,35 +38,20 @@ Scan your TypeScript project for AI-generated technical debt in CI.
|
|
|
27
38
|
|--------|-------------|
|
|
28
39
|
| `score` | Project drift score (0-100) |
|
|
29
40
|
| `grade` | Grade: CLEAN / LOW / MODERATE / HIGH / CRITICAL |
|
|
41
|
+
| `errors` | Error-level issue count |
|
|
42
|
+
| `warnings` | Warning-level issue count |
|
|
43
|
+
| `infos` | Info-level issue count |
|
|
44
|
+
| `total-issues` | Total issue count |
|
|
45
|
+
| `files-affected` | Files with at least one issue |
|
|
46
|
+
| `top-rules` | Top 3 rules as `rule:count` CSV |
|
|
30
47
|
|
|
31
|
-
## Example:
|
|
48
|
+
## Example: consume outputs
|
|
32
49
|
|
|
33
50
|
```yaml
|
|
34
|
-
name:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
steps:
|
|
41
|
-
- uses: actions/checkout@v4
|
|
42
|
-
- uses: eduardbar/drift@v1
|
|
43
|
-
with:
|
|
44
|
-
path: ./src
|
|
45
|
-
min-score: 60
|
|
46
|
-
fail-on-threshold: true
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Example: capture outputs
|
|
50
|
-
|
|
51
|
-
```yaml
|
|
52
|
-
- name: Scan drift
|
|
53
|
-
id: drift
|
|
54
|
-
uses: eduardbar/drift@v1
|
|
55
|
-
with:
|
|
56
|
-
path: ./src
|
|
57
|
-
fail-on-threshold: false
|
|
58
|
-
|
|
59
|
-
- name: Print results
|
|
60
|
-
run: echo "Score ${{ steps.drift.outputs.score }}/100 — ${{ steps.drift.outputs.grade }}"
|
|
51
|
+
- name: Print drift outputs
|
|
52
|
+
run: |
|
|
53
|
+
echo "Score: ${{ steps.drift_scan.outputs.score }}"
|
|
54
|
+
echo "Grade: ${{ steps.drift_scan.outputs.grade }}"
|
|
55
|
+
echo "Errors/Warnings/Infos: ${{ steps.drift_scan.outputs.errors }}/${{ steps.drift_scan.outputs.warnings }}/${{ steps.drift_scan.outputs.infos }}"
|
|
56
|
+
echo "Top rules: ${{ steps.drift_scan.outputs.top-rules }}"
|
|
61
57
|
```
|
|
@@ -22,7 +22,7 @@ inputs:
|
|
|
22
22
|
version:
|
|
23
23
|
description: 'Version of @eduardbar/drift to use'
|
|
24
24
|
required: false
|
|
25
|
-
default: '
|
|
25
|
+
default: '1.3.0'
|
|
26
26
|
|
|
27
27
|
outputs:
|
|
28
28
|
score:
|
|
@@ -31,33 +31,97 @@ outputs:
|
|
|
31
31
|
grade:
|
|
32
32
|
description: 'The drift grade (CLEAN / LOW / MODERATE / HIGH / CRITICAL)'
|
|
33
33
|
value: ${{ steps.scan.outputs.grade }}
|
|
34
|
+
errors:
|
|
35
|
+
description: 'Total error-level issues in the scan'
|
|
36
|
+
value: ${{ steps.scan.outputs.errors }}
|
|
37
|
+
warnings:
|
|
38
|
+
description: 'Total warning-level issues in the scan'
|
|
39
|
+
value: ${{ steps.scan.outputs.warnings }}
|
|
40
|
+
infos:
|
|
41
|
+
description: 'Total info-level issues in the scan'
|
|
42
|
+
value: ${{ steps.scan.outputs.infos }}
|
|
43
|
+
total-issues:
|
|
44
|
+
description: 'Total number of issues in the scan'
|
|
45
|
+
value: ${{ steps.scan.outputs.total_issues }}
|
|
46
|
+
files-affected:
|
|
47
|
+
description: 'Number of files that contain at least one issue'
|
|
48
|
+
value: ${{ steps.scan.outputs.files_affected }}
|
|
49
|
+
top-rules:
|
|
50
|
+
description: 'Top 3 rules as comma-separated pairs rule:count'
|
|
51
|
+
value: ${{ steps.scan.outputs.top_rules }}
|
|
34
52
|
|
|
35
53
|
runs:
|
|
36
54
|
using: 'composite'
|
|
37
55
|
steps:
|
|
38
|
-
- name: Install drift
|
|
39
|
-
shell: bash
|
|
40
|
-
run: npm install -g @eduardbar/drift@${{ inputs.version }}
|
|
41
|
-
|
|
42
56
|
- name: Run drift scan
|
|
43
57
|
id: scan
|
|
44
58
|
shell: bash
|
|
45
59
|
run: |
|
|
60
|
+
set -euo pipefail
|
|
46
61
|
TMPFILE=$(mktemp)
|
|
62
|
+
EXEC_PREFIX="$RUNNER_TEMP/drift-cli"
|
|
63
|
+
mkdir -p "$EXEC_PREFIX"
|
|
47
64
|
|
|
48
|
-
|
|
49
|
-
drift scan ${{ inputs.path }} --
|
|
65
|
+
npm exec --yes --prefix "$EXEC_PREFIX" --package=@eduardbar/drift@${{ inputs.version }} -- drift --version
|
|
66
|
+
npm exec --yes --prefix "$EXEC_PREFIX" --package=@eduardbar/drift@${{ inputs.version }} -- drift scan "${{ inputs.path }}" --json > "$TMPFILE"
|
|
50
67
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
GRADE=$(node -e "const d=JSON.parse(require('fs').readFileSync('$TMPFILE','utf8')); console.log(d.summary.grade)")
|
|
68
|
+
TMPFILE_PATH="$TMPFILE" GITHUB_OUTPUT_PATH="$GITHUB_OUTPUT" node --input-type=module -e "
|
|
69
|
+
import fs from 'node:fs'
|
|
54
70
|
|
|
55
|
-
|
|
71
|
+
const payload = JSON.parse(fs.readFileSync(process.env.TMPFILE_PATH, 'utf8'))
|
|
72
|
+
const score = payload.totalScore
|
|
73
|
+
if (typeof score !== 'number') {
|
|
74
|
+
throw new Error('drift scan JSON did not include numeric totalScore')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const grade = score === 0
|
|
78
|
+
? 'CLEAN'
|
|
79
|
+
: score <= 19
|
|
80
|
+
? 'LOW'
|
|
81
|
+
: score <= 44
|
|
82
|
+
? 'MODERATE'
|
|
83
|
+
: score <= 69
|
|
84
|
+
? 'HIGH'
|
|
85
|
+
: 'CRITICAL'
|
|
86
|
+
|
|
87
|
+
const summary = payload.summary ?? {}
|
|
88
|
+
const errors = Number(summary.errors ?? 0)
|
|
89
|
+
const warnings = Number(summary.warnings ?? 0)
|
|
90
|
+
const infos = Number(summary.infos ?? 0)
|
|
91
|
+
const totalIssues = Number(payload.totalIssues ?? 0)
|
|
92
|
+
const filesAffected = Array.isArray(payload.files) ? payload.files.length : 0
|
|
56
93
|
|
|
57
|
-
|
|
58
|
-
|
|
94
|
+
const byRule = summary.byRule && typeof summary.byRule === 'object' ? summary.byRule : {}
|
|
95
|
+
const topRules = Object.entries(byRule)
|
|
96
|
+
.sort((a, b) => Number(b[1]) - Number(a[1]))
|
|
97
|
+
.slice(0, 3)
|
|
98
|
+
.map(([rule, count]) => `${rule}:${count}`)
|
|
99
|
+
.join(',')
|
|
59
100
|
|
|
60
|
-
|
|
101
|
+
const lines = [
|
|
102
|
+
`score=${score}`,
|
|
103
|
+
`grade=${grade}`,
|
|
104
|
+
`errors=${errors}`,
|
|
105
|
+
`warnings=${warnings}`,
|
|
106
|
+
`infos=${infos}`,
|
|
107
|
+
`total_issues=${totalIssues}`,
|
|
108
|
+
`files_affected=${filesAffected}`,
|
|
109
|
+
`top_rules=${topRules}`,
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
fs.appendFileSync(process.env.GITHUB_OUTPUT_PATH, lines.join('\\n') + '\\n')
|
|
113
|
+
console.log(`Drift Score: ${score}/100 (${grade})`)
|
|
114
|
+
console.log(`Issue summary: errors=${errors} warnings=${warnings} infos=${infos} total=${totalIssues}`)
|
|
115
|
+
console.log(`Top rules: ${topRules || 'n/a'}`)
|
|
116
|
+
"
|
|
117
|
+
|
|
118
|
+
SCORE=$(TMPFILE_PATH="$TMPFILE" node --input-type=module -e "
|
|
119
|
+
import fs from 'node:fs'
|
|
120
|
+
const payload = JSON.parse(fs.readFileSync(process.env.TMPFILE_PATH, 'utf8'))
|
|
121
|
+
process.stdout.write(String(payload.totalScore))
|
|
122
|
+
")
|
|
123
|
+
|
|
124
|
+
rm "$TMPFILE"
|
|
61
125
|
|
|
62
126
|
if [ "${{ inputs.fail-on-threshold }}" = "true" ] && [ "$SCORE" -gt "${{ inputs.min-score }}" ]; then
|
|
63
127
|
echo "::error::Drift score $SCORE/100 exceeds threshold of ${{ inputs.min-score }}"
|
|
@@ -24,14 +24,14 @@ jobs:
|
|
|
24
24
|
|
|
25
25
|
steps:
|
|
26
26
|
- name: Checkout
|
|
27
|
-
uses: actions/checkout@
|
|
27
|
+
uses: actions/checkout@v5
|
|
28
28
|
with:
|
|
29
29
|
ref: ${{ github.event_name == 'release' && github.ref || github.event.repository.default_branch }}
|
|
30
30
|
|
|
31
31
|
- name: Setup Node.js
|
|
32
|
-
uses: actions/setup-node@
|
|
32
|
+
uses: actions/setup-node@v5
|
|
33
33
|
with:
|
|
34
|
-
node-version: '
|
|
34
|
+
node-version: '22'
|
|
35
35
|
cache: 'npm'
|
|
36
36
|
cache-dependency-path: packages/vscode-drift/package-lock.json
|
|
37
37
|
|
|
@@ -18,14 +18,14 @@ jobs:
|
|
|
18
18
|
runs-on: ubuntu-latest
|
|
19
19
|
steps:
|
|
20
20
|
- name: Checkout
|
|
21
|
-
uses: actions/checkout@
|
|
21
|
+
uses: actions/checkout@v5
|
|
22
22
|
with:
|
|
23
23
|
ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/v{0}', inputs.tag) || github.ref }}
|
|
24
24
|
|
|
25
25
|
- name: Setup Node.js
|
|
26
|
-
uses: actions/setup-node@
|
|
26
|
+
uses: actions/setup-node@v5
|
|
27
27
|
with:
|
|
28
|
-
node-version: '
|
|
28
|
+
node-version: '22'
|
|
29
29
|
registry-url: 'https://registry.npmjs.org'
|
|
30
30
|
cache: 'npm'
|
|
31
31
|
|
|
@@ -7,27 +7,104 @@ on:
|
|
|
7
7
|
permissions:
|
|
8
8
|
contents: read
|
|
9
9
|
pull-requests: write
|
|
10
|
+
security-events: write
|
|
10
11
|
|
|
11
12
|
jobs:
|
|
12
13
|
drift-review:
|
|
13
14
|
runs-on: ubuntu-latest
|
|
14
15
|
steps:
|
|
15
16
|
- name: Checkout
|
|
16
|
-
uses: actions/checkout@
|
|
17
|
+
uses: actions/checkout@v5
|
|
17
18
|
with:
|
|
18
19
|
fetch-depth: 0
|
|
19
20
|
|
|
20
21
|
- name: Setup Node.js
|
|
21
|
-
uses: actions/setup-node@
|
|
22
|
+
uses: actions/setup-node@v5
|
|
22
23
|
with:
|
|
23
|
-
node-version: '
|
|
24
|
+
node-version: '22'
|
|
24
25
|
cache: 'npm'
|
|
25
26
|
|
|
26
27
|
- name: Install dependencies
|
|
27
28
|
run: npm ci
|
|
28
29
|
|
|
29
|
-
- name: Generate drift review
|
|
30
|
-
|
|
30
|
+
- name: Generate drift trust/review artifacts and gate
|
|
31
|
+
id: drift_review
|
|
32
|
+
uses: ./.github/actions/drift-review
|
|
33
|
+
with:
|
|
34
|
+
path: .
|
|
35
|
+
base-ref: origin/${{ github.base_ref }}
|
|
36
|
+
version: 1.3.0
|
|
37
|
+
min-trust: 45
|
|
38
|
+
max-risk: HIGH
|
|
39
|
+
fail-on-gate: true
|
|
40
|
+
include-review: true
|
|
41
|
+
|
|
42
|
+
- name: Generate drift SARIF
|
|
43
|
+
run: npx --no-install tsx ./src/cli.ts scan . --format sarif > drift.sarif
|
|
44
|
+
|
|
45
|
+
- name: Publish trust KPI step summary
|
|
46
|
+
run: |
|
|
47
|
+
{
|
|
48
|
+
echo "## drift trust KPI"
|
|
49
|
+
echo
|
|
50
|
+
echo "- Trust score: **${{ steps.drift_review.outputs.trust-score }}**"
|
|
51
|
+
echo "- Merge risk: **${{ steps.drift_review.outputs.merge-risk }}**"
|
|
52
|
+
echo "- New issues vs base: **${{ steps.drift_review.outputs.new-issues }}**"
|
|
53
|
+
echo "- Resolved issues vs base: **${{ steps.drift_review.outputs.resolved-issues }}**"
|
|
54
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
55
|
+
|
|
56
|
+
- name: Aggregate trust KPI from trust JSON artifact
|
|
57
|
+
run: npx --no-install tsx ./src/cli.ts kpi "${{ steps.drift_review.outputs.trust-json }}" --no-summary > drift-trust-kpi.json
|
|
58
|
+
|
|
59
|
+
- name: Extract aggregated KPI values
|
|
60
|
+
id: trust_kpi_aggregate
|
|
61
|
+
run: |
|
|
62
|
+
node --input-type=module -e "
|
|
63
|
+
import fs from 'node:fs'
|
|
64
|
+
const payload = JSON.parse(fs.readFileSync('drift-trust-kpi.json', 'utf8'))
|
|
65
|
+
const files = payload.files ?? {}
|
|
66
|
+
const trustScore = payload.trustScore ?? {}
|
|
67
|
+
const highRiskRatio = payload.highRiskRatio
|
|
68
|
+
fs.appendFileSync(process.env.GITHUB_OUTPUT, [
|
|
69
|
+
'matched=' + (files.matched ?? 'n/a'),
|
|
70
|
+
'parsed=' + (files.parsed ?? 'n/a'),
|
|
71
|
+
'malformed=' + (files.malformed ?? 'n/a'),
|
|
72
|
+
'prs=' + (payload.prsEvaluated ?? 'n/a'),
|
|
73
|
+
'avg=' + (trustScore.average ?? 'n/a'),
|
|
74
|
+
'high_risk_ratio=' + (highRiskRatio == null ? 'n/a' : Number(highRiskRatio * 100).toFixed(2) + '%'),
|
|
75
|
+
].join('\\n') + '\\n')
|
|
76
|
+
"
|
|
77
|
+
|
|
78
|
+
- name: Publish trust KPI aggregate summary
|
|
79
|
+
run: |
|
|
80
|
+
{
|
|
81
|
+
echo
|
|
82
|
+
echo "## drift trust KPI aggregate"
|
|
83
|
+
echo
|
|
84
|
+
echo "- Files matched/parsed/malformed: **${{ steps.trust_kpi_aggregate.outputs.matched }} / ${{ steps.trust_kpi_aggregate.outputs.parsed }} / ${{ steps.trust_kpi_aggregate.outputs.malformed }}**"
|
|
85
|
+
echo "- PR samples evaluated: **${{ steps.trust_kpi_aggregate.outputs.prs }}**"
|
|
86
|
+
echo "- Aggregate trust score avg: **${{ steps.trust_kpi_aggregate.outputs.avg }}**"
|
|
87
|
+
echo "- High-risk ratio (HIGH+CRITICAL): **${{ steps.trust_kpi_aggregate.outputs.high_risk_ratio }}**"
|
|
88
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
89
|
+
|
|
90
|
+
- name: Upload drift trust JSON artifact
|
|
91
|
+
uses: actions/upload-artifact@v4
|
|
92
|
+
with:
|
|
93
|
+
name: drift-trust-json-pr-${{ github.event.pull_request.number }}-run-${{ github.run_attempt }}
|
|
94
|
+
path: |
|
|
95
|
+
${{ steps.drift_review.outputs.trust-json }}
|
|
96
|
+
${{ steps.drift_review.outputs.trust-markdown }}
|
|
97
|
+
${{ steps.drift_review.outputs.review-markdown }}
|
|
98
|
+
drift-trust-kpi.json
|
|
99
|
+
drift.sarif
|
|
100
|
+
if-no-files-found: error
|
|
101
|
+
retention-days: 14
|
|
102
|
+
|
|
103
|
+
- name: Upload SARIF to GitHub Code Scanning (non-fork only)
|
|
104
|
+
if: github.event.pull_request.head.repo.fork == false
|
|
105
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
106
|
+
with:
|
|
107
|
+
sarif_file: drift.sarif
|
|
31
108
|
|
|
32
109
|
- name: Post or update PR comment (non-fork only)
|
|
33
110
|
if: github.event.pull_request.head.repo.fork == false
|
|
@@ -38,7 +115,13 @@ jobs:
|
|
|
38
115
|
run: |
|
|
39
116
|
COMMENT_BODY="<!-- drift-review -->"
|
|
40
117
|
COMMENT_BODY+=$'\n'
|
|
41
|
-
COMMENT_BODY+="
|
|
118
|
+
COMMENT_BODY+="## drift trust"$'\n\n'
|
|
119
|
+
COMMENT_BODY+="$(cat "${{ steps.drift_review.outputs.trust-markdown }}")"
|
|
120
|
+
COMMENT_BODY+=$'\n\n'
|
|
121
|
+
COMMENT_BODY+="## drift review"$'\n\n'
|
|
122
|
+
COMMENT_BODY+="$(cat "${{ steps.drift_review.outputs.review-markdown }}")"
|
|
123
|
+
COMMENT_BODY+=$'\n\n'
|
|
124
|
+
COMMENT_BODY+="<!-- drift-trust-kpi score=${{ steps.drift_review.outputs.trust-score }} risk=${{ steps.drift_review.outputs.merge-risk }} new=${{ steps.drift_review.outputs.new-issues }} resolved=${{ steps.drift_review.outputs.resolved-issues }} -->"
|
|
42
125
|
|
|
43
126
|
EXISTING_ID=$(gh api "repos/$REPO/issues/$PR_NUMBER/comments" --jq '.[] | select(.user.login == "github-actions[bot]") | select(.body | contains("<!-- drift-review -->")) | .id' | sed -n '1p')
|
|
44
127
|
|
|
@@ -52,10 +135,12 @@ jobs:
|
|
|
52
135
|
if: github.event.pull_request.head.repo.fork == true
|
|
53
136
|
run: |
|
|
54
137
|
{
|
|
138
|
+
echo "## drift trust"
|
|
139
|
+
echo
|
|
140
|
+
cat "${{ steps.drift_review.outputs.trust-markdown }}"
|
|
141
|
+
echo
|
|
55
142
|
echo "## drift review"
|
|
56
143
|
echo
|
|
57
|
-
cat
|
|
144
|
+
cat "${{ steps.drift_review.outputs.review-markdown }}"
|
|
58
145
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
59
146
|
|
|
60
|
-
- name: Enforce drift threshold
|
|
61
|
-
run: npx @eduardbar/drift review --base "origin/${{ github.base_ref }}" --fail-on 5 --comment > /dev/null
|