energy-state-analyzer 0.2.0 β 0.3.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/.markdownlint.json +4 -0
- package/CHANGELOG.md +10 -1
- package/README.md +99 -0
- package/action.yml +87 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/package.json +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
last_commit_released:
|
|
2
|
+
last_commit_released: 489d195ffe125b4e3ebcb9905f561f2d94daf1ad
|
|
3
3
|
name: energy-state-analyzer
|
|
4
4
|
updaters:
|
|
5
5
|
- command: npm version {version} --no-git-tag-version --allow-same-version
|
|
@@ -11,6 +11,15 @@ All notable changes to the "energy-state-analyzer" extension are generated by
|
|
|
11
11
|
[EasyBuild.ShipIt](https://github.com/easybuild-org/EasyBuild.ShipIt) from
|
|
12
12
|
[Conventional Commits](https://www.conventionalcommits.org/).
|
|
13
13
|
|
|
14
|
+
## 0.3.0 - 2026-08-25
|
|
15
|
+
|
|
16
|
+
### π Features
|
|
17
|
+
|
|
18
|
+
* Add Kotlin support and lazy-load language grammars (#21) ([3a1078e](https://github.com/cardamomcode/energy-state-analyzer/commit/3a1078ecac31ea1574635cb01f794e767b098183))
|
|
19
|
+
* Repo scanning, PR baseline diffing, and a complexity-scored human report (#20) ([489d195](https://github.com/cardamomcode/energy-state-analyzer/commit/489d195ffe125b4e3ebcb9905f561f2d94daf1ad))
|
|
20
|
+
|
|
21
|
+
<strong><small>[View changes on Github](https://github.com/cardamomcode/energy-state-analyzer/compare/b3dda28dffdd3dc8c6cee2d5b07d5a99d50761cb..489d195ffe125b4e3ebcb9905f561f2d94daf1ad)</small></strong>
|
|
22
|
+
|
|
14
23
|
## 0.2.0 - 2026-08-25
|
|
15
24
|
|
|
16
25
|
### π Features
|
package/README.md
CHANGED
|
@@ -102,6 +102,105 @@ npx energy-state-analyzer path/to/file.py \
|
|
|
102
102
|
|
|
103
103
|
All threshold flags are optional: `--medium-nesting`, `--high-nesting`, `--medium-cyclomatic`, `--high-cyclomatic`, `--medium-cognitive`, `--high-cognitive`.
|
|
104
104
|
|
|
105
|
+
### Scanning a repo or subtree
|
|
106
|
+
|
|
107
|
+
Pass more than one path, a directory, or a `dir/**/*.ext`-style pattern to scan every supported file underneath it (skipping `node_modules`, `.git`, `dist`, `out`, `build`, `.next`, `coverage`, `.vscode-test`) and get an aggregated report instead of a single file's violations:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npx energy-state-analyzer src --report md
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
# Energy State Report
|
|
115
|
+
|
|
116
|
+
**3 files scanned** β 2 clean, 1 with violations
|
|
117
|
+
|
|
118
|
+
| File | Score | High | Medium | Low |
|
|
119
|
+
| --- | --- | --- | --- | --- |
|
|
120
|
+
| src/foo.py | 13 | 1 | 1 | 0 |
|
|
121
|
+
| src/bar.ts | 0 | 0 | 0 | 0 |
|
|
122
|
+
| src/baz.fs | 0 | 0 | 0 | 0 |
|
|
123
|
+
|
|
124
|
+
**Total score: 13** (1 high, 1 medium, 0 low)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`--report json` prints the same data as a structured `{ files, totalScore, totalCounts }` object instead. The per-file **score** is a simple heuristic β `1Γlow + 4Γmedium + 9Γhigh` violation counts β meant for spotting hotspots and tracking direction over time, not a certified complexity metric.
|
|
128
|
+
|
|
129
|
+
Only one glob shape is supported: a trailing `**/*.ext` pattern on an otherwise literal directory prefix (e.g. `src/**/*.py`). There's no brace expansion, negation, or mid-path wildcards β pass explicit directories/files for anything more complex.
|
|
130
|
+
|
|
131
|
+
#### A report for humans: `--report human`
|
|
132
|
+
|
|
133
|
+
`--report md`/`--report json` are compact, built for scripts and PR comments. `--report human` produces a longer, prose-and-tables report meant to be read by a person auditing a repo or subtree: a section per flagged file, each with its findings translated into plain language, followed by a repo-wide "Total evaluation":
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npx energy-state-analyzer src --report human
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
# Energy State Report
|
|
141
|
+
|
|
142
|
+
## Score legend
|
|
143
|
+
|
|
144
|
+
| Score | Risk | Roughly | Cyclomatic/cognitive complexity |
|
|
145
|
+
| --- | --- | --- | --- |
|
|
146
|
+
| 0.0 | None | No violations found | β |
|
|
147
|
+
| 0.1β3.9 | Low | Simple, easy to test exhaustively | 1β10 |
|
|
148
|
+
| 4.0β6.9 | Medium | Getting harder to cover with tests | 11β20 |
|
|
149
|
+
| 7.0β8.9 | High | Complex, testing all paths is impractical | 21β50 |
|
|
150
|
+
| 9.0β10.0 | Critical | Effectively untestable | 50+ |
|
|
151
|
+
|
|
152
|
+
**25 files scanned** β 8 clean, 17 flagged
|
|
153
|
+
|
|
154
|
+
## src/foo.py β High (score 7.8)
|
|
155
|
+
|
|
156
|
+
- **Cyclomatic complexity**: 1 function scores 34 β score 7.8 (High): complex, testing all paths is impractical.
|
|
157
|
+
- **Primitive obsession**: 2 findings (2 medium) β adjacent same-typed values a caller could silently swap without the compiler noticing.
|
|
158
|
+
|
|
159
|
+
...
|
|
160
|
+
|
|
161
|
+
## Total evaluation
|
|
162
|
+
|
|
163
|
+
**Repo score: 7.8 (High)** β driven by the worst file in the scan, `src/foo.py` (complex, testing all paths is impractical).
|
|
164
|
+
|
|
165
|
+
| Risk | Files |
|
|
166
|
+
| --- | --- |
|
|
167
|
+
| None | 8 |
|
|
168
|
+
| Low | 12 |
|
|
169
|
+
| Medium | 3 |
|
|
170
|
+
| High | 2 |
|
|
171
|
+
| Critical | 0 |
|
|
172
|
+
|
|
173
|
+
**51 total findings** (1 high, 25 medium, 25 low) β breadth of issues across the scan, independent of peak severity.
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Risk is reported on a 0.0β10.0 complexity score, sorted into the same None/Low/Medium/High/Critical levels used elsewhere in this tool, rather than a bespoke label set. The score is a direct re-expression of the McCabe risk table above: cyclomatic/cognitive complexity numbers are converted onto it by linear interpolation anchored at the same 10/20/50 breakpoints (see [Interpreting the Scores](#interpreting-the-scores)), so "High" here means the same thing it always has in this project, just expressed as a single number. Every other detector reports a finding count and severity instead, since it flags a pattern rather than a path count β a file with only non-complexity findings gets a fixed score from its worst one (Low 2.0 / Medium 5.0 / High 7.5), which can never reach Critical (Critical is reserved for genuinely extreme complexity).
|
|
177
|
+
|
|
178
|
+
Both the per-file score and the repo-wide "Repo score" are the **maximum** found, not an average. Averaging a file's (or a repo's) scores lets one severely complex function or file hide behind many trivial ones β nine functions at complexity 2 and one at 60 average to about 8 (which itself would still misleadingly read as "Low"), hiding exactly the function most worth fixing. Total finding counts are reported separately as a breadth indicator, deliberately not folded into the same number. Flagged files are listed worst-first.
|
|
179
|
+
|
|
180
|
+
### Diffing a PR against a base branch
|
|
181
|
+
|
|
182
|
+
`--base-ref <ref>` compares the current working tree against a git ref, so a GitHub Actions job can report whether a PR increased or decreased complexity relative to its base branch:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
npx energy-state-analyzer --base-ref origin/main --report md
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
With no path arguments, changed files are discovered via `git diff --name-only <ref>...HEAD`; pass explicit paths to override that. Each changed file's pre-PR content is read with `git show <ref>:<path>` and re-analyzed in memory β a file that doesn't exist at the base ref (new file, or a rename `git diff` didn't resolve) is reported as `new` rather than erroring out.
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
# Energy State Diff vs `origin/main`
|
|
192
|
+
|
|
193
|
+
| File | Base | Head | Ξ | Status |
|
|
194
|
+
| --- | --- | --- | --- | --- |
|
|
195
|
+
| src/foo.py | 4 | 13 | +9 | π΄ worsened |
|
|
196
|
+
| src/bar.ts | 9 | 0 | -9 | π’ improved |
|
|
197
|
+
| src/new.py | β | 5 | β | π new |
|
|
198
|
+
|
|
199
|
+
_2 files changed, 1 worsened, 1 improved, 1 new._
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The exit code in every mode (single-file, scan, or diff) follows the same rule: `1` if any medium/high-severity violation exists in the current (head) code, `0` otherwise β whether a diff made things better or worse is visible in the report, not encoded as a separate exit code. `energy-state-cli <single-file>` with no other flags keeps its original behavior (flat JSON violation array, same exit rule) unchanged.
|
|
203
|
+
|
|
105
204
|
## Requirements
|
|
106
205
|
|
|
107
206
|
The extension activates automatically when you open a Python, F#, or TypeScript file; it bundles its own grammars for parsing (via `web-tree-sitter`), so no external tools are required. F# files only get a `fsharp` language ID (and so trigger analysis) if you have an F# language extension installed (e.g. [Ionide](https://ionide.io/)) β VS Code otherwise treats `.fs` files as plain text.
|
package/action.yml
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
name: 'Energy State Report'
|
|
2
|
+
description: 'Run energy-state-analyzer over a path or PR diff and post a Markdown report as a sticky PR comment.'
|
|
3
|
+
author: 'dbrattli'
|
|
4
|
+
branding:
|
|
5
|
+
icon: 'zap'
|
|
6
|
+
color: 'yellow'
|
|
7
|
+
|
|
8
|
+
# Requires the caller's checkout step to use `fetch-depth: 0` when base-ref is
|
|
9
|
+
# set, so both the head and base commits are present locally for `git diff`/
|
|
10
|
+
# `git show` (see src/cliModes.ts's changedFilesFromGit/readAtRef).
|
|
11
|
+
|
|
12
|
+
inputs:
|
|
13
|
+
path:
|
|
14
|
+
description: 'Path(s) to scan, space-separated. Ignored in diff mode (base-ref set), which only scans changed files.'
|
|
15
|
+
required: false
|
|
16
|
+
default: '.'
|
|
17
|
+
base-ref:
|
|
18
|
+
description: 'Git ref to diff the current HEAD against (e.g. origin/main). When set, runs in PR-diff mode instead of a full scan.'
|
|
19
|
+
required: false
|
|
20
|
+
default: ''
|
|
21
|
+
report-format:
|
|
22
|
+
description: 'Report format: md, json, or human. Diff mode (base-ref set) only supports md/json.'
|
|
23
|
+
required: false
|
|
24
|
+
default: 'md'
|
|
25
|
+
version:
|
|
26
|
+
description: 'energy-state-analyzer npm version or dist-tag to run via npx (e.g. 0.2.0, latest).'
|
|
27
|
+
required: false
|
|
28
|
+
default: 'latest'
|
|
29
|
+
fail-on-regression:
|
|
30
|
+
description: 'Fail the step when the analyzer reports a blocking result (scan mode: any medium/high violation; diff mode: any worsened file).'
|
|
31
|
+
required: false
|
|
32
|
+
default: 'true'
|
|
33
|
+
post-comment:
|
|
34
|
+
description: 'Post the report as a sticky PR comment. No-ops outside pull_request events.'
|
|
35
|
+
required: false
|
|
36
|
+
default: 'true'
|
|
37
|
+
comment-header:
|
|
38
|
+
description: 'Header used to identify and update the sticky PR comment across pushes.'
|
|
39
|
+
required: false
|
|
40
|
+
default: 'energy-state-report'
|
|
41
|
+
|
|
42
|
+
outputs:
|
|
43
|
+
report-path:
|
|
44
|
+
description: 'Path to the generated report file.'
|
|
45
|
+
value: ${{ steps.run.outputs.report-path }}
|
|
46
|
+
exit-code:
|
|
47
|
+
description: 'Exit code returned by energy-state-analyzer (0 = clean, 1 = blocking violations/regressions).'
|
|
48
|
+
value: ${{ steps.run.outputs.exit-code }}
|
|
49
|
+
|
|
50
|
+
runs:
|
|
51
|
+
using: composite
|
|
52
|
+
steps:
|
|
53
|
+
- name: Setup Node
|
|
54
|
+
uses: actions/setup-node@v5
|
|
55
|
+
with:
|
|
56
|
+
node-version: 22
|
|
57
|
+
|
|
58
|
+
- name: Run energy-state-analyzer
|
|
59
|
+
id: run
|
|
60
|
+
shell: bash
|
|
61
|
+
run: |
|
|
62
|
+
set +e
|
|
63
|
+
if [ -n "${{ inputs.base-ref }}" ]; then
|
|
64
|
+
npx --yes "energy-state-analyzer@${{ inputs.version }}" --base-ref "${{ inputs.base-ref }}" --report "${{ inputs.report-format }}" > energy-state-report.md
|
|
65
|
+
else
|
|
66
|
+
npx --yes "energy-state-analyzer@${{ inputs.version }}" ${{ inputs.path }} --report "${{ inputs.report-format }}" > energy-state-report.md
|
|
67
|
+
fi
|
|
68
|
+
code=$?
|
|
69
|
+
echo "exit-code=$code" >> "$GITHUB_OUTPUT"
|
|
70
|
+
echo "report-path=energy-state-report.md" >> "$GITHUB_OUTPUT"
|
|
71
|
+
# decision: always exit 0 here regardless of the analyzer's own exit code β a blocking
|
|
72
|
+
# result must still reach the comment step below (marocchino/sticky-pull-request-comment
|
|
73
|
+
# needs the report file to exist), so failure is deferred to the dedicated step at the
|
|
74
|
+
# end instead of short-circuiting the rest of this composite action.
|
|
75
|
+
exit 0
|
|
76
|
+
|
|
77
|
+
- name: Comment on PR
|
|
78
|
+
if: ${{ inputs.post-comment == 'true' && github.event_name == 'pull_request' }}
|
|
79
|
+
uses: marocchino/sticky-pull-request-comment@v2
|
|
80
|
+
with:
|
|
81
|
+
header: ${{ inputs.comment-header }}
|
|
82
|
+
path: ${{ steps.run.outputs.report-path }}
|
|
83
|
+
|
|
84
|
+
- name: Fail on regression
|
|
85
|
+
if: ${{ inputs.fail-on-regression == 'true' && steps.run.outputs.exit-code != '0' }}
|
|
86
|
+
shell: bash
|
|
87
|
+
run: exit ${{ steps.run.outputs.exit-code }}
|