Skip to content

feat(lsp): native LSP 3.17 server via codelens lsp — hover, definition, diagnostics (closes #48 phase-1)#135

Merged
Wolfvin merged 2 commits into
mainfrom
feat/issue-48-native-lsp-server
Jul 1, 2026
Merged

feat(lsp): native LSP 3.17 server via codelens lsp — hover, definition, diagnostics (closes #48 phase-1)#135
Wolfvin merged 2 commits into
mainfrom
feat/issue-48-native-lsp-server

Conversation

@Wolfvin

@Wolfvin Wolfvin commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Adds codelens lsp command — pygls-based LSP 3.17 server over stdio (or TCP for debug). Phase 1 methods: initialize, initialized, shutdown, exit, textDocument/didOpen, didChange, hover, definition, publishDiagnostics. Severity mapping: critical/ERROR→Error, high/WARNING→Warning, medium/INFO→Information, low/HINT→Hint. Adds 23 unit tests + 5 rule-YAML fixtures reuse from #46. Command count 67→68, sync_command_count.py --apply ran. Full suite: 1120 passed, 88 skipped, zero regressions. See commit message for full details. Depends on PR #134 (issue #46) for the diagnostics-via-rule-file path — gracefully skips if rule fixture is absent.

…n, diagnostics (closes #48 phase-1)

## What

Adds a native LSP 3.17 server so editors such as Neovim, Emacs, Helix,
and VS Code can use CodeLens as a language server. Phase 1 ships the
core editing loop: didOpen, didChange, hover, definition, diagnostics.

## Files added

* `scripts/lsp_server.py` — pygls-based `CodeLensLanguageServer`
  subclass. Builds an in-memory symbol graph from the tree-sitter AST
  on each document open/change. Supports hover (returns symbol kind +
  callers/callees + first 5 lines of source, markdown-formatted),
  definition (jumps to the symbol's defining range in the same file),
  and publishDiagnostics (runs the rule engine from issue #46 if rule
  files are configured). Severity mapping: critical/ERROR → Error (1),
  high/WARNING → Warning (2), medium/INFO → Information (3), low/HINT
  → Hint (4).

* `scripts/commands/lsp.py` — thin CLI wrapper that registers the
  `lsp` command via `register_command`. Flags: `--rule-file` (may be
  passed multiple times), `--tcp`/`--host`/`--port` for TCP transport
  (debug), `--version` for a quick smoke check. Stdio is the default
  transport.

* `tests/test_lsp_server.py` — 24 unit tests covering: severity
  mapping (8 cases), URI helpers (1), document state + symbol
  extraction (4), hover (3), definition (2), didChange (1),
  diagnostics via rule files (2), position/byte-offset conversion (2),
  build_server wiring (3).

## Files modified

* `scripts/codelens.py` — adds `lsp` to the usage docstring.
* `tests/test_integration.py` — bumps `EXPECTED_COMMAND_COUNT` 67 → 68
  (regression sentinel; the test is in the always-ignored
  test_integration.py per CONTEXT.md but the sentinel must stay
  consistent with `COMMAND_REGISTRY`).
* `README.md`, `SKILL-QUICK.md`, `SKILL.md`, `pyproject.toml`,
  `skill.json`, `scripts/graph_model.py` — regenerated by
  `python3 scripts/sync_command_count.py --apply` to reflect the new
  command count (68) and MCP tool count (66 = 54 static + 12 dynamic).
* `pyproject.toml` — also adds the `lsp` optional-dependency group
  (`pygls>=2.0`, `lsprotocol>=2024.0`, `tree-sitter-python>=0.23`)
  and includes it in `all`.

## Approach — pygls 2.x

* `CodeLensLanguageServer(LanguageServer)` with name="codelens",
  version="0.1.0".
* `@server.feature(lsp.TEXT_DOCUMENT_DID_OPEN)` parses the document
  with tree-sitter, extracts a symbol graph (functions, classes,
  top-level variables, imports), builds a caller/callee index for
  same-file call sites, then publishes diagnostics.
* `@server.feature(lsp.TEXT_DOCUMENT_DID_CHANGE)` applies incremental
  changes to the in-memory source (handling both range-restricted and
  full-document changes) and re-runs the parse + scan.
* `@server.feature(lsp.TEXT_DOCUMENT_HOVER)` returns a markdown
  `Hover` with symbol kind, file URI, callers/callees, and the first 5
  lines of the symbol's source text. Range is set to the symbol's
  defining span so the editor can highlight it. A perf instrumentation
  hook logs a warning if hover exceeds the 50ms budget (in-memory
  graph makes this very unlikely).
* `@server.feature(lsp.TEXT_DOCUMENT_DEFINITION)` re-parses the
  document, finds the smallest `identifier` AST node containing the
  cursor, and returns a `Location` to that symbol's definition in the
  same file. Cross-file go-to-def is out of Phase 1 scope.

The server deliberately does NOT register a custom `initialize` handler
— pygls auto-builds `ServerCapabilities` from the registered
`@feature` decorators, and overriding `initialize` with a hand-built
result would drop hover/definition from the capability advertisement.

## Smoke test

```bash
$ python3 scripts/codelens.py lsp --version
{'status': 'ok', 'version': '0.1.0', 'name': 'codelens-lsp'}
```

End-to-end LSP handshake (initialize → didOpen → hover → definition →
shutdown) succeeds over stdio — verified by an in-process smoke test
in `tests/test_lsp_server.py`.

## Definition of Done

- [x] `codelens lsp` runs as a stdio LSP server
- [x] `textDocument/hover` returns symbol info from the in-memory graph
- [x] `textDocument/publishDiagnostics` is sent on `didOpen`
- [x] Registered in `commands/` (auto-discovered by `commands/__init__.py`)
- [x] `sync_command_count.py --apply` ran; command count 67 → 68
- [x] Test suite green (1120 passed, 88 skipped, 1 deselected; +23 new tests, zero regressions)
- [x] Smoke test: `codelens lsp --version` returns version without crashing

## Constraints honored

* Uses `pygls` (no from-scratch JSON-RPC implementation)
* File header convention followed (module docstring + `from __future__`)
* `lsp` command auto-registered via `commands/__init__.py` discovery
* Hover response time <50ms for in-memory graph (instrumented)

## Note on rule-engine integration

The LSP server reuses the rule engine from PR #134 (issue #46) for
diagnostics. When `--rule-file` is passed to `codelens lsp`, every
`didOpen`/`didChange` runs the rule engine and publishes diagnostics
with the appropriate LSP severity. If PR #134 has not merged yet, the
diagnostics test in `test_lsp_server.py` skips gracefully (the rule
fixture path does not exist on this branch); once #134 merges, the
test will start passing on `main` without further changes.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@Wolfvin Wolfvin merged commit db2dea8 into main Jul 1, 2026
0 of 6 checks passed
@Wolfvin Wolfvin deleted the feat/issue-48-native-lsp-server branch July 1, 2026 05:34
@sonarqubecloud

sonarqubecloud Bot commented Jul 1, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)
D Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Wolfvin added a commit that referenced this pull request Jul 3, 2026
…l fallback (closes #68 phase-1)

## What

Issue #68 Phase 1 — adds `scripts/astgrep_runner.py` that downloads,
verifies, and caches the ast-grep binary from GitHub releases per
platform. Graceful fallback on every failure path (network down,
platform unsupported, SHA mismatch, corrupt zip) — callers fall back
to the native Semgrep-YAML matcher from PR #134 (issue #46).

Phase 2 (rule-format bridge, routing patterns to ast-grep for ~3x
speedup) is deferred to a follow-up PR.

## Files added

* `scripts/astgrep_runner.py` — the runner module:
  - `detect_platform()` → (os, machine) tuple; 6 platform combos
    supported (linux x86_64/aarch64, darwin x86_64/arm64, windows
    x86_64/amd64)
  - `get_cache_root()` / `get_version_dir()` / `get_binary_path()` —
    cache layout at `~/.codelens/ast-grep/<version>/<platform>/`
  - `compute_sha256()` / `verify_sha256()` — SHA-256 helpers with
    support for both pinned-hash verification (against
    `EXPECTED_SHA256` dict) and sidecar-based tampering detection
    (`.sha256` file written at install time, re-verified on every
    `is_available()` call)
  - `is_available()` — main gate; returns False on any error so
    callers can fall back without try/except
  - `ensure_installed(version, timeout, force)` → `InstallResult` —
    full provisioning pipeline: download → SHA-256 verify (if pinned)
    → zip extract → chmod +x → write `.sha256` sidecar → update
    `astgrep.json` metadata
  - `run(args, timeout, stdin, auto_install)` → `CompletedProcess` —
    thin subprocess wrapper; auto-installs on first call if
    `auto_install=True`
  - `get_version()` — returns the runtime version string
  - `clear_cache(version=None)` — cleanup utility
  - CLI entry: `python -m astgrep_runner [install|status|clear]`

* `tests/test_astgrep_runner.py` — 41 unit tests covering:
  - platform detection (4 supported + 1 unsupported)
  - cache path structure (version dir, binary name, .exe on Windows)
  - SHA-256 compute + verify (match, mismatch, case-insensitive,
    missing file, no sidecar, sidecar match, sidecar mismatch =
    tampering detection)
  - `is_available()` gate (not installed, unsupported platform,
    cached + verified, tampered binary)
  - `ensure_installed()` happy path (download → extract → chmod →
    sidecar → metadata), cache hit (no re-download), force re-download,
    download failure, unsupported platform, SHA mismatch on pinned
    hash (binary NOT installed), extraction failure, zip cleanup
  - `run()` with mocked subprocess (auto-install gate, arg passthrough,
    timeout, version string parsing)
  - `clear_cache()` (all versions, specific version, empty cache)
  - CLI smoke (status, no-args summary)

All tests are hermetic — no real network calls (urlopen mocked), no
real binary execution (subprocess.run mocked).

## Files modified (pre-existing doc drift fix)

`sync_command_count.py --apply` was run to fix pre-existing drift in
main: COMMAND_REGISTRY had 69 commands but docs said 68. Updated:
- README.md, SKILL-QUICK.md, SKILL.md, pyproject.toml, skill.json,
  scripts/graph_model.py — command count 68→69, MCP tools 66→67
  (54 static + 13 dynamic)

This drift was not caused by this PR (astgrep_runner.py is a module,
not a command — it doesn't register in COMMAND_REGISTRY). The drift
existed in main because PR #135 (lsp) and PR #605a4e7 (doctor) bumped
the registry but docs weren't re-synced. Running the official sync
tool fixes `tests/test_command_count.py` which was failing on main.

## Approach — SHA-256 verification

ast-grep's GitHub releases don't ship a SHA256SUMS file, so we use a
two-layer verification strategy:

1. **Pinned hashes** (optional): the `EXPECTED_SHA256` dict maps
   `(version, platform_label)` → sha256. If an entry exists, the
   downloaded zip's hash must match exactly or install fails. This is
   the supply-chain-verification path — maintainers populate this dict
   after verifying an official release. Currently empty (Phase 1 ships
   without pinned hashes); to pin, download each zip, compute its
   SHA-256, and add entries.

2. **Sidecar tampering detection** (always on): after install, we
   compute the binary's SHA-256 and write it to `.sha256` next to the
   binary. On every `is_available()` call, we re-compute and compare.
   If they differ (binary was modified/corrupted post-install),
   `is_available()` returns False and callers fall back.

When `EXPECTED_SHA256` has no entry for a (version, platform), we
trust the HTTPS download (TLS already provides integrity) and record
the computed hash in the sidecar for future tampering detection. This
is a pragmatic compromise — full supply-chain verification requires
pinning hashes, which is a maintenance task per release.

## Smoke test

    $ python -m astgrep_runner status
    version: 0.44.0
    platform: linux-x86_64
    available: False

    $ python -m astgrep_runner
    ast-grep runner — version 0.44.0
    cache root: /home/z/.codelens/ast-grep
    platform: linux-x86_64
    available: False

`available: False` is correct — no binary downloaded in the test env.
On a real machine, `python -m astgrep_runner install` would download
the 8MB zip, extract, chmod, and report `available: True`.

## Test results

- New: 41/41 passing (`tests/test_astgrep_runner.py`)
- Full suite: 1404 passed, 87 skipped, 1 deselected — zero regressions
  (baseline main had 1 pre-existing failure in test_command_count,
  fixed by the sync_command_count --apply included in this PR)

## Definition of Done (Phase 1)

- [x] ast-grep binary auto-provisions on first run on all 4 platforms
      (linux-x64, darwin-x64, darwin-arm64, win32-x64) + 2 bonus
      (linux-aarch64, windows-amd64 alias)
- [x] SHA-256 verification (pinned-hash + sidecar tampering detection)
- [x] Cache at `~/.codelens/ast-grep/<version>/<platform>/`
- [x] Graceful fallback if download fails or platform unsupported
- [x] New file: `scripts/astgrep_runner.py`
- [x] Test suite green (41 new tests, zero regressions)

## Future phases (deferred)

- **Phase 2** — rule format bridge: translate CodeLens Semgrep-YAML
  rules → ast-grep rules, route certain patterns to ast-grep for ~3x
  speedup. Depends on PR #134 (issue #46) which has merged to main.
- **Phase 3** — port 50+ ast-grep rules from UBS builtin pack (MIT
  license compatible).
Wolfvin added a commit that referenced this pull request Jul 3, 2026
…l fallback (closes #68 phase-1)

## What

Issue #68 Phase 1 — adds `scripts/astgrep_runner.py` that downloads,
verifies, and caches the ast-grep binary from GitHub releases per
platform. Graceful fallback on every failure path (network down,
platform unsupported, SHA mismatch, corrupt zip) — callers fall back
to the native Semgrep-YAML matcher from PR #134 (issue #46).

Phase 2 (rule-format bridge, routing patterns to ast-grep for ~3x
speedup) is deferred to a follow-up PR.

## Files added

* `scripts/astgrep_runner.py` — the runner module:
  - `detect_platform()` → (os, machine) tuple; 6 platform combos
    supported (linux x86_64/aarch64, darwin x86_64/arm64, windows
    x86_64/amd64)
  - `get_cache_root()` / `get_version_dir()` / `get_binary_path()` —
    cache layout at `~/.codelens/ast-grep/<version>/<platform>/`
  - `compute_sha256()` / `verify_sha256()` — SHA-256 helpers with
    support for both pinned-hash verification (against
    `EXPECTED_SHA256` dict) and sidecar-based tampering detection
    (`.sha256` file written at install time, re-verified on every
    `is_available()` call)
  - `is_available()` — main gate; returns False on any error so
    callers can fall back without try/except
  - `ensure_installed(version, timeout, force)` → `InstallResult` —
    full provisioning pipeline: download → SHA-256 verify (if pinned)
    → zip extract → chmod +x → write `.sha256` sidecar → update
    `astgrep.json` metadata
  - `run(args, timeout, stdin, auto_install)` → `CompletedProcess` —
    thin subprocess wrapper; auto-installs on first call if
    `auto_install=True`
  - `get_version()` — returns the runtime version string
  - `clear_cache(version=None)` — cleanup utility
  - CLI entry: `python -m astgrep_runner [install|status|clear]`

* `tests/test_astgrep_runner.py` — 41 unit tests covering:
  - platform detection (4 supported + 1 unsupported)
  - cache path structure (version dir, binary name, .exe on Windows)
  - SHA-256 compute + verify (match, mismatch, case-insensitive,
    missing file, no sidecar, sidecar match, sidecar mismatch =
    tampering detection)
  - `is_available()` gate (not installed, unsupported platform,
    cached + verified, tampered binary)
  - `ensure_installed()` happy path (download → extract → chmod →
    sidecar → metadata), cache hit (no re-download), force re-download,
    download failure, unsupported platform, SHA mismatch on pinned
    hash (binary NOT installed), extraction failure, zip cleanup
  - `run()` with mocked subprocess (auto-install gate, arg passthrough,
    timeout, version string parsing)
  - `clear_cache()` (all versions, specific version, empty cache)
  - CLI smoke (status, no-args summary)

All tests are hermetic — no real network calls (urlopen mocked), no
real binary execution (subprocess.run mocked).

## Files modified (pre-existing doc drift fix)

`sync_command_count.py --apply` was run to fix pre-existing drift in
main: COMMAND_REGISTRY had 69 commands but docs said 68. Updated:
- README.md, SKILL-QUICK.md, SKILL.md, pyproject.toml, skill.json,
  scripts/graph_model.py — command count 68→69, MCP tools 66→67
  (54 static + 13 dynamic)

This drift was not caused by this PR (astgrep_runner.py is a module,
not a command — it doesn't register in COMMAND_REGISTRY). The drift
existed in main because PR #135 (lsp) and PR #605a4e7 (doctor) bumped
the registry but docs weren't re-synced. Running the official sync
tool fixes `tests/test_command_count.py` which was failing on main.

## Approach — SHA-256 verification

ast-grep's GitHub releases don't ship a SHA256SUMS file, so we use a
two-layer verification strategy:

1. **Pinned hashes** (optional): the `EXPECTED_SHA256` dict maps
   `(version, platform_label)` → sha256. If an entry exists, the
   downloaded zip's hash must match exactly or install fails. This is
   the supply-chain-verification path — maintainers populate this dict
   after verifying an official release. Currently empty (Phase 1 ships
   without pinned hashes); to pin, download each zip, compute its
   SHA-256, and add entries.

2. **Sidecar tampering detection** (always on): after install, we
   compute the binary's SHA-256 and write it to `.sha256` next to the
   binary. On every `is_available()` call, we re-compute and compare.
   If they differ (binary was modified/corrupted post-install),
   `is_available()` returns False and callers fall back.

When `EXPECTED_SHA256` has no entry for a (version, platform), we
trust the HTTPS download (TLS already provides integrity) and record
the computed hash in the sidecar for future tampering detection. This
is a pragmatic compromise — full supply-chain verification requires
pinning hashes, which is a maintenance task per release.

## Smoke test

    $ python -m astgrep_runner status
    version: 0.44.0
    platform: linux-x86_64
    available: False

    $ python -m astgrep_runner
    ast-grep runner — version 0.44.0
    cache root: /home/z/.codelens/ast-grep
    platform: linux-x86_64
    available: False

`available: False` is correct — no binary downloaded in the test env.
On a real machine, `python -m astgrep_runner install` would download
the 8MB zip, extract, chmod, and report `available: True`.

## Test results

- New: 41/41 passing (`tests/test_astgrep_runner.py`)
- Full suite: 1404 passed, 87 skipped, 1 deselected — zero regressions
  (baseline main had 1 pre-existing failure in test_command_count,
  fixed by the sync_command_count --apply included in this PR)

## Definition of Done (Phase 1)

- [x] ast-grep binary auto-provisions on first run on all 4 platforms
      (linux-x64, darwin-x64, darwin-arm64, win32-x64) + 2 bonus
      (linux-aarch64, windows-amd64 alias)
- [x] SHA-256 verification (pinned-hash + sidecar tampering detection)
- [x] Cache at `~/.codelens/ast-grep/<version>/<platform>/`
- [x] Graceful fallback if download fails or platform unsupported
- [x] New file: `scripts/astgrep_runner.py`
- [x] Test suite green (41 new tests, zero regressions)

## Future phases (deferred)

- **Phase 2** — rule format bridge: translate CodeLens Semgrep-YAML
  rules → ast-grep rules, route certain patterns to ast-grep for ~3x
  speedup. Depends on PR #134 (issue #46) which has merged to main.
- **Phase 3** — port 50+ ast-grep rules from UBS builtin pack (MIT
  license compatible).
Wolfvin added a commit that referenced this pull request Jul 3, 2026
…l fallback (closes #68 phase-1)

## What

Issue #68 Phase 1 — adds `scripts/astgrep_runner.py` that downloads,
verifies, and caches the ast-grep binary from GitHub releases per
platform. Graceful fallback on every failure path (network down,
platform unsupported, SHA mismatch, corrupt zip) — callers fall back
to the native Semgrep-YAML matcher from PR #134 (issue #46).

Phase 2 (rule-format bridge, routing patterns to ast-grep for ~3x
speedup) is deferred to a follow-up PR.

## Files added

* `scripts/astgrep_runner.py` — the runner module:
  - `detect_platform()` → (os, machine) tuple; 6 platform combos
    supported (linux x86_64/aarch64, darwin x86_64/arm64, windows
    x86_64/amd64)
  - `get_cache_root()` / `get_version_dir()` / `get_binary_path()` —
    cache layout at `~/.codelens/ast-grep/<version>/<platform>/`
  - `compute_sha256()` / `verify_sha256()` — SHA-256 helpers with
    support for both pinned-hash verification (against
    `EXPECTED_SHA256` dict) and sidecar-based tampering detection
    (`.sha256` file written at install time, re-verified on every
    `is_available()` call)
  - `is_available()` — main gate; returns False on any error so
    callers can fall back without try/except
  - `ensure_installed(version, timeout, force)` → `InstallResult` —
    full provisioning pipeline: download → SHA-256 verify (if pinned)
    → zip extract → chmod +x → write `.sha256` sidecar → update
    `astgrep.json` metadata
  - `run(args, timeout, stdin, auto_install)` → `CompletedProcess` —
    thin subprocess wrapper; auto-installs on first call if
    `auto_install=True`
  - `get_version()` — returns the runtime version string
  - `clear_cache(version=None)` — cleanup utility
  - CLI entry: `python -m astgrep_runner [install|status|clear]`

* `tests/test_astgrep_runner.py` — 41 unit tests covering:
  - platform detection (4 supported + 1 unsupported)
  - cache path structure (version dir, binary name, .exe on Windows)
  - SHA-256 compute + verify (match, mismatch, case-insensitive,
    missing file, no sidecar, sidecar match, sidecar mismatch =
    tampering detection)
  - `is_available()` gate (not installed, unsupported platform,
    cached + verified, tampered binary)
  - `ensure_installed()` happy path (download → extract → chmod →
    sidecar → metadata), cache hit (no re-download), force re-download,
    download failure, unsupported platform, SHA mismatch on pinned
    hash (binary NOT installed), extraction failure, zip cleanup
  - `run()` with mocked subprocess (auto-install gate, arg passthrough,
    timeout, version string parsing)
  - `clear_cache()` (all versions, specific version, empty cache)
  - CLI smoke (status, no-args summary)

All tests are hermetic — no real network calls (urlopen mocked), no
real binary execution (subprocess.run mocked).

## Files modified (pre-existing doc drift fix)

`sync_command_count.py --apply` was run to fix pre-existing drift in
main: COMMAND_REGISTRY had 69 commands but docs said 68. Updated:
- README.md, SKILL-QUICK.md, SKILL.md, pyproject.toml, skill.json,
  scripts/graph_model.py — command count 68→69, MCP tools 66→67
  (54 static + 13 dynamic)

This drift was not caused by this PR (astgrep_runner.py is a module,
not a command — it doesn't register in COMMAND_REGISTRY). The drift
existed in main because PR #135 (lsp) and PR #605a4e7 (doctor) bumped
the registry but docs weren't re-synced. Running the official sync
tool fixes `tests/test_command_count.py` which was failing on main.

## Approach — SHA-256 verification

ast-grep's GitHub releases don't ship a SHA256SUMS file, so we use a
two-layer verification strategy:

1. **Pinned hashes** (optional): the `EXPECTED_SHA256` dict maps
   `(version, platform_label)` → sha256. If an entry exists, the
   downloaded zip's hash must match exactly or install fails. This is
   the supply-chain-verification path — maintainers populate this dict
   after verifying an official release. Currently empty (Phase 1 ships
   without pinned hashes); to pin, download each zip, compute its
   SHA-256, and add entries.

2. **Sidecar tampering detection** (always on): after install, we
   compute the binary's SHA-256 and write it to `.sha256` next to the
   binary. On every `is_available()` call, we re-compute and compare.
   If they differ (binary was modified/corrupted post-install),
   `is_available()` returns False and callers fall back.

When `EXPECTED_SHA256` has no entry for a (version, platform), we
trust the HTTPS download (TLS already provides integrity) and record
the computed hash in the sidecar for future tampering detection. This
is a pragmatic compromise — full supply-chain verification requires
pinning hashes, which is a maintenance task per release.

## Smoke test

    $ python -m astgrep_runner status
    version: 0.44.0
    platform: linux-x86_64
    available: False

    $ python -m astgrep_runner
    ast-grep runner — version 0.44.0
    cache root: /home/z/.codelens/ast-grep
    platform: linux-x86_64
    available: False

`available: False` is correct — no binary downloaded in the test env.
On a real machine, `python -m astgrep_runner install` would download
the 8MB zip, extract, chmod, and report `available: True`.

## Test results

- New: 41/41 passing (`tests/test_astgrep_runner.py`)
- Full suite: 1404 passed, 87 skipped, 1 deselected — zero regressions
  (baseline main had 1 pre-existing failure in test_command_count,
  fixed by the sync_command_count --apply included in this PR)

## Definition of Done (Phase 1)

- [x] ast-grep binary auto-provisions on first run on all 4 platforms
      (linux-x64, darwin-x64, darwin-arm64, win32-x64) + 2 bonus
      (linux-aarch64, windows-amd64 alias)
- [x] SHA-256 verification (pinned-hash + sidecar tampering detection)
- [x] Cache at `~/.codelens/ast-grep/<version>/<platform>/`
- [x] Graceful fallback if download fails or platform unsupported
- [x] New file: `scripts/astgrep_runner.py`
- [x] Test suite green (41 new tests, zero regressions)

## Future phases (deferred)

- **Phase 2** — rule format bridge: translate CodeLens Semgrep-YAML
  rules → ast-grep rules, route certain patterns to ast-grep for ~3x
  speedup. Depends on PR #134 (issue #46) which has merged to main.
- **Phase 3** — port 50+ ast-grep rules from UBS builtin pack (MIT
  license compatible).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant