@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.
Files changed (195) hide show
  1. package/.gga +50 -0
  2. package/.github/actions/drift-review/README.md +60 -0
  3. package/.github/actions/drift-review/action.yml +131 -0
  4. package/.github/actions/drift-scan/README.md +28 -32
  5. package/.github/actions/drift-scan/action.yml +78 -14
  6. package/.github/workflows/publish-vscode.yml +3 -3
  7. package/.github/workflows/publish.yml +3 -3
  8. package/.github/workflows/review-pr.yml +94 -9
  9. package/AGENTS.md +75 -245
  10. package/CHANGELOG.md +28 -0
  11. package/README.md +308 -51
  12. package/ROADMAP.md +6 -5
  13. package/dist/analyzer.d.ts +2 -2
  14. package/dist/analyzer.js +420 -159
  15. package/dist/benchmark.d.ts +2 -0
  16. package/dist/benchmark.js +204 -0
  17. package/dist/cli.js +693 -67
  18. package/dist/config.js +16 -2
  19. package/dist/diff.js +66 -10
  20. package/dist/doctor.d.ts +5 -0
  21. package/dist/doctor.js +133 -0
  22. package/dist/format.d.ts +17 -0
  23. package/dist/format.js +45 -0
  24. package/dist/git.js +12 -0
  25. package/dist/guard-types.d.ts +57 -0
  26. package/dist/guard-types.js +2 -0
  27. package/dist/guard.d.ts +14 -0
  28. package/dist/guard.js +239 -0
  29. package/dist/index.d.ts +12 -3
  30. package/dist/index.js +6 -1
  31. package/dist/init.d.ts +15 -0
  32. package/dist/init.js +273 -0
  33. package/dist/map-cycles.d.ts +2 -0
  34. package/dist/map-cycles.js +34 -0
  35. package/dist/map-svg.d.ts +19 -0
  36. package/dist/map-svg.js +97 -0
  37. package/dist/map.js +78 -138
  38. package/dist/metrics.js +70 -55
  39. package/dist/output-metadata.d.ts +13 -0
  40. package/dist/output-metadata.js +17 -0
  41. package/dist/plugins-capabilities.d.ts +4 -0
  42. package/dist/plugins-capabilities.js +21 -0
  43. package/dist/plugins-messages.d.ts +10 -0
  44. package/dist/plugins-messages.js +16 -0
  45. package/dist/plugins-rules.d.ts +9 -0
  46. package/dist/plugins-rules.js +137 -0
  47. package/dist/plugins.d.ts +2 -1
  48. package/dist/plugins.js +80 -28
  49. package/dist/printer.js +4 -0
  50. package/dist/reporter-constants.d.ts +16 -0
  51. package/dist/reporter-constants.js +39 -0
  52. package/dist/reporter.d.ts +3 -3
  53. package/dist/reporter.js +35 -55
  54. package/dist/review.d.ts +2 -1
  55. package/dist/review.js +4 -3
  56. package/dist/rules/comments.js +2 -2
  57. package/dist/rules/complexity.js +2 -7
  58. package/dist/rules/nesting.js +3 -13
  59. package/dist/rules/phase0-basic.js +10 -10
  60. package/dist/rules/phase3-configurable.js +23 -15
  61. package/dist/rules/shared.d.ts +2 -0
  62. package/dist/rules/shared.js +27 -3
  63. package/dist/saas/constants.d.ts +15 -0
  64. package/dist/saas/constants.js +48 -0
  65. package/dist/saas/dashboard.d.ts +8 -0
  66. package/dist/saas/dashboard.js +132 -0
  67. package/dist/saas/errors.d.ts +19 -0
  68. package/dist/saas/errors.js +37 -0
  69. package/dist/saas/helpers.d.ts +21 -0
  70. package/dist/saas/helpers.js +110 -0
  71. package/dist/saas/ingest.d.ts +3 -0
  72. package/dist/saas/ingest.js +249 -0
  73. package/dist/saas/organization.d.ts +5 -0
  74. package/dist/saas/organization.js +82 -0
  75. package/dist/saas/plan-change.d.ts +10 -0
  76. package/dist/saas/plan-change.js +15 -0
  77. package/dist/saas/store.d.ts +21 -0
  78. package/dist/saas/store.js +159 -0
  79. package/dist/saas/types.d.ts +191 -0
  80. package/dist/saas/types.js +2 -0
  81. package/dist/saas.d.ts +8 -82
  82. package/dist/saas.js +7 -320
  83. package/dist/sarif.d.ts +74 -0
  84. package/dist/sarif.js +122 -0
  85. package/dist/trust-advanced.d.ts +14 -0
  86. package/dist/trust-advanced.js +65 -0
  87. package/dist/trust-kpi-fs.d.ts +3 -0
  88. package/dist/trust-kpi-fs.js +141 -0
  89. package/dist/trust-kpi-parse.d.ts +7 -0
  90. package/dist/trust-kpi-parse.js +186 -0
  91. package/dist/trust-kpi-types.d.ts +16 -0
  92. package/dist/trust-kpi-types.js +2 -0
  93. package/dist/trust-kpi.d.ts +7 -0
  94. package/dist/trust-kpi.js +185 -0
  95. package/dist/trust-policy.d.ts +32 -0
  96. package/dist/trust-policy.js +160 -0
  97. package/dist/trust-render.d.ts +9 -0
  98. package/dist/trust-render.js +54 -0
  99. package/dist/trust-scoring.d.ts +9 -0
  100. package/dist/trust-scoring.js +208 -0
  101. package/dist/trust.d.ts +37 -0
  102. package/dist/trust.js +168 -0
  103. package/dist/types/app.d.ts +30 -0
  104. package/dist/types/app.js +2 -0
  105. package/dist/types/config.d.ts +25 -0
  106. package/dist/types/config.js +2 -0
  107. package/dist/types/core.d.ts +100 -0
  108. package/dist/types/core.js +2 -0
  109. package/dist/types/diff.d.ts +55 -0
  110. package/dist/types/diff.js +2 -0
  111. package/dist/types/plugin.d.ts +41 -0
  112. package/dist/types/plugin.js +2 -0
  113. package/dist/types/trust.d.ts +120 -0
  114. package/dist/types/trust.js +2 -0
  115. package/dist/types.d.ts +8 -211
  116. package/docs/PRD.md +187 -109
  117. package/docs/plugin-contract.md +61 -0
  118. package/docs/release-notes-draft.md +40 -0
  119. package/docs/rules-catalog.md +49 -0
  120. package/docs/trust-core-release-checklist.md +87 -0
  121. package/package.json +6 -3
  122. package/packages/vscode-drift/src/code-actions.ts +1 -1
  123. package/schemas/drift-ai-output.v1.json +162 -0
  124. package/schemas/drift-report.v1.json +151 -0
  125. package/schemas/drift-trust.v1.json +131 -0
  126. package/scripts/smoke-repo.mjs +394 -0
  127. package/src/analyzer.ts +484 -155
  128. package/src/benchmark.ts +266 -0
  129. package/src/cli.ts +840 -85
  130. package/src/config.ts +19 -2
  131. package/src/diff.ts +84 -10
  132. package/src/doctor.ts +173 -0
  133. package/src/format.ts +81 -0
  134. package/src/git.ts +16 -0
  135. package/src/guard-types.ts +64 -0
  136. package/src/guard.ts +324 -0
  137. package/src/index.ts +83 -0
  138. package/src/init.ts +298 -0
  139. package/src/map-cycles.ts +38 -0
  140. package/src/map-svg.ts +124 -0
  141. package/src/map.ts +111 -142
  142. package/src/metrics.ts +78 -59
  143. package/src/output-metadata.ts +30 -0
  144. package/src/plugins-capabilities.ts +36 -0
  145. package/src/plugins-messages.ts +35 -0
  146. package/src/plugins-rules.ts +296 -0
  147. package/src/plugins.ts +148 -27
  148. package/src/printer.ts +4 -0
  149. package/src/reporter-constants.ts +46 -0
  150. package/src/reporter.ts +64 -65
  151. package/src/review.ts +6 -4
  152. package/src/rules/comments.ts +2 -2
  153. package/src/rules/complexity.ts +2 -7
  154. package/src/rules/nesting.ts +3 -13
  155. package/src/rules/phase0-basic.ts +11 -12
  156. package/src/rules/phase3-configurable.ts +39 -26
  157. package/src/rules/shared.ts +31 -3
  158. package/src/saas/constants.ts +56 -0
  159. package/src/saas/dashboard.ts +172 -0
  160. package/src/saas/errors.ts +45 -0
  161. package/src/saas/helpers.ts +140 -0
  162. package/src/saas/ingest.ts +278 -0
  163. package/src/saas/organization.ts +99 -0
  164. package/src/saas/plan-change.ts +19 -0
  165. package/src/saas/store.ts +172 -0
  166. package/src/saas/types.ts +216 -0
  167. package/src/saas.ts +49 -433
  168. package/src/sarif.ts +232 -0
  169. package/src/trust-advanced.ts +99 -0
  170. package/src/trust-kpi-fs.ts +169 -0
  171. package/src/trust-kpi-parse.ts +219 -0
  172. package/src/trust-kpi-types.ts +19 -0
  173. package/src/trust-kpi.ts +210 -0
  174. package/src/trust-policy.ts +246 -0
  175. package/src/trust-render.ts +61 -0
  176. package/src/trust-scoring.ts +231 -0
  177. package/src/trust.ts +260 -0
  178. package/src/types/app.ts +30 -0
  179. package/src/types/config.ts +27 -0
  180. package/src/types/core.ts +105 -0
  181. package/src/types/diff.ts +61 -0
  182. package/src/types/plugin.ts +46 -0
  183. package/src/types/trust.ts +134 -0
  184. package/src/types.ts +78 -238
  185. package/tests/cli-sarif.test.ts +92 -0
  186. package/tests/diff.test.ts +124 -0
  187. package/tests/format.test.ts +157 -0
  188. package/tests/new-features.test.ts +80 -1
  189. package/tests/phase1-init-doctor-guard.test.ts +199 -0
  190. package/tests/plugins.test.ts +219 -0
  191. package/tests/rules.test.ts +23 -1
  192. package/tests/saas-foundation.test.ts +358 -1
  193. package/tests/sarif.test.ts +160 -0
  194. package/tests/trust-kpi.test.ts +147 -0
  195. 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
- Scan your TypeScript project for AI-generated technical debt in CI.
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
- uses: eduardbar/drift@v1
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 to use | `latest` |
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: PR gate
48
+ ## Example: consume outputs
32
49
 
33
50
  ```yaml
34
- name: Drift Check
35
- on: [pull_request]
36
-
37
- jobs:
38
- drift:
39
- runs-on: ubuntu-latest
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: 'latest'
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
- # Run scan write JSON to tmp, progress to stderr (already separate)
49
- drift scan ${{ inputs.path }} --ai > "$TMPFILE" 2>/dev/null || true
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
- # Extract score and grade from AIOutput JSON
52
- SCORE=$(node -e "const d=JSON.parse(require('fs').readFileSync('$TMPFILE','utf8')); console.log(d.summary.score)")
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
- rm "$TMPFILE"
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
- echo "score=$SCORE" >> "$GITHUB_OUTPUT"
58
- echo "grade=$GRADE" >> "$GITHUB_OUTPUT"
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
- echo "Drift Score: $SCORE/100 ($GRADE)"
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@v4
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@v4
32
+ uses: actions/setup-node@v5
33
33
  with:
34
- node-version: '20'
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@v4
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@v4
26
+ uses: actions/setup-node@v5
27
27
  with:
28
- node-version: '20'
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@v4
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@v4
22
+ uses: actions/setup-node@v5
22
23
  with:
23
- node-version: '20'
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 markdown
30
- run: npx @eduardbar/drift review --base "origin/${{ github.base_ref }}" --comment > drift-review.md
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+="$(cat drift-review.md)"
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 drift-review.md
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