energy-state-analyzer 0.7.0 → 0.8.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/.esaignore CHANGED
@@ -1,6 +1,6 @@
1
1
  # Paths the energy-state analyzer itself should not scan.
2
2
  # One pattern per line — a literal path/directory (also matches at any depth if it has no
3
- # '/'), or a single '*' basename glob. See src/core/esaignore.ts for the exact rules.
3
+ # '/'), or a single '*' basename glob. See src/Core/Esaignore.fs for the exact rules.
4
4
 
5
5
  # Deliberately bad/deeply-nested code used only to exercise the detectors themselves.
6
6
  src/test/fixtures
package/.vscodeignore CHANGED
@@ -3,14 +3,20 @@
3
3
  out/**
4
4
  node_modules/**
5
5
  src/**
6
+ tests/**
7
+ cli/**
8
+ fable-out/**
9
+ fable-tests/**
6
10
  .gitignore
7
11
  .yarnrc
8
12
  webpack.config.js
9
13
  vsc-extension-quickstart.md
10
- **/tsconfig.json
11
- **/eslint.config.mjs
12
14
  **/*.map
13
- **/*.ts
15
+ **/*.fs
16
+ **/*.fsproj
17
+ **/bin/**
18
+ **/obj/**
19
+ **/fable_modules/**
14
20
  **/.vscode-test.*
15
21
  .claude/**
16
22
  CLAUDE.md
package/AGENTS.md CHANGED
@@ -1,84 +1,100 @@
1
1
  # AGENTS.md
2
2
 
3
- This file provides guidance to AI coding agents when working with code in this repository.
3
+ ## Project overview
4
4
 
5
- ## Project Overview
5
+ Energy State Analyzer is a VS Code extension and CLI that analyze Python, F#, TypeScript, and
6
+ Kotlin through `web-tree-sitter` grammars in `grammars/`. Product source is F# and is compiled
7
+ by Fable 5 to JavaScript before webpack packages the extension and CLI.
6
8
 
7
- Energy State Analyzer is a VS Code extension that visualizes "energy states" in Python, F#, TypeScript, and Kotlin code via real-time static analysis. It parses source with `web-tree-sitter` (per-language WASM grammars in `grammars/`) and highlights code that is complex, deeply nested, or otherwise hard to maintain using editor decorations, gutter icons, and Problems-panel diagnostics.
9
+ See `energy-state.md` for the original design rationale and `docs/fable-rewrite-plan.md` for the
10
+ completed migration and current build graph.
8
11
 
9
- See `energy-state.md` for the original design doc (energy-state principle, planned "detection agents", and known issues/next steps at project inception).
12
+ ## Build commands
10
13
 
11
- ## Build Commands
12
-
13
- Commands are wrapped in a `Justfile`; run `just --list` to see all of them. Prefer these over calling `npm run` directly.
14
+ Prefer `Justfile` recipes:
14
15
 
15
16
  ```bash
16
17
  just install # npm install
17
- just build # Build extension bundle via webpack (dev mode)
18
- just watch # Webpack in watch mode
19
- just lint # ESLint over src/**/*.ts
20
- just format # Format src/**/*.ts in place with Prettier
21
- just format-check # Check formatting without writing changes (used by CI)
22
- just analyze # Run the CLI's own analyzer over src/ (or `just analyze <path...>` for specific files/dirs)
23
- just test # compile-tests + compile + lint, then run the VS Code extension test host
24
- just pack # Production build + package into a .vsix via vsce
25
- just clean # Remove build artifacts (dist, out, *.vsix)
18
+ just fable # F# -> Fable JavaScript in fable-out/
19
+ just build # Fable + webpack development bundles
20
+ just watch # Fable and webpack watchers
21
+ just lint # Fantomas check over F# source and tests
22
+ just format # Format F# source and tests
23
+ just format-check # CI formatting check
24
+ just analyze # Build, then run the F# CLI against src/ or supplied paths
25
+ just test # Fable Scriptorium suite under Node
26
+ just pack # Production bundles + .vsix
27
+ just clean # Generated outputs and packages
26
28
  ```
27
29
 
28
- The underlying `npm run` scripts (`compile`, `watch`, `package`, `lint`, `format`, `format-check`, `compile-tests`, `watch-tests`, `pretest`, `test`, `analyze`) still work directly if you need finer control than the Justfile recipes give you.
29
-
30
- To run and debug the extension interactively, press `F5` in VS Code — this launches an Extension Development Host with the extension loaded, per `.vscode/launch.json`.
30
+ Fable commands use `--lang javascript --noCache`. Fable emits ESM, so test output receives a
31
+ `{"type":"module"}` shim. Webpack consumes those ESM files and preserves the public CommonJS
32
+ contracts: `dist/extension.js`, `dist/cli.js`, and the CLI shebang.
31
33
 
32
- There is a single test suite (`src/test/extension.test.ts`); there's no mechanism yet to run a single test by name — use the Extension Test Runner's Testing view in VS Code, or edit the suite temporarily with `.only`.
34
+ Press `F5` to launch an Extension Development Host; `.vscode/tasks.json` runs the Fable and
35
+ webpack watch pipeline.
33
36
 
34
37
  ## Architecture
35
38
 
36
- Everything lives in one file, `src/extension.ts`, structured as:
37
-
38
- 1. **Activation (`activate`)** — initializes the tree-sitter `Parser`, creates decoration types, registers the `energy-state-analyzer.analyze` command, and wires up editor/document change listeners to re-analyze on the fly. Each language's grammar (`grammars/tree-sitter-<language>.wasm`, path resolved via `context.extensionPath`) is loaded lazily on first use of that language, not up front — see `getOrLoadLanguage`. Activation is gated by the `onLanguage:*` entries in `package.json` (python, fsharp, typescript, kotlin).
39
- 2. **Analysis pipeline (`analyzeDocument`)** — parses the active document's text into a tree-sitter AST, then runs a fixed set of independent detector passes over it, each returning `EnergyViolation[]`:
40
- - `analyzeNesting` flags `if`/`for`/`while`/`with` nesting deeper than 3 levels.
41
- - `analyzeFunctionComplexity` computes cyclomatic complexity per function, flags >10.
42
- - `analyzeFileCoherence` flags files with too many functions or imports (utils/helpers sprawl).
43
- - `analyzeMagicValues` flags "magic" numeric/string literals outside constant context.
44
- - `analyzeParameterCount` — flags functions with >5 parameters.
45
- - `analyzeInversionOpportunities` flags large dominant if-blocks, nested validation chains, and deep if-nesting that could be rewritten as guard clauses / early returns.
46
- - `extractTypeInformation` — walks the AST separately to collect function/class/variable/import type info (currently only logged; scaffolding for future features, not yet used for violations).
47
- Each detector does its own `traverse(node)` walk of the tree-sitter tree; there's no shared visitor abstraction.
48
- `src/core/analyze.ts` (the actual current pipeline entry point) runs a larger, up-to-date set of these plus `applySuppressions` (`src/core/suppressions.ts`) as a final pass — it filters out violations covered by an `esa-ignore`/`esa-ignore-file` comment and emits low-severity `suppression` findings for directives that are unused or name an unknown type. See `docs/detectors/suppression.md`.
49
- 3. **Presentation** — `applyDecorations` maps violations to `vscode.TextEditorDecorationType` ranges (color/severity: red=high, yellow=medium, green=low, rendered as background tint + gutter lightning-bolt icon via `createLightningIcon`), and `updateProblemsPanel` mirrors the same violations into a `vscode.DiagnosticCollection` so they also show in the Problems panel.
50
- 4. **Violation model** — `EnergyViolation { line, column, type, severity, message }`, with `type` and `severity` string-literal unions backed by the `VIOLATION_TYPE`/`SEVERITY` constant objects near the top of the file (keep these two in sync when adding a new detector).
51
-
52
- ### Adding a new detector
53
-
54
- Follow the existing pattern: write an `analyze<Thing>(tree, document): EnergyViolation[]` function that walks `tree.rootNode`, push it into the list in `analyzeDocument`, and add a new `VIOLATION_TYPE` entry if it's a new category. If the violation needs special range highlighting, add a case in `applyDecorations`.
55
-
56
- ### Build/packaging notes
57
-
58
- - Webpack bundles `src/extension.ts` `dist/extension.js` (CommonJS, `vscode` module treated as external).
59
- - `web-tree-sitter`'s own `tree-sitter.wasm` is copied into `dist/` via `CopyWebpackPlugin` (webpack.config.js); the per-language grammar WASMs in `grammars/` ship separately and are loaded at runtime by path, not bundled.
60
- - `tsconfig.json` targets ES2022/commonjs with `strict: true`.
61
-
62
- ## Before Committing or Opening a PR
63
-
64
- Run `just format`, `just lint`, and `just analyze` (this project dogfoods its own analyzer over `src/`) before every commit or PR, and fix what they flag. Don't rely on CI to catch formatting, lint, or energy-state violations you could have caught locally.
65
-
66
- If satisfying `just analyze` on your change requires refactoring existing code (e.g. splitting a file to fix a coherence violation, extracting a function to fix complexity/nesting) rather than just the new code you're adding, do that refactor as its own preceding PR, merged before the PR with the actual change. Don't mix the two in one PR — a refactor bundled with a behavior change makes the diff hard to review and obscures what the change is actually about.
67
-
68
- ## Releasing
69
-
70
- Release automation runs through EasyBuild.ShipIt (see `RELEASING.md`). Use
71
- Conventional Commit subjects (`feat:`, `fix:`, `docs:`, `chore:`, `ci:`, etc.)
72
- for commits and PR titles — CI enforces this on PR titles, and ShipIt uses
73
- them to generate `CHANGELOG.md` and open release PRs. Do not hand-edit
74
- generated changelog entries or bump `package.json`'s version manually.
75
-
76
- ## Agent Decision Comments
77
-
78
- This repository uses Agent Decision Comments.
79
- See `AGENT_DECISION_COMMENTS.md` for the locally adopted convention.
80
- Upstream releases: https://github.com/dbrattli/adc/releases
81
-
82
- Before modifying code, read the ADCs already governing it.
83
- Treat them as active constraints and justify any change explicitly.
84
- Add ADCs for non-obvious rationale introduced by your change.
39
+ Keep every F# file at or below 12 functions and 10 import sources. `src/EnergyState.fsproj` has
40
+ `EnableDefaultCompileItems=false` behavior, so add every new `.fs` file explicitly in dependency
41
+ order.
42
+
43
+ - **`src/Core/`**: host-independent synchronous detector pipeline, tree-sitter facade,
44
+ suppression, scanning, and report rendering. `Analyze.fs` is the one detector composition
45
+ point shared by extension and CLI.
46
+ - **`src/Languages/`**: grammar-specific `LanguageAdapter` records for the four supported
47
+ analyzed languages.
48
+ - **`src/Extension/`**: VS Code Fable facade, configuration boundary, grammar cache, analysis
49
+ orchestration, editor/Problems presentation, and `Extension.fs` composition root. The narrow
50
+ VS Code bindings (`Host`, `Document`, `Workspace`, `Presentation`, `Diagnostics`,
51
+ `DiagnosticValues`, `Identity`) live in the `src/Extension/Vscode/` subfolder with their
52
+ `Vscode*` prefix dropped from both path and module name. `DecorationModel.fs`,
53
+ `DiagnosticModel.fs`, and `ConfigurationValues.fs` remain pure and have direct Scriptorium
54
+ coverage.
55
+ - **`src/Cli*.fs` and `cli/Main.fs`**: CLI argument parsing, modes, runtime, narrow Node
56
+ bindings, and Fable entry point. Scan, legacy single-file, `--report`, `--base-ref`, and
57
+ thresholds share the Core pipeline.
58
+ - **`tests/`**: F# Scriptorium suites. `src/test/fixtures/` is retained only as multi-language
59
+ analyzer input, including `.ts` fixtures; it is not TypeScript product or test code.
60
+
61
+ The extension composition root owns lifecycle state, grammar caches, decorations, diagnostics,
62
+ commands, and editor/document/configuration event subscriptions. Presentation never leaks into
63
+ `Core/`; `Core/TreeSitter.fs` is the only web-tree-sitter facade.
64
+
65
+ `EnergyViolation` is the DU/record model in `src/Core/Violation.fs`. Add a new detector by adding
66
+ its `Detector` to `Core/Analyze.fs`, and add a `ViolationType` case plus special presentation
67
+ mapping only when required.
68
+
69
+ ## Build and packaging
70
+
71
+ `npm run fable` writes generated JavaScript and `fable_modules` to ignored `fable-out/`. It first
72
+ compiles the library project for the extension entry, then the CLI project for `Main.js`.
73
+ `webpack.config.js` consumes only generated `.js`, treats `vscode` as an external, bundles
74
+ `web-tree-sitter`, copies its WASM, and emits the fixed output names. Do not add `ts-loader`,
75
+ `tsconfig.json`, or TypeScript product tooling back.
76
+
77
+ The `.vsix` and npm package ship transpiled bundles, grammar WASMs, and metadata only. Keep F#
78
+ sources, tests, `fable-out/`, `fable_modules/`, `bin/`, `obj/`, and maps excluded by ignore files.
79
+
80
+ ## Before committing or opening a PR
81
+
82
+ Run `just format`, `just lint`, and `just analyze`. Triage every analyzer finding:
83
+
84
+ - **Real violation**: fix it.
85
+ - **Wrong implementation**: add a fixture and integration test, then fix the detector.
86
+ - **Wrong threshold**: adjust the detector/configuration threshold with a boundary test.
87
+ - **Legitimate exception**: use a reasoned `esa-ignore` directive; stale directives remain
88
+ findings.
89
+
90
+ Do not suppress a finding merely to make a check pass. If a required cleanup is unrelated to the
91
+ behavior change, land it separately rather than mixing it into the behavioral change.
92
+
93
+ ## Releasing and decision comments
94
+
95
+ ShipIt release automation is described in `RELEASING.md`. Use Conventional Commit subjects; do
96
+ not hand-edit generated changelog entries or package versions.
97
+
98
+ This repository uses Agent Decision Comments. Read `AGENT_DECISION_COMMENTS.md` before modifying
99
+ governed code, preserve active `decision:` and `invariant:` comments, and add one for a
100
+ non-obvious durable design choice.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- last_commit_released: 5d63923aba3248095bda4074aff14bb133f4f65b
2
+ last_commit_released: 01777cc27c9c08136661a2f1e21409cdf94f05ce
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,22 @@ 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.8.0 - 2026-08-31
15
+
16
+ ### 🚀 Features
17
+
18
+ * Rewrite analyzer in F# for Fable (#46) ([8aaf666](https://github.com/cardamomcode/energy-state-analyzer/commit/8aaf6666c537b9f1655175805128749e56b8d933))
19
+ * *(core)* Calculate McCabe cyclomatic complexity (#55) ([9a42b6a](https://github.com/cardamomcode/energy-state-analyzer/commit/9a42b6a68cf94c7583c1d1a8282ddffa961275c7))
20
+ * *(core)* Add include-test-files toggle for magic detectors (#57) ([271e15c](https://github.com/cardamomcode/energy-state-analyzer/commit/271e15c8a914f550a5079789750a71caba34181d))
21
+ * *(extension)* Split src/extension.ts by domain (#30) (#42) ([b9f1ba3](https://github.com/cardamomcode/energy-state-analyzer/commit/b9f1ba37d93cbfd34275814a7bdbfb591cd9ae06))
22
+
23
+ ### 🐞 Bug Fixes
24
+
25
+ * *(extension)* Construct VS Code diagnostics correctly (#54) ([c9b2f3f](https://github.com/cardamomcode/energy-state-analyzer/commit/c9b2f3f4a8c1c6550776bc1b1b8c611483fba7b3))
26
+ * *(suppressions)* Anchor esa-ignore directive regex so prose isn't matched (#45) ([c99b26a](https://github.com/cardamomcode/energy-state-analyzer/commit/c99b26ae425f16c16460b42d474dd52934f3610d))
27
+
28
+ <strong><small>[View changes on Github](https://github.com/cardamomcode/energy-state-analyzer/compare/5d63923aba3248095bda4074aff14bb133f4f65b..01777cc27c9c08136661a2f1e21409cdf94f05ce)</small></strong>
29
+
14
30
  ## 0.7.0 - 2026-08-28
15
31
 
16
32
  ### 🚀 Features
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Energy State Analyzer
2
2
 
3
- Visualizes "energy states" in Python, F#, and TypeScript code as you edit: parts of a file that are complex, deeply nested, or otherwise harder to understand and maintain get highlighted with colored gutter icons, inline decorations, and entries in the Problems panel.
3
+ Visualizes "energy states" in Python, F#, TypeScript, and Kotlin code as you edit: parts of a file that are complex, deeply nested, or otherwise harder to understand and maintain get highlighted with colored gutter icons, inline decorations, and entries in the Problems panel.
4
4
 
5
5
  ## Features
6
6
 
7
- Real-time analysis of the active Python, F#, or TypeScript file, re-run on every edit and on editor focus change, via these detectors (see [docs/detectors](docs/detectors/README.md) for full detail on each):
7
+ Real-time analysis of the active Python, F#, TypeScript, or Kotlin file, re-run on every edit and on editor focus change, via these detectors (see [docs/detectors](docs/detectors/README.md) for full detail on each):
8
8
 
9
9
  - [Cyclomatic complexity](docs/detectors/cyclomatic-complexity.md), too many independent execution paths.
10
10
  - [Cognitive complexity](docs/detectors/cognitive-complexity.md), too hard to read due to nesting.
@@ -36,14 +36,31 @@ The name is a deliberate analogy to thermodynamics: a function's "energy" is its
36
36
  The same detectors also run headlessly, without VS Code, useful for CI or for an AI coding agent that wants to check the complexity of code it just generated and keep refactoring until it's clean:
37
37
 
38
38
  ```bash
39
- npx energy-state-analyzer path/to/file.py # or .fs / .fsx / .ts
39
+ npx energy-state-analyzer path/to/file.py # or .fs / .fsx / .ts / .kt
40
40
  ```
41
41
 
42
42
  See [docs/cli.md](docs/cli.md) for scanning a whole repo, aggregated markdown/JSON/human reports, and diffing a PR against a base branch.
43
43
 
44
44
  ## Requirements
45
45
 
46
- 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.
46
+ The extension activates automatically when you open a Python, F#, TypeScript, or Kotlin 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.
47
+
48
+ ## Development
49
+
50
+ Product and test sources are F#. Install the pinned .NET tools (Fable and Fantomas) and npm
51
+ dependencies, then use the wrapped commands:
52
+
53
+ ```bash
54
+ just setup
55
+ just install
56
+ just build
57
+ just test
58
+ ```
59
+
60
+ Fable emits JavaScript with `--lang javascript --noCache` into ignored `fable-out/`; webpack then
61
+ creates `dist/extension.js` and `dist/cli.js`. `just lint` checks F# formatting, `just format`
62
+ formats it, and `just analyze` runs the built F# CLI against the production F# source. Press `F5`
63
+ for the Extension Development Host.
47
64
 
48
65
  ## Extension Settings
49
66
 
@@ -73,6 +90,5 @@ To exclude files/folders (e.g. test fixtures, generated code) from both the exte
73
90
 
74
91
  ## Known Issues
75
92
 
76
- - Nesting depth and parameter count thresholds are not yet configurable via VS Code settings, only cyclomatic complexity, cognitive complexity, the large-function coherence check, the match-opportunity branch count, and the magic-number/magic-string detectors are.
77
93
  - TypeScript arrow functions aren't analyzed by complexity/parameter-count/coherence (same limitation Python already has for `lambda`), only named `function` declarations and class methods are.
78
94
  - Several detectors have per-language gaps beyond the above, see the "Known limitations" section of the relevant [detector doc](docs/detectors/README.md).
package/action.yml CHANGED
@@ -7,7 +7,7 @@ branding:
7
7
 
8
8
  # Requires the caller's checkout step to use `fetch-depth: 0` when base-ref is
9
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).
10
+ # `git show` (see src/CliModes.fs's changedFilesFromGit/readAtRef).
11
11
 
12
12
  inputs:
13
13
  path:
@@ -51,7 +51,7 @@ runs:
51
51
  using: composite
52
52
  steps:
53
53
  - name: Setup Node
54
- uses: actions/setup-node@v5
54
+ uses: actions/setup-node@v7
55
55
  with:
56
56
  node-version: 22
57
57
 
@@ -76,7 +76,7 @@ runs:
76
76
 
77
77
  - name: Comment on PR
78
78
  if: ${{ inputs.post-comment == 'true' && github.event_name == 'pull_request' }}
79
- uses: marocchino/sticky-pull-request-comment@v2
79
+ uses: marocchino/sticky-pull-request-comment@v3
80
80
  with:
81
81
  header: ${{ inputs.comment-header }}
82
82
  path: ${{ steps.run.outputs.report-path }}
Binary file