Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ Analyzer
| produces: ForceAnalysis (tension files, bridges, extraction candidates)
v
Core (shared computation)
| result builders used by both MCP and CLI
| result builders used by MCP, CLI, and operation descriptors
|\
| \-> Operation Registry
| typed descriptors, input schemas, CLI/MCP names, result wrappers
v
MCP (stdio) CLI (terminal/CI)
| 17 tools, 2 prompts, | 18 commands with text + JSON
Expand All @@ -35,10 +38,11 @@ src/
graph/index.ts <- graphology graph + circular dep detection
analyzer/index.ts <- All metric computation
core/index.ts <- Shared result computation (MCP + CLI)
operations/index.ts <- Analysis operation descriptors + typed input schemas
config/index.ts <- Config discovery + zod validation
rules/index.ts <- Rules engine + registry (check command + MCP check tool)
mcp/index.ts <- 17 MCP tools for LLM integration
mcp/hints.ts <- Next-step hints for MCP tool responses
mcp/hints.ts <- Operation-keyed next-step hints for MCP tool responses
impact/index.ts <- Symbol-level impact analysis + rename planning
search/index.ts <- BM25 search engine
process/index.ts <- Entry point detection + call chain tracing
Expand Down Expand Up @@ -69,11 +73,15 @@ analyzeGraph(builtGraph, parsedFiles)

startMcpServer(codebaseGraph)
-> stdio MCP server with 17 tools, 2 prompts, 3 resources

runOperation(operation, codebaseGraph, input, context)
-> { ok: true, data } | { ok: false, error, data? }
```

## Key Design Decisions

- **Dual interface**: MCP stdio for LLM agents, CLI subcommands for humans/CI. Both consume `src/core/`.
- **Operation registry foundation**: Analysis operations now have typed descriptors in `src/operations/` with operation names, CLI command names, MCP tool names, input schemas, and discriminated run results. CLI/MCP handlers still call `src/core/` directly until the adapter migration lands.
- **graphology**: In-memory graph with O(1) neighbor lookup. PageRank and betweenness computed via graphology-metrics.
- **Batch git churn**: Single `git log --all --name-only` call, parsed for all files. Avoids O(n) subprocess spawning.
- **Monorepo import resolution**: Root `tsconfig.json` path aliases and local `package.json` package names resolve to source files before graph construction.
Expand All @@ -90,5 +98,6 @@ Vertical slice through all layers:
1. **types/index.ts** — Add field to `FileMetrics` (and `ParsedFile`/`ParsedExport` if extracted at parse time)
2. **parser/index.ts** — Extract raw data from AST or external source (git, filesystem)
3. **analyzer/index.ts** — Compute derived metric, store in `fileMetrics` map
4. **mcp/index.ts** — Expose via `find_hotspots` enum or new tool
5. **Tests** — Cover parser extraction + analyzer computation
4. **operations/index.ts** — Add or update the operation descriptor, input schema, and CLI/MCP mapping
5. **mcp/index.ts / cli.ts** — Expose via existing adapter or new command/tool
6. **Tests** — Cover parser extraction, analyzer computation, operation descriptor parity, and adapter output
13 changes: 12 additions & 1 deletion llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ Analyzer
| computes: churn, complexity, blast radius, dead exports, test coverage
| produces: ForceAnalysis (tension files, bridges, extraction candidates)
v
Core (shared computation)
| result builders used by MCP, CLI, and operation descriptors
|\
| \-> Operation Registry
| typed descriptors, input schemas, CLI/MCP names, result wrappers
v
MCP (stdio) + CLI
| MCP: 17 tools, 2 prompts, 3 resources for LLM agents
| CLI: 18 commands with formatted + JSON output for humans/CI
Expand All @@ -38,8 +44,9 @@ src/
graph/index.ts <- graphology graph + circular dep detection
analyzer/index.ts <- All metric computation
core/index.ts <- Shared result computation (MCP + CLI)
operations/index.ts <- Analysis operation descriptors + typed input schemas
mcp/index.ts <- 17 MCP tools for LLM integration
mcp/hints.ts <- Next-step hints for MCP tool responses
mcp/hints.ts <- Operation-keyed next-step hints for MCP tool responses
impact/index.ts <- Symbol-level impact analysis + rename planning
search/index.ts <- BM25 search engine
process/index.ts <- Entry point detection + call chain tracing
Expand All @@ -66,11 +73,15 @@ analyzeGraph(builtGraph, parsedFiles)
fileMetrics, moduleMetrics, forceAnalysis, stats,
groups, processes, clusters
}

runOperation(operation, codebaseGraph, input, context)
-> { ok: true, data } | { ok: false, error, data? }
```

## Key Design Decisions

- **graphology**: In-memory graph with O(1) neighbor lookup. PageRank and betweenness computed via graphology-metrics.
- **Operation registry foundation**: Analysis operations have typed descriptors in `src/operations/` with operation names, CLI command names, MCP tool names, input schemas, and discriminated run results. CLI/MCP handlers still call `src/core/` directly until the adapter migration lands.
- **Batch git churn**: Single `git log --all --name-only` call, parsed for all files. Avoids O(n) subprocess spawning.
- **Dead export detection**: Cross-references parsed exports against edge symbol lists. May miss `import *` or re-exports.
- **Graceful degradation**: Non-git dirs get churn=0, no-test codebases get coverage=false. Never crashes.
Expand Down
17 changes: 12 additions & 5 deletions roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,15 +260,22 @@ Package public entrypoints already have coverage. Only fix remaining framework/c

Collapse CLI + MCP operation duplication into one descriptor registry before adding more analyzers.

**To do:**
**Foundation slice:**

- Add `Operation<TInput, TResult>` descriptors per analysis operation.
- Use one input schema per operation for CLI coercion and MCP schemas.
- Return discriminated result/error unions instead of process exits in shared code.
- Add typed operation names, CLI command names, MCP tool names, and input schemas.
- Add `runOperation(...)` discriminated result/error wrapper for descriptor-level tests.
- Type MCP next-step hint keys against the operation-name union.
- Add CH-P1-01 coverage for registry -> CLI JSON -> MCP JSON overview parity.

**Remaining:**

- Reuse registry schemas in CLI coercion and MCP tool registration.
- Move shared CLI/MCP failures over descriptor-level validation errors.
- Use one graph-load pipeline with progress callbacks.
- Type hint keys against the operation-name union.
- Move text/SARIF/markdown formatting over result objects into formatters.
- Target tests at `op.run(graph, input)` instead of duplicating CLI and MCP behavior tests for every operation.
- Expand CH-P1-01 from overview to every operation.
- Add CH-P1-02 coverage for success, invalid input, parse failure, and cache reuse through registry adapters.

### Type/Shape Layer

Expand Down
10 changes: 5 additions & 5 deletions src/impact/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import type { CodebaseGraph, CallConfidence } from "../types/index.js";

interface AffectedSymbol {
export interface AffectedSymbol {
file: string;
symbol: string;
confidence: CallConfidence;
}

interface ImpactLevel {
export interface ImpactLevel {
depth: number;
risk: "WILL BREAK" | "LIKELY" | "MAY NEED TESTING";
affected: AffectedSymbol[];
}

interface ImpactResult {
export interface ImpactResult {
symbol: string;
levels: ImpactLevel[];
totalAffected: number;
notFound?: boolean;
}

interface RenameReference {
export interface RenameReference {
file: string;
symbol: string;
confidence: CallConfidence;
}

interface RenameResult {
export interface RenameResult {
dryRun: boolean;
oldName: string;
newName: string;
Expand Down
41 changes: 24 additions & 17 deletions src/mcp/hints.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,53 @@
const TOOL_HINTS: Record<string, string[]> = {
codebase_overview: [
import { getOperationByMcpTool, type OperationName } from "../operations/index.js";

const OPERATION_HINTS: Record<OperationName, string[]> = {
overview: [
"Use file_context to drill into a specific file",
"Use find_hotspots with metric='coupling' to find tightly coupled files",
"Use get_module_structure to see cross-module dependencies",
"Use analyze_forces to check module cohesion and tension",
],
file_context: [
fileContext: [
"Use get_dependents to see blast radius if this file changes",
"Use symbol_context to inspect a specific function or class",
"Use find_dead_exports to check for unused exports in this file's module",
"Use analyze_forces to check if this file is under tension",
],
get_dependents: [
dependents: [
"Use file_context on high-impact dependents to understand coupling",
"Use find_hotspots with metric='blast_radius' for system-wide view",
"Use get_module_structure to see if dependencies cross module boundaries",
],
find_hotspots: [
hotspots: [
"Use file_context on top hotspots to understand why they score high",
"Use analyze_forces to find structural issues behind hotspots",
"Use get_dependents on hotspot files to assess change risk",
],
get_module_structure: [
moduleStructure: [
"Use analyze_forces to find junk-drawer modules with low cohesion",
"Use find_hotspots with metric='escape_velocity' to find extractable modules",
"Use file_context on cross-module boundary files",
],
analyze_forces: [
forces: [
"Use file_context on tension files to understand what pulls them",
"Use get_module_structure on junk-drawer modules to plan restructuring",
"Use find_dead_exports on low-cohesion modules to find cleanup opportunities",
],
find_dead_exports: [
deadExports: [
"Use file_context on files with dead exports to check if they're truly unused",
"Use codebase_overview to see overall API surface reduction opportunity",
],
find_opportunities: [
opportunities: [
"Use file_context on the top opportunity target before editing",
"Use get_dependents for stabilize-hotspot or add-tests opportunities",
"Use analyze_forces for extract-seam, move-file, and split-module opportunities",
],
get_groups: [
groups: [
"Use get_module_structure for detailed per-module breakdown",
"Use find_hotspots with metric='coupling' to find cross-group coupling",
"Use analyze_forces to check group-level cohesion",
],
symbol_context: [
symbolContext: [
"Use file_context on the file containing this symbol for file-level view",
"Use get_dependents on the file to assess change blast radius",
"Use find_hotspots with metric='fan_in' to find other high-traffic symbols",
Expand All @@ -55,33 +57,38 @@ const TOOL_HINTS: Record<string, string[]> = {
"Use symbol_context on a matched symbol for callers/callees",
"Refine query: try camelCase names, class names, or module paths",
],
detect_changes: [
changes: [
"Use symbol_context on changed symbols to assess impact",
"Use get_dependents on affected files for full blast radius",
"Use file_context on changed files for detailed metrics",
],
impact_analysis: [
impact: [
"Use file_context on WILL BREAK files to understand coupling",
"Use rename_symbol to plan safe refactoring of impacted symbols",
"Use get_module_structure to check if impact crosses module boundaries",
],
rename_symbol: [
rename: [
"Use impact_analysis on the symbol first to understand full blast radius",
"Use file_context on referenced files to check for indirect usages",
"Use detect_changes after renaming to verify all references updated",
],
get_processes: [
processes: [
"Use symbol_context on an entry point symbol for detailed callers/callees",
"Use file_context on files in the process steps for metrics",
"Use get_module_structure to see how process crosses module boundaries",
],
get_clusters: [
clusters: [
"Use file_context on files within a cluster for detailed metrics",
"Use get_module_structure to compare clusters against directory structure",
"Use analyze_forces to check if cluster boundaries reveal tension",
],
};

export function getHintsForOperation(operationName: OperationName): string[] {
return OPERATION_HINTS[operationName];
}

export function getHints(toolName: string): string[] {
return TOOL_HINTS[toolName] ?? [];
const operation = getOperationByMcpTool(toolName);
return operation ? getHintsForOperation(operation.name) : [];
}
Loading
Loading