monomind 1.17.0 → 1.17.1
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/.claude/agents/engineering/engineering-security-engineer.md +1 -1
- package/.claude/commands/mastermind/_repeat.md +4 -0
- package/.claude/commands/mastermind/master.md +52 -1
- package/.claude/scheduled_tasks.lock +1 -1
- package/.claude/skills/mastermind/_repeat.md +2 -0
- package/package.json +1 -1
- package/packages/@monomind/cli/.claude/agents/engineering/engineering-security-engineer.md +1 -1
- package/packages/@monomind/cli/.claude/commands/mastermind/_repeat.md +4 -0
- package/packages/@monomind/cli/.claude/commands/mastermind/master.md +52 -1
- package/packages/@monomind/cli/.claude/skills/mastermind/_repeat.md +2 -0
- package/packages/@monomind/cli/dist/src/__tests__/browse-analyzer.test.js +42 -59
- package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +18 -0
- package/packages/@monomind/cli/dist/src/browser/dashboard/ui.html +37 -125
- package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.d.ts +17 -0
- package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.js +320 -0
- package/packages/@monomind/cli/dist/src/commands/agent-ops.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/commands/agent-ops.js +329 -0
- package/packages/@monomind/cli/dist/src/commands/agent.js +5 -907
- package/packages/@monomind/cli/dist/src/commands/analyze-ast.d.ts +26 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-ast.js +284 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-boundaries.d.ts +14 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-boundaries.js +295 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-diff.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-diff.js +395 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-graph.d.ts +14 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-graph.js +304 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-imports.d.ts +11 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-imports.js +287 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-symbols.d.ts +14 -0
- package/packages/@monomind/cli/dist/src/commands/analyze-symbols.js +302 -0
- package/packages/@monomind/cli/dist/src/commands/analyze.d.ts +38 -0
- package/packages/@monomind/cli/dist/src/commands/analyze.js +12 -1827
- package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.d.ts +26 -0
- package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.js +189 -0
- package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.d.ts +19 -0
- package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +388 -0
- package/packages/@monomind/cli/dist/src/commands/doctor.js +51 -942
- package/packages/@monomind/cli/dist/src/commands/hive-mind-comms.d.ts +11 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind-comms.js +242 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind-helpers.d.ts +35 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind-helpers.js +203 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind-ops.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind-ops.js +233 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind-spawn.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind-spawn.js +274 -0
- package/packages/@monomind/cli/dist/src/commands/hive-mind.js +10 -1129
- package/packages/@monomind/cli/dist/src/commands/hooks-coverage-commands.d.ts +4 -4
- package/packages/@monomind/cli/dist/src/commands/hooks-coverage-commands.js +19 -819
- package/packages/@monomind/cli/dist/src/commands/hooks-coverage-gaps.d.ts +7 -0
- package/packages/@monomind/cli/dist/src/commands/hooks-coverage-gaps.js +334 -0
- package/packages/@monomind/cli/dist/src/commands/hooks-coverage-routing.d.ts +7 -0
- package/packages/@monomind/cli/dist/src/commands/hooks-coverage-routing.js +399 -0
- package/packages/@monomind/cli/dist/src/commands/init-subcommands.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/commands/init-subcommands.js +156 -0
- package/packages/@monomind/cli/dist/src/commands/init-upgrade.d.ts +6 -0
- package/packages/@monomind/cli/dist/src/commands/init-upgrade.js +203 -0
- package/packages/@monomind/cli/dist/src/commands/init-wizard.d.ts +6 -0
- package/packages/@monomind/cli/dist/src/commands/init-wizard.js +246 -0
- package/packages/@monomind/cli/dist/src/commands/init.js +6 -623
- package/packages/@monomind/cli/dist/src/commands/memory-admin.d.ts +10 -0
- package/packages/@monomind/cli/dist/src/commands/memory-admin.js +433 -0
- package/packages/@monomind/cli/dist/src/commands/memory-crud.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/commands/memory-crud.js +342 -0
- package/packages/@monomind/cli/dist/src/commands/memory-list.d.ts +10 -0
- package/packages/@monomind/cli/dist/src/commands/memory-list.js +321 -0
- package/packages/@monomind/cli/dist/src/commands/memory-transfer.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/commands/memory-transfer.js +372 -0
- package/packages/@monomind/cli/dist/src/commands/memory.d.ts +6 -0
- package/packages/@monomind/cli/dist/src/commands/memory.js +10 -1441
- package/packages/@monomind/cli/dist/src/commands/neural-core.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/commands/neural-core.js +274 -0
- package/packages/@monomind/cli/dist/src/commands/neural-optimize.d.ts +7 -0
- package/packages/@monomind/cli/dist/src/commands/neural-optimize.js +332 -0
- package/packages/@monomind/cli/dist/src/commands/neural-registry.d.ts +7 -0
- package/packages/@monomind/cli/dist/src/commands/neural-registry.js +290 -0
- package/packages/@monomind/cli/dist/src/commands/neural.js +3 -974
- package/packages/@monomind/cli/dist/src/commands/platforms.js +327 -7
- package/packages/@monomind/cli/dist/src/commands/security-cve.d.ts +6 -0
- package/packages/@monomind/cli/dist/src/commands/security-cve.js +310 -0
- package/packages/@monomind/cli/dist/src/commands/security-misc.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/commands/security-misc.js +293 -0
- package/packages/@monomind/cli/dist/src/commands/security-scan.d.ts +18 -0
- package/packages/@monomind/cli/dist/src/commands/security-scan.js +328 -0
- package/packages/@monomind/cli/dist/src/commands/security.js +3 -958
- package/packages/@monomind/cli/dist/src/commands/session.js +1 -1
- package/packages/@monomind/cli/dist/src/commands/swarm.js +23 -17
- package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +77 -0
- package/packages/@monomind/cli/dist/src/parser.js +11 -6
- package/packages/@monomind/cli/dist/src/routing/llm-caller.js +1 -2
- package/packages/@monomind/cli/package.json +2 -3
- package/packages/@monomind/cli/scripts/understand-analyze.mjs +1 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyze AST subcommand
|
|
3
|
+
* AST-based code analysis using monovector tree-sitter with regex fallback
|
|
4
|
+
*/
|
|
5
|
+
import type { Command } from '../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Helper: Truncate file path for display
|
|
8
|
+
*/
|
|
9
|
+
export declare function truncatePathAst(filePath: string, maxLen?: number): string;
|
|
10
|
+
/**
|
|
11
|
+
* Helper: Format complexity value with color coding
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatComplexityValueAst(value: number): string;
|
|
14
|
+
/**
|
|
15
|
+
* Helper: Get type marker for symbols
|
|
16
|
+
*/
|
|
17
|
+
export declare function getTypeMarkerAst(type: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Helper: Get complexity rating text
|
|
20
|
+
*/
|
|
21
|
+
export declare function getComplexityRatingAst(value: number): string;
|
|
22
|
+
/**
|
|
23
|
+
* AST analysis subcommand
|
|
24
|
+
*/
|
|
25
|
+
export declare const astCommand: Command;
|
|
26
|
+
//# sourceMappingURL=analyze-ast.d.ts.map
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyze AST subcommand
|
|
3
|
+
* AST-based code analysis using monovector tree-sitter with regex fallback
|
|
4
|
+
*/
|
|
5
|
+
import { output } from '../output.js';
|
|
6
|
+
import * as fs from 'fs/promises';
|
|
7
|
+
import { resolve } from 'path';
|
|
8
|
+
import { getASTAnalyzer, safeWriteOutputFile, scanSourceFiles, fallbackAnalyze } from './analyze.js';
|
|
9
|
+
/**
|
|
10
|
+
* Helper: Truncate file path for display
|
|
11
|
+
*/
|
|
12
|
+
export function truncatePathAst(filePath, maxLen = 45) {
|
|
13
|
+
if (filePath.length <= maxLen)
|
|
14
|
+
return filePath;
|
|
15
|
+
return '...' + filePath.slice(-(maxLen - 3));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Helper: Format complexity value with color coding
|
|
19
|
+
*/
|
|
20
|
+
export function formatComplexityValueAst(value) {
|
|
21
|
+
if (value <= 5)
|
|
22
|
+
return output.success(String(value));
|
|
23
|
+
if (value <= 10)
|
|
24
|
+
return output.warning(String(value));
|
|
25
|
+
return output.error(String(value));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Helper: Get type marker for symbols
|
|
29
|
+
*/
|
|
30
|
+
export function getTypeMarkerAst(type) {
|
|
31
|
+
switch (type) {
|
|
32
|
+
case 'function': return output.success('fn');
|
|
33
|
+
case 'class': return output.info('class');
|
|
34
|
+
case 'variable': return output.dim('var');
|
|
35
|
+
case 'type': return output.highlight('type');
|
|
36
|
+
case 'interface': return output.highlight('iface');
|
|
37
|
+
default: return output.dim(type.slice(0, 5));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Helper: Get complexity rating text
|
|
42
|
+
*/
|
|
43
|
+
export function getComplexityRatingAst(value) {
|
|
44
|
+
if (value <= 5)
|
|
45
|
+
return output.success('Simple');
|
|
46
|
+
if (value <= 10)
|
|
47
|
+
return output.warning('Moderate');
|
|
48
|
+
if (value <= 20)
|
|
49
|
+
return output.error('Complex');
|
|
50
|
+
return output.error(output.bold('Very Complex'));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* AST analysis subcommand
|
|
54
|
+
*/
|
|
55
|
+
export const astCommand = {
|
|
56
|
+
name: 'ast',
|
|
57
|
+
description: 'Analyze code using AST parsing (tree-sitter via monovector)',
|
|
58
|
+
options: [
|
|
59
|
+
{
|
|
60
|
+
name: 'complexity',
|
|
61
|
+
short: 'c',
|
|
62
|
+
description: 'Include complexity metrics',
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
default: false,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'symbols',
|
|
68
|
+
short: 's',
|
|
69
|
+
description: 'Include symbol extraction',
|
|
70
|
+
type: 'boolean',
|
|
71
|
+
default: false,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'format',
|
|
75
|
+
short: 'f',
|
|
76
|
+
description: 'Output format (text, json, table)',
|
|
77
|
+
type: 'string',
|
|
78
|
+
default: 'text',
|
|
79
|
+
choices: ['text', 'json', 'table'],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'output',
|
|
83
|
+
short: 'o',
|
|
84
|
+
description: 'Output file path',
|
|
85
|
+
type: 'string',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'verbose',
|
|
89
|
+
short: 'v',
|
|
90
|
+
description: 'Show detailed analysis',
|
|
91
|
+
type: 'boolean',
|
|
92
|
+
default: false,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
examples: [
|
|
96
|
+
{ command: 'monomind analyze ast src/', description: 'Analyze all files in src/' },
|
|
97
|
+
{ command: 'monomind analyze ast src/index.ts --complexity', description: 'Analyze with complexity' },
|
|
98
|
+
{ command: 'monomind analyze ast . --format json', description: 'JSON output' },
|
|
99
|
+
{ command: 'monomind analyze ast src/ --symbols', description: 'Extract symbols' },
|
|
100
|
+
],
|
|
101
|
+
action: async (ctx) => {
|
|
102
|
+
const targetPath = ctx.args[0] || ctx.cwd;
|
|
103
|
+
const showComplexity = ctx.flags.complexity;
|
|
104
|
+
const showSymbols = ctx.flags.symbols;
|
|
105
|
+
const formatType = ctx.flags.format || 'text';
|
|
106
|
+
const outputFile = ctx.flags.output;
|
|
107
|
+
const verbose = ctx.flags.verbose;
|
|
108
|
+
// If no specific flags, show summary
|
|
109
|
+
const showAll = !showComplexity && !showSymbols;
|
|
110
|
+
output.printInfo(`Analyzing: ${output.highlight(targetPath)}`);
|
|
111
|
+
output.writeln();
|
|
112
|
+
const spinner = output.createSpinner({ text: 'Parsing AST...', spinner: 'dots' });
|
|
113
|
+
spinner.start();
|
|
114
|
+
try {
|
|
115
|
+
const astModule = await getASTAnalyzer();
|
|
116
|
+
if (!astModule) {
|
|
117
|
+
spinner.stop();
|
|
118
|
+
output.printWarning('AST analyzer not available, using regex fallback');
|
|
119
|
+
}
|
|
120
|
+
// Resolve path and check if file or directory
|
|
121
|
+
const resolvedPath = resolve(targetPath);
|
|
122
|
+
const stat = await fs.stat(resolvedPath);
|
|
123
|
+
const isDirectory = stat.isDirectory();
|
|
124
|
+
let results = [];
|
|
125
|
+
if (isDirectory) {
|
|
126
|
+
// Scan directory for source files
|
|
127
|
+
const files = await scanSourceFiles(resolvedPath);
|
|
128
|
+
spinner.stop();
|
|
129
|
+
output.printInfo(`Found ${files.length} source files`);
|
|
130
|
+
spinner.start();
|
|
131
|
+
for (const file of files.slice(0, 100)) {
|
|
132
|
+
try {
|
|
133
|
+
const content = await fs.readFile(file, 'utf-8');
|
|
134
|
+
if (astModule) {
|
|
135
|
+
const analyzer = astModule.createASTAnalyzer();
|
|
136
|
+
const analysis = analyzer.analyze(content, file);
|
|
137
|
+
results.push(analysis);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
// Fallback analysis
|
|
141
|
+
results.push(fallbackAnalyze(content, file));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Skip files that can't be analyzed
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// Single file
|
|
151
|
+
const content = await fs.readFile(resolvedPath, 'utf-8');
|
|
152
|
+
if (astModule) {
|
|
153
|
+
const analyzer = astModule.createASTAnalyzer();
|
|
154
|
+
const analysis = analyzer.analyze(content, resolvedPath);
|
|
155
|
+
results.push(analysis);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
results.push(fallbackAnalyze(content, resolvedPath));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
spinner.stop();
|
|
162
|
+
if (results.length === 0) {
|
|
163
|
+
output.printWarning('No files analyzed');
|
|
164
|
+
return { success: true };
|
|
165
|
+
}
|
|
166
|
+
// Calculate totals
|
|
167
|
+
const totals = {
|
|
168
|
+
files: results.length,
|
|
169
|
+
functions: results.reduce((sum, r) => sum + r.functions.length, 0),
|
|
170
|
+
classes: results.reduce((sum, r) => sum + r.classes.length, 0),
|
|
171
|
+
imports: results.reduce((sum, r) => sum + r.imports.length, 0),
|
|
172
|
+
avgComplexity: results.reduce((sum, r) => sum + r.complexity.cyclomatic, 0) / results.length,
|
|
173
|
+
totalLoc: results.reduce((sum, r) => sum + r.complexity.loc, 0),
|
|
174
|
+
};
|
|
175
|
+
// JSON output
|
|
176
|
+
if (formatType === 'json') {
|
|
177
|
+
const jsonOutput = { files: results, totals };
|
|
178
|
+
if (outputFile) {
|
|
179
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(jsonOutput, null, 2));
|
|
180
|
+
output.printSuccess(`Results written to ${outputFile}`);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
output.printJson(jsonOutput);
|
|
184
|
+
}
|
|
185
|
+
return { success: true, data: jsonOutput };
|
|
186
|
+
}
|
|
187
|
+
// Summary box
|
|
188
|
+
output.printBox([
|
|
189
|
+
`Files analyzed: ${totals.files}`,
|
|
190
|
+
`Functions: ${totals.functions}`,
|
|
191
|
+
`Classes: ${totals.classes}`,
|
|
192
|
+
`Total LOC: ${totals.totalLoc}`,
|
|
193
|
+
`Avg Complexity: ${formatComplexityValueAst(Math.round(totals.avgComplexity))}`,
|
|
194
|
+
].join('\n'), 'AST Analysis Summary');
|
|
195
|
+
// Complexity view
|
|
196
|
+
if (showComplexity || showAll) {
|
|
197
|
+
output.writeln();
|
|
198
|
+
output.writeln(output.bold('Complexity by File'));
|
|
199
|
+
output.writeln(output.dim('-'.repeat(60)));
|
|
200
|
+
const complexityData = results
|
|
201
|
+
.map(r => ({
|
|
202
|
+
file: truncatePathAst(r.filePath),
|
|
203
|
+
cyclomatic: r.complexity.cyclomatic,
|
|
204
|
+
cognitive: r.complexity.cognitive,
|
|
205
|
+
loc: r.complexity.loc,
|
|
206
|
+
rating: getComplexityRatingAst(r.complexity.cyclomatic),
|
|
207
|
+
}))
|
|
208
|
+
.sort((a, b) => b.cyclomatic - a.cyclomatic)
|
|
209
|
+
.slice(0, 15);
|
|
210
|
+
output.printTable({
|
|
211
|
+
columns: [
|
|
212
|
+
{ key: 'file', header: 'File', width: 40 },
|
|
213
|
+
{ key: 'cyclomatic', header: 'Cyclo', width: 8, align: 'right', format: (v) => formatComplexityValueAst(v) },
|
|
214
|
+
{ key: 'cognitive', header: 'Cogni', width: 8, align: 'right' },
|
|
215
|
+
{ key: 'loc', header: 'LOC', width: 8, align: 'right' },
|
|
216
|
+
{ key: 'rating', header: 'Rating', width: 15 },
|
|
217
|
+
],
|
|
218
|
+
data: complexityData,
|
|
219
|
+
});
|
|
220
|
+
if (results.length > 15) {
|
|
221
|
+
output.writeln(output.dim(` ... and ${results.length - 15} more files`));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Symbols view
|
|
225
|
+
if (showSymbols || showAll) {
|
|
226
|
+
output.writeln();
|
|
227
|
+
output.writeln(output.bold('Extracted Symbols'));
|
|
228
|
+
output.writeln(output.dim('-'.repeat(60)));
|
|
229
|
+
const allSymbols = [];
|
|
230
|
+
for (const r of results) {
|
|
231
|
+
for (const fn of r.functions) {
|
|
232
|
+
allSymbols.push({ name: fn.name, type: 'function', file: truncatePathAst(r.filePath, 30), line: fn.startLine });
|
|
233
|
+
}
|
|
234
|
+
for (const cls of r.classes) {
|
|
235
|
+
allSymbols.push({ name: cls.name, type: 'class', file: truncatePathAst(r.filePath, 30), line: cls.startLine });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const displaySymbols = allSymbols.slice(0, 20);
|
|
239
|
+
output.printTable({
|
|
240
|
+
columns: [
|
|
241
|
+
{ key: 'type', header: 'Type', width: 8, format: (v) => getTypeMarkerAst(v) },
|
|
242
|
+
{ key: 'name', header: 'Symbol', width: 30 },
|
|
243
|
+
{ key: 'file', header: 'File', width: 35 },
|
|
244
|
+
{ key: 'line', header: 'Line', width: 8, align: 'right' },
|
|
245
|
+
],
|
|
246
|
+
data: displaySymbols,
|
|
247
|
+
});
|
|
248
|
+
if (allSymbols.length > 20) {
|
|
249
|
+
output.writeln(output.dim(` ... and ${allSymbols.length - 20} more symbols`));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Verbose output
|
|
253
|
+
if (verbose) {
|
|
254
|
+
output.writeln();
|
|
255
|
+
output.writeln(output.bold('Import Analysis'));
|
|
256
|
+
output.writeln(output.dim('-'.repeat(60)));
|
|
257
|
+
const importCounts = new Map();
|
|
258
|
+
for (const r of results) {
|
|
259
|
+
for (const imp of r.imports) {
|
|
260
|
+
importCounts.set(imp, (importCounts.get(imp) || 0) + 1);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const topImports = Array.from(importCounts.entries())
|
|
264
|
+
.sort((a, b) => b[1] - a[1])
|
|
265
|
+
.slice(0, 10);
|
|
266
|
+
for (const [imp, count] of topImports) {
|
|
267
|
+
output.writeln(` ${output.highlight(count.toString().padStart(3))} ${imp}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (outputFile) {
|
|
271
|
+
await safeWriteOutputFile(outputFile, JSON.stringify({ files: results, totals }, null, 2));
|
|
272
|
+
output.printSuccess(`Results written to ${outputFile}`);
|
|
273
|
+
}
|
|
274
|
+
return { success: true, data: { files: results, totals } };
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
spinner.stop();
|
|
278
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
279
|
+
output.printError(`AST analysis failed: ${message}`);
|
|
280
|
+
return { success: false, exitCode: 1 };
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
//# sourceMappingURL=analyze-ast.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyze boundaries and modules subcommands
|
|
3
|
+
* MinCut boundary detection and Louvain community detection
|
|
4
|
+
*/
|
|
5
|
+
import type { Command } from '../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Analyze code boundaries using MinCut algorithm
|
|
8
|
+
*/
|
|
9
|
+
export declare const boundariesCommand: Command;
|
|
10
|
+
/**
|
|
11
|
+
* Analyze modules/communities using Louvain algorithm
|
|
12
|
+
*/
|
|
13
|
+
export declare const modulesCommand: Command;
|
|
14
|
+
//# sourceMappingURL=analyze-boundaries.d.ts.map
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyze boundaries and modules subcommands
|
|
3
|
+
* MinCut boundary detection and Louvain community detection
|
|
4
|
+
*/
|
|
5
|
+
import { output } from '../output.js';
|
|
6
|
+
import { resolve } from 'path';
|
|
7
|
+
import { getGraphAnalyzer, safeWriteOutputFile } from './analyze.js';
|
|
8
|
+
/**
|
|
9
|
+
* Analyze code boundaries using MinCut algorithm
|
|
10
|
+
*/
|
|
11
|
+
export const boundariesCommand = {
|
|
12
|
+
name: 'boundaries',
|
|
13
|
+
aliases: ['boundary', 'mincut'],
|
|
14
|
+
description: 'Find natural code boundaries using MinCut algorithm',
|
|
15
|
+
options: [
|
|
16
|
+
{
|
|
17
|
+
name: 'partitions',
|
|
18
|
+
short: 'p',
|
|
19
|
+
description: 'Number of partitions to find',
|
|
20
|
+
type: 'number',
|
|
21
|
+
default: 2,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'output',
|
|
25
|
+
short: 'o',
|
|
26
|
+
description: 'Output file path',
|
|
27
|
+
type: 'string',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'format',
|
|
31
|
+
short: 'f',
|
|
32
|
+
description: 'Output format (text, json, dot)',
|
|
33
|
+
type: 'string',
|
|
34
|
+
default: 'text',
|
|
35
|
+
choices: ['text', 'json', 'dot'],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
examples: [
|
|
39
|
+
{ command: 'monomind analyze boundaries src/', description: 'Find code boundaries in src/' },
|
|
40
|
+
{ command: 'monomind analyze boundaries -p 3 src/', description: 'Find 3 partitions' },
|
|
41
|
+
{ command: 'monomind analyze boundaries -f dot -o graph.dot src/', description: 'Export to DOT format' },
|
|
42
|
+
],
|
|
43
|
+
action: async (ctx) => {
|
|
44
|
+
const targetDir = ctx.args[0] || ctx.cwd;
|
|
45
|
+
const rawPartitions = ctx.flags.partitions || 2;
|
|
46
|
+
const numPartitions = Number.isFinite(rawPartitions) ? Math.max(1, Math.min(rawPartitions, 100)) : 2;
|
|
47
|
+
const outputFile = ctx.flags.output;
|
|
48
|
+
const format = ctx.flags.format || 'text';
|
|
49
|
+
output.printInfo(`Analyzing code boundaries in: ${output.highlight(targetDir)}`);
|
|
50
|
+
output.writeln();
|
|
51
|
+
const spinner = output.createSpinner({ text: 'Building dependency graph...', spinner: 'dots' });
|
|
52
|
+
spinner.start();
|
|
53
|
+
try {
|
|
54
|
+
const analyzer = await getGraphAnalyzer();
|
|
55
|
+
if (!analyzer) {
|
|
56
|
+
spinner.stop();
|
|
57
|
+
output.printError('Graph analyzer module not available');
|
|
58
|
+
return { success: false, exitCode: 1 };
|
|
59
|
+
}
|
|
60
|
+
const result = await analyzer.analyzeGraph(resolve(targetDir), {
|
|
61
|
+
includeBoundaries: true,
|
|
62
|
+
includeModules: false,
|
|
63
|
+
numPartitions,
|
|
64
|
+
});
|
|
65
|
+
spinner.stop();
|
|
66
|
+
// Handle different output formats
|
|
67
|
+
if (format === 'json') {
|
|
68
|
+
const jsonOutput = {
|
|
69
|
+
boundaries: result.boundaries,
|
|
70
|
+
statistics: result.statistics,
|
|
71
|
+
circularDependencies: result.circularDependencies,
|
|
72
|
+
};
|
|
73
|
+
if (outputFile) {
|
|
74
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(jsonOutput, null, 2));
|
|
75
|
+
output.printSuccess(`Results written to ${outputFile}`);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
output.printJson(jsonOutput);
|
|
79
|
+
}
|
|
80
|
+
return { success: true, data: jsonOutput };
|
|
81
|
+
}
|
|
82
|
+
if (format === 'dot') {
|
|
83
|
+
const dotOutput = analyzer.exportToDot(result, {
|
|
84
|
+
includeLabels: true,
|
|
85
|
+
highlightCycles: true,
|
|
86
|
+
});
|
|
87
|
+
if (outputFile) {
|
|
88
|
+
await safeWriteOutputFile(outputFile, dotOutput);
|
|
89
|
+
output.printSuccess(`DOT graph written to ${outputFile}`);
|
|
90
|
+
output.writeln(output.dim('Visualize with: dot -Tpng -o graph.png ' + outputFile));
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
output.writeln(dotOutput);
|
|
94
|
+
}
|
|
95
|
+
return { success: true };
|
|
96
|
+
}
|
|
97
|
+
// Text format (default)
|
|
98
|
+
output.printBox([
|
|
99
|
+
`Files analyzed: ${result.statistics.nodeCount}`,
|
|
100
|
+
`Dependencies: ${result.statistics.edgeCount}`,
|
|
101
|
+
`Avg degree: ${result.statistics.avgDegree.toFixed(2)}`,
|
|
102
|
+
`Density: ${(result.statistics.density * 100).toFixed(2)}%`,
|
|
103
|
+
`Components: ${result.statistics.componentCount}`,
|
|
104
|
+
].join('\n'), 'Graph Statistics');
|
|
105
|
+
if (result.boundaries && result.boundaries.length > 0) {
|
|
106
|
+
output.writeln();
|
|
107
|
+
output.writeln(output.bold('MinCut Boundaries'));
|
|
108
|
+
output.writeln();
|
|
109
|
+
for (let i = 0; i < result.boundaries.length; i++) {
|
|
110
|
+
const boundary = result.boundaries[i];
|
|
111
|
+
output.writeln(output.bold(`Boundary ${i + 1} (cut value: ${boundary.cutValue})`));
|
|
112
|
+
output.writeln();
|
|
113
|
+
output.writeln(output.dim('Partition 1:'));
|
|
114
|
+
const p1Display = boundary.partition1.slice(0, 10);
|
|
115
|
+
output.printList(p1Display);
|
|
116
|
+
if (boundary.partition1.length > 10) {
|
|
117
|
+
output.writeln(output.dim(` ... and ${boundary.partition1.length - 10} more files`));
|
|
118
|
+
}
|
|
119
|
+
output.writeln();
|
|
120
|
+
output.writeln(output.dim('Partition 2:'));
|
|
121
|
+
const p2Display = boundary.partition2.slice(0, 10);
|
|
122
|
+
output.printList(p2Display);
|
|
123
|
+
if (boundary.partition2.length > 10) {
|
|
124
|
+
output.writeln(output.dim(` ... and ${boundary.partition2.length - 10} more files`));
|
|
125
|
+
}
|
|
126
|
+
output.writeln();
|
|
127
|
+
output.writeln(output.success('Suggestion:'));
|
|
128
|
+
output.writeln(` ${boundary.suggestion}`);
|
|
129
|
+
output.writeln();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Show circular dependencies
|
|
133
|
+
if (result.circularDependencies.length > 0) {
|
|
134
|
+
output.writeln();
|
|
135
|
+
output.writeln(output.bold(output.warning('Circular Dependencies Detected')));
|
|
136
|
+
output.writeln();
|
|
137
|
+
for (const cycle of result.circularDependencies.slice(0, 5)) {
|
|
138
|
+
const severityColor = cycle.severity === 'high' ? output.error : cycle.severity === 'medium' ? output.warning : output.dim;
|
|
139
|
+
output.writeln(`${severityColor(`[${cycle.severity.toUpperCase()}]`)} ${cycle.cycle.join(' -> ')}`);
|
|
140
|
+
output.writeln(output.dim(` ${cycle.suggestion}`));
|
|
141
|
+
output.writeln();
|
|
142
|
+
}
|
|
143
|
+
if (result.circularDependencies.length > 5) {
|
|
144
|
+
output.writeln(output.dim(`... and ${result.circularDependencies.length - 5} more cycles`));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (outputFile) {
|
|
148
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(result, null, 2));
|
|
149
|
+
output.printSuccess(`Full results written to ${outputFile}`);
|
|
150
|
+
}
|
|
151
|
+
return { success: true, data: result };
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
spinner.stop();
|
|
155
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
156
|
+
output.printError(`Analysis failed: ${message}`);
|
|
157
|
+
return { success: false, exitCode: 1 };
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Analyze modules/communities using Louvain algorithm
|
|
163
|
+
*/
|
|
164
|
+
export const modulesCommand = {
|
|
165
|
+
name: 'modules',
|
|
166
|
+
aliases: ['communities', 'louvain'],
|
|
167
|
+
description: 'Detect module communities using Louvain algorithm',
|
|
168
|
+
options: [
|
|
169
|
+
{
|
|
170
|
+
name: 'output',
|
|
171
|
+
short: 'o',
|
|
172
|
+
description: 'Output file path',
|
|
173
|
+
type: 'string',
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: 'format',
|
|
177
|
+
short: 'f',
|
|
178
|
+
description: 'Output format (text, json, dot)',
|
|
179
|
+
type: 'string',
|
|
180
|
+
default: 'text',
|
|
181
|
+
choices: ['text', 'json', 'dot'],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: 'min-size',
|
|
185
|
+
short: 'm',
|
|
186
|
+
description: 'Minimum community size to display',
|
|
187
|
+
type: 'number',
|
|
188
|
+
default: 2,
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
examples: [
|
|
192
|
+
{ command: 'monomind analyze modules src/', description: 'Detect module communities' },
|
|
193
|
+
{ command: 'monomind analyze modules -f dot -o modules.dot src/', description: 'Export colored DOT graph' },
|
|
194
|
+
{ command: 'monomind analyze modules -m 3 src/', description: 'Only show communities with 3+ files' },
|
|
195
|
+
],
|
|
196
|
+
action: async (ctx) => {
|
|
197
|
+
const targetDir = ctx.args[0] || ctx.cwd;
|
|
198
|
+
const outputFile = ctx.flags.output;
|
|
199
|
+
const format = ctx.flags.format || 'text';
|
|
200
|
+
const minSize = ctx.flags['min-size'] || 2;
|
|
201
|
+
output.printInfo(`Detecting module communities in: ${output.highlight(targetDir)}`);
|
|
202
|
+
output.writeln();
|
|
203
|
+
const spinner = output.createSpinner({ text: 'Building dependency graph...', spinner: 'dots' });
|
|
204
|
+
spinner.start();
|
|
205
|
+
try {
|
|
206
|
+
const analyzer = await getGraphAnalyzer();
|
|
207
|
+
if (!analyzer) {
|
|
208
|
+
spinner.stop();
|
|
209
|
+
output.printError('Graph analyzer module not available');
|
|
210
|
+
return { success: false, exitCode: 1 };
|
|
211
|
+
}
|
|
212
|
+
const result = await analyzer.analyzeGraph(resolve(targetDir), {
|
|
213
|
+
includeBoundaries: false,
|
|
214
|
+
includeModules: true,
|
|
215
|
+
});
|
|
216
|
+
spinner.stop();
|
|
217
|
+
// Filter communities by size
|
|
218
|
+
const communities = result.communities?.filter(c => c.members.length >= minSize) || [];
|
|
219
|
+
// Handle different output formats
|
|
220
|
+
if (format === 'json') {
|
|
221
|
+
const jsonOutput = {
|
|
222
|
+
communities,
|
|
223
|
+
statistics: result.statistics,
|
|
224
|
+
};
|
|
225
|
+
if (outputFile) {
|
|
226
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(jsonOutput, null, 2));
|
|
227
|
+
output.printSuccess(`Results written to ${outputFile}`);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
output.printJson(jsonOutput);
|
|
231
|
+
}
|
|
232
|
+
return { success: true, data: jsonOutput };
|
|
233
|
+
}
|
|
234
|
+
if (format === 'dot') {
|
|
235
|
+
const dotOutput = analyzer.exportToDot(result, {
|
|
236
|
+
includeLabels: true,
|
|
237
|
+
colorByCommunity: true,
|
|
238
|
+
highlightCycles: true,
|
|
239
|
+
});
|
|
240
|
+
if (outputFile) {
|
|
241
|
+
await safeWriteOutputFile(outputFile, dotOutput);
|
|
242
|
+
output.printSuccess(`DOT graph written to ${outputFile}`);
|
|
243
|
+
output.writeln(output.dim('Visualize with: dot -Tpng -o modules.png ' + outputFile));
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
output.writeln(dotOutput);
|
|
247
|
+
}
|
|
248
|
+
return { success: true };
|
|
249
|
+
}
|
|
250
|
+
// Text format (default)
|
|
251
|
+
output.printBox([
|
|
252
|
+
`Files analyzed: ${result.statistics.nodeCount}`,
|
|
253
|
+
`Dependencies: ${result.statistics.edgeCount}`,
|
|
254
|
+
`Communities found: ${result.communities?.length || 0}`,
|
|
255
|
+
`Showing: ${communities.length} (min size: ${minSize})`,
|
|
256
|
+
].join('\n'), 'Module Detection Results');
|
|
257
|
+
if (communities.length > 0) {
|
|
258
|
+
output.writeln();
|
|
259
|
+
output.writeln(output.bold('Detected Communities'));
|
|
260
|
+
output.writeln();
|
|
261
|
+
for (const community of communities.slice(0, 10)) {
|
|
262
|
+
const cohesionIndicator = community.cohesion > 0.5 ? output.success('High') :
|
|
263
|
+
community.cohesion > 0.2 ? output.warning('Medium') : output.dim('Low');
|
|
264
|
+
output.writeln(output.bold(`Community ${community.id}: ${community.suggestedName || 'unnamed'}`));
|
|
265
|
+
output.writeln(` ${output.dim('Cohesion:')} ${cohesionIndicator} (${(community.cohesion * 100).toFixed(1)}%)`);
|
|
266
|
+
output.writeln(` ${output.dim('Central node:')} ${community.centralNode || 'none'}`);
|
|
267
|
+
output.writeln(` ${output.dim('Members:')} ${community.members.length} files`);
|
|
268
|
+
const displayMembers = community.members.slice(0, 5);
|
|
269
|
+
for (const member of displayMembers) {
|
|
270
|
+
output.writeln(` - ${member}`);
|
|
271
|
+
}
|
|
272
|
+
if (community.members.length > 5) {
|
|
273
|
+
output.writeln(output.dim(` ... and ${community.members.length - 5} more`));
|
|
274
|
+
}
|
|
275
|
+
output.writeln();
|
|
276
|
+
}
|
|
277
|
+
if (communities.length > 10) {
|
|
278
|
+
output.writeln(output.dim(`... and ${communities.length - 10} more communities`));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (outputFile) {
|
|
282
|
+
await safeWriteOutputFile(outputFile, JSON.stringify(result, null, 2));
|
|
283
|
+
output.printSuccess(`Full results written to ${outputFile}`);
|
|
284
|
+
}
|
|
285
|
+
return { success: true, data: result };
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
spinner.stop();
|
|
289
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
290
|
+
output.printError(`Analysis failed: ${message}`);
|
|
291
|
+
return { success: false, exitCode: 1 };
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
//# sourceMappingURL=analyze-boundaries.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyze diff and code subcommands
|
|
3
|
+
* Handles git diff risk assessment and static code quality analysis
|
|
4
|
+
*/
|
|
5
|
+
import type { Command } from '../types.js';
|
|
6
|
+
export declare const diffCommand: Command;
|
|
7
|
+
export declare const codeCommand: Command;
|
|
8
|
+
//# sourceMappingURL=analyze-diff.d.ts.map
|