All notable changes to CodeLens will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
CodeLens's workspace auto-detection walks up the directory tree looking
for project markers (.git, .codelens, pyproject.toml, etc.). When
a user runs CodeLens inside a git worktree that does not have its
own .codelens/ index, the walk-up silently picks up the main
checkout's .codelens/ — which was built from a different branch.
Every subsequent query / trace / dataflow / taint answer is
then grounded in the wrong file set, with no warning to the user or
the agent.
Added:
- New module
scripts/sync/worktree.pywith:detect_worktree_index_mismatch(project_root)— returns a structured record withmismatch,reason,worktree_root,main_checkout_root,index_root, andsuggestionfields.format_worktree_warning(mismatch)— multi-line human-readable warning for CLI output.format_worktree_banner(mismatch)— single-line banner for MCP responses.
- New
doctorcheckworkspace.worktree_index_mismatchthat surfaces awarningstatus when the workspace is a worktree using a foreign index. The check runs beforeworkspace.codelens_writable(which creates.codelens/as a side effect of probing write permissions — running mismatch first gives an honest picture of the pre-doctor state). - MCP server attaches a
_worktree_warningfield to read-tool responses when a mismatch is detected. The field contains a human-readablebannerstring plus the fullmismatchrecord. Agents that know about the field surface the banner; agents that don't ignore it (per MCP spec — unknown top-level fields are ignored). - The mismatch verdict is cached per workspace for the server's
lifetime (detection shells out to
gittwice, too expensive to repeat per tool call). The cache is invalidated whenscanruns — the user may have just runcodelens init -iin the worktree to fix the mismatch.
Why a top-level field, not a content prepend:
Prepending text to the content array would corrupt the JSON payload
agents parse out of the response. A top-level field keeps the JSON
intact while still surfacing the warning prominently.
Reason codes returned by detect_worktree_index_mismatch:
| Reason | Mismatch | Meaning |
|---|---|---|
not_a_git_repo |
False |
git not installed, or workspace not under git |
not_a_worktree |
False |
main checkout, no worktree concerns |
worktree_has_own_index |
False |
worktree with its own .codelens/ (correct setup) |
no_index_found |
False |
worktree, but no .codelens/ anywhere up the tree |
worktree_uses_main_index |
True |
MISMATCH — worktree reading main's index |
The codelens --lsp-status top-level flag (intercepted in
scripts/codelens.py) and the codelens lsp-status subcommand
(scripts/commands/lsp_status.py) returned structurally different
payloads for what the documentation treats as the same operation:
--lsp-statuscalledhybrid_engine.get_lsp_status()— the richer payload withavailable_count,total_servers, per-serverpath+extensions, and arecommendationfield.lsp-statuscalledlsp_client.detect_available_servers()directly and rebuilt a smaller dict — noavailable_count/total_servers, per-server entries missingpath/extensions, and ahintfield instead ofrecommendation.
The MCP server dynamically discovers subcommands, so MCP agents
(codelens_lsp_status tool) got the smaller payload while CLI
users got the richer one — two different "truths" for the same
question.
Fix (Option B from the issue): both entry points now delegate to
hybrid_engine.get_lsp_status() — single source of truth. The
top-level --lsp-status flag is preserved as a backward-compatible
alias of the lsp-status subcommand. Option A (remove the flag
entirely) was rejected because the issue's DoD explicitly requires
both entry points to produce byte-identical output (repro diff exit
code 0), which is unsatisfiable if one entry point is removed.
A pre-existing determinism bug was also fixed in
lsp_client.detect_available_servers(): extensions was returned as
list(config["extensions"]) where config["extensions"] is a set,
so order varied across Python invocations (hash randomization). Now
sorted, so the repro diff is byte-identical, not just structurally
equal.
scripts/commands/lsp_status.py:execute()— Now delegates tohybrid_engine.get_lsp_status()instead of rebuilding a smaller dict fromlsp_client.detect_available_servers(). The ImportError fallback was updated to match the new (richer) shape so error responses are still structurally consistent.scripts/lsp_client.py:detect_available_servers()—extensionsfield is nowsorted(config["extensions"])instead oflist(config["extensions"])for deterministic cross-invocation output.
SKILL-QUICK.md— Trigger map now has an explicit"LSP servers available?"→lsp-statusentry, noting that--lsp-statusis an alias. The Setup & Lifecycle command list also documents the alias relationship.
tests/test_cli.py:TestLspStatusEntryPointParity— New class with 3 tests: top-level key parity, per-server field parity, and full byte-identical payload equality. Guards against future regressions of the dual-truth problem.
Phase 1 roadmap (#21) checklist item: "Fix vuln DB staleness (OSV.dev
API, update scheduler)". The OSV client had a 24h TTL cache with
cleanup() but no staleness indicator in vuln-scan output and
no way to force a refresh — agents consuming vuln-scan had no
way to know whether the cached CVE data was fresh or stale, and no
way to override the 24h TTL for a single run.
This change adds three things:
-
cache_infoblock in vuln-scan output — a new top-level key in thevuln-scanJSON describing OSV cache freshness:"cache_info": { "last_refresh": "2026-06-28T10:00:00Z", "age_hours": 23.5, "ttl_hours": 24, "is_stale": false, "stale_packages": [] }
last_refreshis the ISO 8601 UTC timestamp of the most-recent cache entry among the packages queried in this run.age_hoursis its age.is_staleistruewhen any queried package's cache entry is past TTL or missing.stale_packageslists the"name@version"strings of stale/missing packages (sorted for deterministic output). -
--refreshflag —codelens vuln-scan --refreshbypasses the OSV cache and forces a fresh OSV.dev API call for every package. The cache is updated with the new results. Silently ignored in--offlinemode (no network to refresh from). -
--max-age Nhflag —codelens vuln-scan --max-age 6htreats cache entries older than 6 hours as stale for this run only, re-fetching them from the API. The stored TTL is not modified (per-run override only). AcceptsNh(hours),Nm(minutes),Ns(seconds),Nd(days), or a bare integer (interpreted as hours, matching--osv-ttlsemantics).--max-age 0is equivalent to--refreshfor cached entries.
Network calls happen only when --refresh is set OR the cache is
expired/missing/stale-per---max-age. Default behaviour (no flags)
is unchanged: cached entries within TTL are served from the cache.
scripts/osv_client.py:OSVCache.peek(key)— New method. Returns the raw(response, timestamp, ttl)tuple WITHOUT applying the stored TTL or deleting the entry. This is what--max-agerelies on to apply a per-run TTL threshold without mutating stored state. Corrupt entries (invalid JSON) are deleted and treated as missing, matchingget()'s behaviour.scripts/osv_client.py:OSVClient.query_packages(packages, force_refresh=False, max_age=None)— New optional params.force_refresh=Truebypasses the cache entirely (issue #30--refresh);max_age=N(seconds) usespeek()to apply a per-run TTL threshold (issue #30--max-age). Behaviour is unchanged when both are unset.scripts/osv_client.py:OSVClient._parse_cached_response(cached, package)— New private helper. Factors the two-shape cache parsing (list of vuln IDs vs list of full vuln dicts) out ofquery_packagesso all three code paths (normal, force_refresh, max_age) share it. Zero dead code — the inline parsing logic was moved, not duplicated.scripts/osv_client.py:OSVClient.get_cache_info(packages)— New method. Returns thecache_infodict described above. Packages with unsupported ecosystems are skipped. Missing entries are treated as stale.scripts/commands/vuln_scan.py:_parse_max_age(raw)— New helper. Parses--max-ageduration strings into seconds.scripts/commands/vuln_scan.py— New--refreshand--max-ageCLI flags.tests/test_vuln_staleness.py— 39 tests across 7 classes covering_parse_max_age,OSVCache.peek,get_cache_info(empty/all-stale/all-fresh/mixed/sorted/ttl),force_refresh(bypasses cache / uses cache / ignored offline),max_age(old→stale / young→fresh / stored TTL unchanged /0=refresh), end-to-endscan_vulnerabilitiesoutput onclean_appandvulnerable_appfixtures, and CLI arg wiring. All network-free (API calls mocked viaunittest.mock.patch.object).
scripts/vulnscan_engine.py:scan_vulnerabilities()— Gainsrefreshandmax_ageparams, forwarded toosv_client.query_packages(force_refresh=, max_age=). Computes acache_infoblock after the OSV query (three code paths: success → fromget_cache_info(); no packages → empty shape; OSV exception → empty shape witherrorfield). The return dict now includes acache_infokey.scripts/commands/vuln_scan.py:execute()— Validates--max-agevia_parse_max_age()before calling the engine. Invalid--max-agereturns a structured{status:'error', error:'invalid_argument', message:...}dict instead of raising.
- The
cache_infoblock is additive — no existingvuln-scanoutput key is removed or renamed. Consumers who don't readcache_infosee no change. scan_vulnerabilities()'s new params (refresh,max_age) are optional with defaults (False,None), so existing callers are unaffected.OSVClient.query_packages()'s new params are optional with defaults (False,None); existing callers (includingquery_single,batch_query, andscan_with_osv) are unaffected.OSVCache.peek()is a new method; no existing method's signature or behaviour changes.- Network behaviour is unchanged by default: the OSV API is only
contacted when
--refreshis set OR a cache entry is expired / missing / stale per--max-age. The default 24h TTL path is byte-for-byte identical to the pre-issue-#30 code. --refreshis silently ignored in--offlinemode (matches the existing offline contract — no network calls are ever attempted whenoffline=True).
Agents that consume vuln-scan output can now check
cache_info.is_stale to decide whether to trust the cached CVE
results. If stale, re-run with --refresh (force fresh API calls
for all packages) or --max-age 6h (only re-fetch entries older
than 6 hours, cheaper than a full refresh). stale_packages lists
the specific packages that need attention.
Previously, scan --incremental updated only the flat backend registry
and skipped graph population entirely. As a result, graph_nodes and
graph_edges became stale after any incremental scan — trace --use-graph
returned outdated callers/callees, and the recommended post-edit workflow
(scan --incremental) silently broke the graph backend that #8 introduced.
This fix adds a slice-level update path: only the changed files' nodes
and edges are deleted and re-inserted from the flat registry, then
refine_call_edges (from #13) is re-invoked to rebuild IMPORTS edges
and re-refine CALLS edges. The full scan path is unchanged — it still
calls populate_graph_tables for a bulk rebuild.
-
scripts/graph_model.py:incremental_graph_update(workspace, db_path, changed_files)— New function. Performs a slice-level graph update:- Normalize
changed_files(absolute paths) to workspace-relative paths. Empty input is a no-op (returns zero counts) so the no-changes path inscan --incrementalis safe. - Identify
graph_nodesrows whosefileis in the changed set (the stale node ids that must be replaced). - Delete
graph_edgesrows that touch any changed file:- edges whose
file(originating file) is in the changed set (covers CALLS edges from changed files + IMPORTS edges whose importer changed), AND - edges whose
source_idortarget_idreferences a stale node id from step 2 (covers cross-file edges from an unchanged file into a changed file — the target may have been renamed/moved).
- edges whose
- Delete the stale
graph_nodesrows themselves. - Re-read the flat backend registry (already updated by
merge_backend_datain the scan pipeline) and INSERT only the nodes whosefileis in the changed set, plus CALLS edges that touch the changed set (either endpoint's file is in the set). - Call
refine_call_edges(workspace, db_path)so IMPORTS edges and import-aware CALLS-edge refinement are rebuilt for the affected slice.refine_call_edgesis idempotent (it clears and rebuilds theimport_registrytable and IMPORTS edges from scratch each call), so invoking it here is safe regardless of whether a previous scan already ran it.
Returns:
{nodes, edges, edges_refined, edges_unresolved}wherenodes/edgesare the TOTAL graph row counts after the update (not the delta) so the return shape matchespopulate_graph_tables. - Normalize
-
tests/test_graph_incremental.py— 19 tests across 9 classes covering: no-op cases, equivalence with full populate, idempotency, slice isolation, file modification reflection (rename/add/remove), stale edge dropping (no orphan edges), return-value shape, end-to-end viacmd_scan(incremental=True)(graph field present in BOTH full and incremental scan output with matching counts), and a performance assertion (<200ms for 5 changed files; issue spec targets <100ms).
scripts/commands/scan.py— Incremental path now callsincremental_graph_update(workspace, db_path, changed_files)instead of skipping graph population. The full-scan path is unchanged (still callspopulate_graph_tables+refine_call_edges). Scan output now ALWAYS includes agraphfield with the actual final state ({nodes, edges}) ofgraph_nodes+graph_edges, regardless of scan mode — previously the incremental path emitted nographfield at all. Thetype_resolutionfield is populated by the incremental path too (fromincremental_graph_update's return value).
- Full-scan behavior is unchanged —
populate_graph_tablesis still called for a clean bulk rebuild on every full scan. - The incremental path is best-effort: any failure inside
incremental_graph_updateis logged at WARNING level and swallowed, so the flat registry remains the source of truth and the scan still succeeds (matches the existing full-scan error-handling contract). - The function is idempotent — running twice with the same
changed_filesyields the same final graph state. - The
graphfield added to the incremental-scan output is additive; no existing field is removed or renamed. Consumers who previously special-cased the missinggraphfield on incremental scans see a populated{nodes, edges}shape identical to the full-scan output. - On the
clean_appfixture: full scan reportsgraph: {nodes: 31, edges: 134}(CALLS + IMPORTS, after refine). Subsequent incremental scan with no changes reports the same counts. Incremental scan after renamingformat_text→format_text_renamedinsrc/utils.pyupdates the graph (renamed node present, old name gone from that file) and reports updated edge counts.
Engines that read graph_nodes / graph_edges after an incremental
scan no longer need to fall back to the flat registry or trigger a
manual full scan — the graph is always in sync with the flat registry
after cmd_scan returns, regardless of incremental=True/False.
Previously, the confidence / confidence_distribution fields were only
attached to query / impact / dead-code output when the --deep flag
was passed (which triggers LSP verification). This meant consumers of the
default (non-deep) output had no way to know the analysis provenance. The
hybrid engine's module docstring already documents the intended semantics
(high = LSP verified, medium = AST matched, low = regex only), and
HybridEngine.enhance_* methods already set confidence = MEDIUM when LSP
is not active — but those methods were only invoked from the --deep
post-processing path in codelens.py, never from the command execute()
entry points.
This fix completes the partially-implemented feature by attaching baseline
confidence = "medium" (and confidence_distribution for dead-code) at
command execution time, before the --deep post-processing layer runs.
When --deep is later applied, LSP verification may override individual
fields to high or low as before.
querycommand — top-levelconfidencefield is now always present onfoundresults (value:"medium"for AST-based analysis,"high"or"low"when--deep+ LSP verifies).impactcommand — top-levelconfidencefield is now always present onstatus: okresults.dead-codecommand — each finding inresultsnow carries aconfidencefield;stats.confidence_distribution(counts ofhigh/medium/low) is now always present.
- All previously-passing tests continue to pass.
- The new fields are additive — no existing field is removed or renamed.
- When
--deepis used, the--deeppost-processing layer incodelens.pystill runs and may override the baseline confidence based on LSP verification, exactly as before. - The
confidencefield is also surfaced in the--format ainormalized output via the existing_META_KEYSextraction informatters/__init__.py.
Adds a 5th output format (compact) and pagination to all list-type commands
so AI agents pay fewer tokens for the same information. A single trace call
that previously returned 5-10KB of verbose JSON now returns ~2.5-5KB of
compact single-char-key JSON. Target: 5 structural queries cost <5k tokens
total (down from 30-80k).
-
--format compact— New 5th output format alongsidejson/markdown/ai/sarif. Implemented inscripts/formatters/compact.py:- Omits null/empty fields (saves ~15% on average).
- Abbreviates node types:
function→fn,class→cls,file→f,module→m,route→r,type→t,interface→i. - Abbreviates edge types:
CALLS→C,IMPORTS→I,DEFINES→D,INHERITS→H,IMPLEMENTS→M,USES_TYPE→U. - Uses single-char keys:
name→n,file→f,line→l,type→t,status→s,confidence→c, etc. (full map informatters/compact.py:FIELD_KEY_ABBR). - Strips the workspace prefix from absolute paths.
- Output is still valid JSON — MCP clients parse it directly.
-
graph-schemacommand +codelens_graph_schemaMCP tool — Returns node + edge counts, node-type distribution, edge-type distribution, and index count in one cheap call. Example compact output:{"s":"ok","n":31,"e":97,"nts":{"function":30,"class":1},"ets":{"CALLS":97},"ix":6}. The cheapest way for an agent to understand the graph shape before issuing structural queries. -
--limit N/--offset Npagination onlist,search,trace,symbols,outline. Default--limit 20. All paginated commands now returntotal_count,count,offset,limit,has_morefields. The existing--top Nflag is preserved as an alias for--limit N --offset 0. -
formatparameter on every MCP tool — All MCP tools now accept aformatparameter with the enum[json, markdown, ai, sarif, compact]. Default remainsai(normalized schema). Passformat: "compact"for token-efficient responses. -
tests/test_compact_format.py— 28 test cases covering compact formatter rules, pagination behavior, graph-schema command, MCP tool advertisement, and token-savings assertions.
scripts/codelens.py— Global--formatflag (and per-subparser flag) now acceptcompactas a 5th choice. Pre-parse loop updated to recognizecompactbefore subcommand dispatch.scripts/formatters/__init__.py—format_output()now dispatches toformatters.compact.format_compactwhenformat_type == "compact".scripts/commands/search.py— Adds--limit/--offset, paginates thematcheslist, addstotal_count/count/offset/limit/has_morefields.scripts/commands/list.py— Default--limitlowered from 200 to 20 (per issue #17 spec); addstotal_countfield alongside the existingtotal.--limit 0means unlimited (preserves backward compat).scripts/commands/trace.py— Adds--limit/--offset, paginateschains.upandchains.down, addstotal_countfield.scripts/commands/symbols.py— Adds--limit/--offset, paginatesresults, addstotal_count/has_morefields.scripts/commands/outline.py— Adds--limit/--offset, paginatesoutlines, addstotal_count/has_morefields.scripts/mcp_server.py— Addsgraph-schemato_TOOL_DEFINITIONS. Adds_inject_format_enum()helper that injects the sharedformatproperty into every tool's inputSchema._execute_commandnow respectsarguments["format"]— when set to"compact", returns the compacted dict viaformatters.compact.compact_dictinstead of the AI-normalized schema.
- Existing
--format json/ai/markdown/sarifoutputs are unchanged. - Existing
--top Nflag still works (alias for--limit N --offset 0). - Existing
list --limit 200(the old default) still works — only the default value changed from 200 to 20. Pass--limit 200explicitly to restore the old behavior, or--limit 0for unlimited. - All 56 existing CLI commands continue to work unchanged.
- 28 new tests pass; 4 pre-existing
test_hybrid_engine.pyfailures (confidence-field assertions) are unchanged — NOT caused by this change.
The compact formatter is purely a presentation-layer concern. Engines do not need to know about it — the formatter reads the engine's existing output dict and produces a compacted JSON string. To verify your engine's output compacts well, run:
$CLI <your-command> --format compact | python3 -m json.toolIf a field you depend on disappears in compact output, it's because the value was null/empty (the formatter drops these). Either populate the field with a meaningful default, or accept that null fields are noise.
Replaces the ad-hoc flat-registry graph traversal with a true node + edge graph backed by SQLite. This unblocks structural queries like "who calls this function across the entire codebase", "blast radius if I rename this class", and "circular dependency chains" — engines no longer need to reimplement partial graph traversal logic.
-
scripts/graph_model.py— New module implementing the graph data model:init_graph_schema(conn)— Createsgraph_nodes+graph_edgestables and 6 indexes (idempotent, called during database initialization).populate_graph_tables(workspace, db_path)— Reads the flat backend registry and bulk-inserts all nodes + edges in a single transaction. Clears stale rows first so re-scans don't duplicate.query_callers(node_id, db_path, max_depth=1)— BFS over CALLS edges in reverse (who calls this node).query_callees(node_id, db_path, max_depth=1)— BFS over CALLS edges forward (what this node calls).clear_graph_tables(db_path)— DELETE FROM both tables.find_nodes_by_name,graph_tables_exist,graph_tables_populated,graph_stats— introspection helpers for engines and tests.
-
Graph schema (additive, prefixed
graph_to avoid collisions):graph_nodes(id, node_id UNIQUE, node_type, name, file, line, extra_json) graph_edges(id, source_id, target_id, edge_type, file, line, confidence, extra_json)
Node types:
function|class|file|module|route|type|interfaceEdge types:CALLS|IMPORTS|DEFINES|INHERITS|IMPLEMENTS|USES_TYPE(OnlyCALLSis populated in v8.2; other types are reserved for future engine migrations —impact,circular,dependents.) -
trace --use-graph/--no-graphflags — Thetracecommand now queries the graph tables by default, with the flat-registry path retained as fallback. Use--no-graphto force the flat path for A/B testing. -
tests/test_graph_model.py— 20 test cases covering schema init, population, query_callers, query_callees, re-population idempotency, and the trace pilot A/B comparison.
Replaces the file-watcher-only change detection with optional git-diff awareness so incremental scans target exactly the files git knows changed (tracked + untracked), instead of relying solely on filesystem mtimes. All features gracefully degrade to None / [] / False / mtime-fallback when git is unavailable or the workspace is not a git repo.
-
scripts/git_aware.py— New module implementing the git-aware change-detection layer:get_current_sha(workspace)/get_current_branch(workspace)— HEAD SHA + branch (None when not a git repo).get_changed_files(workspace, since_sha=None)—git diff --name-only(HEAD or<sha>).get_untracked_files(workspace)—git ls-files --others --exclude-standardso newly-created (not-yet-added) files are visible to incremental scans.get_last_indexed_sha/set_last_indexed_sha— registry_meta key/value bookmark of the HEAD SHA + branch at the time of the last successful scan.detect_branch_switch(workspace, db_path)— True when HEAD moved AND the branch name changed (catchesgit checkout, not same-branch commits).rescan_recommended(workspace, db_path)— True when a branch switch is detected OR any changed files exist since the last index.init_registry_meta(conn)— creates theregistry_meta(key TEXT PRIMARY KEY, value TEXT)table (idempotent).
-
scripts/commands/git_status.py— Newgit-statuscommand (auto-registered). Single-call "do I need to re-scan?" check for AI agents. Reports: current_sha, current_branch, last_indexed_sha, last_indexed_branch, changed_files_count, branch_switch_detected, rescan_recommended. Always returns status=ok; git-unavailable is reported via git_available=False (not an error). -
tests/test_git_aware.py— 32 test cases across 9 classes (TestCurrentSha, TestChangedFiles, TestRegistryMeta, TestBranchSwitch, TestRescanRecommended, TestGitStatusCommand, TestDiffGitAware, TestIncrementalGitPath, TestScanStoresBookmark). All git operations use a temp directory +git init— no dependency on the CodeLens repo's git state. Tests skip withpytest.skip('git not available')when git is missing.
scripts/incremental.py—find_changed_filesnow tries the git-aware path FIRST: if alast_indexed_shabookmark exists inregistry_meta, usesgit diff <sha> --name-only+git ls-files --othersto enumerate exactly the files git knows changed. Deleted files (in diff but not on disk) are returned in the deleted slot soscan.py's existing deletion-cleanup path runs unchanged. Falls back to the existing mtime-based detection when git is unavailable, no bookmark is stored, or any unexpected error occurs. Signature is backward-compatible —db_pathis a new optional kwarg.scripts/commands/scan.py— After a successful scan (full or incremental), if git is available, persistslast_indexed_sha+last_indexed_branchviaset_last_indexed_sha(). Scan output now includes agitfield with{last_indexed_sha, last_indexed_branch}so agents can verify the bookmark was recorded. Fail-soft: if the bookmark write fails, the scan still succeeds.scripts/commands/diff.py— New--git-awareflag. When set, the diff command produces a single-call "what changed + what's affected" view: changed_files (from git), symbols (from flat backend registry, filtered to changed files), impact (callers fromgraph_model.query_callerswhen graph tables are populated). Default snapshot-diff behavior is unchanged —--git-awareis purely additive. Falls back togit_available=Falsewhen git is unavailable (status stays "ok").scripts/commands/watch.py— New--git-modeflag (default off). When set, switches from watchdog file events to git-diff polling: every--intervalseconds (default 2.0), runsgit diff --name-only+git ls-files --othersand re-indexes only the files git knows changed. Falls back to mtime polling when git is unavailable or the workspace is not a git repo. Default watchdog behavior is preserved (BOS decision: keep watchdog as default, ADD git-awareness as alternative).scripts/persistent_registry.py— Callsinit_registry_meta(conn)during_init_schemaso theregistry_metatable always exists by the time any git-aware function tries to read or write a bookmark. Additive — no existing table or column modified.
- All 56 existing CLI commands continue to work unchanged.
- The git-aware layer is purely additive — when git is unavailable, all functions return None / [] / False and the existing mtime path runs.
- The
registry_metatable is additive — no existing table or column was modified. - Scan output gained a new top-level
gitfield; existing fields are untouched. - The
diff --git-awareflag is opt-in; defaultdiffbehavior is unchanged.
- Issue #25 (incremental graph population) — Incremental scans
still don't populate the graph tables (
graph_nodes+graph_edges); only full scans do.diff --git-awarereports an emptyimpactarray when graph tables aren't populated (e.g. after an incremental-only scan). This is a pre-existing gap tracked in #25 and is NOT made worse by this change.
Adds a post-AST-pass type resolution layer that uses the per-file import
registry to refine CALLS edges. Previously user.profile.update() was
recorded as a call to update with no target type — the call graph had
holes wherever methods were called on imported objects. Now the receiver
type is resolved via the import registry, and the CALLS edge's
target_id is refined to the correct target node (e.g. Profile.update
in models.py instead of an arbitrary update match).
-
scripts/hybrid_type_resolver.py— New module with:build_import_registry(workspace, db_path)— Scans Pythonfrom X import Y/import X.Y as Zand TS/JSimport {Y} from 'X'/import * as X from 'Y'statements. Stores results in a newimport_registrySQLite table(file, local_name, module_path, symbol_name, line). Also writes IMPORTS edges tograph_edges(edge_type='IMPORTS') so the graph model now carries import relationships alongside CALLS.resolve_receiver_type(file_path, receiver_expr, import_registry)— Resolves a dotted receiver expression (user.profile) to a fully qualified type (models.Profile) via the import registry + class definitions ingraph_nodes. Best-effort: returnsNonewhen unresolvable, never crashes.refine_call_edges(workspace, db_path)— For each CALLS edge with a generic/unresolvedtarget_id, attempts to resolve the receiver type and updatestarget_idto the resolved node. Stores{"resolved_type": "...", "resolution_method": "import_registry"}in the edge'sextra_jsonon success, or{"resolution_attempted": true, "failure_reason": "..."}on failure. Returns stats:{edges_total, edges_refined, edges_unresolved}.
-
resolve-typescommand +codelens_resolve_typesMCP tool — Manually triggers type resolution without a full re-scan. Useful for agents who want to refresh type resolution after adding new imports. Output:{status, edges_total, edges_refined, edges_unresolved, import_registry_size}. -
IMPORTS edges in graph model — The graph now carries two edge types:
CALLS(from #8) andIMPORTS(from #13). Futurequery_graphwork (#9, Phase 3) can traverse both.
scripts/commands/scan.py— Afterpopulate_graph_tables()(from #8), callsrefine_call_edges(workspace, db_path). Scan output now includes atype_resolutionfield:{edges_refined, edges_unresolved}.scripts/graph_model.py—graph_stats()now reports IMPORTS edges in theedge_typesbreakdown alongside CALLS.
- Type resolution is best-effort: unresolvable edges are left unchanged
with a
resolution_attemptedflag. No CALLS edge is ever deleted. - The
import_registrytable is additive — no existing table modified. - On the
clean_appfixture: 11/97 CALLS edges refined, 55 unresolved (the remaining 31 are self-referential or std-lib calls that don't need refinement). On the synthetictype_resolutionfixture (tests/fixtures/type_resolution/),user.profile.update()correctly refines toProfile.updateeven when aCache.updatecompetitor exists.
scripts/persistent_registry.py— Callsinit_graph_schema(conn)during_init_schemaso the graph tables always exist by the time any engine tries to query them. Additive — existing tables untouched.scripts/commands/scan.py— After the flat backend registry is built, callspopulate_graph_tables(workspace, db_path)to populate the graph tables in a single bulk transaction. Scan output now includes agraphfield with node + edge counts.scripts/trace_engine.py— Pilot engine migration:trace_symbolis now a dispatcher that picks betweentrace_via_graph(default) andtrace_via_flat(fallback). Falls back to flat automatically when graph tables are empty (pre-8.2 databases). Output shape is identical regardless of backend — callers and formatters don't need to know which backend ran.
- All 56 existing CLI commands continue to work unchanged.
- Existing flat tables (
symbols,refs,files,analysis_cache,scan_metadata) and JSON registries (frontend.json,backend.json) are untouched. - The graph tables are additive — no existing table or column was modified.
- Scan performance impact is negligible (single bulk INSERT in one transaction; <5ms on the clean_app fixture with 31 nodes + 97 edges).
The flat registry remains the source of truth during scan. The graph tables are a derived projection that engines can query for structural traversals. To migrate an engine to the graph backend:
- Check
graph_model.graph_tables_populated(db_path)— if False, fall back to the flat path (don't hard-fail). - Use
graph_model.find_nodes_by_name(name, db_path)to find start nodes. - Use
graph_model.query_callers/query_calleesfor BFS traversal. - Preserve the existing flat-path output shape so callers and formatters
don't break. See
trace_engine.trace_via_graphfor a reference impl.
Future engine migrations (post-v8.2): impact, circular, dependents.
Orientation on an unfamiliar codebase previously required 4-6 chained commands
(scan → list → detect → entrypoints → api-map → read entry files), burning
10-20k tokens before any real work started. The new architecture command
codelens_architectureMCP tool collapses that into a single call returning a compact overview: languages, frameworks, entry points, packages, top routes, graph hotspots, total symbol count, and anadrsplaceholder (ADR feature is issue #16, Phase 3).
-
scripts/architecture_engine.py— New engine module orchestrating existing engines into one overview:get_architecture(workspace, lite=False)— single public entry point._compute_languages— three-tier resolution: fresh scan_result'sfiles_scanned→.codelens/summary.json→ cheap stat-only extension walk. Scan-result buckets are collapsed to canonical language names (e.g.js_backend+js_frontend→javascript)._compute_frameworks— thin wrapper aroundframework_detect._compute_entry_points— wrapsentrypoints_engine.map_entrypoints, dedupes by file path, prioritises main/handler/cli types._compute_packages— scanssrc/,app/,lib/,packages/,server/,internal/for immediate subdirectories that contain source files (one level of recursion allowed for flat two-tier packages)._compute_routes— wrapsapimap_engine.map_api_routes, normalises each route to{method, path, handler}, capped at 20._compute_hotspots— single SQL round-trip:SELECT gn.file, COUNT(*) FROM graph_edges JOIN graph_nodes ... GROUP BY gn.file ORDER BY cnt DESC LIMIT 5. This is the killer feature of the new graph model — files ranked by total incoming CALLS edges across all their symbols (blast-radius surface)._compute_total_symbols—graph_stats(db_path).nodes.- Cache layer: writes
.codelens/architecture_cache.jsonon first call; subsequent calls return the cache as long as.codelens/codelens.dbmtime hasn't advanced (i.e. scan hasn't been re-run). Lite and full payloads have different shapes — the cache won't serve the wrong shape even when the db is unchanged.
-
scripts/commands/architecture.py— New CLI command auto-registered viacommands/__init__.py. Flags:--lite(omit routes/packages/hotspots for <1k token orientation),--no-cache(force rebuild). -
codelens_architectureMCP tool — Added to_TOOL_DEFINITIONSinscripts/mcp_server.py. Schema:{workspace: string}required;{format: string, lite: boolean}optional. Calls thearchitecturecommand internally. -
tests/test_architecture.py— 24 test cases covering all eight spec verification points: status ok, all required fields, lite mode shape, hotspots sorting + graph query match + distinct files, cache create + reuse + invalidation + lite/full shape isolation, MCP tool listing + end-to-end call, <4000-byte token budget (raw + MCP-normalised).
- Architecture-specific fields nested inside
stats— The MCP_normalize_to_aiformatter preservesstatsas-is but drops unknown top-level keys. To deliver the full architecture data through MCP without modifying the shared formatter (which would collide with parallel worker 2-a's compact-format changes), all architecture fields (languages, frameworks, entry_points, packages, routes, hotspots, total_symbols, adrs) are nested insidestats. CLI consumers see the same shape. adrsplaceholder is[]— ADR detection is issue #16 (Phase 3). The field is reserved so consumers can rely on the shape today.- Auto-scan on fresh workspace — If
.codelens/codelens.dbdoesn't exist or the graph tables are empty whenarchitectureis called, the engine runscmd_scanfirst. This makes the tool self-sufficient for the issue #19 use-case ("agent starts on an unfamiliar codebase"). - File-level hotspots (not per-symbol) — The SQL query groups by
gn.file, notge.target_id, so each hotspot is a distinct file with its total blast radius. Matches the issue spec example"src/models/user.py (47 dependents)".
--literaw engine payload: 284 bytes (~71 tokens)--liteMCP-normalised response: 298 bytes (~75 tokens)- Full raw engine payload: 502 bytes (~125 tokens)
- Full MCP-normalised response: 515 bytes (~128 tokens)
All well under the 1k-token target.
- Avg F1: 0.803 → 0.872 (+8.6%)
- Avg FPR (clean): 0.153 → 0.050 (-67%)
- Targets met: 28.6% → 57.1%
- Added module-level cycle detection in
_detect_function_cycles - Added bidirectional import pair safety net in
_detect_import_cycles - Added cross-type deduplication between
function_callandimport_chaincycles - Result: circular F1 0.667 → 1.000, circular FPR (clean) 0.222 → 0.000
- Fixed JS local export vs re-export differentiation (
export { X }vsexport { X } from) - Fixed Python unreachable code indent comparison (
<=→<) - Fixed Python multi-line return statement bracket counting
- Result: dead-code F1 0.800 → 0.952, dead-code FPR (clean) 0.500 → 0.000
- Return value propagation — functions returning tainted data now propagate taint to callers
- Scope-hierarchical TaintState — parent chain lookup prevents cross-scope contamination
- Branch condition refinement —
branch_conditionis now used during propagation for path-sensitive analysis
- Added
.github/workflows/codelens-ci.yml(test + benchmark + self-check + SARIF upload) - Added
.github/workflows/codelens-quality-gate.yml(PR quality gate) - Added
.github/workflows/codelens-sarif.yml(SARIF upload to GitHub Security) - Added
.github/workflows/codelens-benchmark.yml(regression benchmark) - Added
.gitlab-ci.yml(GitLab CI pipeline)
- Added honest competitive positioning table to README (vs SonarQube, CodeQL, Semgrep)
- Updated README command tables to cover all 56 commands (was ~39)
- Updated MCP tool count: 54 (49 static + 5 dynamic)
- Updated architecture tree to reflect actual
scripts/directory structure - Synced SKILL.md, SKILL-QUICK.md version numbers to v8.1
Real-world tested against multiple large open-source codebases (spacedriveapp/spacedrive, exercism/python, redis/redis, neovim/neovim, readest/readest, BurntSushi/ripgrep, calcom/cal.com, excalidraw/excalidraw, n8n-io/n8n, cockroachdb/cockroach, denoland/deno, Vercel Turborepo).
-
AST-based Taint Analysis Engine (
ast_taint_engine.py, 3057 lines)- Real tree-sitter AST traversal replaces regex line-by-line
- Path-sensitive, scope-aware, inter-procedural taint tracking
- Confidence scoring with taint path rendering
- Default engine when tree-sitter is available
- New command:
taint
-
Live CVE/OSV Database Integration (
osv_client.py, 1600 lines)- Real-time vulnerability data from OSV.dev API
- 9 ecosystems: PyPI, npm, crates.io, Go, Maven, NuGet, RubyGems, Pub, Hex
- SQLite cache with configurable TTL (24h default)
- Rate limiting + offline mode fallback
- Phase 0 in
vuln-scanpipeline (before native audit tools)
-
Plugin System & Rule Marketplace (
plugin_system.py, 1462 lines)- 4 plugin types:
rule_pack,engine,formatter,command - 3-tier discovery: local (
.codelens/plugins/) > user (~/.codelens/plugins/) > built-in (scripts/plugins/) - New command:
plugin <install|list|search|update|info|validate> - Built-in OWASP Top 10 plugin (36 rules, all 10 categories A01-A10)
- Built-in Compliance plugin (53 rules: PCI-DSS v4.0 + HIPAA Security Rule)
- 4 plugin types:
-
VS Code Extension (
vscode-codelens/, 2011 lines)- Diagnostics Provider (SARIF → VS Code on save/open)
- Code Actions Provider (QuickFix + Fix All)
- Guard pre-save hooks
- Status bar health indicator (green/yellow/red)
- 8 configuration settings
- Supports Python, JavaScript, TypeScript
-
Enhanced Cross-File Dataflow Engine (
callgraph_engine.py, 3539 lines)- Workspace-wide call graph using tree-sitter
- Cross-file import resolution (
from/import,requiredestructuring) - Data flow graph with forward + reverse taint propagation
- Inter-procedural taint across file boundaries
-
OWASP Top 10 + Compliance Mapping (89 rules total)
- A01-A10 all covered with Python + JavaScript rules
- PCI-DSS v4.0: 32 rules mapping Requirements 1-12
- HIPAA Security Rule: 21 rules mapping 45 CFR § 164.312
-
Bug Fixes (10 critical bugs)
semantic_engine.py: sanitizer detection logichybrid_engine.py: loose path comparison →os.path.samefilepersistent_registry.py: thread-local SQLite connections + WAL modejavascript_security.yaml: normalized schema (id/name)--deepargument conflict betweenartifact-scanand global flag
- Auto-fix engine (
autofix_engine.py) — confidence-scored fixes, dry-run by default. New command:fix - HTML dashboard engine (
dashboard_engine.py) — visual dashboards with trend tracking. New command:dashboard - Historical trend tracking (
history_engine.py). New command:history - Semantic rules engine (
semantic_engine.py) — taint analysis for vulnerability detection - Hybrid analysis engine (
hybrid_engine.py) — LSP integration for deep accuracy (--deepflag) - SQLite persistent registry (
persistent_registry.py) — incremental scanning + analysis cache. New command:migrate - LSP status command —
lsp-statuschecks which language servers are available for--deepanalysis - Benchmark suite (
benchmarks/) — accuracy metrics, regression testing, fixtures (clean_app+vulnerable_app). New command:benchmark - Self-analyze command —
self-analyzeruns CodeLens on its own codebase - Pre-commit hook (
pre_commit_hook.py) — Git hook integration
- 248 new tests added across
tests/ - SARIF v2.1.0 output formatter (
formatters/sarif.py) - Markdown output formatter (
formatters/markdown.py) checkcommand — CI/CD quality gate that exits non-zero on failureanalyzecommand — full repo analysis: init + scan + all engines in one shotsummarycommand — auto-summary with prioritized findings (anti-overload)handbookcommand — generate project handbook for AI agents
- CLI version: v7.2 → v8.0 → v8.1
- Total commands: 45 → 56 (+11 new)
- Total MCP tools: 49 static → 54 (49 static + 5 dynamic)
- Total engines: ~23 → ~35
- Total parsers: 9 tree-sitter + 28 fallback = 37 total parser modules
analyze, artifact-scan, benchmark, binary-scan, check, fix, guard, handbook, lsp-status, migrate, plugin, serve, summary, taint, dashboard, history
(Plus serve which was the MCP server entry point — previously internal, now a registered command.)
Real-world test on a Tauri VDFS (Virtual Distributed Filesystem) file explorer monorepo — a unique architecture combining a Rust core with P2P networking, React/TSX frontend, Tauri desktop app, React Native mobile app, and WASM extensions. This repo exposed false positives in the secrets engine and dead code in the query command.
-
secretsengine URI scheme false positive (HIGH): The URL-embedded password pattern[\w+\-\.]+:([^\s@"\']{4,})@matched URI paths likesidecar://content_id/thumbs/grid@2x.webpas passwords because the capture group[^\s@"\']allowed/characters. URI schemes with://and@in the path (common in custom protocol handlers) were incorrectly flagged aspasswordwith severitycritical. Fixed by adding/to the excluded character class:[^\s/@"\']{4,}. This still correctly captures real URL-embedded passwords likeuser:password@host.comandftp://user:pass@host.com(which match from theuser:passportion), while rejecting paths with/that are clearly URI paths, not credentials. -
askcommand symbol extraction missed common question prefixes (MEDIUM): The_extract_symbol_name()function only stripped prefixes like "what is", "where is", "how does" but missed "what does", "what do", "why does", "why do", "when does", "when do", "how can", "how should". This caused questions like "What does the Library struct do?" to extract "what" as the symbol instead of "library", making the ask command completely useless for these common question patterns. Added all 8 missing prefixes. Also added pronoun fillers ("i ", "we ", "you ", "they ", "my ", "our ") to prevent them from being extracted as symbol names (e.g., "How can I find..." now extracts "find" instead of "i"). -
query --fuzzywas dead code when zero exact matches (HIGH): The fuzzy matching block was placed AFTER thetotal_matches == 0early return, meaning--fuzzywas completely non-functional when there were no exact matches — precisely the scenario where fuzzy matching is most needed. For example,query spawn --fuzzyreturned "Name does not exist. Safe to create." even thoughsymbols spawnfound 12 matches. Moved the fuzzy matching block BEFORE the early return so it executes when either--fuzzyis enabled OR no exact matches are found. Removed the duplicate fuzzy block that was unreachable at the bottom of the function.
Real-world test on a pure Python project with no web frameworks — exposed multiple blind spots in framework detection, project identity classification, and broken command imports.
is_bundled_filemissing fromutils.py(CRITICAL):complexity_engine.pyandperfhint_engine.pyimportedis_bundled_filefromutils, but the function never existed. This causedImportErrorduring command registration, silently breakingcomplexity,perf-hint,ask, andcontextcommands (4 of 45 commands non-functional). Addedis_bundled_file()toutils.pywith detection for dist/build/vendor dirs, minified files, source maps, and common bundled naming patterns.analyzecommand env check used wrong API (CRITICAL):_detect_env()calledaudit_environment()which doesn't exist — the correct function ischeck_env_vars(). Also used wrong return keys (total_issues,issuesinstead oftotal_vars,required_without_fallback). Theenv_issuescategory was always skipped in analysis output.analyzecommand hardcoded version: Output showedcodelens_version: "6.0"instead of usingCODELENS_VERSIONconstant (was6.3.0). Now imports fromutils.py.pyproject.tomlformatting error: Missing newlines betweendescription/readmeandrequires-python/authorscaused TOML parse failure.
- Python tooling framework detection: Added 7 new Python framework signatures:
pytest,poetry,setuptools,tox,sphinx,nox,hatch. Includespip_packages,config_files, andindicatorsfor each. Addedhas_pytest,has_poetry,has_pythonflags to detection output. - Pipfile dependency parsing: Comment said "Check Python dependency files (requirements.txt, pyproject.toml, Pipfile)" but Pipfile was never actually parsed. Now parses
[packages]and[dev-packages]sections. - Improved pyproject.toml Poetry dependency parsing: Poetry uses list-style deps like
dependencies = ["requests>=2.0", "flask"]and section-scoped deps under[tool.poetry.dependencies]. Added section-aware TOML parsing for Poetry and PEP 621 dependency formats. - Python project identity fallback:
_extract_project_identity()now detects Python projects fromrequirements.txt(with content analysis: web framework →backend-api, testing →python-test-suite, else →python-project),setup.py/setup.cfg→python-library, and.pyfile existence →python-project. No moretype: "unknown"for pure Python repos. scan_tauri_artifacts()implementation:binary_scan.pyimportedscan_tauri_artifactsfromutilsbut it didn't exist (gracefully caught by try/except). Now implemented: parsestauri.conf.jsonfor IPC commands, security settings (CSP, asset protocol), sidecar binaries, and warns about dangerous patterns.- Command import error logging level: Changed from
WARNINGtoERRORincommands/__init__.pyso broken command modules are more discoverable. .pyfile detection in framework walk: Added Python file detection alongside.vue,.svelte,.phpin the file pattern walking loop, settinghas_python: True.
Tested against redis/redis (1,844 files: 471 C + 311 H + 20 Lua + 46 Python + 228 TCL + 69 Shell, in-memory database)
Real-world test on a pure C project with Makefile build system, embedded Lua scripting, and polyglot codebase (C+Lua+Python+TCL+Shell). Exposed critical gaps in C/C++ project support that were invisible on JS/TS/Rust/Go projects.
-
is_bundled_file()missing fromutils.py:perfhint_engine.pyandcomplexity_engine.pyimportedis_bundled_filefromutils, but the function was never defined there. This broke 4 commands silently:ask,complexity,context,perf-hint. Addedis_bundled_file()toutils.pywith detection fordeps/,vendor/,third_party/,external/,submodules/, and minified/bundled file patterns. -
Drupal false positive from
modules/indicator: Redis (and many non-Drupal projects) have amodules/directory, which was listed as a Drupal indicator. Replacedmodules/andthemes/withsites/default/andsites/all/— directories that are truly unique to Drupal installations. This eliminates the false positive on Redis and similar C projects with module systems. -
C/C++ function name false positives in
smell_engine.py: The regexr'(?:static\s+|inline\s+)*(?:\w+[\s*]+)+(\w+)\s*\('matched C type keywords likevoid,const,unsigned,signed,volatile,extern,register,auto,static,inlineas function names, producing absurd findings like "Function 'void' is 248 lines". Added all C type keywords and storage-class specifiers to the skip list. -
C/C++ function name false positives in
fallback_c.py: Same issue as smell_engine — the parser's skip list was missingvoid,const,unsigned,signed,volatile,extern,register,auto,static,inline. Extended the skip list to match. -
C/C++ listed as
unsupported_langs: Despite having working fallback parsers (790 C/C++ files successfully parsed on redis/redis), C and C++ were listed inUNSUPPORTED_MARKERSinframework_detect.py, causing the scan output to say "these languages are not yet supported". Removed C/C++ fromUNSUPPORTED_MARKERSsince they have fallback parser support.
-
C/C++ project framework detection: Added
c_projectframework detection inframework_detect.pywhen a Makefile/CMakeLists.txt is found alongside C/C++ source files. This gives C projects proper framework recognition instead of empty framework lists. -
C/C++ project identity detection in handbook: Added C/C++ project type detection in
_extract_project_identity()with Makefile version/name extraction. Supports classification asc-database(projects with.conffiles like redis.conf),c-infrastructure(nginx-like structure), orc-project(generic). Polyglot C+Python/Lua projects get combined type likec-python-polyglot. -
c_typein polyglot detection: Extended the polyglot type builder to include C projects alongside Rust, Go, JS, and Python types.
Tested against neovim/neovim (3,856 files: 506 C/C++ + 816 Lua + 12 Shell + 8 Python + 4 JS, C/Lua text editor project)
Real-world test on a C/Lua polyglot project (CMake + Lua runtime). This test exposed critical issues with non-web projects that have no package.json, pyproject.toml, or Cargo.toml.
- Critical:
is_bundled_filemissing fromutils.py— 4 commands (ask,complexity,context,perf-hint) crashed on import becausecomplexity_engine.pyandperfhint_engine.pyimportedis_bundled_filefromutilsbut it didn't exist. Addedis_bundled_file()with directory segment detection (dist/, build/, vendor/, etc.) and bundled filename suffix detection (.bundle.js, .chunk.js, .umd.js, etc.). - Critical:
audit_environmentImportError inanalyzecommand —_detect_env()inanalyze.pycalledfrom envcheck_engine import audit_environmentbut the actual function name ischeck_env_vars. Fixed to use the correct import and adapt the response structure. - C/C++ no longer listed as "unsupported" —
framework_detect.pylisted C, C++, Java, Kotlin, C#, Swift, Ruby as "unsupported" based on build system markers (CMakeLists.txt, pom.xml, etc.), even though fallback parsers for ALL these languages were working and extracting thousands of nodes. Now only truly unsupported languages (Zig, OCaml, Perl, Clojure, F#, Erlang, Fortran) are listed. Updatedlang_notemessage to be more accurate. - Identity detection for C/CMake projects — Handbook reported
type: unknown,version: 0.0.0,name: <folder>for C/C++ projects. Added CMakeLists.txt parsing to extract project name (project(Name)), version (project(Name VERSION x.y.z)andset(VERSION_MAJOR/MINOR/PATCH)), and classify project type (c-lua-application, c-gui-application, c-service, c-library, c-application, c-project).
- Languages field in handbook output — New
languageskey in handbook response with accurate language distribution (e.g.,{"Lua": 816, "C/C++": 506, "Shell/Bash": 12}). Merges outline engine data with scan result'sfiles_scannedfor complete coverage including fallback parser languages. - Architecture detection in handbook — New
architecturekey with pattern detection:core-plugin(C core + Lua runtime),client-server,mvc,core-api,fullstack,monorepo, etc. Also includeskey_directoriesanddescription. - CMake
set(VERSION_MAJOR/MINOR/PATCH)version extraction — Projects that don't useproject(Name VERSION x.y.z)but instead useset(NVIM_VERSION_MAJOR 0)etc. now get version detected correctly. compute_summarynow includes fallback parser languages —files_by_languageinsummary.jsonpreviously only contained tree-sitter supported languages (Python, JS, etc.). Now mergesscan_result.files_scanneddata so C/C++, Lua, Go, Java, Kotlin, etc. appear in the summary.
Tested against fastapi/fastapi (1,130 Python files, 48 core library + 582 tests + 454 docs examples)
Real-world test on a pure Python library project. FastAPI's unique structure — a small core library with massive docs_src/ example directories and comprehensive test suites — exposed critical false positive patterns that were invisible on application-type repos (prior testing: vercel/swr for React hooks, n8n-io/n8n for Vue/TS monorepo).
-
CRITICAL: Missing
is_bundled_file()function — 4 engines crashed on import (ask.py,complexity.py,context.py,perf_hint.py). The function was referenced inperfhint_engine.pyandcomplexity_engine.pybut never defined inutils.py. Added proper implementation that detects dist/, build/, out/, minified, and bundled file patterns. -
CRITICAL:
api-mapcommand crash —map_api_routes()received unexpected keyword argumentproduction_only. The command passed it but the engine function signature didn't accept it. Addedproduction_onlyparameter tomap_api_routes()with route source filtering. -
SQL injection false positives (16 → 0 on FastAPI) — f-strings containing English words like "Updated", "Created", "update", "DELETE" were flagged as SQL injection. Examples:
f"Updated {path}",f"Created PR: {pr.number}",f"Please update the response model {type_!r}". Fixed by requiring: (1) a secondary SQL keyword (FROM, WHERE, SET, INTO, TABLE, VALUES, JOIN, etc.) in the same string, AND (2) the primary keyword must appear at or near the start of the string content. -
docs_src/example directory inflation — 454 docs example files in FastAPI inflated ALL metric categories.
smell_engine.pynow downgrades all smells from docs/examples/test files to "info" severity withsource: "docs_example"tag.debugleak_engine.pyskips docs/example directories entirely.deadcode_engine.pyskips docs_src paths in unused_exports and registry_dead.deep_nestingdetection skips /tests/, /docs_src/, /examples/ directories. -
Library code false positives in deadcode — 189 "unused exports" in FastAPI core library were actually public API, not dead code. Added
_detect_library_package()that detects Python packages (with init.py re-exports,__all__), JS libraries (main/module/exports in package.json without scripts.start). For detected libraries: capitalized exports are assumed public API, severity downgraded to "info", message includes "library public API — may be used by consumers". Also skip init.py files entirely (re-export entry points). -
Secrets false positives in test files (10 → 0 on FastAPI) — All 10 "secrets" were dummy test data:
hashed_password="secrethashed","password": "incorrect". Added_is_obvious_test_value()that catches: dictionary dummy passwords (secret, test, incorrect, fake, mock, etc.), very short alpha-only values (≤4 chars), and test-prefixed patterns (test_, fake_, mock_*). Only applied in test files to preserve real secret detection. -
Deep nesting false positives in test directories (925 items removed) — Test files in /tests/ directory had deep nesting from test setup patterns (pytest fixtures, nested describe blocks). Added /tests/, /docs_src/, /examples/ to skip_dirs in
_detect_deep_nesting(). -
Debug leak false positives in docs/examples — docs_src example files used print() as demo output, not debug code. Added
DOCS_EXAMPLE_PATTERNSand skip logic todebugleak_engine.py. -
analyzecommand showed wrong version "6.0" — Hard-coded string instead of importingCODELENS_VERSIONfrom utils. Now imports and uses the constant.
is_bundled_file()utility function — Detects bundled/compiled files (dist/, build/, .min.js, .bundle.js, .d.ts, etc.) for engine skip logic. Used by complexity and perf_hint engines._detect_library_package()in deadcode engine — Detects if workspace is a library vs application, adjusts unused_exports severity accordingly._is_obvious_test_value()in secrets engine — Filters out clearly fake test credentials (dummy passwords, test patterns).- docs_src/doc_src/examples/documentation directory patterns — Added across all engines (apimap, smell, deadcode, debugleak) for consistent exclusion of documentation example code.
- API map
production_onlyfiltering — Now actually works, filtering routes tagged as "test" source.
Real-world test on a massive virtual distributed filesystem Tauri desktop app (38K+ GitHub stars) with 16+ Rust crates (including procedural/derive macros), 1,166 Rust files, 405 TS/TSX files, 17 Swift files, 3 Kotlin files, and complex cross-language FFI boundaries. The registry built 13,350 backend nodes and 62,780 edges — one of the most diverse test targets to date.
- CRITICAL:
is_bundled_file()missing from utils.py — The function was imported bycomplexity_engine.pyandperfhint_engine.py, but never defined inutils.py. This caused ImportError cascade that completely disabled 4 commands:ask,complexity,context, andperf-hint. Added the missing function with detection for dist/build/out directories, bundled file extensions (.bundle.js, .chunk.js, .global.js), minified files, and declaration files. api-mapcrash onproduction_onlykwarg — Theapi-mapcommand passedproduction_onlyargument tomap_api_routes(), but the engine function did not accept this parameter. Addedproduction_only: bool = Falseparameter tomap_api_routes()and implemented the filter that removes test-sourced routes when the flag is set.
- CRITICAL: 4 broken commands restored —
ask,complexity,context,perf-hintcommands failed to import due to missingis_bundled_filesymbol inutils.py. The new v6.3.0 engines (complexity_engine.py,perfhint_engine.py) and their consumers referenceis_bundled_file()but the function was never added toutils.py. - HIGH: apimap_engine crash on None path —
_build_route_groups()and_is_route_deprecated()crashed withAttributeError: 'NoneType' object has no attribute 'split'when route dict hadpath: None. Fixed by usingroute.get("path") or "/"instead ofroute.get("path", "/")which doesn't handle explicitNonevalues.
is_bundled_file()function (utils.py): Detects bundled/compiled artifacts (minified JS/CSS, vendor bundles, webpack chunks with content hashes, dist/build output directories). Used bycomplexity_engineandperfhint_engineto skip non-source files.BUNDLED_FILE_PATTERNSandBUNDLED_DIR_SEGMENTSconstants inutils.pyfor consistent bundled file detection across engines.
- meilisearch/meilisearch (GitHub): Used as test target for v6.3.1 — a search engine written in Rust with 21 workspace crates, 692 .rs files, 12214 backend nodes, 490543 edges. Detected frameworks: rust, tokio, actix-web. Monorepo with cargo-workspace. Health score: 50/100 (677 critical smells, god object Index with 99 methods). 2 potential secrets in open_api_utils.rs. 1299 debug leaks (851 commented code, 310 debug_log). 402 dead code items. 200 circular dependencies.
Round 2 real-world testing targeting database systems (Redis C, LevelDB C++) and HTTP/network libraries (Axios JS, Undici JS, libuv C). Exposed critical dead-code accuracy gaps where exported symbols were falsely marked as "dead".
- CRITICAL: JS/TS exported symbols falsely marked as dead (
js_backend_parser.py,ts_backend_parser.py,fallback_js_backend.py): Theexportkeyword was never propagated to backend registry nodes. Exported classes likeAxiosError,EventEmitter, andCustomErrorappeared as "dead" (0 ref_count,exported: False). Now all three parsers detectexport_statementAST nodes and setexported: Trueon function/class/variable declarations. AxiosError now correctly showsstatus: "active". - CRITICAL: Incremental scan status computation ignores exported/component/pub flags (
incremental.py):merge_backend_data()used simpleref_count == 0 -> deadwithout checkingexported,component, orpubflags. Now uses the same 3-condition check asedge_resolver.resolve_edges(). - HIGH: Rust
pub fnfalsely marked as dead (edge_resolver.py):resolve_edges()only checkedexportedandcomponentflags, but Rust uses"pub": True(separate key). Apub fnwith no internal callers was marked"dead". Now also checksnode.get("pub", False). - HIGH: Dead-code engine misses exported/component flags (
deadcode_engine.py):_detect_dead_from_registry()skippedpubfunctions but notexportedorcomponentnodes. Now checks all three flags. - MEDIUM: Drupal false positive on non-PHP repos (
framework_detect.py): Generic indicatorsmodules/andthemes/matched Redis directory. Replaced with specific indicatorssites/default/andsites/all/. Redis no longer falsely detected as Drupal. - MEDIUM: HTTP/network libraries not detected (
framework_detect.py): Added 7 HTTP library signatures (axios, undici, got, ky, superagent, node-fetch, request),has_http_libraryflag, andpackage.jsonname field detection for when the repo IS the library. - MEDIUM: PascalCase classes too narrow in tree-sitter JS parser (
js_backend_parser.py): Only React-extending classes werecomponent: True. Now any PascalCase class is marked ascomponent: True.
| Repo | Language | Size | Theme | Key Finding |
|---|---|---|---|---|
| redis/redis | C | ~70MB | Database | Drupal FP (modules/ dir) |
| axios/axios | JS | ~5MB | XHR/Network | AxiosError dead, HTTP lib detection |
| nodejs/undici | JS/TS | ~40MB | XHR/Network | HTTP lib detection |
Tested against n8n-io/n8n (20,355 files: 9,101 JS + 4,626 TSX + 1,092 Vue + 66 Python, workflow automation monorepo)
Real-world test on a massive TypeScript/Vue/Express monorepo (pnpm-workspace + Turborepo). This is the largest repo tested to date, exposing critical scalability and accuracy issues that were invisible on smaller projects.
- Frontend registry CSS class name validation (2,853 false positives removed, 7,792 → 4,687 classes): Vue
:classbinding expressions like!!hint,,!!item.disabled,!==,!action.completed,were stored as CSS class names. Added_is_valid_css_class_name()validation inregistry.pythat rejects names starting with!, containing operators (()?.<>=+*/), longer than 80 chars, or not matching^[a-zA-Z_-][\w-]*$. - Framework detection for monorepo sub-directory packages:
detect_frameworks()only checked rootpackage.json, missing React/Vue/Express in workspace packages. Now scansapps/*/package.json,packages/*/package.json, andpackages/@scope/name/package.json. Correctly detectshas_vue: true,has_express: truefor n8n. - Edge resolver built-in JS method filtering:
resolve_edges()created resolved edges toadd,then,setTimeout,race,clearTimeout,includes,indexOf,substring,trim,reject, etc. — treating JS built-in methods as project-defined functions. Now checks_STD_LIB_METHODSbefore resolution (expanded from 80 to 110+ entries). Impact analysis no longer shows these as dependents. - API map false positives (2,922 → 0 test routes with
--production-only): Added--production-onlyflag. Vue plugins (ChatPlugin, SentryPlugin, PiniaVuePlugin) no longer detected as Express middleware. Tauri detection now requiressrc-tauri/Cargo.toml(not justinvoke()calls). Auth-protected routes now detected by middleware name patterns (jwt,passport,authenticate,verifyToken). - Entrypoints garbage test names: Test names like
:,,,=from malformedit()parsing. Fixed with word boundary regex and punctuation-only name filtering. - Dead code numeric literal false positives: Numeric literals like
300_000and10000detected as "unused variables". Added^\d[\d_]*$pattern check to skip numeric literals. - Debug leak config file false positives:
testEnvironment: 'node'andtestRegexinjest.config.jsflagged as "mock data". Config files (*.config.js/ts,jest.config.*,vite.config.*, etc.) now get severity downgraded to "info" with note "in config file — not production code". - Complexity output not sorted by complexity: Functions listed in file order. Now sorted by complexity level (untamable → very_complex → complex → moderate → simple), then cyclomatic descending.
analyzecommand timeout on large repos: No--max-filesor per-engine timeout. Added--max-filesargument (default 5000) and per-engine 30s timeout usingsignal.SIGALRM. Timed-out engines report gracefully instead of blocking.
api-map --production-onlyflag: Filter out test routes for a clearer picture of production endpoints.has_expressfield in framework detection output.- Auth detection in API map: middleware names containing auth patterns are flagged.
- Config file awareness in debug-leak engine: config files get
severity: "info"andshould_remove: false.
Tested against vercel/swr (254 source files: 114 TSX + 99 JS backend + 34 JS frontend, React+Next.js monorepo)
Real-world test on a TypeScript/React data-fetching library. Confirmed significant false positive reduction across all analysis engines after targeted fixes based on SWR analysis findings.
- Dataflow
command_execfalse positives (79% reduction: 19 → 4 violations):Function\s*\(regex matchedisFunction(),createFunction(), etc. Added word boundary(?:^|[^\w.])Function\s*\(to only match the bare JSFunctionconstructor. Same fix applied toexec(?:Sync)?\s*\(which matchedexecQuery(),execSql(). These utility type-checks and database helpers are NOT command execution sinks. - Smell
long_fnreports test files (9% critical reduction: 43 → 39):_detect_long_functions()did not skip test/story/fixture files. Added same_skip_keywordsfilter that_detect_deep_nesting()already uses ('.test.', '.spec.', '.fixture.', '.stories.', '.story.', '__tests__'). Long test blocks are expected and not actionable. - A11y engine scans test files (85% reduction: 122 → 18 issues): No test file exclusion existed in the accessibility scan loop. Added skip filter for test/spec/story/fixture files. Mock JSX in test files (
<img />without alt,<button>without keyboard handler) are not real accessibility issues. - Dead code
unused_varsfalse positives (94% reduction: 51 → 3):_detect_unused_variables()flagged exported variables as unused because it only checked single-file usage. Addedexported_namescollection (named exports, re-exports, default exports) and skip them. Also expandedskip_nameswith common patterns (result,data,value,options,args,params,callback,next,dispatch,action,payload). - Dead code
registry_deadtest file false positives (37% reduction: 200 → 127):_detect_dead_from_registry()only checked directory paths (/test,/tests), missing filename patterns like.test.ts,.spec.tsx. Added.test.,.spec.,.e2e.,.stories.,.story.patterns and/__tests__/. - Module system detection wrong for TypeScript projects (cjs → esm):
framework_detect.pydefaulted to"cjs"whenpackage.jsonlacked"type": "module". Many TS projects compile to ESM without this field. Added detection oftsconfig.jsoncompilerOptions.module,.mjs/.cjsfile extensions, andexportsfield with"import"key. Reports"mixed"when both ESM and CJS indicators exist. - Context engine fuzzy matching too loose: Used pure substring match sorted by shortest name. Ported scoring logic from
query.py: exact case-insensitive match priority, active vs dead status priority, ref_count (popularity) ranking. Prevents"use"matching"refuse"and prefers the most relevant function. - Version mismatch:
CODELENS_VERSIONwas"5.8.1"whilepyproject.tomlwas"5.9.1". Both now synced to"5.9.2". pyproject.tomlparse error:descriptionandreadmefields were concatenated on one line. Fixed line break.
Real-world test on a pure Go distributed SQL database with 116,033 backend nodes and 113,338 edges. Confirmed: 2,287 smells (health score 70), 200 dead items, 106 circular deps, 11,291 debug leaks, 1,716 entrypoints, 13 secrets, 4 CVEs, 32 API routes, 15 state stores.
- Go project type detection in handbook:
handbooknow parsesgo.modto extract module name, Go version, and classify Go projects into types:go-database,go-web-service,go-grpc-service,go-infrastructure,go-project. Module name extraction (e.g.,github.com/cockroachdb/cockroach→ name:cockroach, version fromgodirective). - Go framework content-based detection:
detect_frameworks()now readsgo.modcontent instead of just checking file existence. Detectsgin,echo,fiber,chi,mux,grpc,protobufonly when the dependency string actually appears in go.mod. Prevents false positives where every Go project was classified as gin/echo. - Go-specific code indicators for debug-leak: Added
code_indicators_gowith Go-specific patterns (func,var,const,type,:=,chan,select,defer,range). Previously defaulted to JS indicators which caused massive over-detection. - License block detection in debug-leak:
_score_commented_code_likelihood()now returns 0 for comment blocks that start with copyright/license keywords (copyright, SPDX, Apache License, BSD, MIT, GPL, etc.). Eliminates thousands of false positives from license headers.
get_workspace_outline()TypeError:write_output_files()inutils.pycalledget_workspace_outline(workspace, max_files=max_files)but the function doesn't acceptmax_files. Removed invalid keyword argument.perf-hintTypeError crash:perf_hint.pycalleddetect_perf_hints(workspace, ..., max_files=5000)but the function doesn't acceptmax_files. Removed invalid keyword argument.- gin/echo false positive for Go projects: Every Go project with a
go.modwas incorrectly classified as using gin and echo frameworks because both had"config_files": ["go.mod"]. Changed toconfig_files: []and use content-based detection instead. - Go listed as "unsupported language": Go has a fallback parser (
fallback_go.py) and is actively parsed during scan, but was still listed inunsupported_langswith the message "not yet supported by tree-sitter parsers". Removed Go from the unsupported markers list. - Handbook
type: unknownandversion: 0.0.0for Go projects: Go projects without package.json or Cargo.toml had no identity detection. Addedgo.modparsing to extract name, version, and type classification. - Debug-leak Go commented_code false positives: Go projects use multi-line
//comments heavily for godoc, generating 22,433 false "commented code" findings on cockroachdb. Fixed by: (1) requiring 5+ consecutive lines for Go (vs 3 for other languages), (2) requiring score ≥ 3 for Go (vs 2), (3) adding Go-specific code indicators, (4) skipping license/copyright blocks. Result: 22,433 → 6,734 (70% reduction).
- Go project classification priority: Module name patterns (cockroachdb, postgres, mysql, etc.) now take priority over dependency-based classification for more accurate type detection.
- Version bump: 5.8.0 → 5.8.1
Real-world test on a Rust+TypeScript runtime with 36,186 backend nodes and 269,678 edges. Confirmed: 19,994 smells (health score 50), 676 dead items, 775 circular deps, 1,959 functions analyzed, 3,709 debug leaks, 1,010 entrypoints, 283 state stores, 302 regex patterns, 164 a11y issues, 313 perf hints, 50 env vars.
- Rust framework detection:
detect_frameworks()now parsesCargo.tomlfor dependencies and detectsrust,tokio,actix-web,axum,warp,rocket,deno_corefrom Cargo dependencies. Also scans workspace members'Cargo.tomlincrates/,ext/,libs/,packages/directories. - Rust HTTP route extraction:
api-mapcommand now detects routes from Rust web frameworks:- actix-web / rocket:
#[get("/path")],#[post("/path")]attribute macros - actix-web:
web::resource("/path")programmatic routes - axum:
.route("/path", get(handler))method chaining - warp:
warp::path("segment")filter chains
- actix-web / rocket:
- Cargo workspace monorepo detection:
handbooknow detects[workspace]sections inCargo.tomland sub-directory crate patterns (crates/*/Cargo.toml,ext/*/Cargo.toml). Reportsis_monorepo: truewithmonorepo_tools: ["cargo-workspace"]. is_generated_file()utility: Added toutils.pyfor detecting lock files, declaration files, minified files, and other generated artifacts. Fixesrefactor_safe_engine.pyimport crash (was importing non-existent function). Total commands: 42 → 43.has_rustfield in framework detection:detect_frameworks()now includeshas_rust: truewhenCargo.tomlis found, and adds Rust-specific backend paths to recommended config.
refactor_safecommand crash:refactor_safe_engine.pyimportedis_generated_filefromutilsbut the function did not exist, causing the entire command module to fail loading (42/43 commands loaded). Now all 43 commands load successfully.- State-map
__dunderfalse positives: Runtime binding helpers (__default,__createBinding,__exportStar,importDefault,__reexport,__buffer,__default_export__,__telemetry,__esModule, etc.) were classified as state stores. Added 15+ JS/TS runtime helper names to post-filter skip set, plus a general__dunderruntime helper detection pattern. Result: 0__dunderfalse positives (was 8 in deno test). handbookcrash oncmd_scan()call: Handbook calledcmd_scan(workspace, max_files=max_files)butcmd_scan()doesn't acceptmax_filesparameter. Removed the invalid keyword argument.- Smell
health_scorenot at top level:health_scorewas only available insidestatsdict, making it harder to access programmatically. Now also returned as a top-level key in the response dict. - Markdown formatter for smell: Now reads
health_scorefrom top-level first, then falls back tostats.health_scorefor backward compatibility. - Version mismatch:
skill.jsonversion was5.7.1but description referenced v5.10/v6.1. Updated to5.8.0with accurate description.
- Complexity engine file cap: Increased from 3,000 → 5,000 files. Function cap increased from 5,000 → 8,000. Prevents missing analysis on large repos.
- Debug-leak engine file cap: Increased from 3,000 → 5,000 files per run for better coverage on large repos.
- Rust framework config paths: When Rust is detected, recommended config now includes
crates/*/src/andext/*/src/as backend paths.
- state-map markdown crash:
_md_state_map()called.get('name')on action/slice entries, but entries are strings in Pinia/Vuex/Redux/Zustand stores. Now handles both dict and string formats gracefully. - binary-scan ImportError:
scan_binary_artifactsfunction was missing fromutils.py. Now fully implemented with extension-based detection and binary signature scanning (ELF, PE, Mach-O, WASM, etc.). - Pinia/Vuex/Redux false positive actions: JS/TS keywords (
if,for,while, etc.) and built-in methods (push,includes,toUpperCase, etc.) were being extracted as store actions. Added_is_js_keyword_or_builtin()filter with 80+ entries. Also improved section extraction using_extract_section()with proper brace-matching instead of fragile regex.
- binary-scan command fully functional: New
_md_binary_scanmarkdown formatter. Scans for compiled binaries, archives, images, and Python bytecode with size reporting and recommendations. - Tauri IPC route mapping in api-map: Frontend
invoke('command')calls and backend#[tauri::command]Rust handlers are now extracted as IPC routes. Shows full invoke:// endpoint paths. - Unsupported language detection: Framework detection now identifies Go, Java, Kotlin, C/C++, C#, Swift, and Ruby projects. Scan output shows a
lang_notewarning when unsupported languages are detected. - Go framework signatures: Added
golang,gin,echoto framework detection signatures. _extract_section()helper: New brace-matching helper for state management extractors that properly handles nested braces and string literals, replacing fragile regex patterns.
- Monorepo-aware framework detection: Detects turborepo, pnpm-workspace, lerna, nx. Walks sub-directory package.json (apps/, packages/) to find frameworks in workspace packages. Detects Rust/Cargo workspaces. Build tool detection (Vite, webpack, esbuild).
- Polyglot project identity: Handbook detects combined types (e.g.,
rust-js-monorepo) when both package.json and Cargo.toml exist. - Dead code from registry cross-reference: Uses backend registry's
ref_countdata to find functions with zero references. - Monorepo-aware config defaults:
initnow addsapps/*/,packages/*/,crates/*/paths when monorepo detected. should_ignore_dir()utility: New shared utility inutils.pyfor path-segment-aware directory ignore checking. Replaces inline implementations across multiple engines.safe_read_file()utility: New shared utility for safe file reading with size limits and encoding handling. Prevents out-of-memory on large files.time_budget_expired()utility: New shared utility for checking global timeout budgets in engines. Prevents runaway scans on massive codebases.- Performance safeguards in
utils.py:MAX_FILE_SIZE(200KB),MAX_FILES_DEFAULT(5000),GLOBAL_TIMEOUT_SEC(120s) constants for all engines. handbook --quickmode: New flag to skip expensive engines (secrets, vuln-scan, circular, dead-code) for faster results on large codebases.- Engine status tracking in handbook: Handbook now reports
engines_okandengines_failedlists inmeta. Overall status isok,degraded, orerrorbased on engine results. - Lazy imports in
askcommand: All 17 engine imports moved from module-level to inside_execute_ask_command(). Reduces CLI startup time significantly. - Thread-safe grammar loader:
GrammarLoadersingleton now usesthreading.Lock()for thread safety in watch command. - Modern tree-sitter API support:
GrammarLoader.get_parser()now handles both legacy (Parser(lang)) and modern (parser.language = lang) tree-sitter APIs. - Graceful command import:
commands/__init__.pynow wraps each command module import in try/except, so one failing module doesn't prevent others from registering. truncatedfield in env-check output: Indicates when file count or timeout limits were hit, so users know results are partial.
- God object detection: Class method counting now scoped to actual class/impl body via brace-depth tracking. Was counting ALL function calls in the file as methods (10-30x inflation).
- API route false positives: Routes must start with
/for non-router objects. Expanded skip list (80+ objects). Preventsheaders.get('user-agent')from being reported asGET /user-agent. - CSS specificity false positives: Tracks brace depth to distinguish CSS rule selectors from property values. Was flagging
rgba(),var(), gradient values as selectors. - State map over-classification: Skips ALL_CAPS constants, React components (arrow functions, forwardRef, memo, styled), and immutable values. Removed module.exports scanning.
- Entrypoints markdown formatting: Bracket types like
[main]no longer get mangled by markdown link reference interpretation. - Dead code zero results: Fixed registry cross-reference to use correct field names (
fninstead ofname). Added filtering for main(), pub functions, and test fixtures. - Handbook type detection: No longer defaults to
node-projectfor Rust+TS monorepos. Cargo.toml is always checked regardless of existing type. should_ignore_dirImportError in tailwind_detector.py: Was importing a function that didn't exist inutils.py. Now uses shared implementation fromutils.py.safe_read_fileImportError in a11y_engine.py: Removed unused import of non-existent function. a11y_engine now uses the sharedsafe_read_filefromutils.py.- Silent exception swallowing in
context.py:except Exception: passreplaced with properlogger.debug()call. - Silent exception swallowing in
handbook.py:except Exception: passfor sub-directory package.json replaced withlogger.debug(). - Handbook always reports
status: ok: Now reportsok,degraded, orerrorbased on engine success/failure counts. - env-check returns empty output on large repos: Added
MAX_FILE_SIZE,MAX_FILES(5000), andGLOBAL_TIMEOUT_SEC(90s) limits. Now usessafe_read_file()instead of rawopen(). - Version inconsistency: SKILL.md said "v6" but code said "5.7.1". All version references now unified to "6.0.0".
- CLI version hardcoded:
codelens.pydescription now usesCODELENS_VERSIONconstant instead of hardcoded "v5".
- TSX backend extraction: When tree-sitter-typescript is not installed, TSX files are now parsed with BOTH frontend AND backend fallback parsers. Backend nodes jumped from 124 → 764 (6.2x) on typical Next.js projects.
- Shared utils module (
scripts/utils.py): Centralizedwrite_output_files,compute_summary,is_file_path,deduplicate_callers,DEFAULT_IGNORE_DIRS,DEFAULT_IGNORE_EXTENSIONS,CODELENS_VERSION, andlogger. Eliminates 290+ lines of duplicated code across 5 files. - Proper logging: Replaced silent
except Exception: passblocks withlogger.warning()/logger.debug()calls across all engine and utility files. Errors are now visible when they occur instead of being silently swallowed. - Fuzzy file path lookup:
context layout.tsxandquery layout.tsxnow match partial paths (end-of-path matching). Previously required exact path likeapps/web/app/[locale]/layout.tsx. Returns grouped results when multiple files match. - Auto-incremental scan with registry counts: When no changes detected, the response now includes actual backend/frontend counts instead of zeros.
- Handbook registry freshness check: Handbook skips re-scan if
backend.jsonis less than 5 minutes old. Reduces handbook execution time from 2.8s → 0.3s for consecutive runs.
- is_frontend_file / is_backend_file: Now uses path segment matching instead of substring matching.
"src/"no longer falsely matchessrc/server/api/auth.tsas a frontend file. - _detect_workspace depth limit: Walks up at most 10 directory levels (was unlimited). Prevents matching a
.gitdirectory many levels up. - Incremental scan with deleted files: Instead of falling back to full rescan, deleted files are selectively removed from the registry. Preserves incremental scan performance.
- god_objects Python scoping: Method count is now scoped to each class using indentation analysis (was counting ALL
defin the file). - Consistent status field:
contextandqueryfile-path responses now includestatus: "ok"(was missing). - Context multi-file response: New
type: "files"response format when multiple files match a partial path query, with markdown formatting support. - Handbook version: Now uses
CODELENS_VERSIONconstant fromutils.py(was hardcoded as"5.2.0"). - Centralized
DEFAULT_IGNORE_DIRS: All 30 engine/command files now importDEFAULT_IGNORE_DIRSfromutils.pyinstead of defining local copies. Single source of truth ensures consistency across all scanners. - pyproject.toml version: Aligned with skill.json and CODELENS_VERSION (was 5.1.0, now 5.6.0). Description updated from "39 commands" to "41 commands".
- TSX files produced zero backend nodes: When TSXParser failed to import, only CSS class/ID data was extracted. Now uses
parse_js_backend_fallbackon TSX files too. - Auto-incremental returned zero counts: "No changes detected" response had
backend.nodes: 0, backend.edges: 0even when registry had thousands of entries. - Handbook version stale: Was hardcoded as 5.2.0 in output, now dynamically reads from
CODELENS_VERSION. - Test import errors: 6 test files (test_cli, test_css_parser, test_html_parser, test_js_backend_parser, test_js_frontend_parser, test_rust_parser) were importing from old monolithic
codelens.py. Updated to import from the new modular structure (commands.scan,parsers.fallback_*). - Scan edge filter for deleted files: Edge cleanup was overly permissive — kept ALL unresolved edges regardless of whether they referenced deleted nodes. Now only keeps edges where
fromis in remaining nodes. - setup.sh version reference: Updated from "v2" to "v5" to match current version.
- CLI test suite:
__tests__/cli/test_scan.pynow uses hermetic temporary workspaces instead of scanning the host project, and added 3 new test cases (init, scan+query integration, registry creation).
- Auto-incremental scan: Scan now automatically uses incremental mode when a registry already exists (
.codelens/backend.jsonpresent). No need to pass--incrementalflag. First scan is always full; subsequent scans auto-detect changes. - oRPC route detection: API-map now detects oRPC-style routers (
.procedure(),router({}),protectedProcedure/adminProcedurechains). Detects 67 routes in typical oRPC projects (was 2). - tRPC v10+ detection: Improved tRPC extraction with
t.procedure,publicProcedure.query/mutation,initTRPC, and router body parsing for named procedure paths. - Context by file path:
context src/lib/auth.tsnow returns all symbols defined in that file, not just symbol-name lookups. - Query by file path:
query src/lib/auth.tsreturns all symbols in the file, grouped by file. - bun.lock support: Vulnerability scanner now parses Bun's text-based
bun.lockformat for dependency checking. - Next.js destructured route exports: API-map now detects
export const { GET, POST } = handler()andexport const GET = ...patterns in Next.js App Router.
- Health score calibration: Deep nesting now reports per-block instead of per-line (was 6419 findings → 300). Magic values skip config/test/fixture files and JSX style props. Weighted density formula (
critical*3 + warning + info*0.1) prevents info-level smells from tanking scores. Typical React project health: 90 (was 25). - Deep nesting thresholds: Raised from 4→5 (warning) and 6→8 (critical) to account for natural React component nesting.
- Duplicate caller filtering:
queryandcontextcommands now deduplicate callers by (file, line) tuple.
- Secrets markdown truncation: Severity "high" was truncated to "igh]" in markdown output due to f-string variable name collision. Now displays correctly as
[HIGH],[CRITICAL], etc.
- True incremental scan: Partial registry merge — changed files' entries are updated in-place instead of rebuilding the entire registry. Unchanged files' data is preserved, making
--incrementalsignificantly faster for large codebases. - Complete markdown formatters: All 41 commands now have specific markdown formatters (was 15/41). No command falls through to generic formatting anymore.
- Score-based ask routing: Natural language query router now uses weighted scoring instead of first-match. Technical terms score 3x, action words 1x, generic words 0x. Correctly routes "show me the API routes" to api-map instead of context.
- 8 new ask patterns: CSS issues→css-deep, accessibility→a11y, regex→regex-audit, what changed→diff, tech stack→detect, how to configure→env-check, which files import→dependents, is this code safe→refactor-safe.
- 3 new semantic convention detectors: CSS framework (Tailwind/Bootstrap/MUI/Chakra/Ant/Bulma), Authentication (NextAuth/Passport/JWT/OAuth/Firebase/Supabase/Clerk), Deployment (Vercel/Netlify/Docker/Fly.io/Railway/Render/Heroku/AWS/GCP).
- Better error messages: Command-specific error suggestions with
_suggest_fix(). Split error handling into FileNotFoundError, ImportError, and generic Exception with helpful suggestions. - Consistent status field: All commands now return
status: "ok"(orstatus: "error"on failure). Previously some commands likelist,query,detect, anddiffwere missing this field.
codelens.pymonolith reduced from 3504 → 307 lines (modular architecture)- Ask command accuracy: 12/12 test cases pass (was ~8/12 with first-match routing)
- Health score: percentile-based formula (clean=95, average=85, messy=55, CodeLens=5)
- Convention engine: 8 semantic detectors total (was 5)
- Workspace Auto-Detect: The
workspaceargument is now optional for ALL commands. Fallback chain: current directory → parent directories → source files → last workspace cache → cwd - Python Parser: Full tree-sitter Python parsing for function declarations, class methods, and function calls
.codelensdirectory exclusion: Scanner now skips.codelens/directory during file discovery- SCSS/Less/Sass support: Preprocessor CSS files are now discovered and parsed
- Vue SFC parser: Single-file component parser for Vue.js
- Svelte parser: Component parser for Svelte
- Tailwind CSS detector: Analyzes Tailwind utility class usage
- TSX/JSX parser: React component parser with className tracking
- Open-source standards: README.md, CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, .gitignore, pyproject.toml
- Comprehensive test suite: Unit tests for all parsers and core engines
codelens.pynow supports optionalworkspaceargument with auto-detection- Scan command supports
--incrementalflag for faster re-scans - Watch mode now uses incremental scanning for file changes
- CLI version bumped from v4 to v5
- Total commands: 36 → 39
- Total engines: 23
- Total parsers: 9
vuln-scan— Dependency vulnerability scanning (npm audit, cargo audit, pip-audit, govulncheck + built-in CVE database with 35+ entries)perf-hint— Performance anti-pattern detection (N+1 queries, sync blocking, memory leaks, expensive renders, large bundles, inefficient iteration, unoptimized images, cache misses)css-deep— Deep CSS analysis (unused CSS variables, orphan @keyframes, specificity wars, duplicate properties, unused @media, z-index abuse)- Priority system: Tools now have explicit priority weights (P0 > P1 > P2 > P3)
- State prerequisites: Explicit init→scan→tools ordering documented
- Context-aware hints: Auto-init if registry missing, re-scan if stale (>24h)
- Colloquial triggers: Non-technical phrases mapped to tools
- Negative triggers: When NOT to activate CodeLens
- Default fallback chains: Vague requests get default tool chains
- SKILL-QUICK.md: Concise quick-reference for fast AI consumption
- CLI version: v4 → v5
- Total commands: 36 → 39
- Total engines: 23
- Total parsers: 9
- Expanded
skill.jsondescription with explicit trigger phrases - Added 6-category trigger rules to SKILL.md frontmatter
- Added Auto-Trigger Map section with 7 sub-tables
- Added 5 new scenario flows (Pre-Deploy, Onboarding, Feature Development, Performance, Code Review)
- Updated
agent-integration.mdwith Section 0: Auto-Activation & Trigger Guide
secrets(P0) — Hardcoded secret detectionentrypoints(P0) — Execution entry point mappingapi-map(P1) — REST/GraphQL/gRPC route→handler mappingstate-map(P1) — Global state management trackingenv-check(P1) — Environment variable auditingdebug-leak(P2) — Debug code leak detectioncomplexity(P2) — Cyclomatic/cognitive complexity scoringregex-audit(P3) — ReDoS-vulnerable regex auditinga11y(P3) — Accessibility auditing (WCAG 2.1)
- Python file discovery now works (was missing .py handling)
- Top-level error handling added (clean JSON errors instead of tracebacks)
- Side-effect argparse bug fixed (name as optional --name flag)
- Outline positional arg consistency fixed
dataflow(P0) — Data flow analysis (source→sink, taint detection)smell(P0) — Code smell detection (10 categories, health score)side-effect(P1) — Function side-effect analysis (pure vs impure)refactor-safe(P1) — Pre-flight rename/move safety checkdead-code(P1) — Enhanced dead code detectionstack-trace(P2) — Error propagation simulationtest-map(P2) — Test coverage mappingconfig-drift(P2) — Dependency drift detectiontype-infer(P3) — Lightweight type inferenceownership(P3) — Git blame code ownership analysis
search— Code search across workspacesymbols— Registry-based symbol searchtrace— Deep call chain tracingimpact— Change impact analysisoutline— File structure outlinemissing-refs— CSS/HTML mismatch detectiondiff— Registry snapshot comparisoncircular— Circular dependency detectioncontext— Rich symbol contextdependents— Module-level import trackingvalidate— Registry sanity check- Tree-sitter powered AST parsing
- 9 tree-sitter parsers
- Framework auto-detection
- Incremental scanning
init,scan,query,list,detect,watchcommands- Frontend registry (classes + ids)
- Backend registry (nodes + edges)
- Status tracking (active, dead, collision, duplicate_ref, duplicate_define)
- HTML, CSS, JS, Rust basic regex parsers
These changelog entries were previously embedded in SKILL.md and have been moved here to reduce SKILL.md size for AI consumption. The reference documentation remains in SKILL.md.
Before an AI writes a new class/id/function, CodeLens must be checked. This is not optional.
- Bugfix: Vue false positive from Vite config:
vite.config.jswas listed as a Vue config file, causing ALL Vite-based projects (React, Svelte, etc.) to be falsely detected as using Vue. Nowvite.config.jsis correctly associated with the newviteframework entry. Frameworks now reporthas_vite: trueseparately fromhas_vue: true. Thevue_modeconfig is only set when actual.vuefiles orvuepackage dependency exists. Eliminates the contradiction wherehas_vue: falsebut "vue" appeared in frameworks list. - Bugfix: Secrets false positives in enum definitions: TypeScript/JavaScript enum values like
IncorrectEmailPassword = "incorrect-email-password"andUserMissingPassword = "missing-password"were flagged as criticalpasswordsecrets. Added_is_enum_or_constant_definition()context-aware filter that detects PascalCase/ALL_CAPS identifiers assigned kebab-case string values (enum pattern) and skips them. Critical findings dropped from 8 to 1 on cal.com. - Bugfix: Secrets false positives in .env.example:
.env.example,.env.sample,.env.template, and.env.demofiles were scanned for secrets in Phase 2 (.env file scanner) and reportedDATABASE_URLand other template values as criticalconnection_stringfindings. Now skipped entirely — these files contain placeholder/example values, not real secrets. - Bugfix: API map keyword false positives: GraphQL resolver extraction pattern
(\w+)\s*[:=]\s*\(matched JS/TS reserved words likeif,else,foras GraphQL field names, producing routes likeQUERY Query.if. Added reserved word filtering (36 keywords) to_extract_graphql_schema(),_extract_graphql_code(), and_find_next_js_function(). Keyword false positives eliminated entirely. - Bugfix: Dead code false positives for Next.js lifecycle functions:
generateMetadata,getServerSideProps,getStaticProps,getStaticPaths,getInitialProps,generateStaticParams,generateViewportwere flagged as dead code because they haveref_count == 0— they're called by the Next.js framework at runtime, not by user code. Added Next.js lifecycle function skip list to_detect_dead_from_registry(), only applied for.ts/.tsx/.js/.jsxfiles. - Bugfix: Dataflow timeout crash:
dataflowcommand had hardcodedmax_files=5000andtimeout_sec=120with no CLI override. On repos with 5000+ files, it would timeout and produce no output (JSONDecodeError). Added--max-files(default 3000) and--timeout(default 120) CLI arguments to the dataflow command. Now produces valid output even on large repos. - Vite as first-class framework: Added
viteframework entry with config filesvite.config.js,vite.config.ts,vite.config.mts. Projects using Vite are now correctly identified without being misattributed to Vue.has_vite: trueflag added todetect_frameworks()output. - Version: 6.3.1 → 6.5.0.
- Bugfix:
is_bundled_filemissing from utils.py: 4 commands (ask,complexity,context,perf-hint) were silently broken due to missingis_bundled_filefunction inutils.py. Now added with proper path-based and extension-based detection for minified, bundled, and dist/build output files. - Bugfix:
analyzeenv_issues engine ImportError:_detect_env()called non-existentaudit_environmentfromenvcheck_engine. Fixed to use correctcheck_env_vars()function. The env_issues engine now runs successfully inanalyze. - Bugfix: Risk score saturation to 0:
_compute_risk_score()used linear deduction that immediately saturated to 0/100 on projects with multiple finding categories. Now uses logarithmic scaling (log2(1+n)) with per-category caps and exponential decay for negative scores, producing meaningful risk scores (e.g., 30/100 instead of 0/100 for a project with 367 critical issues). - Bugfix:
dependentsworkspace auto-swap: When passing a workspace directory as the first argument todependents, the auto-swap correctly updatedargs.workspacebut not theworkspaceparameter passed to engine functions. Fixed by updating both. - Bugfix:
askrouter specificity: "show me the architecture" was misrouted tocontext(score 4.0) instead ofhandbook(score 3.27) because the coverage bonus favored short keyword patterns. Added a 1.5x specificity bonus for patterns matching weight-3 technical terms. - Auto-detect detail level:
summary --detail auto(now the default) automatically adapts detail level based on codebase size: <100 files → "full", 100-1000 → "standard", >1000 → "minimal". Prevents information overload on large repos. - Smart truncation:
summary --max-tokens 8000estimates output token count and progressively truncatestop_itemslists to stay within budget. Prevents AI agent context overflow. - AGENT.md generation:
summary --write-agent-mdwrites a condensed markdown file to.codelens/AGENT.mdoptimized for AI agent system prompts. Includes identity, frameworks, priority findings, and actionable recommendations. - Version: 6.3.0 → 6.4.0.
- Large repo timeout fixes:
missing_refsO(n²) typo detection now time-budgeted (15s cap, 2-char prefix filtering, 500K comparison cap, pre-built lookup dict).analyzecommand gets--timeout(default 300s) with per-engine time budget and graceful degradation (skips engines when <20% budget remains).handbookcommand gets--timeout(default 120s) with per-engine skip andpartial: trueoutput flag. - api-map tauri false positive fix: Removed overly broad
invoke\s*\(pattern from tauri import detection. Many non-Tauri projects (AWS Lambda, gRPC, n8n workflow nodes) useinvoke()calls that were falsely detected as Tauri IPC. Now only matches explicit@tauri-apps/apiimports. - state-map react_context false positive fix:
react_contextdetection now requires actual React dependency (has_reactcheck via framework_detect or package.json). Vue/Pinia projects no longer producereact_contextfalse positives. File-level import check also added:createContextmust come from a React import. - entrypoints
--exclude-testsflag: New--exclude-testsflag on theentrypointscommand filters outtest_entrytype from scanning. Reduces n8n entrypoints from 71K (98% test entries) to 1.6K production entries.test_entryoutput also capped at 100 items max. Analyze command passesexclude_tests=Trueby default. - smell god_object JS/TS brace-depth tracking: Replaced naive regex that counted ALL function-like patterns in the entire file (10-30x inflation) with proper brace-depth tracking like Rust impl blocks. Now only counts methods inside actual
class { }body blocks. Example:N8NStartupErrorwent from 87 false methods to 3 actual methods. - missing_refs output improvements: Per-category truncation (max 200 items),
truncated_countsfor actual totals,findingsflat list for consistency with other engines,typo_truncatedflag when time budget expires. - analyze graceful degradation: Skipped engines report
skipped: truewithskip_reasonandaction(suggests running individually).skipped_enginessummary in output. Per-engineelapsed_secondstiming. - Version: 5.9.2 → 6.3.0.
- Performance:
--max-fileson remaining heavy engines: Commands that still timed out on repos with 1000+ files now accept--max-files(default: 3000). Added to:a11y,side-effect,test-map. Already present in:dead-code,complexity,smell,debug-leak. - Performance:
--max-resultson dead-code: New--max-resultsflag (default: 100) to cap results per category. Prevents massive JSON output on repos with thousands of dead code items. - Workspace auto-detect improvement:
resolve_workspace()now prioritizes last-used workspace over cwd/project-marker auto-detection. This fixes the common issue where subcommands likesymbols,search,trace,impact,context,dependentswould resolve to the wrong workspace when the workspace argument is omitted (e.g., resolving to/home/z/my-projectinstead of the actual project). - a11y truncated flag:
a11yengine now reportstruncated: truewhen file-count limit is reached, making it clear that results are partial.
analyzecommand (P0): One-shot full repository analysis. Automatically runs init + scan + all engines (secrets, smells, complexity, debug-leak, dead-code, circular, perf-hints, config-drift, binary-artifacts, dataflow, env-check, vuln-scan). Produces comprehensive report with project identity, frameworks, languages, architecture overview, API routes, entry points, risk assessment (0-100 score), prioritized action plan, and contextual recommendations.- PHP support in all engines:
.phpadded to SOURCE_EXTENSIONS indebugleak_engine.py,smell_engine.py,complexity_engine.py, andperfhint_engine.py. PHP files now scanned for code smells, complexity, debug leaks, and performance hints. - PHP debug leak detection:
var_dump(),print_r(),phpinfo(),dd(),dump(),ray(),dpm(),kint(),xdebug_var_dump(),exit;,die(). - PHP complexity detection: New
_extract_php_functions()— detectspublic/private/protected functionand standalonefunctiondeclarations. - PHP smell detection: Long functions, deep nesting, many parameters for PHP methods.
- PHP performance hints: 8 PHP-specific patterns — Doctrine N+1, Eloquent N+1, sleep(), blocking file_get_contents(), exec()/shell_exec(), memory leaks in long-running processes, Redis KEYS command, missing TTL.
- Multi-language SOURCE_EXTENSIONS: Added
.java,.cs,.dart,.luato all applicable engines. - Risk assessment: 0-100 risk score with emoji indicators (🔴🟠🟡🟢) based on finding severity.
- Prioritized action plan: Auto-generates P0-P3 action items with concrete next steps.
- Contextual recommendations: Language/framework-specific recommendations (PHP: phpstan, Go: go vet, Python: mypy+ruff).
- Total commands: 44 → 45.
- Go project type detection:
handbookparsesgo.modfor module name, Go version, and classifies projects asgo-database,go-web-service,go-grpc-service,go-infrastructure, orgo-project. - Go framework content-based detection:
detect_frameworks()reads go.mod content (not just file existence). Detects gin/echo/fiber/chi/mux/grpc/protobuf only when dependency actually appears. No more false positives on non-web Go projects. - Go removed from unsupported_langs: Go has fallback parser support and is actively scanned, so it's no longer listed as "unsupported".
- Go debug-leak commented_code false positive reduction: 22,433 → 6,734 findings (70% reduction) via Go-specific code indicators, higher block length threshold (5 vs 3), higher score threshold (3 vs 2), and license block skip.
- Bugfix:
get_workspace_outline()TypeError: Removed invalidmax_fileskwarg. - Bugfix:
perf-hintTypeError crash: Removed invalidmax_fileskwarg fromdetect_perf_hints()call. - Bugfix: Handbook
type: unknownandversion: 0.0.0for Go projects: Now extracts identity from go.mod.
- Rust framework detection:
detect_frameworks()now parsesCargo.tomlfor dependencies and detectsrust,tokio,actix-web,axum,warp,rocket,deno_core. Also scans workspace members'Cargo.tomlincrates/,ext/,libs/,packages/. - Rust HTTP route extraction:
api-mapnow detects routes from Rust web frameworks: actix-web (#[get]/#[post]attributes,web::resource()), axum (.route("/path", get(handler))), warp (warp::path("segment")), rocket (#[get]/#[post]attributes). - Cargo workspace monorepo detection:
handbookdetects[workspace]inCargo.tomland sub-crate patterns. Reportsis_monorepo: truewithmonorepo_tools: ["cargo-workspace"]. is_generated_file()utility: Detects lock files, declaration files, minified files. Fixesrefactor_safecommand crash. Total commands: 42 → 43.- State-map
__dunderruntime helper filtering: JS/TS runtime binding helpers (__default,__createBinding,__exportStar,__importDefault,__reexport,__buffer,__esModule, etc.) no longer classified as state stores. General__prefix pattern also filtered. handbookcrash fix: Removed invalidmax_fileskeyword argument fromcmd_scan()call.- Smell
health_scoreat top level:health_scorenow also returned as top-level key for easier programmatic access. - File scan cap increases: Complexity engine 3,000→5,000 files. Debug-leak 3,000→5,000 files.
- Version alignment: skill.json version
5.7.1→5.8.0. Description now accurately reflects current capabilities.
- State map false positive reduction: Expanded skip lists for Node.js globals (__dirname, __filename, process, Buffer, etc.), CLI argument constants, path aliases (ROOT, HOME, CWD), environment variable references, and import-like assignments. ALL_CAPS single-word constants (VERBOSE, CLI, CHECK, PRUNE) now correctly skipped. Python global filtering also improved with builtin/dunder/path skips. State stores dropped from ~1493 false positives to significantly fewer real ones.
- Entrypoints markdown fix (v2): Angle brackets like
<module_export>and<main>were treated as HTML tags by markdown renderers, silently consumed. Now uses backticks for reliable rendering:module_export,main. - Performance: --max-files limit: Scan and handbook commands now accept
--max-files(default: 5000) to prevent timeout on very large repos. Proportionally truncates file categories with a warning. Use--max-files 0to scan all files. - Debug leak output improvement: Each leak item now includes
pattern(the detected pattern name),message(human-readable description), andcontent(the matched line content). Markdown formatter shows descriptive messages like "Debug console statement: console.log()" instead of raw category names. - Python global state filtering: Skips ALL_CAPS constants, dunder attributes (name, file, all), and path/env references (os.path, Path, os.getenv). Reduces false positives in Python projects.
- Monorepo-aware framework detection: Detects turborepo, pnpm-workspace, lerna, nx. Walks sub-directory package.json (apps/, packages/) to find Next.js, React, etc. in workspace packages, not just root. Detects Rust/Cargo workspaces. Build tool detection (Vite, webpack, esbuild).
- Accurate god object detection: Class method counting now scoped to actual class/impl body via brace-depth tracking. Was counting ALL function calls in the file as methods (10-30x inflation). Rust impl blocks also properly scoped.
- API route false positive elimination: Routes must start with
/for non-router objects. Expanded skip list (80+ objects: request, headers, cache, store, etc.). Preventsheaders.get('user-agent')from being reported asGET /user-agent. - CSS specificity false positive fix: Tracks brace depth to distinguish CSS rule selectors from property values. Was flagging
rgba(0, 0, 0, 0.1),var(--x),from -160degas selectors. Specificity wars dropped from 31 false positives to 4 real ones. - Dead code from registry cross-reference: Uses backend registry's
ref_countdata to find functions with zero references. Skips main(), pub functions, and test fixtures. Found 200+ genuine dead items that the text-only scanner missed. - State map constant/component filtering: Skips ALL_CAPS constants (MAX_FILES, etc.), React components (arrow functions, forwardRef, memo, styled), and immutable values. State stores dropped from 825 false positives to ~150 real ones. Removed module.exports scanning that classified every exported function as a store.
- Polyglot project identity: Handbook detects combined types (e.g.,
rust-js-monorepo) when both package.json and Cargo.toml exist. No longer defaults tonode-projectfor Rust+TS monorepos. - Entrypoints markdown fix: Bracket types like
[main]no longer get mangled by markdown link reference interpretation. Uses backticks instead (v5.8: angle brackets were still broken —<main>treated as HTML tag).
- Vulnerability Scanning: Dependency CVE scanning via native audit tools (npm audit, cargo audit, pip-audit, govulncheck) + built-in vulnerability database with 35+ entries
- Performance Anti-Pattern Detection: 8 categories — N+1 queries, sync blocking, memory leaks, expensive re-renders, large bundles, inefficient iterations, unoptimized images, cache misses
- Deep CSS Analysis: Unused custom properties (--var), orphan @keyframes, specificity wars (!important overuse), duplicate property declarations, z-index abuse, non-standard @media breakpoints
- TSX backend extraction: 6.2x more backend nodes from TSX files when tree-sitter-typescript is unavailable. Uses
parse_js_backend_fallbackon TSX to extract functions and imports. - Shared utils module: Centralized
write_output_files,compute_summary,is_file_path,deduplicate_callers,DEFAULT_IGNORE_DIRS,CODELENS_VERSION, andlogger. Eliminates 290+ lines of duplicated code. - Proper logging: Replaced 56
except Exception: passblocks withlogger.warning()/logger.debug(). Errors are now visible instead of silently swallowed. - Fuzzy file path lookup:
context layout.tsxandquery layout.tsxnow match partial paths (end-of-path matching). Returns grouped results for multiple matches. - Registry freshness check: Handbook skips re-scan if registry is less than 5 minutes old (2.8s → 0.3s for consecutive runs).
- Incremental deleted file handling: Selectively removes deleted file entries from registry instead of full rescan.
- Path segment matching:
is_frontend_file/is_backend_fileno longer use substring matching. Preventssrc/from falsely matchingsrc/server/api/auth.ts. - Workspace detection depth limit: Walks up at most 10 directory levels (was unlimited).
- God objects Python scoping: Method count now scoped to each class using indentation (was counting ALL
defin file).
handbookcommand: One-stop project orientation for AI agents. Aggregates identity, structure, health, conventions, risks, and quick reference into a single output. Writes.codelens/handbook.jsonand.codelens/AGENT.md.askcommand: Natural language query router. Agents don't need to memorize 41 commands — just ask a question and CodeLens routes to the right tool.--format markdown: Global flag on ALL commands. Output markdown instead of JSON for direct LLM consumption.scangeneratesoutline.json+summary.json: Previously onlywatchproduced these AI-friendly files. Nowscandoes too.- Decision trees in output:
queryreturnsaction+action_reason,impactreturnsrisk_level+recommended_action,smellreturnsactionable_items,dead-codereturnsremoval_safety. contextenriched with quality metrics: Addsqualityblock with complexity, side effects, safety assessment, smells, and test coverage.- Convention detection: New
convention_engine.pydetects naming conventions, file organization, import styles, component patterns, and error handling. .codelens/AGENT.md: Auto-generated markdown project brief that can be included as system prompt context.