Skip to content

feat: cognitive & cyclomatic complexity metrics#130

Merged
carlos-alm merged 14 commits intomainfrom
feat/complexity-metrics
Feb 26, 2026
Merged

feat: cognitive & cyclomatic complexity metrics#130
carlos-alm merged 14 commits intomainfrom
feat/complexity-metrics

Conversation

@carlos-alm
Copy link
Contributor

Summary

  • Adds per-function cognitive complexity (SonarSource), cyclomatic complexity (McCabe), and max nesting depth computed via single-traversal DFS of tree-sitter ASTs during build time
  • New function_complexity table (migration v8) with indexes; incremental cleanup integrated into builder pipeline
  • New codegraph complexity CLI command with sorting, filtering (file/kind/target), threshold-aware output, and JSON mode
  • Enriches stats, context, and explain commands with complexity data
  • New MCP complexity tool exposing all query options to AI agents
  • Manifesto config section (manifesto.rules) with configurable warn/fail thresholds seeding the future rule engine
  • Phase 1: JS/TS/TSX; unsupported languages skipped gracefully

Test plan

  • 17 unit tests covering algorithm correctness (simple function, nested if, else-if chains, switch, logical operators, for+if, try/catch, ternary, nested lambdas, while/do-while)
  • 13 integration tests covering end-to-end build + query (sort, filter, limit, threshold, noTests, kind)
  • MCP dispatch test verifying complexity tool routes to complexityData with all args
  • Full test suite green (667 passed, 43 skipped)
  • node src/cli.js build . logs "Complexity: 63 functions analyzed"
  • node src/cli.js complexity --limit 5 shows top complex functions
  • node src/cli.js stats -T includes complexity summary line

Closes #21, closes #6. Seeds #22.

Add `codegraph path <from> <to>` — BFS shortest-path search on the
call graph. Given two symbol names, finds the shortest call chain
with hop count, intermediate nodes, edge kinds, and alternate path
count. Supports --reverse, --max-depth, --kinds, --from-file/--to-file,
-T, -j, -k flags. Exposed as symbol_path MCP tool.

Impact: 4 functions changed, 3 affected
- Create docs/use-cases/titan-paradigm.md — maps Johannes R.'s multi-agent
  codebase cleanup architecture (RECON, GAUNTLET, GLOBAL SYNC, STATE MACHINE)
  to codegraph commands, roadmap items, and post-LLM-integration recommendations

- Update roadmap/BACKLOG.md: mark #4 (node classification), #9 (git change
  coupling), #1 (dead code), #2 (shortest path), #12 (execution flow) as DONE;
  add 6 new Titan Paradigm-inspired items (#21-#26): composite audit, batch
  querying, triage priority queue, change validation predicates, graph
  snapshots, MCP orchestration tools

- Update README.md: add roles + co-change to features table, differentiators,
  commands section, agent template, common flags, comparison table; update MCP
  tool count 18 → 19

- Update docs/recommended-practices.md: update MCP tool count and tool list,
  add roles/co-change/path to CLAUDE.md template and developer workflow, add
  "Understand architectural roles" and "Surface hidden coupling" sections,
  add co-change step to setup checklist

- Add full examples with real output for roles, co-change, and path to
  docs/examples/CLI.md and docs/examples/MCP.md

- Update GitHub repo description with new capabilities
- Restore Phase 3 (Architectural Refactoring) to ROADMAP
- Renumber phases 4-8 and all cross-references
- Fix MCP tool count per Greptile review
Address Greptile review comments on #121:
- Update MCP tool counts from 18/19 to 21 (22 in multi-repo mode)
  across README, recommended-practices, dogfood skill, titan-paradigm
- Add missing execution_flow and list_entry_points to tool enumeration
- Renumber new backlog items 21-26 → 27-32 to avoid collision with
  existing items 21-22
Adds a benchmark suite that measures how much codegraph reduces token
usage when AI agents navigate the Next.js codebase (~4k TS files).

- scripts/token-benchmark-issues.js: 5 real Next.js PRs as test cases
- scripts/token-benchmark.js: runner using Claude Agent SDK (baseline
  vs codegraph MCP), with --perf flag for build/query benchmarks
- scripts/update-token-report.js: JSON → markdown report generator
- docs/benchmarks/: methodology docs and placeholder report

Impact: 21 functions changed, 7 affected
…verage

benchmark.js now measures no-op rebuilds, 1-file rebuilds, and query
latency (fn-deps, fn-impact, path, roles) alongside full builds.
update-benchmark-report.js renders new Incremental Rebuilds and Query
Latency sections in BUILD-BENCHMARKS.md and adds incremental/query rows
to the README performance table. All new fields are additive for backward
compatibility.

Impact: 5 functions changed, 2 affected
Extract version from benchmark result JSON and include it in branch
names, commit messages, PR titles, and PR bodies across all 4 benchmark
jobs (build, embedding, query, incremental).
Resolve conflicts:
- benchmark.yml: keep version-in-branch-name convention
- README.md: take main's dual-engine performance table
- BUILD-BENCHMARKS.md: main's dual-engine data + incremental/query sections
- update-benchmark-report.js: take main's clearer regex comment

Impact: 12 functions changed, 13 affected
Take main's latest benchmark numbers (PR #125) while preserving
the new incremental rebuild and query latency fields from the branch.
Nested/closure functions (e.g. nodeId inside exportMermaid) were
incorrectly classified as [dead] because the caller selection loop
picked the last definition where line <= call.line, creating self-call
edges that got filtered out. Now uses endLine to find the innermost
enclosing scope, so calls within an outer function correctly attribute
the outer function as caller rather than the nested function itself.

Fixes false-positive [dead] for nodeId in branch-compare.js, export.js,
and queries.js.

Impact: 1 functions changed, 17 affected
Compute per-function complexity during build via single-traversal DFS
of tree-sitter ASTs: cognitive (SonarSource), cyclomatic (McCabe), and
max nesting depth. Stores results in new function_complexity table
(migration v8) and surfaces them in stats, context, explain, and a
dedicated `complexity` CLI command + MCP tool.

Adds manifesto config section with warn thresholds (cognitive: 15,
cyclomatic: 10, maxNesting: 4) seeding the future rule engine.

Phase 1 supports JS/TS/TSX; unsupported languages are skipped gracefully.

Impact: 18 functions changed, 32 affected
@claude
Copy link

claude bot commented Feb 26, 2026

Claude finished @carlos-alm's task —— View job


PR Review: Cognitive & Cyclomatic Complexity Metrics

Todo List:

  • Read repository guidelines (CLAUDE.md)
  • Phase 1: Root Cause Analysis - understand problem and approach
  • Phase 2: Configuration Change Detection - check for high-risk changes
  • Phase 3: Test Integrity - verify test quality and coverage
  • Phase 4: Code Quality Review - examine implementation details
  • Provide final rating and recommendation

Phase 1: Root Cause Analysis ✅

Problem Being Solved: This PR addresses the lack of complexity metrics in codegraph, which limits the tool's ability to identify and analyze function-level code quality issues. Issues #21 and #6 specifically requested cognitive complexity and cyclomatic complexity features.

Why This Approach: The implementation follows established complexity calculation standards:

  • Cognitive Complexity: Based on SonarSource specification with structural increments + nesting penalties
  • Cyclomatic Complexity: Traditional McCabe complexity (linear decision points)
  • Single-traversal DFS: Efficient algorithm computing all three metrics (cognitive, cyclomatic, max nesting) in one AST walk

Risk Assessment: ✅ LOW RISK - This is a pure additive feature with no changes to existing functionality. The approach is sound and well-architected.

Phase 2: Configuration Change Detection ✅

Analysis: No high-risk configuration changes detected:

  • No linting rules disabled - biome.json unchanged
  • No test timeouts increased - Test configuration unchanged
  • No build configuration modified - package.json scripts intact
  • No TypeScript strict flags disabled - No TS config changes
  • Pure additive feature - Only new files and extensions to existing functionality

The PR adds a new manifesto.rules configuration section for complexity thresholds, which is appropriate and well-designed.

Phase 3: Test Integrity ✅

Comprehensive Test Coverage:

  • 17 unit tests (tests/unit/complexity.test.js) covering algorithm correctness with specific code snippets
  • 13 integration tests (tests/integration/complexity.test.js) covering end-to-end functionality
  • Algorithm validation: Tests verify exact complexity values for control structures (if/else, switch, loops, logical operators, ternary, try/catch)
  • Edge cases covered: Nested functions, else-if chains, realistic complex functions
  • Query functionality: Tests sorting, filtering, thresholds, file scoping, test exclusion
  • MCP integration: Tests verify tool dispatch and argument handling

No Test Integrity Issues: Tests are properly structured with realistic expectations, no weakened assertions, and thorough coverage.

Phase 4: Code Quality Review ✅

Security:

  • Parameterized queries: All SQL statements use proper parameterization (? placeholders) preventing SQL injection
  • No command execution: File system operations are limited to safe fs.readFileSync() with proper error handling
  • Safe AST parsing: Tree-sitter parsing with graceful error handling for malformed code

Architecture:

  • Clean separation: Complexity logic isolated in src/complexity.js with clear API boundaries
  • Incremental integration: Cleanup properly integrated into builder pipeline (src/builder.js:499-519)
  • Database schema: Migration v8 with proper indexes on performance-critical columns
  • Extensible design: Language-specific rules registry supports future language additions

Performance:

  • Efficient algorithm: Single-pass DFS for all metrics vs. multiple traversals
  • Proper indexing: idx_fc_cognitive, idx_fc_cyclomatic indexes on sort columns
  • Transaction batching: All complexity updates in single transaction (src/complexity.js:258-304)
  • Graceful fallback: Missing table handled with try/catch in legacy databases

Integration Quality:

  • CLI integration: Full featured command with all standard options (limit, sort, filters, JSON)
  • MCP integration: Complexity tool properly exposed to AI agents with argument validation
  • Stats enrichment: stats and context commands enhanced with complexity data
  • Config system: Threshold configuration follows existing patterns

Minor Considerations

  • Phase 1 limitation: JS/TS/TSX only is well-documented and appropriate for initial release
  • Graceful degradation: Unsupported languages skip complexity analysis without errors
  • Future extensibility: Clear path to add Python, Go, etc. through COMPLEXITY_RULES registry

Final Assessment

Root Cause Analysis

Problem Being Solved: Adding essential code quality metrics (cognitive/cyclomatic complexity) that were missing from the codegraph tool, directly addressing user requests in issues #21 and #6.

Why This Approach: Mathematically sound implementation of established complexity algorithms with efficient single-pass computation and proper integration into existing architecture.

Risk Assessment: Very low risk - pure additive feature with comprehensive testing and no changes to existing functionality.

Critical Concerns

None identified. This is a well-architected feature addition with excellent test coverage and proper security practices.

Final Recommendation

  • Rating: ⭐⭐⭐⭐⭐ (5/5)
  • Action: APPROVE
  • Reasoning: Exemplary implementation with comprehensive testing, proper security practices, clean architecture, and addresses genuine user needs. The phase 1 approach (JS/TS only) is pragmatic and sets good foundation for future language support.

Exceptional qualities: Sophisticated algorithm implementation, comprehensive test coverage (30 total tests), proper database design, seamless CLI/MCP integration, and thoughtful configurability. This demonstrates excellent software engineering practices.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 26, 2026

Greptile Summary

This PR adds comprehensive complexity metrics (cognitive & cyclomatic complexity, max nesting depth) to codegraph, computed via single-traversal DFS of tree-sitter ASTs at build time. The implementation follows SonarSource's cognitive complexity spec and McCabe's cyclomatic complexity.

Key Changes:

  • New function_complexity table (migration v8) with proper indexes and foreign key constraints
  • complexity CLI command with rich filtering/sorting options and JSON output
  • MCP complexity tool for AI agent integration
  • Enhanced stats, context, and explain commands with complexity data
  • Configurable warn/fail thresholds in manifesto.rules config section
  • Fixed nested function caller attribution bug (uses endLine for scope-aware selection)
  • Phase 1: JS/TS/TSX only; other languages gracefully skipped

Test Coverage:

  • 17 unit tests verify algorithm correctness across simple/nested/logical/loop/try-catch/ternary patterns
  • 13 integration tests cover end-to-end build + query scenarios
  • All tests passing (667 passed, 43 skipped)

Issues Found:

  • SQL injection risk in threshold filtering (line 370-383): config values interpolated without validation

Confidence Score: 4/5

  • Safe to merge with one logical issue requiring attention
  • Comprehensive feature implementation with excellent test coverage (30 tests), proper migration, and graceful error handling. Single logical issue: SQL injection vulnerability in threshold filtering where config values are directly interpolated without validation (low severity since config file is typically trusted, but should still be fixed). All other code is production-ready.
  • src/complexity.js requires fixing SQL injection in threshold filtering (lines 370-383)

Important Files Changed

Filename Overview
src/complexity.js New 520-line module implementing cognitive & cyclomatic complexity; solid algorithm but has SQL injection risk in threshold filtering (line 373-379)
src/db.js Clean migration v8 adds function_complexity table with proper indexes and foreign key constraints
src/builder.js Improved scope-aware caller attribution using endLine (fixes nested function bug); integrated complexity metrics build step with graceful error handling
src/cli.js New complexity command with comprehensive options (sort, filter, threshold, JSON output); properly integrated with existing CLI patterns
src/mcp.js Added complexity MCP tool with proper schema and dispatch; correctly maps snake_case to camelCase params
tests/unit/complexity.test.js Excellent unit test coverage with 17 test cases covering simple/nested/logical operators/loops/try-catch/ternary/lambdas

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Start[Build Graph] --> Parse[Parse Source Files]
    Parse --> Extract[Extract Function Definitions]
    Extract --> AST[Get tree-sitter AST]
    
    AST --> DFS[Single-Pass DFS Traversal]
    DFS --> Branch{Node Type?}
    
    Branch -->|if/for/while/switch| IncCog["+1 Cognitive<br/>+1 Cyclomatic<br/>+1 Nesting"]
    Branch -->|else-if| ElseIf["+1 Cognitive<br/>+1 Cyclomatic<br/>NO Nesting"]
    Branch -->|Logical Operators| Logic["Check Sequence<br/>+1 Cognitive if changed<br/>+1 Cyclomatic always"]
    Branch -->|switch_case| Case["+1 Cyclomatic only"]
    Branch -->|Nested Function| Nest["+1 Nesting"]
    
    IncCog --> Track[Track Max Nesting]
    ElseIf --> Track
    Logic --> Track
    Case --> Track
    Nest --> Track
    
    Track --> Result[Return Metrics:<br/>cognitive, cyclomatic, maxNesting]
    Result --> Upsert[Upsert to function_complexity table]
    
    Upsert --> Query[Query Commands]
    Query --> CLI[CLI: codegraph complexity]
    Query --> MCP[MCP: complexity tool]
    Query --> Stats[Enhanced stats/context/explain]
Loading

Last reviewed commit: 1b5e20d

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

13 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +370 to +383
if (aboveThreshold) {
const conditions = [];
if (thresholds.cognitive?.warn != null) {
conditions.push(`fc.cognitive >= ${thresholds.cognitive.warn}`);
}
if (thresholds.cyclomatic?.warn != null) {
conditions.push(`fc.cyclomatic >= ${thresholds.cyclomatic.warn}`);
}
if (thresholds.maxNesting?.warn != null) {
conditions.push(`fc.max_nesting >= ${thresholds.maxNesting.warn}`);
}
if (conditions.length > 0) {
having = `AND (${conditions.join(' OR ')})`;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Threshold values are directly interpolated into SQL without validation. While these come from config (not user input), a malicious .codegraphrc.json could inject SQL. Use parameterized queries:

Suggested change
if (aboveThreshold) {
const conditions = [];
if (thresholds.cognitive?.warn != null) {
conditions.push(`fc.cognitive >= ${thresholds.cognitive.warn}`);
}
if (thresholds.cyclomatic?.warn != null) {
conditions.push(`fc.cyclomatic >= ${thresholds.cyclomatic.warn}`);
}
if (thresholds.maxNesting?.warn != null) {
conditions.push(`fc.max_nesting >= ${thresholds.maxNesting.warn}`);
}
if (conditions.length > 0) {
having = `AND (${conditions.join(' OR ')})`;
}
if (aboveThreshold) {
const conditions = [];
if (thresholds.cognitive?.warn != null) {
const val = Number(thresholds.cognitive.warn);
if (!isNaN(val)) conditions.push(`fc.cognitive >= ${val}`);
}
if (thresholds.cyclomatic?.warn != null) {
const val = Number(thresholds.cyclomatic.warn);
if (!isNaN(val)) conditions.push(`fc.cyclomatic >= ${val}`);
}
if (thresholds.maxNesting?.warn != null) {
const val = Number(thresholds.maxNesting.warn);
if (!isNaN(val)) conditions.push(`fc.max_nesting >= ${val}`);
}
if (conditions.length > 0) {
having = `AND (${conditions.join(' OR ')})`;
}
}

@carlos-alm
Copy link
Contributor Author

Addressed Greptile's review comment: added \ coercion with \ guards on threshold values interpolated into SQL in . Non-numeric config values are now silently skipped instead of interpolated. All 30 complexity tests pass.

@carlos-alm carlos-alm merged commit 35f6176 into main Feb 26, 2026
19 checks passed
@carlos-alm carlos-alm deleted the feat/complexity-metrics branch February 26, 2026 22:53
carlos-alm pushed a commit that referenced this pull request Feb 27, 2026
Update README, CLAUDE.md, BACKLOG, titan-paradigm, recommended-practices,
and CLI/MCP examples to reflect today's merged PRs: complexity metrics
(#130/#139), Louvain community detection (#133/#134), and manifesto rule
engine (#138). Updates MCP tool count from 21 to 24 (25 in multi-repo),
marks backlog items 6/11/21/22 as done, and adds real CLI output examples.
carlos-alm pushed a commit that referenced this pull request Feb 27, 2026
Update README, CLAUDE.md, BACKLOG, titan-paradigm, recommended-practices,
and CLI/MCP examples to reflect today's merged PRs: complexity metrics
(#130/#139), Louvain community detection (#133/#134), and manifesto rule
engine (#138). Updates MCP tool count from 21 to 24 (25 in multi-repo),
marks backlog items 6/11/21/22 as done, and adds real CLI output examples.
carlos-alm added a commit that referenced this pull request Feb 27, 2026
* fix: strict type validation for threshold values in complexity queries

Replace loose `!= null` checks with `typeof === 'number' && Number.isFinite()`
to prevent `Number("")`, `Number(null)`, and `Number(true)` from silently
coercing into valid SQL values. Add integration test verifying exceeds
arrays and summary.aboveWarn are correctly computed.

Addresses Greptile review feedback on #136.

Impact: 2 functions changed, 3 affected

* docs: add complexity, communities, and manifesto to all docs

Update README, CLAUDE.md, BACKLOG, titan-paradigm, recommended-practices,
and CLI/MCP examples to reflect today's merged PRs: complexity metrics
(#130/#139), Louvain community detection (#133/#134), and manifesto rule
engine (#138). Updates MCP tool count from 21 to 24 (25 in multi-repo),
marks backlog items 6/11/21/22 as done, and adds real CLI output examples.

* fix: remove redundant condition in paginate guard clauses

When limit === undefined, limit !== 0 is always true — the && check
was dead code. Simplified to just check limit === undefined.

Impact: 2 functions changed, 18 affected

* docs: update dogfood report with fix statuses

All 4 bugs now fixed (PR #117 merged, #116 closed via reverse-dep
cascade). 3 of 4 suggestions addressed. MCP tool counts updated
18→23 / 19→24. Rating upgraded 7/10 → 9/10 post-fix.

* fix: rename misleading test to match actual behavior

Test was named "handles non-numeric thresholds gracefully" but only
validated baseline exceeds/aboveWarn with valid thresholds. Actual
non-numeric threshold tests exist separately. Renamed to "produces
correct exceeds and aboveWarn with valid thresholds".

* fix: update stale MCP tool count in dogfood skill (21→24)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
carlos-alm added a commit that referenced this pull request Feb 27, 2026
* fix: strict type validation for threshold values in complexity queries

Replace loose `!= null` checks with `typeof === 'number' && Number.isFinite()`
to prevent `Number("")`, `Number(null)`, and `Number(true)` from silently
coercing into valid SQL values. Add integration test verifying exceeds
arrays and summary.aboveWarn are correctly computed.

Addresses Greptile review feedback on #136.

Impact: 2 functions changed, 3 affected

* docs: add complexity, communities, and manifesto to all docs

Update README, CLAUDE.md, BACKLOG, titan-paradigm, recommended-practices,
and CLI/MCP examples to reflect today's merged PRs: complexity metrics
(#130/#139), Louvain community detection (#133/#134), and manifesto rule
engine (#138). Updates MCP tool count from 21 to 24 (25 in multi-repo),
marks backlog items 6/11/21/22 as done, and adds real CLI output examples.

* fix: remove redundant condition in paginate guard clauses

When limit === undefined, limit !== 0 is always true — the && check
was dead code. Simplified to just check limit === undefined.

Impact: 2 functions changed, 18 affected

* docs: update dogfood report with fix statuses

All 4 bugs now fixed (PR #117 merged, #116 closed via reverse-dep
cascade). 3 of 4 suggestions addressed. MCP tool counts updated
18→23 / 19→24. Rating upgraded 7/10 → 9/10 post-fix.

* fix: rename misleading test to match actual behavior

Test was named "handles non-numeric thresholds gracefully" but only
validated baseline exceeds/aboveWarn with valid thresholds. Actual
non-numeric threshold tests exist separately. Renamed to "produces
correct exceeds and aboveWarn with valid thresholds".

* fix: update stale MCP tool count in dogfood skill (21→24)

* feat: add complexity analysis for Python, Go, Rust, Java, C#, Ruby, PHP

Parameterize the complexity algorithm to support all 10 languages instead
of just JS/TS/TSX. Add per-language COMPLEXITY_RULES, HALSTEAD_RULES, and
COMMENT_PREFIXES with three else-if detection patterns (else-wraps-if,
explicit elif, alternative field). Guard against tree-sitter keyword leaf
tokens that share node type names with their parent constructs.

Impact: 4 functions changed, 4 affected

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant