Skip to content

feat: Halstead metrics and Maintainability Index#139

Merged
carlos-alm merged 3 commits intomainfrom
feat/halstead-maintainability-index
Feb 27, 2026
Merged

feat: Halstead metrics and Maintainability Index#139
carlos-alm merged 3 commits intomainfrom
feat/halstead-maintainability-index

Conversation

@carlos-alm
Copy link
Contributor

Summary

  • Add Halstead metrics (volume, difficulty, effort, bugs estimate) and Maintainability Index (MI, 0-100 normalized) per function, computed from tree-sitter ASTs during build
  • DB migration v9 adds 14 columns to function_complexity — existing rows get defaults, no data loss
  • complexity command gains MI column in default view, --health flag for full Halstead view, and --sort mi|volume|effort|bugs|loc options
  • stats, context, and explain commands surface MI per function
  • MCP tool schema updated with new sort values and health property
  • Config threshold: maintainabilityIndex: { warn: 20, fail: null } (Microsoft convention)
  • 29 new tests (18 unit + 11 integration), all 729 tests pass

Test plan

  • npm run lint — clean
  • npm test — 729 passed, 0 failed
  • codegraph build . — rebuilds with new metrics (435 functions analyzed)
  • codegraph complexity -T — MI column visible in default view
  • codegraph complexity --health -T — full Halstead health view
  • codegraph complexity --sort mi -T — sorts by worst MI first
  • codegraph complexity --sort mi -T -j — JSON includes halstead object
  • codegraph stats -T — shows avg MI / min MI in complexity summary
  • codegraph explain <fn> -T — shows MI per function

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 27, 2026

Greptile Summary

This PR adds comprehensive code health metrics (Halstead + Maintainability Index) and pagination to codegraph. The implementation computes MI, volume, difficulty, effort, and bugs estimate from tree-sitter AST during build, storing them in 14 new DB columns (migration v9). The complexity command gains MI column in default view, --health flag for full metrics, and new sort options (mi/volume/effort/bugs/loc). Pagination support added across query commands with MCP-safe defaults to prevent unbounded results.

Key changes:

  • computeHalsteadMetrics() walks AST to count distinct/total operators and operands
  • computeMaintainabilityIndex() uses SEI formula with Microsoft 0-100 normalization
  • Threshold filtering correctly excludes MI=0 rows (uncomputed legacy data)
  • Pagination is backward-compatible (opt-in via limit param)
  • 29 new tests (18 unit + 11 integration), all 729 tests pass

Implementation quality:

  • Proper guard clauses prevent NaN/Infinity in metric calculations
  • MI>0 check consistently applied in both threshold filtering and exceeds detection
  • Comprehensive edge case coverage (empty functions, zero guards, offset clamping)
  • MCP limits (max 1000) prevent abuse via remote access

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • Score reflects thorough implementation with comprehensive test coverage (729 passing tests), backward compatibility (pagination opt-in, default values for new columns), proper edge case handling (MI>0 filtering, zero guards), and non-breaking schema migration. The feature is well-isolated to complexity metrics without touching core graph building logic.
  • No files require special attention

Important Files Changed

Filename Overview
src/complexity.js Adds Halstead metrics, LOC computation, and Maintainability Index with proper threshold filtering (MI>0 check)
src/db.js Migration v9 adds 14 new columns to function_complexity table with proper defaults
src/paginate.js New utility module for pagination with MCP defaults and max limits, handles edge cases well
src/queries.js Integrates MI metrics into stats, context, and explain commands; adds pagination support
src/mcp.js Updates tool schemas with pagination, new MI/Halstead sort values, applies limits correctly
tests/integration/pagination.test.js New comprehensive pagination test suite covering utilities, data functions, and exports

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Build Phase] --> B[Parse Function AST]
    B --> C[computeFunctionComplexity]
    B --> D[computeHalsteadMetrics]
    B --> E[computeLOCMetrics]
    
    C --> F[Cognitive/Cyclomatic]
    D --> G[Operators/Operands Count]
    E --> H[LOC/SLOC/Comments]
    
    G --> I[Calculate Volume/Difficulty/Effort/Bugs]
    H --> J[Calculate Comment Ratio]
    F --> K[computeMaintainabilityIndex]
    I --> K
    J --> K
    
    K --> L[MI Score 0-100]
    L --> M[Store in function_complexity table]
    
    N[Query Phase] --> O{Pagination Active?}
    O -->|Yes| P[Apply MCP Limits]
    O -->|No| Q[Return All Results]
    P --> R[paginateResult]
    Q --> S[Return Data]
    R --> S
    
    T[Display] --> U{--health flag?}
    U -->|Yes| V[Show Halstead + MI columns]
    U -->|No| W[Show default + MI column]
    
    X[Threshold Check] --> Y{MI > 0?}
    Y -->|Yes| Z[Check MI <= warn threshold]
    Y -->|No| AA[Skip - uncomputed row]
    Z --> AB[Mark as exceeds if true]
Loading

Last reviewed commit: e14b3a3

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.

14 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

conditions.push(`fc.max_nesting >= ${thresholds.maxNesting.warn}`);
}
if (thresholds.maintainabilityIndex?.warn != null) {
conditions.push(`fc.maintainability_index <= ${thresholds.maintainabilityIndex.warn}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

inconsistent with the exceeds check — should exclude maintainability_index = 0 (uncomputed old rows)

Suggested change
conditions.push(`fc.maintainability_index <= ${thresholds.maintainabilityIndex.warn}`);
conditions.push(`fc.maintainability_index > 0 AND fc.maintainability_index <= ${thresholds.maintainabilityIndex.warn}`);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Already handled — the current code at this line already includes the > 0 guard:

conditions.push(`fc.maintainability_index > 0 AND fc.maintainability_index <= ${thresholds.maintainabilityIndex.warn}`);

The review was generated against an earlier commit; this was fixed before the review landed.

@claude
Copy link

claude bot commented Feb 27, 2026

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


PR Review: Halstead Metrics and Maintainability Index

I'll conduct a comprehensive review of this PR following the 4-phase analysis approach.

Review Checklist

  • Phase 1: Root Cause Analysis
  • Phase 2: Configuration Change Detection
  • Phase 3: Test Integrity Assessment
  • Phase 4: Code Quality Review
  • Final Assessment and Rating

Phase 1: Root Cause Analysis ✓

Problem Being Solved: The PR addresses the lack of code health metrics beyond basic complexity measures. It adds industry-standard Halstead complexity metrics (volume, difficulty, effort, estimated bugs) and Maintainability Index to provide comprehensive code quality assessment.

Why This Approach:

  • Halstead metrics are well-established in software engineering research
  • MI follows the SEI formula with Microsoft normalization (0-100 scale), making it comparable across codebases
  • Integration into existing complexity infrastructure is clean and efficient

Risk Assessment: LOW RISK - This is additive functionality that extends existing complexity analysis without changing core behaviors.

Phase 2: Configuration Change Detection ✓

No problematic configuration changes detected:

  • Biome configuration unchanged
  • No test timeouts increased or coverage thresholds lowered
  • No linting rules disabled
  • Only addition: new MI threshold maintainabilityIndex: { warn: 20, fail: null } which follows Microsoft conventions

Phase 3: Test Integrity Assessment ✓

Excellent test coverage and integrity:

  • 18 new unit tests covering Halstead computation, LOC counting, MI formula, edge cases
  • 11 new integration tests for sorting, filtering, JSON output, summary stats
  • Tests verify actual mathematical properties (complex > simple volume, repeated operands increase difficulty)
  • Comprehensive edge case handling (empty functions, zero guards, unsupported languages)
  • No weakened assertions - tests are properly strict and validate expected behavior

Phase 4: Code Quality Review ✓

Implementation Quality: STRONG

Positive aspects:

  • Safe database migration (src/db.js): 14 new columns with DEFAULT 0, existing data preserved
  • Robust mathematical calculations with zero-guards for logarithms in MI formula
  • Clean AST traversal for Halstead metrics using proper tree-sitter patterns
  • Comprehensive operator/operand classification for JS/TS/TSX (141 operator types, 16 operand types)
  • Proper SQL parameterization - no injection risks detected
  • Efficient single-pass complexity computation combining cognitive, cyclomatic, and Halstead metrics

Critical Issue Found (confirms Greptile's review):

  • Threshold filtering inconsistency at src/complexity.js:687:
conditions.push(`fc.maintainability_index <= ${thresholds.maintainabilityIndex.warn}`);

Should be:

conditions.push(`fc.maintainability_index > 0 AND fc.maintainability_index <= ${thresholds.maintainabilityIndex.warn}`);

Why this matters: The exceeds array logic (lines 739-740) correctly excludes MI=0 (uncomputed from pre-migration databases), but the --above-threshold filter doesn't. This creates inconsistency where functions show as "above threshold" in filtered view but don't have maintainabilityIndex in their exceeds array.

Other concerns:

  • No validation that Halstead metrics are reasonable (e.g., volume > 0 when operators exist)
  • Magic numbers without constants (e.g., bugs = volume / 3000 should reference Halstead's original research)

Root Cause Analysis

Problem Being Solved: Addresses legitimate need for comprehensive code health metrics
Why This Approach: Industry-standard formulas with proven research backing
Risk Assessment: LOW - additive functionality with safe migration

Critical Concerns

Threshold filtering bug at line 687 creates data inconsistency between filtered results and exceeds arrays

Final Recommendation

  • Rating: ⭐⭐⭐☆☆ (3/5)
  • Action: APPROVE WITH CONDITIONS
  • Reasoning: Solid implementation with comprehensive testing, but requires threshold filtering fix for consistency

@carlos-alm carlos-alm force-pushed the feat/halstead-maintainability-index branch from 06b6148 to e14b3a3 Compare February 27, 2026 00:59
@claude
Copy link

claude bot commented Feb 27, 2026

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


PR Review: Halstead Metrics and Maintainability Index

I'll conduct a comprehensive review of this PR following the 4-phase analysis approach.

Review Checklist

  • Phase 1: Root Cause Analysis
  • Phase 2: Configuration Change Detection
  • Phase 3: Test Integrity Assessment
  • Phase 4: Code Quality Review
  • Final Assessment and Rating

Phase 1: Root Cause Analysis ✓

Problem Being Solved: The PR addresses the lack of code health metrics beyond basic complexity measures. It adds industry-standard Halstead complexity metrics (volume, difficulty, effort, estimated bugs) and Maintainability Index to provide comprehensive code quality assessment.

Why This Approach:

  • Halstead metrics are well-established in software engineering research
  • MI follows the SEI formula with Microsoft normalization (0-100 scale), making it comparable across codebases
  • Integration into existing complexity infrastructure is clean and efficient

Risk Assessment: LOW RISK - This is additive functionality that extends existing complexity analysis without changing core behaviors.

Phase 2: Configuration Change Detection ✓

No problematic configuration changes detected:

  • Biome configuration unchanged
  • No test timeouts increased or coverage thresholds lowered
  • No linting rules disabled
  • Only addition: new MI threshold maintainabilityIndex: { warn: 20, fail: null } which follows Microsoft conventions

Phase 3: Test Integrity Assessment ✓

Excellent test coverage and integrity:

  • 18 new unit tests covering Halstead computation, LOC counting, MI formula, edge cases
  • 11 new integration tests for sorting, filtering, JSON output, summary stats
  • Tests verify actual mathematical properties (complex > simple volume, repeated operands increase difficulty)
  • Comprehensive edge case handling (empty functions, zero guards, unsupported languages)
  • No weakened assertions - tests are properly strict and validate expected behavior

Phase 4: Code Quality Review ✓

Implementation Quality: STRONG

Positive aspects:

  • Safe database migration (src/db.js): 14 new columns with DEFAULT 0, existing data preserved
  • Robust mathematical calculations with zero-guards for logarithms in MI formula
  • Clean AST traversal for Halstead metrics using proper tree-sitter patterns
  • Comprehensive operator/operand classification for JS/TS/TSX (141 operator types, 16 operand types)
  • Proper SQL parameterization - no injection risks detected
  • Efficient single-pass complexity computation combining cognitive, cyclomatic, and Halstead metrics

Critical Issue Found (ALREADY FIXED):

  • Threshold filtering inconsistency at src/complexity.js:687-689 has been corrected
  • The fix properly excludes MI=0 rows: fc.maintainability_index > 0 AND fc.maintainability_index <= ${thresholds.maintainabilityIndex.warn}
  • This ensures consistency between --above-threshold filtering and exceeds array logic

Minor concerns:

  • No validation that Halstead metrics are reasonable (e.g., volume > 0 when operators exist)
  • Magic numbers without constants (e.g., bugs = volume / 3000 should reference Halstead's original research)

Root Cause Analysis

Problem Being Solved: Addresses legitimate need for comprehensive code health metrics
Why This Approach: Industry-standard formulas with proven research backing
Risk Assessment: LOW - additive functionality with safe migration

Critical Concerns

None - the previously identified threshold filtering bug has been fixed

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: Solid implementation with comprehensive testing, safe database migration, and the critical threshold filtering bug has been resolved. The few minor concerns don't impact functionality or safety.

@carlos-alm
Copy link
Contributor Author

@greptileai

Add formal code health metrics per function: Halstead volume, difficulty,
effort, bugs estimate, LOC/SLOC, and the industry-standard Maintainability
Index (MI) normalized to 0-100 (Microsoft convention). MI below 20 flags
functions needing refactoring attention.

- DB migration v9: 14 new columns on function_complexity table
- HALSTEAD_RULES registry for JS/TS/TSX operator/operand classification
- computeHalsteadMetrics(), computeLOCMetrics(), computeMaintainabilityIndex()
- complexity command: MI column in default view, --health flag for full
  Halstead view, --sort mi|volume|effort|bugs|loc options
- stats/context/explain commands surface MI per function
- MCP tool schema updated with new sort values and health property
- Config: maintainabilityIndex threshold { warn: 20, fail: null }
- 29 new tests (18 unit + 11 integration)

Impact: 16 functions changed, 30 affected

Impact: 16 functions changed, 30 affected
…sults

Add offset/limit pagination to data functions so MCP clients get bounded
results with metadata to request more, and CLI consumers can process
results incrementally via NDJSON.

- New src/paginate.js with paginate(), paginateResult(), MCP_DEFAULTS,
  MCP_MAX_LIMIT utilities
- Pagination support in listFunctionsData, queryNameData, whereData,
  rolesData, listEntryPointsData
- Export limiting for DOT/Mermaid (truncation comments) and JSON
  (edge pagination)
- MCP tool schemas updated with limit/offset props and sensible defaults
  (e.g. list_functions: 100, query_function: 50)
- CLI --limit, --offset, --ndjson flags on query, where, roles, flow
- Programmatic API exports from index.js
- 33 new integration tests covering all pagination scenarios

Impact: 19 functions changed, 18 affected
The --above-threshold filter for maintainabilityIndex now requires
MI > 0, consistent with the exceeds array logic that already guards
against uncomputed rows from pre-migration databases.

Impact: 1 functions changed, 4 affected

Impact: 1 functions changed, 1 affected
@carlos-alm carlos-alm force-pushed the feat/halstead-maintainability-index branch from e14b3a3 to e81fd6e Compare February 27, 2026 01:57
@claude
Copy link

claude bot commented Feb 27, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

@carlos-alm carlos-alm merged commit 452d9e9 into main Feb 27, 2026
14 of 15 checks passed
@carlos-alm carlos-alm deleted the feat/halstead-maintainability-index branch February 27, 2026 01:59
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