Skip to content
Merged
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
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 70 CLI commands, an MCP server with 68 tools (54 static + 14 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`)
- **70 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 (68 Tools)** — Native AI agent integration via Model Context Protocol (JSON-RPC over stdio), 54 statically-defined tools + 14 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 (70 commands registered)
│ ├── mcp_server.py # MCP JSON-RPC server (68 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 70 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: 70 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 (68 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 68 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)
- 14 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. 70 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 68 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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "codelens"
version = "8.2.0"
description = "Live Codebase Reference Intelligence — 68 commands for AI-powered code analysis, security auditing, and quality scoring"
description = "Live Codebase Reference Intelligence — 70 commands for AI-powered code analysis, security auditing, and quality scoring"
readme = "README.md"
license = {text = "MIT"}
requires-python = ">=3.8"
Expand Down
12 changes: 11 additions & 1 deletion scripts/codelens.py
Original file line number Diff line number Diff line change
Expand Up @@ -873,9 +873,12 @@ def main():

# Detect which args the command already defines
existing_dests = set()
existing_option_strings = set()
for action in sub._actions:
if hasattr(action, 'dest'):
existing_dests.add(action.dest)
if hasattr(action, 'option_strings'):
existing_option_strings.update(action.option_strings)
_existing_subparser_args[cmd_name] = existing_dests

# Add --format to each subparser (UNLESS the command defines its own).
Expand All @@ -884,7 +887,14 @@ def main():
# a text table, not JSON. Skipping the global add here lets that
# command-specific format work without an argparse conflict.
if "format" not in existing_dests:
sub.add_argument("--format", "-f", choices=["json", "markdown", "ai", "sarif", "compact"], default=None,
# Issue #62 Phase 1: ``affected`` command uses ``-f`` for
# ``--filter``. Avoid the ``-f`` shortcut clash by only adding
# the global ``-f`` shortcut when the command doesn't already
# claim it. The long form ``--format`` is always safe.
format_args = ["--format"]
if "-f" not in existing_option_strings:
format_args.append("-f")
sub.add_argument(*format_args, choices=["json", "markdown", "ai", "sarif", "compact"], default=None,
help="Output format: json, markdown, ai (normalized schema), sarif (GitHub/VS Code), or compact (token-efficient single-char keys)")

# Add AI-optimized flags to subparser ONLY if the command doesn't already have them
Expand Down
151 changes: 151 additions & 0 deletions scripts/commands/affected.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"""Affected command — identify test files affected by source changes.

Issue #62 Phase 1: quick-win CI/CD tool. Given a list of changed source
files (positional args, or piped via ``--stdin`` from ``git diff --name-only``),
walks the reverse import-dependency graph and prints every test file that
transitively depends on any of the changed files.

The classic CI pattern is::

AFFECTED=$(git diff --name-only HEAD | codelens affected --stdin --quiet)
pytest $AFFECTED

By default only test files are returned (so ``pytest $AFFECTED`` doesn't try
to run non-test modules). Pass ``--include-source`` to also list non-test
dependents — useful for impact analysis in code review.

Resolution of changed-file paths is forgiving: absolute paths, workspace-
relative paths, and bare basenames are all accepted. Ambiguous basenames
(files with the same name in multiple directories) are skipped rather
than silently picking the wrong one.

Backed by :func:`dependents_engine.get_affected_files`.
"""

from __future__ import annotations

import sys

from commands import register_command


def add_args(parser):
"""Add `affected` command arguments."""
parser.add_argument(
"files",
nargs="*",
default=None,
help="Changed file paths (absolute, relative, or bare basenames). "
"If omitted, reads from stdin — pipe `git diff --name-only`.",
)
parser.add_argument(
"workspace",
nargs="?",
default=None,
help="Path to workspace root (auto-detected if omitted).",
)
parser.add_argument(
"--stdin",
action="store_true",
default=False,
help="Read changed file paths from stdin (one per line). "
"Useful for piping: `git diff --name-only HEAD | codelens affected --stdin`.",
)
parser.add_argument(
"-d", "--depth",
type=int,
default=5,
help="BFS depth cap (default 5). -1 = unlimited (with a 5000-node safety cap).",
)
parser.add_argument(
"-f", "--filter",
default=None,
metavar="<glob>",
help="Only return affected files matching this glob (e.g. 'tests/*.py'). "
"Does not affect traversal. Default: no filter.",
)
parser.add_argument(
"-j", "--json",
dest="as_json",
action="store_true",
default=False,
help="Emit full result dict as JSON instead of plain file list.",
)
parser.add_argument(
"-q", "--quiet",
action="store_true",
default=False,
help="Only print affected file paths, one per line. No stats, no headers. "
"Default when piping to another command.",
)
parser.add_argument(
"--include-source",
action="store_true",
default=False,
help="Also return non-test dependents. By default only test files are returned.",
)


def execute(args, workspace):
"""Run the affected-files analysis.

Returns a dict (when ``--json`` or default) or prints paths to stdout
(when ``--quiet``). Reads from stdin when ``--stdin`` is set.
"""
# Gather changed files from args + stdin
changed_files: list[str] = list(args.files or [])

if getattr(args, "stdin", False):
try:
stdin_text = sys.stdin.read()
except (KeyboardInterrupt, OSError):
stdin_text = ""
for line in stdin_text.splitlines():
line = line.strip()
if line and not line.startswith("#"):
changed_files.append(line)

if not changed_files:
return {
"status": "error",
"error": (
"no changed files provided. Pass file paths as args, or use "
"--stdin to read from stdin (e.g. `git diff --name-only | "
"codelens affected --stdin`)."
),
"workspace": workspace,
}

# Lazy import — keeps the command light if only --help is invoked
from dependents_engine import get_affected_files

result = get_affected_files(
changed_files=changed_files,
workspace=workspace,
depth=args.depth,
file_filter=args.filter,
include_source=args.include_source,
)

# --quiet mode: print only file paths, one per line, to stdout
if getattr(args, "quiet", False):
for f in result.get("affected", []):
print(f)
# Return a minimal dict so the CLI wrapper doesn't double-print
return {
"status": "ok",
"quiet": True,
"affected_count": len(result.get("affected", [])),
"workspace": workspace,
}

# Default + --json: return the full result dict (formatter handles output)
return result


register_command(
"affected",
"Identify test files affected by source changes (issue #62 Phase 1)",
add_args,
execute,
)
Loading
Loading