Skip to content

refactor: split presentation formatting and extract CLI/MCP dispatch#493

Merged
carlos-alm merged 27 commits intomainfrom
refactor/titan-presentation-cli
Mar 17, 2026
Merged

refactor: split presentation formatting and extract CLI/MCP dispatch#493
carlos-alm merged 27 commits intomainfrom
refactor/titan-presentation-cli

Conversation

@carlos-alm
Copy link
Contributor

Summary

Titan quality sweep — phase 5c + 6.2–6.3: presentation layer and CLI/MCP entry points.

Presentation

  • explain() → extract section renderers (header, deps, callers, source)
  • stats() → extract section printers (graph, quality, community, complexity)
  • diffImpact (cyc 21→7) → extract printDiffFunctions/Coupled/Ownership/Boundaries/Summary
  • fileExports → extract header and symbol formatting
  • symbolPath → extract not-found and path-step printing

CLI commands

  • execute (check.js) (cyc 14→10) → extract runManifesto and validateKind
  • execute (triage.js) (cyc 13→4) → extract runHotspots, validateFilters, parseWeights

MCP server

  • startMCPServer (cog 34→13, cyc 19→7) → extract loadMCPSdk, createLazyLoaders, resolveDbPath, validateMultiRepoAccess

Stacked on

Files changed (8)

  • src/presentation/queries-cli/inspect.js, overview.js, impact.js, exports.js, path.js
  • src/cli/commands/check.js, triage.js
  • src/mcp/server.js

Test plan

  • All 1883 tests pass
  • Lint clean
  • No new cycles
  • All complexity metrics under thresholds

- Remove dead `truncate` function from ast-analysis/shared.js (0 consumers)
- Remove dead `truncStart` function from presentation/table.js (0 consumers)
- Un-export `BATCH_CHUNK` in builder/helpers.js (only used internally)

Skipped sync.json targets that were false positives:
- BUILTIN_RECEIVERS: used by incremental.js + build-edges.js
- TRANSIENT_CODES/RETRY_DELAY_MS: internal to readFileSafe
- MAX_COL_WIDTH: internal to printAutoTable
- findFunctionNode: re-exported from index.js, used in tests

Impact: 1 functions changed, 32 affected
…ures

Impact: 5 functions changed, 7 affected
connection.js: add debug() logging to all 8 catch-with-fallback blocks
so failures are observable without changing behavior.

migrations.js: replace 14 try/catch blocks in initSchema with hasColumn()
and hasTable() guards. CREATE INDEX calls use IF NOT EXISTS directly.
getBuildMeta uses hasTable() check instead of try/catch.

Impact: 10 functions changed, 19 affected
Add debug() logging to 10 empty catch blocks across context.js,
symbol-lookup.js, exports.js, impact.js, and module-map.js.
All catches retain their fallback behavior but failures are now
observable via debug logging.

Impact: 6 functions changed, 18 affected
Add debug() logging to 6 empty catch blocks: 3 in disposeParsers()
for WASM resource cleanup, 2 in ensureWasmTrees() for file read and
parse failures, and 1 in getActiveEngine() for version lookup.

Impact: 3 functions changed, 0 affected
Add debug() logging to 9 empty catch blocks across complexity.js (5),
cfg.js (2), and dataflow.js (2). All catches for file read and parse
failures now log the error message before continuing.

Impact: 4 functions changed, 2 affected
Split the monolithic walkJavaScriptNode switch (13 cases, cognitive 228)
into 11 focused handler functions. The dispatcher is now a thin switch
that delegates to handleFunctionDecl, handleClassDecl, handleMethodDef,
handleInterfaceDecl, handleTypeAliasDecl, handleVariableDecl,
handleEnumDecl, handleCallExpr, handleImportStmt, handleExportStmt,
and handleExpressionStmt.

The expression_statement case now reuses the existing
handleCommonJSAssignment helper, eliminating ~50 lines of duplication.

Worst handler complexity: handleVariableDecl (cognitive 20), down from
the original monolithic function (cognitive 279).

Impact: 13 functions changed, 3 affected
Split walkPythonNode switch into 7 focused handlers: handlePyFunctionDef,
handlePyClassDef, handlePyCall, handlePyImport, handlePyExpressionStmt,
handlePyImportFrom, plus the decorated_definition inline dispatch.

Moved extractPythonParameters, extractPythonClassProperties, walkInitBody,
and findPythonParentClass from closures to module-scope functions.

Impact: 12 functions changed, 5 affected
Split walkJavaNode switch into 8 focused handlers plus an
extractJavaInterfaces helper. Moved findJavaParentClass to module scope.
The class_declaration case (deepest nesting in the file) is now split
between handleJavaClassDecl and extractJavaInterfaces.

Impact: 12 functions changed, 5 affected
Apply the same per-category handler decomposition to all remaining
language extractors: Go (6 handlers), Ruby (8 handlers), PHP (11
handlers), C# (11 handlers), Rust (9 handlers), HCL (4 handlers).

Each extractor now follows the template established by the JS extractor:
- Thin entry function creates ctx, delegates to walkXNode
- walkXNode is a thin dispatcher switch
- Each case is a named handler function at module scope
- Helper functions (findParentClass, etc.) moved to module scope

Impact: 66 functions changed, 23 affected
…pers

Move nested handler functions to module level in cfg-visitor.js,
dataflow-visitor.js, and complexity-visitor.js — reducing cognitive
complexity of each factory function from 100-337 down to thin
coordinators. Extract WASM pre-parse, visitor setup, result storage,
and build delegation from runAnalyses into focused helper functions.

Impact: 66 functions changed, 43 affected
Extract edge-building by type (import, call-native, call-JS, class
hierarchy) from buildEdges. Extract per-phase insertion logic from
insertNodes. Extract scoped/incremental/full-build paths and
reverse-dep cascade from detectChanges. Extract setup, engine init,
alias loading from pipeline.js. Extract node/edge-building helpers
from incremental.js rebuildFile.

Impact: 44 functions changed, 19 affected
Impact: 5 functions changed, 3 affected
…sification

Impact: 8 functions changed, 3 affected
Impact: 10 functions changed, 5 affected
Impact: 5 functions changed, 2 affected
Impact: 12 functions changed, 6 affected
…age)

Extract per-section validators from validateBoundaryConfig (cog 101→2).
Extract buildCommunityObjects and analyzeDrift from communitiesData (cog 32→4).
Extract buildTriageItems and computeTriageSummary from triageData (bugs 1.4→0.48).

Impact: 13 functions changed, 11 affected
Extract printDiffFunctions/Coupled/Ownership/Boundaries/Summary from
diffImpact (cog 28→6, cyc 21→7). Extract printExportHeader/Symbols from
fileExports. Extract printNotFound/PathSteps from symbolPath.

Impact: 12 functions changed, 7 affected
Extract runManifesto/validateKind from check execute (cyc 14→10).
Extract runHotspots/validateFilters/parseWeights from triage execute (cyc 13→4).
Extract loadMCPSdk/createLazyLoaders/resolveDbPath/validateMultiRepoAccess
from startMCPServer (cog 34→13, cyc 19→7).

Impact: 14 functions changed, 4 affected
@claude
Copy link

claude bot commented Mar 17, 2026

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


Code Review Complete

Conducted a comprehensive review of PR #493 following the 5-phase analysis process.

Root Cause Analysis

Problem Being Solved: This PR addresses excessive cyclomatic complexity in presentation layer functions by extracting smaller, focused functions from larger ones. Specific complexity reductions achieved: diffImpact (cyc 21→7), check execute (cyc 14→10), triage execute (cyc 13→4), startMCPServer (cog 34→13, cyc 19→7).

Why This Approach: Function extraction is the correct approach for reducing complexity while maintaining behavior. The extracted functions have clear, single responsibilities and are logically separated.

Risk Assessment: Low risk - purely organizational refactoring with no behavioral changes.

Backlog Compliance

  • Zero-dep: ✓ — No new runtime dependencies added
  • Foundation-aligned: ✓ — Fully compliant with all core principles
  • Problem-fit: 3 — Improves code maintainability and readability, making the codebase easier for AI agents and humans to understand, but doesn't directly address the core "lost AI" problem
  • Breaking: No — Purely internal refactoring, no API changes
  • Tier: 1 (zero-dep ✓ + foundation-aligned ✓)

Code Quality Assessment

✅ Excellent refactoring execution:

  1. Well-structured function extraction:

    • check.js:5-9: validateKind() properly extracted with clear error handling
    • triage.js:4-11,13-20,22-35: Clean separation into validateFilters(), parseWeights(), runHotspots()
    • server.js:24-40,42-58,60-75,77-88: MCP server decomposition into focused helpers
    • Presentation files: Logical formatting function separation
  2. Measurable complexity reduction: Specific metrics provided showing significant improvements

  3. Code quality preserved:

    • Error handling maintained (e.g., server.js:35-39 SDK loading)
    • Clean function names and signatures
    • No duplicated logic or anti-patterns introduced
    • Import/export structure properly maintained
  4. No security or performance concerns: Changes are purely organizational

Minor observation: While the refactoring is excellent, some extracted functions are quite small (e.g., validateKind() is only 4 lines). However, this follows the single responsibility principle and improves readability.

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: High-quality refactoring that meaningfully reduces complexity while maintaining all functionality. Clean implementation with proper separation of concerns. Part of a systematic quality improvement effort. No risks identified.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 17, 2026

Greptile Summary

This PR is the fifth phase of the Titan quality sweep, decomposing the presentation layer and CLI/MCP entry points into smaller, focused helper functions. The primary goal is reducing cyclomatic and cognitive complexity across check.js, triage.js, server.js, and the five queries-cli modules, with no intended behavioral changes.

Key changes:

  • check.js: validateKind + runManifesto extracted from two duplicated inline blocks (cyc 14→10)
  • triage.js: validateFilters, parseWeights, runHotspots extracted (cyc 13→4)
  • server.js: loadMCPSdk, createLazyLoaders, resolveDbPath, validateMultiRepoAccess extracted (cog 34→13, cyc 19→7); createRequire also hoisted to module scope
  • impact.js, exports.js, path.js: presentation rendering split into small, single-purpose printer helpers

Notable concern: In resolveDbPath, the original let dbPath = customDbPath || undefined guard was simplified to let dbPath = customDbPath, which removes the normalization of falsy values (null, '') to undefined. While unlikely to be triggered in practice given the typed parameter, it is a subtle semantic difference worth verifying.

Confidence Score: 4/5

  • Safe to merge — pure refactoring with one minor edge-case behavioral difference in resolveDbPath.
  • All 1883 tests pass and every extraction faithfully preserves the original logic. The only issue is the removal of the || undefined normalization in resolveDbPath, which changes behavior only when customDbPath is a non-undefined falsy value (e.g. null or '') — an unlikely scenario given the typed API surface and existing test coverage.
  • src/mcp/server.js — specifically the resolveDbPath function (line 53) where the || undefined normalization was dropped.

Important Files Changed

Filename Overview
src/mcp/server.js Refactors startMCPServer into four extracted helpers: loadMCPSdk, createLazyLoaders, resolveDbPath, validateMultiRepoAccess. One subtle behavioral change: resolveDbPath no longer normalizes falsy customDbPath to undefined.
src/cli/commands/check.js Extracts validateKind and runManifesto from the duplicated inline blocks, reducing cyclomatic complexity from 14 to 10. Behavior is preserved exactly.
src/cli/commands/triage.js Extracts validateFilters, parseWeights, and runHotspots; reduces cyclomatic complexity from 13 to 4. All logic is faithfully replicated with no behavioral changes.
src/presentation/queries-cli/impact.js Extracts five presentation helpers (printDiffFunctions/Coupled/Ownership/Boundaries/Summary) from diffImpact. Optional-chaining guards (?.length) used in place of explicit && .length > 0 checks — semantically equivalent.
src/presentation/queries-cli/exports.js Extracts printExportHeader and printExportSymbols from fileExports. Clean decomposition with no behavioral changes.
src/presentation/queries-cli/path.js Extracts printNotFound and printPathSteps from symbolPath. Clean extraction; behavior is identical to the original.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[startMCPServer] --> B[loadMCPSdk]
    A --> C[createLazyLoaders]
    A --> D[server.setRequestHandler - CallTool]

    D --> E[validateMultiRepoAccess]
    D --> F[resolveDbPath]
    F --> G{args.repo?}
    G -- yes --> H[resolveRepoDbPath]
    G -- no --> I[use customDbPath]
    D --> J[TOOL_HANDLERS.get - name]
    J --> K[toolEntry.handler]

    C --> L[getQueries - lazy]
    C --> M[getDatabase - lazy]

    subgraph CLI
        N[check execute] --> O[runManifesto]
        O --> P[validateKind]
        O --> Q[manifesto]

        R[triage execute] --> S{level?}
        S -- file/dir --> T[runHotspots]
        S -- function --> U[validateFilters]
        U --> V[parseWeights]
        V --> W[triage]
    end

    subgraph Presentation
        X[diffImpact] --> X1[printDiffFunctions]
        X --> X2[printDiffCoupled]
        X --> X3[printDiffOwnership]
        X --> X4[printDiffBoundaries]
        X --> X5[printDiffSummary]

        Y[fileExports] --> Y1[printExportHeader]
        Y --> Y2[printExportSymbols]

        Z[symbolPath] --> Z1[printNotFound]
        Z --> Z2[printPathSteps]
    end
Loading

Comments Outside Diff (1)

  1. src/mcp/server.js, line 53 (link)

    Dropped || undefined normalization changes semantics

    The original code read let dbPath = customDbPath || undefined; which normalized any falsy value (null, '') to undefined. The refactored version assigns customDbPath directly.

    If startMCPServer is ever called with customDbPath = '' or null (e.g., from a CLI path option that receives an empty string), the falsy value now propagates as the dbPath passed to tool handlers. Downstream consumers of dbPath that check if (dbPath) would behave differently than ones that check if (dbPath !== undefined).

Last reviewed commit: 22ae887

@carlos-alm
Copy link
Contributor Author

Addressed the misplaced JSDoc issue: fixed in fc721f3 — moved the startMCPServer JSDoc block from above loadMCPSdk() to directly above export async function startMCPServer(), where it belongs.

@carlos-alm
Copy link
Contributor Author

@greptileai

Move createRequire assignment after all import declarations
to satisfy Biome's import sorting rule.

Impact: 3 functions changed, 1 affected
@carlos-alm
Copy link
Contributor Author

Addressed both Greptile observations from the latest review:

  1. import.meta.url inside factory closure — Hoisted createRequire(import.meta.url) to module scope (line 8+11), so the intent is clear and the closure no longer references import.meta.url directly.

  2. Redundant || undefined coercion — Simplified to let dbPath = customDbPath; in resolveDbPath.

Both changes pushed in commits fc721f3 and 22ae887.

@carlos-alm
Copy link
Contributor Author

@greptileai

1 similar comment
@carlos-alm
Copy link
Contributor Author

@greptileai

Base automatically changed from refactor/titan-domain-features to main March 17, 2026 13:12
@carlos-alm carlos-alm merged commit cedd67a into main Mar 17, 2026
13 checks passed
@carlos-alm carlos-alm deleted the refactor/titan-presentation-cli branch March 17, 2026 13:46
@github-actions github-actions bot locked and limited conversation to collaborators Mar 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant