purecontext-mcp 1.1.8 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_INSTRUCTIONS.md +393 -0
- package/AGENT_INSTRUCTIONS_SHORT.md +53 -0
- package/AST-SEARCH.md +274 -0
- package/CHANGELOG.md +62 -0
- package/CODE-INTELLIGENCE.md +369 -0
- package/HEALTH-DASHBOARDS.md +241 -0
- package/README.md +7 -0
- package/REFACTORING-SAFELY.md +279 -0
- package/UNDERSTANDING-RELATIONSHIPS.md +240 -0
- package/USER-GUIDE.md +14 -0
- package/VISUALIZING-CODE.md +199 -0
- package/WORKFLOW-TECH-DEBT.md +286 -0
- package/dist/core/db/dep-store.d.ts +75 -0
- package/dist/core/db/dep-store.d.ts.map +1 -1
- package/dist/core/db/dep-store.js +277 -0
- package/dist/core/db/dep-store.js.map +1 -1
- package/dist/core/db/schema.d.ts.map +1 -1
- package/dist/core/db/schema.js +12 -0
- package/dist/core/db/schema.js.map +1 -1
- package/dist/core/index-manager.js +1 -1
- package/dist/core/index-manager.js.map +1 -1
- package/dist/core/types.d.ts +5 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/graph/diagram-renderer.d.ts +83 -0
- package/dist/graph/diagram-renderer.d.ts.map +1 -0
- package/dist/graph/diagram-renderer.js +294 -0
- package/dist/graph/diagram-renderer.js.map +1 -0
- package/dist/graph/graph-traversal.d.ts +92 -0
- package/dist/graph/graph-traversal.d.ts.map +1 -1
- package/dist/graph/graph-traversal.js +440 -2
- package/dist/graph/graph-traversal.js.map +1 -1
- package/dist/server/mcp-server.d.ts.map +1 -1
- package/dist/server/mcp-server.js +145 -0
- package/dist/server/mcp-server.js.map +1 -1
- package/dist/server/tools/check-delete-safe.d.ts +50 -0
- package/dist/server/tools/check-delete-safe.d.ts.map +1 -0
- package/dist/server/tools/check-delete-safe.js +308 -0
- package/dist/server/tools/check-delete-safe.js.map +1 -0
- package/dist/server/tools/check-move-safe.d.ts +44 -0
- package/dist/server/tools/check-move-safe.d.ts.map +1 -0
- package/dist/server/tools/check-move-safe.js +266 -0
- package/dist/server/tools/check-move-safe.js.map +1 -0
- package/dist/server/tools/check-rename-safe.d.ts +48 -0
- package/dist/server/tools/check-rename-safe.d.ts.map +1 -0
- package/dist/server/tools/check-rename-safe.js +218 -0
- package/dist/server/tools/check-rename-safe.js.map +1 -0
- package/dist/server/tools/diff-health-radar.d.ts +44 -0
- package/dist/server/tools/diff-health-radar.d.ts.map +1 -0
- package/dist/server/tools/diff-health-radar.js +192 -0
- package/dist/server/tools/diff-health-radar.js.map +1 -0
- package/dist/server/tools/find-cycles.d.ts +31 -0
- package/dist/server/tools/find-cycles.d.ts.map +1 -0
- package/dist/server/tools/find-cycles.js +85 -0
- package/dist/server/tools/find-cycles.js.map +1 -0
- package/dist/server/tools/find-implementations.d.ts +47 -0
- package/dist/server/tools/find-implementations.d.ts.map +1 -0
- package/dist/server/tools/find-implementations.js +167 -0
- package/dist/server/tools/find-implementations.js.map +1 -0
- package/dist/server/tools/find-untested-symbols.d.ts +52 -0
- package/dist/server/tools/find-untested-symbols.d.ts.map +1 -0
- package/dist/server/tools/find-untested-symbols.js +308 -0
- package/dist/server/tools/find-untested-symbols.js.map +1 -0
- package/dist/server/tools/get-architecture-snapshot.d.ts +43 -0
- package/dist/server/tools/get-architecture-snapshot.d.ts.map +1 -0
- package/dist/server/tools/get-architecture-snapshot.js +292 -0
- package/dist/server/tools/get-architecture-snapshot.js.map +1 -0
- package/dist/server/tools/get-call-hierarchy.d.ts +43 -0
- package/dist/server/tools/get-call-hierarchy.d.ts.map +1 -0
- package/dist/server/tools/get-call-hierarchy.js +119 -0
- package/dist/server/tools/get-call-hierarchy.js.map +1 -0
- package/dist/server/tools/get-class-hierarchy.d.ts +36 -0
- package/dist/server/tools/get-class-hierarchy.d.ts.map +1 -0
- package/dist/server/tools/get-class-hierarchy.js +125 -0
- package/dist/server/tools/get-class-hierarchy.js.map +1 -0
- package/dist/server/tools/get-complexity-hotspots.d.ts +50 -0
- package/dist/server/tools/get-complexity-hotspots.d.ts.map +1 -0
- package/dist/server/tools/get-complexity-hotspots.js +282 -0
- package/dist/server/tools/get-complexity-hotspots.js.map +1 -0
- package/dist/server/tools/get-coupling-map.d.ts +39 -0
- package/dist/server/tools/get-coupling-map.d.ts.map +1 -0
- package/dist/server/tools/get-coupling-map.js +107 -0
- package/dist/server/tools/get-coupling-map.js.map +1 -0
- package/dist/server/tools/get-debt-report.d.ts +44 -0
- package/dist/server/tools/get-debt-report.d.ts.map +1 -0
- package/dist/server/tools/get-debt-report.js +606 -0
- package/dist/server/tools/get-debt-report.js.map +1 -0
- package/dist/server/tools/get-entry-points.d.ts +79 -0
- package/dist/server/tools/get-entry-points.d.ts.map +1 -0
- package/dist/server/tools/get-entry-points.js +362 -0
- package/dist/server/tools/get-entry-points.js.map +1 -0
- package/dist/server/tools/get-public-api.d.ts +53 -0
- package/dist/server/tools/get-public-api.d.ts.map +1 -0
- package/dist/server/tools/get-public-api.js +218 -0
- package/dist/server/tools/get-public-api.js.map +1 -0
- package/dist/server/tools/get-test-coverage-map.d.ts +66 -0
- package/dist/server/tools/get-test-coverage-map.d.ts.map +1 -0
- package/dist/server/tools/get-test-coverage-map.js +588 -0
- package/dist/server/tools/get-test-coverage-map.js.map +1 -0
- package/dist/server/tools/get-todos.d.ts +51 -0
- package/dist/server/tools/get-todos.d.ts.map +1 -0
- package/dist/server/tools/get-todos.js +180 -0
- package/dist/server/tools/get-todos.js.map +1 -0
- package/dist/server/tools/get-type-graph.d.ts +73 -0
- package/dist/server/tools/get-type-graph.d.ts.map +1 -0
- package/dist/server/tools/get-type-graph.js +437 -0
- package/dist/server/tools/get-type-graph.js.map +1 -0
- package/dist/server/tools/health-radar.d.ts +50 -0
- package/dist/server/tools/health-radar.d.ts.map +1 -0
- package/dist/server/tools/health-radar.js +426 -0
- package/dist/server/tools/health-radar.js.map +1 -0
- package/dist/server/tools/plan-refactoring.d.ts +74 -0
- package/dist/server/tools/plan-refactoring.d.ts.map +1 -0
- package/dist/server/tools/plan-refactoring.js +644 -0
- package/dist/server/tools/plan-refactoring.js.map +1 -0
- package/dist/server/tools/render-call-graph.d.ts +40 -0
- package/dist/server/tools/render-call-graph.d.ts.map +1 -0
- package/dist/server/tools/render-call-graph.js +215 -0
- package/dist/server/tools/render-call-graph.js.map +1 -0
- package/dist/server/tools/render-class-hierarchy.d.ts +42 -0
- package/dist/server/tools/render-class-hierarchy.d.ts.map +1 -0
- package/dist/server/tools/render-class-hierarchy.js +265 -0
- package/dist/server/tools/render-class-hierarchy.js.map +1 -0
- package/dist/server/tools/render-dep-matrix.d.ts +38 -0
- package/dist/server/tools/render-dep-matrix.d.ts.map +1 -0
- package/dist/server/tools/render-dep-matrix.js +186 -0
- package/dist/server/tools/render-dep-matrix.js.map +1 -0
- package/dist/server/tools/render-diagram.d.ts +47 -0
- package/dist/server/tools/render-diagram.d.ts.map +1 -0
- package/dist/server/tools/render-diagram.js +266 -0
- package/dist/server/tools/render-diagram.js.map +1 -0
- package/dist/server/tools/render-import-graph.d.ts +41 -0
- package/dist/server/tools/render-import-graph.d.ts.map +1 -0
- package/dist/server/tools/render-import-graph.js +158 -0
- package/dist/server/tools/render-import-graph.js.map +1 -0
- package/dist/server/tools/search-ast.d.ts +55 -0
- package/dist/server/tools/search-ast.d.ts.map +1 -0
- package/dist/server/tools/search-ast.js +279 -0
- package/dist/server/tools/search-ast.js.map +1 -0
- package/dist/server/tools/search-by-complexity.d.ts +92 -0
- package/dist/server/tools/search-by-complexity.d.ts.map +1 -0
- package/dist/server/tools/search-by-complexity.js +268 -0
- package/dist/server/tools/search-by-complexity.js.map +1 -0
- package/dist/server/tools/search-by-decorator.d.ts +48 -0
- package/dist/server/tools/search-by-decorator.d.ts.map +1 -0
- package/dist/server/tools/search-by-decorator.js +518 -0
- package/dist/server/tools/search-by-decorator.js.map +1 -0
- package/dist/server/tools/search-by-signature.d.ts +56 -0
- package/dist/server/tools/search-by-signature.d.ts.map +1 -0
- package/dist/server/tools/search-by-signature.js +200 -0
- package/dist/server/tools/search-by-signature.js.map +1 -0
- package/dist/ui/assets/BlastRadius-QdgESOL8.js +1 -0
- package/dist/ui/assets/{DependencyGraph-B60SMPbX.js → DependencyGraph-BSMhzwWV.js} +1 -1
- package/dist/ui/assets/{NotFound-Bsg8Wppk.js → NotFound-CipFP_s1.js} +1 -1
- package/dist/ui/assets/{RepoDetail-Cie0D4_s.js → RepoDetail-Dfp5z5Kq.js} +1 -1
- package/dist/ui/assets/{RepoList-CldNt89M.js → RepoList-BKtST3hB.js} +1 -1
- package/dist/ui/assets/{Search-CgvpHMOi.js → Search-DzhGDViy.js} +1 -1
- package/dist/ui/assets/{SymbolView-B9h0LZTz.js → SymbolView-ryVEwAHG.js} +1 -1
- package/dist/ui/assets/{index-DADf5y_L.css → index-Ny8gn9F0.css} +1 -1
- package/dist/ui/assets/{index-eK0wMkUR.js → index-nX2chMqi.js} +2 -2
- package/dist/ui/assets/{useSearch-KAl3Qyi2.js → useSearch-BnBCRKui.js} +1 -1
- package/dist/ui/index.html +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/dev/jcodemunch-gap-analysis.md +198 -0
- package/package.json +9 -1
- package/user-manual.md +2466 -0
- package/dist/ui/assets/BlastRadius-RP7vJEyQ.js +0 -1
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plan-refactoring.ts
|
|
3
|
+
*
|
|
4
|
+
* MCP tool: plan_refactoring
|
|
5
|
+
*
|
|
6
|
+
* Synthesize the outputs of check_rename_safe, check_delete_safe,
|
|
7
|
+
* check_move_safe, find_cycles, get_coupling_map, detect_antipatterns, and
|
|
8
|
+
* get_quality_metrics into a sequenced, risk-annotated refactoring plan.
|
|
9
|
+
*
|
|
10
|
+
* This tool is read-only — it produces a plan but writes nothing.
|
|
11
|
+
* Execution of the plan is left to the calling agent.
|
|
12
|
+
*
|
|
13
|
+
* Supported goals:
|
|
14
|
+
* 'rename-symbol' — rename a symbol everywhere (requires symbolId + newName)
|
|
15
|
+
* 'delete-symbol' — safely remove a symbol and its references (requires symbolId)
|
|
16
|
+
* 'break-cycle' — resolve a circular import dependency (requires filePath or symbolId)
|
|
17
|
+
* 'extract-module' — move a file to a new location (requires filePath + newFilePath)
|
|
18
|
+
* 'reduce-coupling' — split a highly-coupled file (requires filePath or symbolId)
|
|
19
|
+
* 'general' — open-ended analysis, surfaces top findings (requires filePath or symbolId)
|
|
20
|
+
*
|
|
21
|
+
* Step ordering:
|
|
22
|
+
* Always edit leaf files before hub files (bottom-up). For rename: update all call/import
|
|
23
|
+
* sites first, then rename the declaration last. For delete: remove all references first,
|
|
24
|
+
* then delete the declaration last.
|
|
25
|
+
*/
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
import { openDatabase, getRepo } from '../../core/db/schema.js';
|
|
28
|
+
import { getSymbolById } from '../../core/db/symbol-store.js';
|
|
29
|
+
import { buildMeta } from './_meta.js';
|
|
30
|
+
// Sub-tool handlers — each opens/closes its own DB connection
|
|
31
|
+
import { handler as checkRenameSafeHandler } from './check-rename-safe.js';
|
|
32
|
+
import { handler as checkDeleteSafeHandler } from './check-delete-safe.js';
|
|
33
|
+
import { handler as checkMoveSafeHandler } from './check-move-safe.js';
|
|
34
|
+
import { handler as findCyclesHandler } from './find-cycles.js';
|
|
35
|
+
import { handler as getCouplingMapHandler } from './get-coupling-map.js';
|
|
36
|
+
import { handler as detectAntipatternsHandler } from './detect-antipatterns.js';
|
|
37
|
+
import { handler as getQualityMetricsHandler } from './get-quality-metrics.js';
|
|
38
|
+
// ─── Goal type ────────────────────────────────────────────────────────────────
|
|
39
|
+
const refactoringGoalEnum = z.enum([
|
|
40
|
+
'extract-module',
|
|
41
|
+
'rename-symbol',
|
|
42
|
+
'delete-symbol',
|
|
43
|
+
'break-cycle',
|
|
44
|
+
'reduce-coupling',
|
|
45
|
+
'general',
|
|
46
|
+
]);
|
|
47
|
+
export const name = 'plan_refactoring';
|
|
48
|
+
export const description = 'Generate a sequenced, risk-annotated refactoring plan for a symbol or file. ' +
|
|
49
|
+
'Synthesizes check_rename_safe, check_delete_safe, check_move_safe, find_cycles, ' +
|
|
50
|
+
'get_coupling_map, detect_antipatterns, and get_quality_metrics into a prioritised ' +
|
|
51
|
+
'step list. Read-only — produces a plan but writes nothing. ' +
|
|
52
|
+
'Goals: rename-symbol (symbolId + newName), delete-symbol (symbolId), ' +
|
|
53
|
+
'break-cycle (filePath or symbolId), extract-module (filePath + newFilePath), ' +
|
|
54
|
+
'reduce-coupling (filePath or symbolId), general (filePath or symbolId).';
|
|
55
|
+
export const inputSchema = {
|
|
56
|
+
repoId: z.string().describe('Repo ID from index_folder or resolve_repo'),
|
|
57
|
+
symbolId: z
|
|
58
|
+
.string()
|
|
59
|
+
.optional()
|
|
60
|
+
.describe('Symbol ID of the target symbol. Required for rename-symbol and delete-symbol. ' +
|
|
61
|
+
'Used to derive filePath for break-cycle, reduce-coupling, and general when filePath is omitted.'),
|
|
62
|
+
filePath: z
|
|
63
|
+
.string()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe('File path relative to the repo root. Required for extract-module and reduce-coupling ' +
|
|
66
|
+
'when symbolId is not provided. Used as scope for break-cycle and general.'),
|
|
67
|
+
goal: refactoringGoalEnum.describe('Refactoring goal: rename-symbol, delete-symbol, break-cycle, extract-module, ' +
|
|
68
|
+
'reduce-coupling, or general.'),
|
|
69
|
+
newName: z
|
|
70
|
+
.string()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe('Proposed new name. Required for rename-symbol.'),
|
|
73
|
+
newFilePath: z
|
|
74
|
+
.string()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe('Proposed new file location (relative to repo root). Required for extract-module.'),
|
|
77
|
+
contextHint: z
|
|
78
|
+
.string()
|
|
79
|
+
.optional()
|
|
80
|
+
.describe('Free-text description of what you are trying to achieve. ' +
|
|
81
|
+
'Included verbatim in the plan summary.'),
|
|
82
|
+
};
|
|
83
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
84
|
+
/**
|
|
85
|
+
* Parse a CallToolResult, returning null if isError is true.
|
|
86
|
+
* Throws on JSON parse failure.
|
|
87
|
+
*/
|
|
88
|
+
function parseResult(result) {
|
|
89
|
+
if (result.isError)
|
|
90
|
+
return null;
|
|
91
|
+
const text = result.content[0].text;
|
|
92
|
+
return JSON.parse(text);
|
|
93
|
+
}
|
|
94
|
+
/** Aggregate risk: return the worst of all step risks. */
|
|
95
|
+
function aggregateRisk(steps) {
|
|
96
|
+
if (steps.some((s) => s.risk === 'high'))
|
|
97
|
+
return 'high';
|
|
98
|
+
if (steps.some((s) => s.risk === 'medium'))
|
|
99
|
+
return 'medium';
|
|
100
|
+
return 'low';
|
|
101
|
+
}
|
|
102
|
+
/** Number steps starting from 1. */
|
|
103
|
+
function numberSteps(steps) {
|
|
104
|
+
return steps.map((s, i) => ({ ...s, order: i + 1 }));
|
|
105
|
+
}
|
|
106
|
+
// ─── Goal implementations ─────────────────────────────────────────────────────
|
|
107
|
+
async function planRenameSymbol(repoId, symbolId, symbolName, symbolFilePath, newName) {
|
|
108
|
+
const warnings = [];
|
|
109
|
+
const renameResult = await checkRenameSafeHandler({ repoId, symbolId, newName });
|
|
110
|
+
const renameData = parseResult(renameResult);
|
|
111
|
+
if (!renameData) {
|
|
112
|
+
return {
|
|
113
|
+
steps: [],
|
|
114
|
+
warnings: ['check_rename_safe returned an error — cannot generate rename plan.'],
|
|
115
|
+
summary: `Cannot plan rename of "${symbolName}" → "${newName}": safety check failed.`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (renameData.conflicts.length > 0) {
|
|
119
|
+
warnings.push(`"${newName}" conflicts with an existing symbol in ${symbolFilePath}. ` +
|
|
120
|
+
'Resolve the conflict before proceeding.');
|
|
121
|
+
}
|
|
122
|
+
const steps = [];
|
|
123
|
+
// Separate sites by type for ordering:
|
|
124
|
+
// imports → call sites → type-refs → comments (all before declaration)
|
|
125
|
+
const importSites = renameData.affectedSites.filter((s) => s.changeType === 'import');
|
|
126
|
+
const callSites = renameData.affectedSites.filter((s) => s.changeType === 'call');
|
|
127
|
+
const typeRefSites = renameData.affectedSites.filter((s) => s.changeType === 'type-reference');
|
|
128
|
+
const commentSites = renameData.affectedSites.filter((s) => s.changeType === 'comment');
|
|
129
|
+
const stringLitSites = renameData.affectedSites.filter((s) => s.changeType === 'string-literal');
|
|
130
|
+
if (stringLitSites.length > 0) {
|
|
131
|
+
warnings.push(`${stringLitSites.length} string-literal reference(s) to "${symbolName}" require manual update ` +
|
|
132
|
+
'— a text rename will not fix dynamic string usage.');
|
|
133
|
+
}
|
|
134
|
+
// Add call/import/type-ref steps (bottom-up: leaf callers first via file sort)
|
|
135
|
+
// Sort by filePath — a reasonable proxy for bottom-up when no topology is available
|
|
136
|
+
const referenceOrdered = [...importSites, ...callSites, ...typeRefSites]
|
|
137
|
+
.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
138
|
+
for (const site of referenceOrdered) {
|
|
139
|
+
steps.push({
|
|
140
|
+
order: 0,
|
|
141
|
+
action: `Update ${site.changeType} in ${site.filePath} line ${site.line}`,
|
|
142
|
+
filePath: site.filePath,
|
|
143
|
+
line: site.line,
|
|
144
|
+
detail: `Rename "${symbolName}" to "${newName}". Context: ${site.context.trim()}`,
|
|
145
|
+
automated: true,
|
|
146
|
+
risk: 'low',
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
// Comment sites — informational, not blocking
|
|
150
|
+
for (const site of commentSites) {
|
|
151
|
+
steps.push({
|
|
152
|
+
order: 0,
|
|
153
|
+
action: `Update comment in ${site.filePath} line ${site.line}`,
|
|
154
|
+
filePath: site.filePath,
|
|
155
|
+
line: site.line,
|
|
156
|
+
detail: `Update comment reference to "${symbolName}". Context: ${site.context.trim()}`,
|
|
157
|
+
automated: true,
|
|
158
|
+
risk: 'low',
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
// String-literal sites — manual
|
|
162
|
+
for (const site of stringLitSites) {
|
|
163
|
+
steps.push({
|
|
164
|
+
order: 0,
|
|
165
|
+
action: `Manually update string literal in ${site.filePath} line ${site.line}`,
|
|
166
|
+
filePath: site.filePath,
|
|
167
|
+
line: site.line,
|
|
168
|
+
detail: `String literal "${symbolName}" requires manual judgment — ` +
|
|
169
|
+
`a text rename will not fix this. Context: ${site.context.trim()}`,
|
|
170
|
+
automated: false,
|
|
171
|
+
risk: 'medium',
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
// Declaration rename — always last
|
|
175
|
+
steps.push({
|
|
176
|
+
order: 0,
|
|
177
|
+
action: `Rename declaration in ${symbolFilePath}`,
|
|
178
|
+
filePath: symbolFilePath,
|
|
179
|
+
detail: `Rename the "${symbolName}" declaration to "${newName}" in its definition file.`,
|
|
180
|
+
automated: true,
|
|
181
|
+
risk: 'low',
|
|
182
|
+
});
|
|
183
|
+
const totalSites = renameData.affectedSites.length;
|
|
184
|
+
const callCount = callSites.length;
|
|
185
|
+
const importCount = importSites.length;
|
|
186
|
+
const breakdown = [];
|
|
187
|
+
if (callCount > 0)
|
|
188
|
+
breakdown.push(`${callCount} call site${callCount !== 1 ? 's' : ''}`);
|
|
189
|
+
if (importCount > 0)
|
|
190
|
+
breakdown.push(`${importCount} import${importCount !== 1 ? 's' : ''}`);
|
|
191
|
+
const summary = `Rename "${symbolName}" → "${newName}" across ${renameData.affectedFileCount} file${renameData.affectedFileCount !== 1 ? 's' : ''}. ` +
|
|
192
|
+
`Update ${totalSites} reference${totalSites !== 1 ? 's' : ''}` +
|
|
193
|
+
(breakdown.length > 0 ? ` (${breakdown.join(', ')})` : '') +
|
|
194
|
+
'. Rename the declaration last.';
|
|
195
|
+
return { steps, warnings, summary };
|
|
196
|
+
}
|
|
197
|
+
async function planDeleteSymbol(repoId, symbolId, symbolName, symbolFilePath) {
|
|
198
|
+
const warnings = [];
|
|
199
|
+
const deleteResult = await checkDeleteSafeHandler({ repoId, symbolId });
|
|
200
|
+
const deleteData = parseResult(deleteResult);
|
|
201
|
+
if (!deleteData) {
|
|
202
|
+
return {
|
|
203
|
+
steps: [],
|
|
204
|
+
warnings: ['check_delete_safe returned an error — cannot generate delete plan.'],
|
|
205
|
+
summary: `Cannot plan deletion of "${symbolName}": safety check failed.`,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const steps = [];
|
|
209
|
+
if (deleteData.isExported) {
|
|
210
|
+
warnings.push(`"${symbolName}" is exported and may be consumed by external npm packages. ` +
|
|
211
|
+
'Verify there are no external consumers before deleting.');
|
|
212
|
+
}
|
|
213
|
+
if (deleteData.isEntryPoint) {
|
|
214
|
+
warnings.push(`"${symbolName}" appears to be an entry point (route handler, main export, or framework default). ` +
|
|
215
|
+
'Deleting it may break application startup or routing.');
|
|
216
|
+
}
|
|
217
|
+
// Sort live-reference risks by file path for deterministic ordering
|
|
218
|
+
const liveRefs = deleteData.risks
|
|
219
|
+
.filter((r) => r.kind === 'live-reference')
|
|
220
|
+
.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
221
|
+
const testSubjectRisks = deleteData.risks.filter((r) => r.kind === 'test-subject');
|
|
222
|
+
// Steps to remove each live reference first
|
|
223
|
+
for (const ref of liveRefs) {
|
|
224
|
+
steps.push({
|
|
225
|
+
order: 0,
|
|
226
|
+
action: `Remove reference to "${symbolName}" in ${ref.filePath}${ref.line != null ? ` line ${ref.line}` : ''}`,
|
|
227
|
+
filePath: ref.filePath,
|
|
228
|
+
line: ref.line,
|
|
229
|
+
detail: ref.detail,
|
|
230
|
+
automated: false,
|
|
231
|
+
risk: 'medium',
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
// Test references — inform but don't block
|
|
235
|
+
for (const testRef of testSubjectRisks) {
|
|
236
|
+
steps.push({
|
|
237
|
+
order: 0,
|
|
238
|
+
action: `Remove test reference to "${symbolName}" in ${testRef.filePath}${testRef.line != null ? ` line ${testRef.line}` : ''}`,
|
|
239
|
+
filePath: testRef.filePath,
|
|
240
|
+
line: testRef.line,
|
|
241
|
+
detail: testRef.detail,
|
|
242
|
+
automated: false,
|
|
243
|
+
risk: 'low',
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
// Deletion step — always last
|
|
247
|
+
steps.push({
|
|
248
|
+
order: 0,
|
|
249
|
+
action: `Delete "${symbolName}" declaration in ${symbolFilePath}`,
|
|
250
|
+
filePath: symbolFilePath,
|
|
251
|
+
detail: `Remove the "${symbolName}" symbol from ${symbolFilePath}. ` +
|
|
252
|
+
'Ensure all references above have been cleaned up first.',
|
|
253
|
+
automated: false,
|
|
254
|
+
risk: 'high',
|
|
255
|
+
});
|
|
256
|
+
const refCount = deleteData.liveReferenceCount;
|
|
257
|
+
const safeWord = deleteData.safe ? 'Safe' : 'Unsafe';
|
|
258
|
+
const summary = `${safeWord} to delete "${symbolName}". ` +
|
|
259
|
+
(refCount > 0
|
|
260
|
+
? `${refCount} live reference${refCount !== 1 ? 's' : ''} must be removed first. `
|
|
261
|
+
: 'No live references. ') +
|
|
262
|
+
(testSubjectRisks.length > 0
|
|
263
|
+
? `${testSubjectRisks.length} test reference${testSubjectRisks.length !== 1 ? 's' : ''} will also need updating. `
|
|
264
|
+
: '') +
|
|
265
|
+
'Delete declaration last.';
|
|
266
|
+
return { steps, warnings, summary };
|
|
267
|
+
}
|
|
268
|
+
async function planBreakCycle(repoId, filePath) {
|
|
269
|
+
const warnings = [];
|
|
270
|
+
const cyclesResult = await findCyclesHandler({ repoId, filePath, maxCycles: 5 });
|
|
271
|
+
const cyclesData = parseResult(cyclesResult);
|
|
272
|
+
if (!cyclesData || cyclesData.cycles.length === 0) {
|
|
273
|
+
return {
|
|
274
|
+
steps: [],
|
|
275
|
+
warnings: [`No import cycles found involving "${filePath}".`],
|
|
276
|
+
summary: `No import cycles detected for "${filePath}". No refactoring needed.`,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
if (cyclesData.totalFound > cyclesData.cycles.length) {
|
|
280
|
+
warnings.push(`${cyclesData.totalFound} cycles found; showing first ${cyclesData.cycles.length}. ` +
|
|
281
|
+
'Run find_cycles without filePath to see all.');
|
|
282
|
+
}
|
|
283
|
+
// Also get coupling data to identify weakest edge in each cycle
|
|
284
|
+
const couplingResult = await getCouplingMapHandler({ repoId });
|
|
285
|
+
const couplingData = parseResult(couplingResult);
|
|
286
|
+
const couplingMap = new Map();
|
|
287
|
+
if (couplingData) {
|
|
288
|
+
for (const fc of couplingData.files) {
|
|
289
|
+
couplingMap.set(fc.filePath, fc);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const steps = [];
|
|
293
|
+
for (const cycle of cyclesData.cycles) {
|
|
294
|
+
// Find the weakest edge: the file in the cycle with the fewest afferent
|
|
295
|
+
// (incoming) deps — it has the least impact if modified.
|
|
296
|
+
let weakestFile = cycle.files[0];
|
|
297
|
+
let lowestAfferent = Infinity;
|
|
298
|
+
for (const f of cycle.files) {
|
|
299
|
+
const fc = couplingMap.get(f);
|
|
300
|
+
const afferent = fc ? fc.afferentCoupling : 0;
|
|
301
|
+
if (afferent < lowestAfferent) {
|
|
302
|
+
lowestAfferent = afferent;
|
|
303
|
+
weakestFile = f;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const cycleStr = cycle.files.join(' → ');
|
|
307
|
+
steps.push({
|
|
308
|
+
order: 0,
|
|
309
|
+
action: `Create a shared module to break the cycle: ${cycleStr}`,
|
|
310
|
+
filePath: weakestFile,
|
|
311
|
+
detail: `The weakest edge in this cycle is on "${weakestFile}" ` +
|
|
312
|
+
`(${lowestAfferent} afferent coupling). ` +
|
|
313
|
+
`Extract the shared dependency from "${weakestFile}" into a new lower-level module ` +
|
|
314
|
+
'that both sides of the cycle can import without creating a cycle.',
|
|
315
|
+
automated: false,
|
|
316
|
+
risk: 'medium',
|
|
317
|
+
});
|
|
318
|
+
steps.push({
|
|
319
|
+
order: 0,
|
|
320
|
+
action: `Update imports in cycle members to use the new shared module`,
|
|
321
|
+
filePath: weakestFile,
|
|
322
|
+
detail: `After extracting to the shared module, update ${cycle.files.length} files ` +
|
|
323
|
+
`in the cycle (${cycleStr}) to import from the new location instead.`,
|
|
324
|
+
automated: true,
|
|
325
|
+
risk: 'low',
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
const cycleWord = cyclesData.cycles.length === 1 ? 'cycle' : 'cycles';
|
|
329
|
+
const summary = `Break ${cyclesData.cycles.length} import ${cycleWord} involving "${filePath}". ` +
|
|
330
|
+
'Extract shared types/interfaces into lower-level modules that all cycle members can import. ' +
|
|
331
|
+
'Steps require design judgment to decide which symbols to extract.';
|
|
332
|
+
return { steps, warnings, summary };
|
|
333
|
+
}
|
|
334
|
+
async function planExtractModule(repoId, filePath, newFilePath) {
|
|
335
|
+
const warnings = [];
|
|
336
|
+
const moveResult = await checkMoveSafeHandler({ repoId, filePath, newFilePath });
|
|
337
|
+
const moveData = parseResult(moveResult);
|
|
338
|
+
if (!moveData) {
|
|
339
|
+
return {
|
|
340
|
+
steps: [],
|
|
341
|
+
warnings: ['check_move_safe returned an error — cannot generate extract-module plan.'],
|
|
342
|
+
summary: `Cannot plan move of "${filePath}" to "${newFilePath}": safety check failed.`,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
if (moveData.wouldIntroduceCycles) {
|
|
346
|
+
warnings.push(`Moving "${filePath}" to "${newFilePath}" would introduce ${moveData.newCycles.length} ` +
|
|
347
|
+
'import cycle(s). Review the cycle details before proceeding.');
|
|
348
|
+
}
|
|
349
|
+
const steps = [];
|
|
350
|
+
// Import update steps first (show the full edit list before the physical move)
|
|
351
|
+
for (const update of moveData.importUpdates) {
|
|
352
|
+
steps.push({
|
|
353
|
+
order: 0,
|
|
354
|
+
action: `Update import in ${update.importerFilePath} line ${update.line}`,
|
|
355
|
+
filePath: update.importerFilePath,
|
|
356
|
+
line: update.line,
|
|
357
|
+
detail: `Change import path from "${update.currentImportPath}" to "${update.newImportPath}".`,
|
|
358
|
+
automated: true,
|
|
359
|
+
risk: 'low',
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
// Physical file move step — last
|
|
363
|
+
steps.push({
|
|
364
|
+
order: 0,
|
|
365
|
+
action: `Move file from "${filePath}" to "${newFilePath}"`,
|
|
366
|
+
filePath,
|
|
367
|
+
detail: `Physically move the file and update any build-tool references ` +
|
|
368
|
+
`(tsconfig paths, barrel files, package.json exports). ` +
|
|
369
|
+
`All ${moveData.importUpdates.length} import statement(s) above must be updated.`,
|
|
370
|
+
automated: true,
|
|
371
|
+
risk: moveData.wouldIntroduceCycles ? 'high' : 'medium',
|
|
372
|
+
});
|
|
373
|
+
const count = moveData.importUpdates.length;
|
|
374
|
+
const summary = `Move "${filePath}" to "${newFilePath}". ` +
|
|
375
|
+
`${count} import statement${count !== 1 ? 's' : ''} across ` +
|
|
376
|
+
`${new Set(moveData.importUpdates.map((u) => u.importerFilePath)).size} file(s) need updating.` +
|
|
377
|
+
(moveData.wouldIntroduceCycles
|
|
378
|
+
? ` WARNING: move would introduce ${moveData.newCycles.length} import cycle(s).`
|
|
379
|
+
: ' No cycles introduced.');
|
|
380
|
+
return { steps, warnings, summary };
|
|
381
|
+
}
|
|
382
|
+
async function planReduceCoupling(repoId, filePath) {
|
|
383
|
+
const warnings = [];
|
|
384
|
+
const couplingResult = await getCouplingMapHandler({ repoId, filePath });
|
|
385
|
+
const couplingData = parseResult(couplingResult);
|
|
386
|
+
if (!couplingData || couplingData.files.length === 0) {
|
|
387
|
+
return {
|
|
388
|
+
steps: [],
|
|
389
|
+
warnings: [`No coupling data found for "${filePath}".`],
|
|
390
|
+
summary: `No coupling data available for "${filePath}".`,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
const fc = couplingData.files[0];
|
|
394
|
+
const totalCoupling = fc.efferentCoupling + fc.afferentCoupling;
|
|
395
|
+
const steps = [];
|
|
396
|
+
if (totalCoupling < 3) {
|
|
397
|
+
warnings.push(`"${filePath}" has low total coupling (${totalCoupling}). ` +
|
|
398
|
+
'Splitting it may not yield significant benefits.');
|
|
399
|
+
}
|
|
400
|
+
// Suggest extracting the most-imported dependency groups
|
|
401
|
+
const topEfferentDeps = fc.efferentDeps.slice(0, 3);
|
|
402
|
+
if (topEfferentDeps.length > 0) {
|
|
403
|
+
for (const dep of topEfferentDeps) {
|
|
404
|
+
steps.push({
|
|
405
|
+
order: 0,
|
|
406
|
+
action: `Evaluate extracting functionality related to "${dep}" into a dedicated module`,
|
|
407
|
+
filePath,
|
|
408
|
+
detail: `"${filePath}" imports from "${dep}". If these imports represent a cohesive concern, ` +
|
|
409
|
+
'consider extracting related symbols into a separate module to reduce coupling. ' +
|
|
410
|
+
`Current efferent coupling: ${fc.efferentCoupling}.`,
|
|
411
|
+
automated: false,
|
|
412
|
+
risk: 'medium',
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// Suggest addressing high afferent coupling (many dependents)
|
|
417
|
+
if (fc.afferentCoupling >= 3) {
|
|
418
|
+
steps.push({
|
|
419
|
+
order: 0,
|
|
420
|
+
action: `Split "${filePath}" to reduce its ${fc.afferentCoupling} afferent dependencies`,
|
|
421
|
+
filePath,
|
|
422
|
+
detail: `${fc.afferentCoupling} files depend on "${filePath}". Split it into focused modules ` +
|
|
423
|
+
'(e.g., separate public API from implementation details) so consumers only import what they need. ' +
|
|
424
|
+
`Instability score: ${fc.instability.toFixed(2)} (0=stable hub, 1=leaf).`,
|
|
425
|
+
automated: false,
|
|
426
|
+
risk: 'medium',
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
if (steps.length === 0) {
|
|
430
|
+
steps.push({
|
|
431
|
+
order: 0,
|
|
432
|
+
action: `Review "${filePath}" for extraction opportunities`,
|
|
433
|
+
filePath,
|
|
434
|
+
detail: `Total coupling: ${totalCoupling} (efferent: ${fc.efferentCoupling}, afferent: ${fc.afferentCoupling}). ` +
|
|
435
|
+
'No strong coupling signals detected. Review manually for logical groupings.',
|
|
436
|
+
automated: false,
|
|
437
|
+
risk: 'low',
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
const summary = `Reduce coupling in "${filePath}" (efferent: ${fc.efferentCoupling}, afferent: ${fc.afferentCoupling}, ` +
|
|
441
|
+
`instability: ${fc.instability.toFixed(2)}). ` +
|
|
442
|
+
'Extract cohesive groups of functionality into focused modules.';
|
|
443
|
+
return { steps, warnings, summary };
|
|
444
|
+
}
|
|
445
|
+
async function planGeneral(repoId, filePath, symbolId, contextHint) {
|
|
446
|
+
const warnings = [];
|
|
447
|
+
const steps = [];
|
|
448
|
+
// Run antipattern detection
|
|
449
|
+
const antipatternsResult = await detectAntipatternsHandler({ repoId });
|
|
450
|
+
const antipatternsData = parseResult(antipatternsResult);
|
|
451
|
+
// Run quality metrics (scoped to filePath directory if provided)
|
|
452
|
+
const qualityResult = await getQualityMetricsHandler({
|
|
453
|
+
repoId,
|
|
454
|
+
scope: filePath ?? undefined,
|
|
455
|
+
limit: 5,
|
|
456
|
+
});
|
|
457
|
+
const qualityData = parseResult(qualityResult);
|
|
458
|
+
const targetFile = filePath ?? '';
|
|
459
|
+
// Filter antipattern findings to the target file (or all if no filePath)
|
|
460
|
+
const relevantFindings = antipatternsData
|
|
461
|
+
? antipatternsData.findings.filter((f) => !targetFile || f.filePath.includes(targetFile))
|
|
462
|
+
: [];
|
|
463
|
+
// Sort findings: errors before warnings, then alphabetically
|
|
464
|
+
relevantFindings.sort((a, b) => {
|
|
465
|
+
if (a.severity !== b.severity)
|
|
466
|
+
return a.severity === 'error' ? -1 : 1;
|
|
467
|
+
return a.filePath.localeCompare(b.filePath);
|
|
468
|
+
});
|
|
469
|
+
// Add steps for top antipatterns (up to 2)
|
|
470
|
+
for (const finding of relevantFindings.slice(0, 2)) {
|
|
471
|
+
steps.push({
|
|
472
|
+
order: 0,
|
|
473
|
+
action: `Fix "${finding.check}" in ${finding.filePath}`,
|
|
474
|
+
filePath: finding.filePath,
|
|
475
|
+
detail: `${finding.description} ${finding.suggestion}`,
|
|
476
|
+
automated: false,
|
|
477
|
+
risk: finding.severity === 'error' ? 'high' : 'medium',
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
// Add steps for worst quality offenders (up to 2, from the target file if scoped)
|
|
481
|
+
if (qualityData) {
|
|
482
|
+
const worstOffenders = targetFile
|
|
483
|
+
? qualityData.worstOffenders.filter((s) => s.filePath.includes(targetFile))
|
|
484
|
+
: qualityData.worstOffenders;
|
|
485
|
+
for (const sym of worstOffenders.slice(0, 2)) {
|
|
486
|
+
if (sym.riskLevel === 'good')
|
|
487
|
+
continue;
|
|
488
|
+
steps.push({
|
|
489
|
+
order: 0,
|
|
490
|
+
action: `Reduce complexity of "${sym.name}" in ${sym.filePath}`,
|
|
491
|
+
filePath: sym.filePath,
|
|
492
|
+
detail: `"${sym.name}" has cyclomatic complexity ${sym.cyclomaticComplexity}, ` +
|
|
493
|
+
`${sym.lineCount} lines, and quality score ${sym.qualityScore}/100 (${sym.riskLevel}). ` +
|
|
494
|
+
'Consider splitting into smaller focused functions.',
|
|
495
|
+
automated: false,
|
|
496
|
+
risk: sym.riskLevel === 'critical' ? 'high' : 'medium',
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (steps.length === 0) {
|
|
501
|
+
warnings.push('No quality issues or antipatterns found for the target. No refactoring needed.');
|
|
502
|
+
}
|
|
503
|
+
const scope = targetFile ? `"${targetFile}"` : 'the repo';
|
|
504
|
+
const hint = contextHint ? ` Context: ${contextHint}.` : '';
|
|
505
|
+
const summary = `General refactoring analysis for ${scope}. ` +
|
|
506
|
+
`${steps.length} improvement${steps.length !== 1 ? 's' : ''} identified.${hint}`;
|
|
507
|
+
return { steps, warnings, summary };
|
|
508
|
+
}
|
|
509
|
+
// ─── Handler ──────────────────────────────────────────────────────────────────
|
|
510
|
+
export async function handler(args) {
|
|
511
|
+
const t0 = Date.now();
|
|
512
|
+
const { repoId, symbolId, goal, newName, newFilePath, contextHint } = args;
|
|
513
|
+
let { filePath } = args;
|
|
514
|
+
// ── Validate repo ──────────────────────────────────────────────────────────
|
|
515
|
+
const db = openDatabase(repoId);
|
|
516
|
+
let symbolName = '';
|
|
517
|
+
let symbolFilePath = '';
|
|
518
|
+
try {
|
|
519
|
+
const repo = getRepo(db, repoId);
|
|
520
|
+
if (!repo) {
|
|
521
|
+
return {
|
|
522
|
+
content: [
|
|
523
|
+
{
|
|
524
|
+
type: 'text',
|
|
525
|
+
text: JSON.stringify({
|
|
526
|
+
error: `Repo "${repoId}" not found. Run index_folder first.`,
|
|
527
|
+
}),
|
|
528
|
+
},
|
|
529
|
+
],
|
|
530
|
+
isError: true,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
// ── Resolve symbol → filePath ────────────────────────────────────────────
|
|
534
|
+
if (symbolId) {
|
|
535
|
+
const symbol = getSymbolById(db, repoId, symbolId);
|
|
536
|
+
if (!symbol) {
|
|
537
|
+
return {
|
|
538
|
+
content: [
|
|
539
|
+
{
|
|
540
|
+
type: 'text',
|
|
541
|
+
text: JSON.stringify({
|
|
542
|
+
error: `Symbol "${symbolId}" not found in repo "${repoId}".`,
|
|
543
|
+
}),
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
isError: true,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
symbolName = symbol.name;
|
|
550
|
+
symbolFilePath = symbol.filePath;
|
|
551
|
+
// Derive filePath from symbol when not explicitly provided
|
|
552
|
+
if (!filePath)
|
|
553
|
+
filePath = symbol.filePath;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
finally {
|
|
557
|
+
db.close();
|
|
558
|
+
}
|
|
559
|
+
// ── Goal-specific validation ───────────────────────────────────────────────
|
|
560
|
+
if (goal === 'rename-symbol') {
|
|
561
|
+
if (!symbolId) {
|
|
562
|
+
return {
|
|
563
|
+
content: [{ type: 'text', text: JSON.stringify({ error: 'rename-symbol requires symbolId.' }) }],
|
|
564
|
+
isError: true,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
if (!newName) {
|
|
568
|
+
return {
|
|
569
|
+
content: [{ type: 'text', text: JSON.stringify({ error: 'rename-symbol requires newName.' }) }],
|
|
570
|
+
isError: true,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (goal === 'delete-symbol' && !symbolId) {
|
|
575
|
+
return {
|
|
576
|
+
content: [{ type: 'text', text: JSON.stringify({ error: 'delete-symbol requires symbolId.' }) }],
|
|
577
|
+
isError: true,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
if ((goal === 'break-cycle' || goal === 'reduce-coupling') && !filePath) {
|
|
581
|
+
return {
|
|
582
|
+
content: [
|
|
583
|
+
{
|
|
584
|
+
type: 'text',
|
|
585
|
+
text: JSON.stringify({
|
|
586
|
+
error: `${goal} requires filePath or a symbolId whose file will be used as scope.`,
|
|
587
|
+
}),
|
|
588
|
+
},
|
|
589
|
+
],
|
|
590
|
+
isError: true,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
if (goal === 'extract-module') {
|
|
594
|
+
if (!filePath) {
|
|
595
|
+
return {
|
|
596
|
+
content: [{ type: 'text', text: JSON.stringify({ error: 'extract-module requires filePath.' }) }],
|
|
597
|
+
isError: true,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
if (!newFilePath) {
|
|
601
|
+
return {
|
|
602
|
+
content: [{ type: 'text', text: JSON.stringify({ error: 'extract-module requires newFilePath.' }) }],
|
|
603
|
+
isError: true,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
// ── Dispatch to goal handler ───────────────────────────────────────────────
|
|
608
|
+
let goalResult;
|
|
609
|
+
switch (goal) {
|
|
610
|
+
case 'rename-symbol':
|
|
611
|
+
goalResult = await planRenameSymbol(repoId, symbolId, symbolName, symbolFilePath, newName);
|
|
612
|
+
break;
|
|
613
|
+
case 'delete-symbol':
|
|
614
|
+
goalResult = await planDeleteSymbol(repoId, symbolId, symbolName, symbolFilePath);
|
|
615
|
+
break;
|
|
616
|
+
case 'break-cycle':
|
|
617
|
+
goalResult = await planBreakCycle(repoId, filePath);
|
|
618
|
+
break;
|
|
619
|
+
case 'extract-module':
|
|
620
|
+
goalResult = await planExtractModule(repoId, filePath, newFilePath);
|
|
621
|
+
break;
|
|
622
|
+
case 'reduce-coupling':
|
|
623
|
+
goalResult = await planReduceCoupling(repoId, filePath);
|
|
624
|
+
break;
|
|
625
|
+
case 'general':
|
|
626
|
+
goalResult = await planGeneral(repoId, filePath, symbolId, contextHint);
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
const steps = numberSteps(goalResult.steps);
|
|
630
|
+
const estimatedRisk = aggregateRisk(steps);
|
|
631
|
+
const responseText = JSON.stringify({ steps, warnings: goalResult.warnings });
|
|
632
|
+
const output = {
|
|
633
|
+
goal,
|
|
634
|
+
summary: goalResult.summary,
|
|
635
|
+
steps,
|
|
636
|
+
totalSteps: steps.length,
|
|
637
|
+
estimatedRisk,
|
|
638
|
+
warnings: goalResult.warnings,
|
|
639
|
+
_tokenEstimate: Math.ceil(responseText.length / 4),
|
|
640
|
+
_meta: buildMeta({ timingMs: Date.now() - t0 }),
|
|
641
|
+
};
|
|
642
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
643
|
+
}
|
|
644
|
+
//# sourceMappingURL=plan-refactoring.js.map
|