Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: Publish to PyPI

# Issue #54 Phase 1: publish codelens to PyPI on tag push.
# Triggers on tags matching v*.*.* (e.g. v8.2.0, v8.3.0-rc1).
# Uses trusted publishing (OIDC) — no PyPI API token needed.

on:
push:
tags:
- "v*.*.*"
workflow_dispatch: # Allow manual trigger for testing

permissions:
contents: read

Check warning on line 14 in .github/workflows/publish-pypi.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move this read permission from workflow level to job level.

See more on https://sonarcloud.io/project/issues?id=Wolfvin_CodeLens&issues=AZ8dF-lxdKauj2fYabik&open=AZ8dF-lxdKauj2fYabik&pullRequest=144
id-token: write # Required for PyPI trusted publishing (OIDC)

Check warning on line 15 in .github/workflows/publish-pypi.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move this write permission from workflow level to job level.

See more on https://sonarcloud.io/project/issues?id=Wolfvin_CodeLens&issues=AZ8dF-lxdKauj2fYabin&open=AZ8dF-lxdKauj2fYabin&pullRequest=144

jobs:
build:
name: Build sdist + wheel
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for version detection

- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install build dependencies
run: |
python -m pip install --upgrade pip
pip install build twine

- name: Build sdist and wheel
run: |
python -m build

- name: Verify distributions
run: |
twine check dist/*

- name: List built artifacts
run: |
ls -la dist/

- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

publish-pypi:
name: Publish to PyPI
runs-on: ubuntu-latest
needs: build
environment: pypi # Requires manual approval for first-time publishes

steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist/

- name: Publish to PyPI (trusted publishing via OIDC)
uses: pypa/gh-action-pypi-publish@release/v1

Check failure on line 70 in .github/workflows/publish-pypi.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use full commit SHA hash for this dependency.

See more on https://sonarcloud.io/project/issues?id=Wolfvin_CodeLens&issues=AZ8dF-lxdKauj2fYabil&open=AZ8dF-lxdKauj2fYabil&pullRequest=144
# No password/token — uses GitHub OIDC trusted publishing.
# Configure at https://pypi.org/manage/account/publishing/
# with environment "pypi" and this workflow's name.

publish-testpypi:
name: Publish to TestPyPI (on manual trigger)
runs-on: ubuntu-latest
needs: build
if: github.event_name == 'workflow_dispatch'
environment: testpypi

steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist/

- name: Publish to TestPyPI (trusted publishing via OIDC)
uses: pypa/gh-action-pypi-publish@release/v1

Check failure on line 90 in .github/workflows/publish-pypi.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use full commit SHA hash for this dependency.

See more on https://sonarcloud.io/project/issues?id=Wolfvin_CodeLens&issues=AZ8dF-lxdKauj2fYabim&open=AZ8dF-lxdKauj2fYabim&pullRequest=144
with:
repository-url: https://test.pypi.org/legacy/
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

> **Before an AI writes a new class/id/function, CodeLens must be checked. This is not optional.**

CodeLens is an AI-native code intelligence platform that gives AI agents **full visibility** into a codebase before they write any code. It prevents collision, overwrite of existing logic, security vulnerabilities, and dead code through 68 CLI commands, an MCP server with 66 tools (54 static + 12 dynamic), AST-based taint analysis, live CVE/OSV scanning, a plugin system with OWASP Top 10 + Compliance rule packs, a true graph data model (nodes + edges) for structural code queries, and token-efficient `--format compact` output for high-volume agent workflows (issue #17).
CodeLens is an AI-native code intelligence platform that gives AI agents **full visibility** into a codebase before they write any code. It prevents collision, overwrite of existing logic, security vulnerabilities, and dead code through 69 CLI commands, an MCP server with 67 tools (54 static + 13 dynamic), AST-based taint analysis, live CVE/OSV scanning, a plugin system with OWASP Top 10 + Compliance rule packs, a true graph data model (nodes + edges) for structural code queries, and token-efficient `--format compact` output for high-volume agent workflows (issue #17).

## Features

- **68 CLI Commands** — From basic scan/query to AST taint analysis, CVE scanning, plugin management, auto-fix, dashboards, CI/CD quality gates, and `graph-schema` for cheap graph-shape introspection
- **MCP Server (66 Tools)** — Native AI agent integration via Model Context Protocol (JSON-RPC over stdio), 54 statically-defined tools + 12 dynamically discovered, every tool accepts a `format` parameter (`json`/`markdown`/`ai`/`sarif`/`compact`)
- **69 CLI Commands** — From basic scan/query to AST taint analysis, CVE scanning, plugin management, auto-fix, dashboards, CI/CD quality gates, and `graph-schema` for cheap graph-shape introspection
- **MCP Server (67 Tools)** — Native AI agent integration via Model Context Protocol (JSON-RPC over stdio), 54 statically-defined tools + 13 dynamically discovered, every tool accepts a `format` parameter (`json`/`markdown`/`ai`/`sarif`/`compact`)
- **Token-Efficient Compact Output (v8.2, issue #17)** — `--format compact` produces single-char-key JSON with abbreviated types, omitted null fields, and relative paths — ~50% smaller than `json` on real trace output. Combined with `--limit`/`--offset` pagination, 5 structural queries now cost <5k tokens (down from 30-80k)
- **AST Taint Engine** — Tree-sitter based taint analysis with return-value propagation, scope hierarchy, and branch condition refinement
- **Live CVE/OSV Scanning** — Real-time vulnerability data from OSV.dev API with SQLite cache, 9 ecosystems (PyPI, npm, crates.io, Go, Maven, NuGet, RubyGems, Pub, Hex)
Expand Down Expand Up @@ -225,8 +225,8 @@ codelens/
│ ├── changelog.md # Older changelog (per-version highlights)
│ └── agent-integration.md # AI agent integration guide
├── scripts/
│ ├── codelens.py # CLI entry point (68 commands registered)
│ ├── mcp_server.py # MCP JSON-RPC server (66 tools)
│ ├── codelens.py # CLI entry point (69 commands registered)
│ ├── mcp_server.py # MCP JSON-RPC server (67 tools)
│ ├── registry.py # Registry read/write/build
│ ├── persistent_registry.py # SQLite persistent storage (WAL mode)
│ ├── base_parser.py # Base tree-sitter parser
Expand Down
10 changes: 5 additions & 5 deletions SKILL-QUICK.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ $CLI list --limit 5 --offset 10 --format compact # → paginated + co
| "Cross-file taint" | `dataflow` | `taint` (taint is single-file, AST-deep) |
| "Auto-fix issues" | `fix` | `check` (check just gates, doesn't fix) |

## All 68 Commands
## All 69 Commands

### Setup & Lifecycle (8+)
`init` · `scan [--incremental] [--max-files N] [--full]` · `registry-validate` · `detect` · `watch [--debounce SECS] [--git-mode] [--interval SECS]` · `git-status` · `migrate` · `serve` · `lsp-status` (issue #33: `codelens --lsp-status` top-level flag is an alias of `codelens lsp-status` — both delegate to `hybrid_engine.get_lsp_status()` and return the identical payload)
Expand Down Expand Up @@ -146,19 +146,19 @@ $CLI list --limit 5 --offset 10 --format compact # → paginated + co
### Tooling (1)
`plugin <install|list|search|update|info|validate>`

**Total: 68 commands** (auto-registered via `commands/__init__.py`; rerun `python3 scripts/sync_command_count.py --apply` after adding/removing a command)
**Total: 69 commands** (auto-registered via `commands/__init__.py`; rerun `python3 scripts/sync_command_count.py --apply` after adding/removing a command)

## MCP Server (66 Tools)
## MCP Server (67 Tools)

Start the MCP server for AI agent integration:

```bash
python3 scripts/codelens.py serve
```

Exposes 66 tools as `codelens_<command>` (e.g., `codelens_query`, `codelens_taint`, `codelens_graph_schema`, `codelens_architecture`, `codelens_resolve_types`, `codelens_git_status`):
Exposes 67 tools as `codelens_<command>` (e.g., `codelens_query`, `codelens_taint`, `codelens_graph_schema`, `codelens_architecture`, `codelens_resolve_types`, `codelens_git_status`):
- 50 statically-defined tools (full JSON schemas in `mcp_server.py`)
- 12 dynamically-discovered tools (auto-discovered from `COMMAND_REGISTRY`; long-running `watch` and `serve` are excluded)
- 13 dynamically-discovered tools (auto-discovered from `COMMAND_REGISTRY`; long-running `watch` and `serve` are excluded)
- Every tool accepts a `format` parameter (`json`/`markdown`/`ai`/`sarif`/`compact`). Use `format: "compact"` for token-efficient responses (~50% smaller than `json`).
- `watch` and `serve` itself are excluded (long-running)

Expand Down
4 changes: 2 additions & 2 deletions SKILL.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
name: codelens
description: >
CodeLens — AI-Native Code Intelligence. 68 commands for AI-powered code analysis,
CodeLens — AI-Native Code Intelligence. 69 commands for AI-powered code analysis,
security auditing, quality scoring, AST-based taint analysis, live CVE scanning,
and pre-write safety checks. Supports 28+ languages with tree-sitter + regex
fallback parsing. MCP server exposes 66 tools for AI agent integration.
fallback parsing. MCP server exposes 67 tools for AI agent integration.
For quick command reference with validated output schemas, see SKILL-QUICK.md.
For version history, see CHANGELOG.md.
---
Expand Down
140 changes: 140 additions & 0 deletions codelens/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
"""CodeLens — AI-native code intelligence CLI + MCP server.

This is the top-level package for the ``codelens`` distribution. It
provides a thin entry-point wrapper that delegates to the legacy
``scripts/codelens.py`` implementation so that ``pip install codelens``
exposes a ``codelens`` console script.

The actual implementation lives under ``scripts/`` (which is also the
``codelens`` import root via ``pyproject.toml``'s ``packages.find``).
This ``codelens/`` package only contains the ``__main__`` shim and
re-exports the ``main`` function for programmatic use.

Issue #54 Phase 1 — Python package + PyPI distribution.
"""

from __future__ import annotations

import os
import sys
import warnings
from typing import Optional, List


__version__ = "8.2.0"

# Path to the bundled scripts/ directory (legacy implementation).
# When installed as a package, scripts/ is shipped inside the codelens
# distribution and we add it to sys.path so the legacy imports keep
# working. When running from a source checkout, scripts/ is already
# accessible relative to the repo root.
_SCRIPTS_DIR: Optional[str] = None


def _resolve_scripts_dir() -> str:
"""Locate the bundled ``scripts/`` directory.

Search order:
1. ``<package_dir>/../scripts`` — source checkout (repo root)
2. ``<package_dir>/scripts`` — installed package (scripts bundled inside)
3. Fall back to the legacy ``python3 scripts/codelens.py`` path

Returns the absolute path to the scripts directory.
"""
global _SCRIPTS_DIR
if _SCRIPTS_DIR is not None:
return _SCRIPTS_DIR

here = os.path.dirname(os.path.abspath(__file__))

# 1. Source checkout: <repo_root>/scripts/
candidate = os.path.join(here, "..", "scripts")
if os.path.isfile(os.path.join(candidate, "codelens.py")):

Check failure on line 52 in codelens/__init__.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "codelens.py" 3 times.

See more on https://sonarcloud.io/project/issues?id=Wolfvin_CodeLens&issues=AZ8dF-qsdKauj2fYabio&open=AZ8dF-qsdKauj2fYabio&pullRequest=144
_SCRIPTS_DIR = os.path.abspath(candidate)
return _SCRIPTS_DIR

# 2. Installed package: <site-packages>/codelens/scripts/
candidate = os.path.join(here, "scripts")
if os.path.isfile(os.path.join(candidate, "codelens.py")):
_SCRIPTS_DIR = os.path.abspath(candidate)
return _SCRIPTS_DIR

# 3. Fall back: try importlib.resources or just leave it to the
# caller's sys.path (legacy ``python3 scripts/codelens.py`` mode).
_SCRIPTS_DIR = ""
return _SCRIPTS_DIR


def _ensure_scripts_on_path() -> str:
"""Add the bundled scripts/ directory to sys.path if not present.

Returns the scripts directory path (empty string if not found).
"""
scripts_dir = _resolve_scripts_dir()
if scripts_dir and scripts_dir not in sys.path:
sys.path.insert(0, scripts_dir)
return scripts_dir


def main(argv: Optional[List[str]] = None) -> int:
"""Run the CodeLens CLI.

Args:
argv: Optional argument list (defaults to ``sys.argv[1:]``).

Returns:
Process exit code (0 on success, 1 on error).
"""
_ensure_scripts_on_path()

# Import the legacy entry point. The scripts/codelens.py module uses
# sys.path-based imports relative to scripts/, so it must be on the
# path (handled above).
try:
# ``import codelens`` would be ambiguous here (this package is
# also named codelens), so import the module by file path.
import importlib.util
scripts_dir = _resolve_scripts_dir()
if not scripts_dir:
print(
"[codelens] ERROR: could not locate the scripts/ directory. "
"If you installed from source, run from the repo root or "
"install with 'pip install .'.",
file=sys.stderr,
)
return 1

codelens_py = os.path.join(scripts_dir, "codelens.py")
spec = importlib.util.spec_from_file_location(
"_codelens_cli", codelens_py
)
if spec is None or spec.loader is None:
print(
f"[codelens] ERROR: could not load {codelens_py}",
file=sys.stderr,
)
return 1
module = importlib.util.module_from_spec(spec)
# The module needs to be in sys.modules for its relative imports
# (e.g. ``from commands import ...``) to resolve correctly when
# scripts/ is on sys.path.
sys.modules["_codelens_cli"] = module
spec.loader.exec_module(module)

# Temporarily replace sys.argv so the legacy main() picks up the
# caller's arguments.
old_argv = sys.argv
try:
sys.argv = ["codelens"] + (list(argv) if argv is not None else sys.argv[1:])
module.main()
return 0
except SystemExit as e:

Check failure on line 131 in codelens/__init__.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Reraise this exception to stop the application as the user expects

See more on https://sonarcloud.io/project/issues?id=Wolfvin_CodeLens&issues=AZ8dF-qsdKauj2fYabip&open=AZ8dF-qsdKauj2fYabip&pullRequest=144
return int(e.code) if e.code is not None else 0
finally:
sys.argv = old_argv
except Exception as e:
print(f"[codelens] ERROR: {e}", file=sys.stderr)
return 1


__all__ = ["main", "__version__"]
9 changes: 9 additions & 0 deletions codelens/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Allow ``python -m codelens`` to run the CLI."""

import sys

from . import main


if __name__ == "__main__":
sys.exit(main())
Loading
Loading