monomind 1.17.0 → 1.17.2
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/agents/registry-builder.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/agents/registry-builder.js +22 -0
- 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 +20 -0
- package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +432 -0
- package/packages/@monomind/cli/dist/src/commands/doctor.js +54 -943
- 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/index.js +8 -37
- 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
|
@@ -4,759 +4,11 @@
|
|
|
4
4
|
* Extracted from hooks.ts to reduce file size.
|
|
5
5
|
*/
|
|
6
6
|
import { output } from '../output.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// Coverage route subcommand
|
|
13
|
-
export const coverageRouteCommand = {
|
|
14
|
-
name: 'coverage-route',
|
|
15
|
-
description: 'Route task to agents based on test coverage gaps (monovector integration)',
|
|
16
|
-
options: [
|
|
17
|
-
{
|
|
18
|
-
name: 'task',
|
|
19
|
-
short: 't',
|
|
20
|
-
description: 'Task description to route',
|
|
21
|
-
type: 'string',
|
|
22
|
-
required: true
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
name: 'threshold',
|
|
26
|
-
description: 'Coverage threshold percentage (default: 80)',
|
|
27
|
-
type: 'number',
|
|
28
|
-
default: 80
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
name: 'no-monovector',
|
|
32
|
-
description: 'Disable monovector integration',
|
|
33
|
-
type: 'boolean',
|
|
34
|
-
default: false
|
|
35
|
-
}
|
|
36
|
-
],
|
|
37
|
-
examples: [
|
|
38
|
-
{ command: 'monomind hooks coverage-route -t "fix bug in auth"', description: 'Route with coverage awareness' },
|
|
39
|
-
{ command: 'monomind hooks coverage-route -t "add tests" --threshold 90', description: 'Route with custom threshold' }
|
|
40
|
-
],
|
|
41
|
-
action: async (ctx) => {
|
|
42
|
-
const task = ctx.args[0] || ctx.flags.task;
|
|
43
|
-
const threshold = ctx.flags.threshold || 80;
|
|
44
|
-
const useMonovector = !ctx.flags['no-monovector'];
|
|
45
|
-
if (!task) {
|
|
46
|
-
output.printError('Task description is required. Use --task or -t flag.');
|
|
47
|
-
return { success: false, exitCode: 1 };
|
|
48
|
-
}
|
|
49
|
-
const spinner = output.createSpinner({ text: 'Analyzing coverage and routing task...' });
|
|
50
|
-
spinner.start();
|
|
51
|
-
// Try reading coverage from disk first
|
|
52
|
-
const diskCoverage = readCoverageFromDisk();
|
|
53
|
-
if (diskCoverage.found) {
|
|
54
|
-
spinner.succeed(`Coverage data loaded from ${diskCoverage.source}`);
|
|
55
|
-
// Find files with lowest coverage that may relate to the task
|
|
56
|
-
const taskLower = task.toLowerCase();
|
|
57
|
-
const taskWords = taskLower.split(/\s+/).filter(w => w.length > 2);
|
|
58
|
-
// Score each file by relevance to the task and how low its coverage is
|
|
59
|
-
const scoredFiles = diskCoverage.entries
|
|
60
|
-
.filter(e => e.lines < threshold)
|
|
61
|
-
.map(e => {
|
|
62
|
-
const fileNameLower = e.filePath.toLowerCase();
|
|
63
|
-
let relevance = 0;
|
|
64
|
-
for (const word of taskWords) {
|
|
65
|
-
if (fileNameLower.includes(word))
|
|
66
|
-
relevance += 2;
|
|
67
|
-
}
|
|
68
|
-
// Penalize high coverage (we care about low coverage)
|
|
69
|
-
const coveragePenalty = e.lines / 100;
|
|
70
|
-
return { ...e, relevance, score: relevance + (1 - coveragePenalty) };
|
|
71
|
-
})
|
|
72
|
-
.sort((a, b) => b.score - a.score);
|
|
73
|
-
const gaps = scoredFiles.slice(0, 8).map(e => {
|
|
74
|
-
const { gapType, priority } = classifyCoverageGap(e.lines, threshold);
|
|
75
|
-
return {
|
|
76
|
-
filePath: e.filePath,
|
|
77
|
-
coveragePercent: e.lines,
|
|
78
|
-
gapType,
|
|
79
|
-
priority,
|
|
80
|
-
suggestedAgents: suggestAgentsForFile(e.filePath),
|
|
81
|
-
reason: `${e.lines.toFixed(1)}% coverage, below ${threshold}%`,
|
|
82
|
-
};
|
|
83
|
-
});
|
|
84
|
-
const criticalGaps = gaps.filter(g => g.gapType === 'critical').length;
|
|
85
|
-
const primaryAgent = taskLower.includes('test') ? 'tester' :
|
|
86
|
-
taskLower.includes('security') || taskLower.includes('auth') ? 'security-auditor' :
|
|
87
|
-
taskLower.includes('fix') || taskLower.includes('bug') ? 'coder' : 'tester';
|
|
88
|
-
const suggestions = [];
|
|
89
|
-
if (criticalGaps > 0)
|
|
90
|
-
suggestions.push(`${criticalGaps} critical coverage gaps need immediate attention`);
|
|
91
|
-
if (diskCoverage.summary.overallLineCoverage < threshold) {
|
|
92
|
-
suggestions.push(`Overall line coverage (${diskCoverage.summary.overallLineCoverage.toFixed(1)}%) is below ${threshold}% threshold`);
|
|
93
|
-
}
|
|
94
|
-
if (scoredFiles.length > 8)
|
|
95
|
-
suggestions.push(`${scoredFiles.length - 8} additional files with low coverage`);
|
|
96
|
-
const result = {
|
|
97
|
-
success: true,
|
|
98
|
-
task,
|
|
99
|
-
coverageAware: true,
|
|
100
|
-
gaps,
|
|
101
|
-
routing: {
|
|
102
|
-
primaryAgent,
|
|
103
|
-
confidence: gaps.length > 0 ? 0.85 : 0.6,
|
|
104
|
-
reason: gaps.length > 0
|
|
105
|
-
? `Routing to ${primaryAgent} based on ${gaps.length} coverage gaps related to task`
|
|
106
|
-
: `No coverage gaps found related to task, routing to ${primaryAgent}`,
|
|
107
|
-
coverageImpact: gaps.length > 0 ? 'high' : 'low',
|
|
108
|
-
},
|
|
109
|
-
suggestions,
|
|
110
|
-
metrics: {
|
|
111
|
-
filesAnalyzed: diskCoverage.summary.totalFiles,
|
|
112
|
-
totalGaps: scoredFiles.length,
|
|
113
|
-
criticalGaps,
|
|
114
|
-
avgCoverage: diskCoverage.summary.overallLineCoverage,
|
|
115
|
-
},
|
|
116
|
-
source: diskCoverage.source,
|
|
117
|
-
};
|
|
118
|
-
if (ctx.flags.format === 'json') {
|
|
119
|
-
output.printJson(result);
|
|
120
|
-
return { success: true, data: result };
|
|
121
|
-
}
|
|
122
|
-
output.writeln();
|
|
123
|
-
output.printBox([
|
|
124
|
-
`Agent: ${output.highlight(result.routing.primaryAgent)}`,
|
|
125
|
-
`Confidence: ${(result.routing.confidence * 100).toFixed(1)}%`,
|
|
126
|
-
`Coverage-Aware: ${output.success('Yes')} (from ${diskCoverage.source})`,
|
|
127
|
-
`Reason: ${result.routing.reason}`
|
|
128
|
-
].join('\n'), 'Coverage-Aware Routing');
|
|
129
|
-
if (gaps.length > 0) {
|
|
130
|
-
output.writeln();
|
|
131
|
-
output.writeln(output.bold('Priority Coverage Gaps'));
|
|
132
|
-
output.printTable({
|
|
133
|
-
columns: [
|
|
134
|
-
{ key: 'filePath', header: 'File', width: 35, format: (v) => {
|
|
135
|
-
const s = String(v);
|
|
136
|
-
return s.length > 32 ? '...' + s.slice(-32) : s;
|
|
137
|
-
} },
|
|
138
|
-
{ key: 'coveragePercent', header: 'Coverage', width: 10, align: 'right', format: (v) => `${Number(v).toFixed(1)}%` },
|
|
139
|
-
{ key: 'gapType', header: 'Type', width: 10 },
|
|
140
|
-
{ key: 'suggestedAgents', header: 'Agent', width: 15, format: (v) => Array.isArray(v) ? v[0] || '' : String(v) }
|
|
141
|
-
],
|
|
142
|
-
data: gaps.slice(0, 8)
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
if (result.metrics.filesAnalyzed > 0) {
|
|
146
|
-
output.writeln();
|
|
147
|
-
output.writeln(output.bold('Coverage Metrics'));
|
|
148
|
-
output.printList([
|
|
149
|
-
`Files Analyzed: ${result.metrics.filesAnalyzed}`,
|
|
150
|
-
`Total Gaps: ${result.metrics.totalGaps}`,
|
|
151
|
-
`Critical Gaps: ${result.metrics.criticalGaps}`,
|
|
152
|
-
`Average Coverage: ${result.metrics.avgCoverage.toFixed(1)}%`
|
|
153
|
-
]);
|
|
154
|
-
}
|
|
155
|
-
if (suggestions.length > 0) {
|
|
156
|
-
output.writeln();
|
|
157
|
-
output.writeln(output.bold('Suggestions'));
|
|
158
|
-
output.printList(suggestions.map(s => output.dim(s)));
|
|
159
|
-
}
|
|
160
|
-
return { success: true, data: result };
|
|
161
|
-
}
|
|
162
|
-
// No disk coverage - fall back to MCP tool
|
|
163
|
-
try {
|
|
164
|
-
const result = await callMCPTool('hooks_coverage-route', {
|
|
165
|
-
task,
|
|
166
|
-
threshold,
|
|
167
|
-
useMonovector,
|
|
168
|
-
});
|
|
169
|
-
spinner.stop();
|
|
170
|
-
if (ctx.flags.format === 'json') {
|
|
171
|
-
output.printJson(result);
|
|
172
|
-
return { success: true, data: result };
|
|
173
|
-
}
|
|
174
|
-
output.writeln();
|
|
175
|
-
output.printBox([
|
|
176
|
-
`Agent: ${output.highlight(result.routing.primaryAgent)}`,
|
|
177
|
-
`Confidence: ${(result.routing.confidence * 100).toFixed(1)}%`,
|
|
178
|
-
`Coverage-Aware: ${result.coverageAware ? output.success('Yes') : output.dim('No coverage data')}`,
|
|
179
|
-
`Reason: ${result.routing.reason}`
|
|
180
|
-
].join('\n'), 'Coverage-Aware Routing');
|
|
181
|
-
if (result.gaps.length > 0) {
|
|
182
|
-
output.writeln();
|
|
183
|
-
output.writeln(output.bold('Priority Coverage Gaps'));
|
|
184
|
-
output.printTable({
|
|
185
|
-
columns: [
|
|
186
|
-
{ key: 'filePath', header: 'File', width: 35, format: (v) => {
|
|
187
|
-
const s = String(v);
|
|
188
|
-
return s.length > 32 ? '...' + s.slice(-32) : s;
|
|
189
|
-
} },
|
|
190
|
-
{ key: 'coveragePercent', header: 'Coverage', width: 10, align: 'right', format: (v) => `${Number(v).toFixed(1)}%` },
|
|
191
|
-
{ key: 'gapType', header: 'Type', width: 10 },
|
|
192
|
-
{ key: 'suggestedAgents', header: 'Agent', width: 15, format: (v) => Array.isArray(v) ? v[0] || '' : String(v) }
|
|
193
|
-
],
|
|
194
|
-
data: result.gaps.slice(0, 8)
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
if (result.metrics.filesAnalyzed > 0) {
|
|
198
|
-
output.writeln();
|
|
199
|
-
output.writeln(output.bold('Coverage Metrics'));
|
|
200
|
-
output.printList([
|
|
201
|
-
`Files Analyzed: ${result.metrics.filesAnalyzed}`,
|
|
202
|
-
`Total Gaps: ${result.metrics.totalGaps}`,
|
|
203
|
-
`Critical Gaps: ${result.metrics.criticalGaps}`,
|
|
204
|
-
`Average Coverage: ${result.metrics.avgCoverage.toFixed(1)}%`
|
|
205
|
-
]);
|
|
206
|
-
}
|
|
207
|
-
if (result.suggestions.length > 0) {
|
|
208
|
-
output.writeln();
|
|
209
|
-
output.writeln(output.bold('Suggestions'));
|
|
210
|
-
output.printList(result.suggestions.map(s => output.dim(s)));
|
|
211
|
-
}
|
|
212
|
-
return { success: true, data: result };
|
|
213
|
-
}
|
|
214
|
-
catch (error) {
|
|
215
|
-
spinner.fail('No coverage data found');
|
|
216
|
-
output.writeln();
|
|
217
|
-
output.printWarning('No coverage data found. Run your test suite with coverage first.');
|
|
218
|
-
output.writeln();
|
|
219
|
-
output.printList([
|
|
220
|
-
'Jest: npx jest --coverage',
|
|
221
|
-
'Vitest: npx vitest --coverage',
|
|
222
|
-
'nyc: npx nyc npm test',
|
|
223
|
-
'c8: npx c8 npm test',
|
|
224
|
-
]);
|
|
225
|
-
output.writeln();
|
|
226
|
-
output.writeln(output.dim('Expected files: coverage/coverage-summary.json, coverage/lcov.info, or .nyc_output/out.json'));
|
|
227
|
-
return { success: false, exitCode: 1 };
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
// Coverage suggest subcommand
|
|
232
|
-
export const coverageSuggestCommand = {
|
|
233
|
-
name: 'coverage-suggest',
|
|
234
|
-
description: 'Suggest coverage improvements for a path (monovector integration)',
|
|
235
|
-
options: [
|
|
236
|
-
{
|
|
237
|
-
name: 'path',
|
|
238
|
-
short: 'p',
|
|
239
|
-
description: 'Path to analyze for coverage suggestions',
|
|
240
|
-
type: 'string',
|
|
241
|
-
required: true
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
name: 'threshold',
|
|
245
|
-
description: 'Coverage threshold percentage (default: 80)',
|
|
246
|
-
type: 'number',
|
|
247
|
-
default: 80
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
name: 'limit',
|
|
251
|
-
short: 'l',
|
|
252
|
-
description: 'Maximum number of suggestions (default: 20)',
|
|
253
|
-
type: 'number',
|
|
254
|
-
default: 20
|
|
255
|
-
}
|
|
256
|
-
],
|
|
257
|
-
examples: [
|
|
258
|
-
{ command: 'monomind hooks coverage-suggest -p src/', description: 'Suggest improvements for src/' },
|
|
259
|
-
{ command: 'monomind hooks coverage-suggest -p src/services --threshold 90', description: 'Stricter threshold' }
|
|
260
|
-
],
|
|
261
|
-
action: async (ctx) => {
|
|
262
|
-
const targetPath = ctx.args[0] || ctx.flags.path;
|
|
263
|
-
const threshold = ctx.flags.threshold || 80;
|
|
264
|
-
const limit = ctx.flags.limit || 20;
|
|
265
|
-
if (!targetPath) {
|
|
266
|
-
output.printError('Path is required. Use --path or -p flag.');
|
|
267
|
-
return { success: false, exitCode: 1 };
|
|
268
|
-
}
|
|
269
|
-
const spinner = output.createSpinner({ text: `Analyzing coverage for ${targetPath}...` });
|
|
270
|
-
spinner.start();
|
|
271
|
-
// Try reading coverage from disk first
|
|
272
|
-
const diskCoverage = readCoverageFromDisk();
|
|
273
|
-
if (diskCoverage.found) {
|
|
274
|
-
spinner.succeed(`Coverage data loaded from ${diskCoverage.source}`);
|
|
275
|
-
// Filter entries to those matching the target path
|
|
276
|
-
const pathLower = targetPath.toLowerCase().replace(/\\/g, '/');
|
|
277
|
-
const matchingEntries = diskCoverage.entries.filter(e => {
|
|
278
|
-
const fileLower = e.filePath.toLowerCase().replace(/\\/g, '/');
|
|
279
|
-
return fileLower.includes(pathLower);
|
|
280
|
-
});
|
|
281
|
-
const belowThreshold = matchingEntries.filter(e => e.lines < threshold);
|
|
282
|
-
const suggestions = belowThreshold.slice(0, limit).map(e => {
|
|
283
|
-
const { gapType, priority } = classifyCoverageGap(e.lines, threshold);
|
|
284
|
-
return {
|
|
285
|
-
filePath: e.filePath,
|
|
286
|
-
coveragePercent: e.lines,
|
|
287
|
-
gapType,
|
|
288
|
-
priority,
|
|
289
|
-
suggestedAgents: suggestAgentsForFile(e.filePath),
|
|
290
|
-
reason: e.lines === 0 ? 'No coverage at all' :
|
|
291
|
-
e.lines < 20 ? 'Very low coverage, needs tests' :
|
|
292
|
-
e.lines < 50 ? 'Below 50%, add more tests' :
|
|
293
|
-
`Below ${threshold}% threshold`,
|
|
294
|
-
};
|
|
295
|
-
});
|
|
296
|
-
const totalLinesCov = matchingEntries.length > 0
|
|
297
|
-
? matchingEntries.reduce((acc, e) => acc + e.lines, 0) / matchingEntries.length
|
|
298
|
-
: 0;
|
|
299
|
-
const totalBranchesCov = matchingEntries.length > 0
|
|
300
|
-
? matchingEntries.reduce((acc, e) => acc + e.branches, 0) / matchingEntries.length
|
|
301
|
-
: 0;
|
|
302
|
-
const prioritizedFiles = belowThreshold.slice(0, 5).map(e => e.filePath);
|
|
303
|
-
const result = {
|
|
304
|
-
success: true,
|
|
305
|
-
path: targetPath,
|
|
306
|
-
suggestions,
|
|
307
|
-
summary: {
|
|
308
|
-
totalFiles: matchingEntries.length,
|
|
309
|
-
overallLineCoverage: totalLinesCov,
|
|
310
|
-
overallBranchCoverage: totalBranchesCov,
|
|
311
|
-
filesBelowThreshold: belowThreshold.length,
|
|
312
|
-
},
|
|
313
|
-
prioritizedFiles,
|
|
314
|
-
monovectorAvailable: false,
|
|
315
|
-
source: diskCoverage.source,
|
|
316
|
-
};
|
|
317
|
-
if (ctx.flags.format === 'json') {
|
|
318
|
-
output.printJson(result);
|
|
319
|
-
return { success: true, data: result };
|
|
320
|
-
}
|
|
321
|
-
output.writeln();
|
|
322
|
-
output.printBox([
|
|
323
|
-
`Path: ${output.highlight(targetPath)}`,
|
|
324
|
-
`Files Analyzed: ${result.summary.totalFiles}`,
|
|
325
|
-
`Line Coverage: ${result.summary.overallLineCoverage.toFixed(1)}%`,
|
|
326
|
-
`Branch Coverage: ${result.summary.overallBranchCoverage.toFixed(1)}%`,
|
|
327
|
-
`Below Threshold: ${result.summary.filesBelowThreshold} files`,
|
|
328
|
-
`Source: ${output.highlight(diskCoverage.source)}`
|
|
329
|
-
].join('\n'), 'Coverage Summary');
|
|
330
|
-
if (suggestions.length > 0) {
|
|
331
|
-
output.writeln();
|
|
332
|
-
output.writeln(output.bold('Coverage Improvement Suggestions'));
|
|
333
|
-
output.printTable({
|
|
334
|
-
columns: [
|
|
335
|
-
{ key: 'filePath', header: 'File', width: 40, format: (v) => {
|
|
336
|
-
const s = String(v);
|
|
337
|
-
return s.length > 37 ? '...' + s.slice(-37) : s;
|
|
338
|
-
} },
|
|
339
|
-
{ key: 'coveragePercent', header: 'Coverage', width: 10, align: 'right', format: (v) => `${Number(v).toFixed(1)}%` },
|
|
340
|
-
{ key: 'gapType', header: 'Priority', width: 10 },
|
|
341
|
-
{ key: 'reason', header: 'Reason', width: 25 }
|
|
342
|
-
],
|
|
343
|
-
data: suggestions.slice(0, 15)
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
else {
|
|
347
|
-
output.writeln();
|
|
348
|
-
output.printSuccess('All files meet coverage threshold!');
|
|
349
|
-
}
|
|
350
|
-
if (prioritizedFiles.length > 0) {
|
|
351
|
-
output.writeln();
|
|
352
|
-
output.writeln(output.bold('Priority Files (Top 5)'));
|
|
353
|
-
output.printList(prioritizedFiles.slice(0, 5).map(f => output.highlight(f)));
|
|
354
|
-
}
|
|
355
|
-
return { success: true, data: result };
|
|
356
|
-
}
|
|
357
|
-
// No disk coverage - fall back to MCP tool
|
|
358
|
-
try {
|
|
359
|
-
const result = await callMCPTool('hooks_coverage-suggest', {
|
|
360
|
-
path: targetPath,
|
|
361
|
-
threshold,
|
|
362
|
-
limit,
|
|
363
|
-
});
|
|
364
|
-
spinner.stop();
|
|
365
|
-
if (ctx.flags.format === 'json') {
|
|
366
|
-
output.printJson(result);
|
|
367
|
-
return { success: true, data: result };
|
|
368
|
-
}
|
|
369
|
-
output.writeln();
|
|
370
|
-
output.printBox([
|
|
371
|
-
`Path: ${output.highlight(result.path)}`,
|
|
372
|
-
`Files Analyzed: ${result.summary.totalFiles}`,
|
|
373
|
-
`Line Coverage: ${result.summary.overallLineCoverage.toFixed(1)}%`,
|
|
374
|
-
`Branch Coverage: ${result.summary.overallBranchCoverage.toFixed(1)}%`,
|
|
375
|
-
`Below Threshold: ${result.summary.filesBelowThreshold} files`,
|
|
376
|
-
`Keyword routing: ${result.monovectorAvailable ? output.success('Available') : output.dim('Unavailable')}`
|
|
377
|
-
].join('\n'), 'Coverage Summary');
|
|
378
|
-
if (result.suggestions.length > 0) {
|
|
379
|
-
output.writeln();
|
|
380
|
-
output.writeln(output.bold('Coverage Improvement Suggestions'));
|
|
381
|
-
output.printTable({
|
|
382
|
-
columns: [
|
|
383
|
-
{ key: 'filePath', header: 'File', width: 40, format: (v) => {
|
|
384
|
-
const s = String(v);
|
|
385
|
-
return s.length > 37 ? '...' + s.slice(-37) : s;
|
|
386
|
-
} },
|
|
387
|
-
{ key: 'coveragePercent', header: 'Coverage', width: 10, align: 'right', format: (v) => `${Number(v).toFixed(1)}%` },
|
|
388
|
-
{ key: 'gapType', header: 'Priority', width: 10 },
|
|
389
|
-
{ key: 'reason', header: 'Reason', width: 25 }
|
|
390
|
-
],
|
|
391
|
-
data: result.suggestions.slice(0, 15)
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
else {
|
|
395
|
-
output.writeln();
|
|
396
|
-
output.printSuccess('All files meet coverage threshold!');
|
|
397
|
-
}
|
|
398
|
-
if (result.prioritizedFiles.length > 0) {
|
|
399
|
-
output.writeln();
|
|
400
|
-
output.writeln(output.bold('Priority Files (Top 5)'));
|
|
401
|
-
output.printList(result.prioritizedFiles.slice(0, 5).map(f => output.highlight(f)));
|
|
402
|
-
}
|
|
403
|
-
return { success: true, data: result };
|
|
404
|
-
}
|
|
405
|
-
catch (error) {
|
|
406
|
-
spinner.fail('No coverage data found');
|
|
407
|
-
output.writeln();
|
|
408
|
-
output.printWarning('No coverage data found. Run your test suite with coverage first.');
|
|
409
|
-
output.writeln();
|
|
410
|
-
output.printList([
|
|
411
|
-
'Jest: npx jest --coverage',
|
|
412
|
-
'Vitest: npx vitest --coverage',
|
|
413
|
-
'nyc: npx nyc npm test',
|
|
414
|
-
'c8: npx c8 npm test',
|
|
415
|
-
]);
|
|
416
|
-
output.writeln();
|
|
417
|
-
output.writeln(output.dim('Expected files: coverage/coverage-summary.json, coverage/lcov.info, or .nyc_output/out.json'));
|
|
418
|
-
return { success: false, exitCode: 1 };
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
};
|
|
422
|
-
// Coverage gaps subcommand
|
|
423
|
-
export const coverageGapsCommand = {
|
|
424
|
-
name: 'coverage-gaps',
|
|
425
|
-
description: 'List all coverage gaps with priority scoring and agent assignments',
|
|
426
|
-
options: [
|
|
427
|
-
{
|
|
428
|
-
name: 'threshold',
|
|
429
|
-
description: 'Coverage threshold percentage (default: 80)',
|
|
430
|
-
type: 'number',
|
|
431
|
-
default: 80
|
|
432
|
-
},
|
|
433
|
-
{
|
|
434
|
-
name: 'group-by-agent',
|
|
435
|
-
description: 'Group gaps by suggested agent (default: true)',
|
|
436
|
-
type: 'boolean',
|
|
437
|
-
default: true
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
name: 'critical-only',
|
|
441
|
-
description: 'Show only critical gaps',
|
|
442
|
-
type: 'boolean',
|
|
443
|
-
default: false
|
|
444
|
-
}
|
|
445
|
-
],
|
|
446
|
-
examples: [
|
|
447
|
-
{ command: 'monomind hooks coverage-gaps', description: 'List all coverage gaps' },
|
|
448
|
-
{ command: 'monomind hooks coverage-gaps --critical-only', description: 'Only critical gaps' },
|
|
449
|
-
{ command: 'monomind hooks coverage-gaps --threshold 90', description: 'Stricter threshold' }
|
|
450
|
-
],
|
|
451
|
-
action: async (ctx) => {
|
|
452
|
-
const threshold = ctx.flags.threshold || 80;
|
|
453
|
-
const groupByAgent = ctx.flags['group-by-agent'] !== false;
|
|
454
|
-
const criticalOnly = ctx.flags['critical-only'] || false;
|
|
455
|
-
const spinner = output.createSpinner({ text: 'Analyzing project coverage gaps...' });
|
|
456
|
-
spinner.start();
|
|
457
|
-
// Try reading coverage from disk first
|
|
458
|
-
const diskCoverage = readCoverageFromDisk();
|
|
459
|
-
if (diskCoverage.found) {
|
|
460
|
-
spinner.succeed(`Coverage data loaded from ${diskCoverage.source}`);
|
|
461
|
-
// Build gaps from disk data
|
|
462
|
-
const allGaps = diskCoverage.entries
|
|
463
|
-
.filter(e => e.lines < threshold)
|
|
464
|
-
.map(e => {
|
|
465
|
-
const { gapType, priority } = classifyCoverageGap(e.lines, threshold);
|
|
466
|
-
return {
|
|
467
|
-
filePath: e.filePath,
|
|
468
|
-
coveragePercent: e.lines,
|
|
469
|
-
gapType,
|
|
470
|
-
complexity: Math.round((100 - e.lines) / 10),
|
|
471
|
-
priority,
|
|
472
|
-
suggestedAgents: suggestAgentsForFile(e.filePath),
|
|
473
|
-
reason: `Line coverage ${e.lines.toFixed(1)}% below ${threshold}% threshold`,
|
|
474
|
-
};
|
|
475
|
-
});
|
|
476
|
-
const gaps = criticalOnly
|
|
477
|
-
? allGaps.filter(g => g.gapType === 'critical')
|
|
478
|
-
: allGaps;
|
|
479
|
-
// Build agent assignments
|
|
480
|
-
const agentAssignments = {};
|
|
481
|
-
if (groupByAgent) {
|
|
482
|
-
for (const gap of gaps) {
|
|
483
|
-
const agent = gap.suggestedAgents[0] || 'tester';
|
|
484
|
-
if (!agentAssignments[agent])
|
|
485
|
-
agentAssignments[agent] = [];
|
|
486
|
-
agentAssignments[agent].push(gap.filePath);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
const result = {
|
|
490
|
-
success: true,
|
|
491
|
-
gaps,
|
|
492
|
-
summary: {
|
|
493
|
-
totalFiles: diskCoverage.summary.totalFiles,
|
|
494
|
-
overallLineCoverage: diskCoverage.summary.overallLineCoverage,
|
|
495
|
-
overallBranchCoverage: diskCoverage.summary.overallBranchCoverage,
|
|
496
|
-
filesBelowThreshold: gaps.length,
|
|
497
|
-
coverageThreshold: threshold,
|
|
498
|
-
},
|
|
499
|
-
agentAssignments,
|
|
500
|
-
monovectorAvailable: false,
|
|
501
|
-
source: diskCoverage.source,
|
|
502
|
-
};
|
|
503
|
-
if (ctx.flags.format === 'json') {
|
|
504
|
-
output.printJson(result);
|
|
505
|
-
return { success: true, data: result };
|
|
506
|
-
}
|
|
507
|
-
output.writeln();
|
|
508
|
-
output.printBox([
|
|
509
|
-
`Total Files: ${result.summary.totalFiles}`,
|
|
510
|
-
`Line Coverage: ${result.summary.overallLineCoverage.toFixed(1)}%`,
|
|
511
|
-
`Branch Coverage: ${result.summary.overallBranchCoverage.toFixed(1)}%`,
|
|
512
|
-
`Below ${threshold}%: ${result.summary.filesBelowThreshold} files`,
|
|
513
|
-
`Source: ${output.highlight(diskCoverage.source)}`
|
|
514
|
-
].join('\n'), 'Coverage Gap Analysis');
|
|
515
|
-
if (gaps.length > 0) {
|
|
516
|
-
output.writeln();
|
|
517
|
-
output.writeln(output.bold(`Coverage Gaps (${gaps.length} files)`));
|
|
518
|
-
output.printTable({
|
|
519
|
-
columns: [
|
|
520
|
-
{ key: 'filePath', header: 'File', width: 35, format: (v) => {
|
|
521
|
-
const s = String(v);
|
|
522
|
-
return s.length > 32 ? '...' + s.slice(-32) : s;
|
|
523
|
-
} },
|
|
524
|
-
{ key: 'coveragePercent', header: 'Coverage', width: 10, align: 'right', format: (v) => `${Number(v).toFixed(1)}%` },
|
|
525
|
-
{ key: 'gapType', header: 'Type', width: 10, format: (v) => {
|
|
526
|
-
const t = String(v);
|
|
527
|
-
if (t === 'critical')
|
|
528
|
-
return output.error(t);
|
|
529
|
-
if (t === 'high')
|
|
530
|
-
return output.warning(t);
|
|
531
|
-
return t;
|
|
532
|
-
} },
|
|
533
|
-
{ key: 'priority', header: 'Priority', width: 8, align: 'right' },
|
|
534
|
-
{ key: 'suggestedAgents', header: 'Agent', width: 12, format: (v) => Array.isArray(v) ? v[0] || '' : String(v) }
|
|
535
|
-
],
|
|
536
|
-
data: gaps.slice(0, 20)
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
output.writeln();
|
|
541
|
-
output.printSuccess('No coverage gaps found! All files meet threshold.');
|
|
542
|
-
}
|
|
543
|
-
if (groupByAgent && Object.keys(agentAssignments).length > 0) {
|
|
544
|
-
output.writeln();
|
|
545
|
-
output.writeln(output.bold('Agent Assignments'));
|
|
546
|
-
for (const [agent, files] of Object.entries(agentAssignments)) {
|
|
547
|
-
output.writeln();
|
|
548
|
-
output.writeln(` ${output.highlight(agent)} (${files.length} files)`);
|
|
549
|
-
files.slice(0, 3).forEach(f => {
|
|
550
|
-
output.writeln(` - ${output.dim(f)}`);
|
|
551
|
-
});
|
|
552
|
-
if (files.length > 3) {
|
|
553
|
-
output.writeln(` ... and ${files.length - 3} more`);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
return { success: true, data: result };
|
|
558
|
-
}
|
|
559
|
-
// No coverage files on disk - try MCP tool as fallback
|
|
560
|
-
try {
|
|
561
|
-
const result = await callMCPTool('hooks_coverage-gaps', {
|
|
562
|
-
threshold,
|
|
563
|
-
groupByAgent,
|
|
564
|
-
});
|
|
565
|
-
spinner.stop();
|
|
566
|
-
const gaps = criticalOnly
|
|
567
|
-
? result.gaps.filter(g => g.gapType === 'critical')
|
|
568
|
-
: result.gaps;
|
|
569
|
-
if (ctx.flags.format === 'json') {
|
|
570
|
-
output.printJson({ ...result, gaps });
|
|
571
|
-
return { success: true, data: result };
|
|
572
|
-
}
|
|
573
|
-
output.writeln();
|
|
574
|
-
output.printBox([
|
|
575
|
-
`Total Files: ${result.summary.totalFiles}`,
|
|
576
|
-
`Line Coverage: ${result.summary.overallLineCoverage.toFixed(1)}%`,
|
|
577
|
-
`Branch Coverage: ${result.summary.overallBranchCoverage.toFixed(1)}%`,
|
|
578
|
-
`Below ${result.summary.coverageThreshold}%: ${result.summary.filesBelowThreshold} files`,
|
|
579
|
-
`Keyword routing: ${result.monovectorAvailable ? output.success('Available') : output.dim('Unavailable')}`
|
|
580
|
-
].join('\n'), 'Coverage Gap Analysis');
|
|
581
|
-
if (gaps.length > 0) {
|
|
582
|
-
output.writeln();
|
|
583
|
-
output.writeln(output.bold(`Coverage Gaps (${gaps.length} files)`));
|
|
584
|
-
output.printTable({
|
|
585
|
-
columns: [
|
|
586
|
-
{ key: 'filePath', header: 'File', width: 35, format: (v) => {
|
|
587
|
-
const s = String(v);
|
|
588
|
-
return s.length > 32 ? '...' + s.slice(-32) : s;
|
|
589
|
-
} },
|
|
590
|
-
{ key: 'coveragePercent', header: 'Coverage', width: 10, align: 'right', format: (v) => `${Number(v).toFixed(1)}%` },
|
|
591
|
-
{ key: 'gapType', header: 'Type', width: 10, format: (v) => {
|
|
592
|
-
const t = String(v);
|
|
593
|
-
if (t === 'critical')
|
|
594
|
-
return output.error(t);
|
|
595
|
-
if (t === 'high')
|
|
596
|
-
return output.warning(t);
|
|
597
|
-
return t;
|
|
598
|
-
} },
|
|
599
|
-
{ key: 'priority', header: 'Priority', width: 8, align: 'right' },
|
|
600
|
-
{ key: 'suggestedAgents', header: 'Agent', width: 12, format: (v) => Array.isArray(v) ? v[0] || '' : String(v) }
|
|
601
|
-
],
|
|
602
|
-
data: gaps.slice(0, 20)
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
else {
|
|
606
|
-
output.writeln();
|
|
607
|
-
output.printSuccess('No coverage gaps found! All files meet threshold.');
|
|
608
|
-
}
|
|
609
|
-
if (groupByAgent && Object.keys(result.agentAssignments).length > 0) {
|
|
610
|
-
output.writeln();
|
|
611
|
-
output.writeln(output.bold('Agent Assignments'));
|
|
612
|
-
for (const [agent, files] of Object.entries(result.agentAssignments)) {
|
|
613
|
-
output.writeln();
|
|
614
|
-
output.writeln(` ${output.highlight(agent)} (${files.length} files)`);
|
|
615
|
-
files.slice(0, 3).forEach(f => {
|
|
616
|
-
output.writeln(` - ${output.dim(f)}`);
|
|
617
|
-
});
|
|
618
|
-
if (files.length > 3) {
|
|
619
|
-
output.writeln(` ... and ${files.length - 3} more`);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
return { success: true, data: result };
|
|
624
|
-
}
|
|
625
|
-
catch (error) {
|
|
626
|
-
spinner.fail('No coverage data found');
|
|
627
|
-
output.writeln();
|
|
628
|
-
output.printWarning('No coverage data found. Run your test suite with coverage first.');
|
|
629
|
-
output.writeln();
|
|
630
|
-
output.printList([
|
|
631
|
-
'Jest: npx jest --coverage',
|
|
632
|
-
'Vitest: npx vitest --coverage',
|
|
633
|
-
'nyc: npx nyc npm test',
|
|
634
|
-
'c8: npx c8 npm test',
|
|
635
|
-
]);
|
|
636
|
-
output.writeln();
|
|
637
|
-
output.writeln(output.dim('Expected files: coverage/coverage-summary.json, coverage/lcov.info, or .nyc_output/out.json'));
|
|
638
|
-
return { success: false, exitCode: 1 };
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
};
|
|
642
|
-
// Progress hook command
|
|
643
|
-
export const progressHookCommand = {
|
|
644
|
-
name: 'progress',
|
|
645
|
-
description: 'Check implementation progress via hooks',
|
|
646
|
-
options: [
|
|
647
|
-
{
|
|
648
|
-
name: 'detailed',
|
|
649
|
-
short: 'd',
|
|
650
|
-
description: 'Show detailed breakdown by category',
|
|
651
|
-
type: 'boolean',
|
|
652
|
-
default: false
|
|
653
|
-
},
|
|
654
|
-
{
|
|
655
|
-
name: 'sync',
|
|
656
|
-
short: 's',
|
|
657
|
-
description: 'Sync and persist progress to file',
|
|
658
|
-
type: 'boolean',
|
|
659
|
-
default: false
|
|
660
|
-
},
|
|
661
|
-
{
|
|
662
|
-
name: 'summary',
|
|
663
|
-
description: 'Show human-readable summary',
|
|
664
|
-
type: 'boolean',
|
|
665
|
-
default: false
|
|
666
|
-
}
|
|
667
|
-
],
|
|
668
|
-
examples: [
|
|
669
|
-
{ command: 'monomind hooks progress', description: 'Check current progress' },
|
|
670
|
-
{ command: 'monomind hooks progress -d', description: 'Detailed breakdown' },
|
|
671
|
-
{ command: 'monomind hooks progress --sync', description: 'Sync progress to file' },
|
|
672
|
-
{ command: 'monomind hooks progress --summary', description: 'Human-readable summary' }
|
|
673
|
-
],
|
|
674
|
-
action: async (ctx) => {
|
|
675
|
-
const detailed = ctx.flags.detailed;
|
|
676
|
-
const sync = ctx.flags.sync;
|
|
677
|
-
const summary = ctx.flags.summary;
|
|
678
|
-
try {
|
|
679
|
-
if (summary) {
|
|
680
|
-
const spinner = output.createSpinner({ text: 'Getting progress summary...' });
|
|
681
|
-
spinner.start();
|
|
682
|
-
const result = await callMCPTool('progress_summary', {});
|
|
683
|
-
spinner.stop();
|
|
684
|
-
if (ctx.flags.format === 'json') {
|
|
685
|
-
output.printJson(result);
|
|
686
|
-
return { success: true, data: result };
|
|
687
|
-
}
|
|
688
|
-
output.writeln();
|
|
689
|
-
output.writeln(result.summary);
|
|
690
|
-
return { success: true, data: result };
|
|
691
|
-
}
|
|
692
|
-
if (sync) {
|
|
693
|
-
const spinner = output.createSpinner({ text: 'Syncing progress...' });
|
|
694
|
-
spinner.start();
|
|
695
|
-
const result = await callMCPTool('progress_sync', {});
|
|
696
|
-
spinner.stop();
|
|
697
|
-
if (ctx.flags.format === 'json') {
|
|
698
|
-
output.printJson(result);
|
|
699
|
-
return { success: true, data: result };
|
|
700
|
-
}
|
|
701
|
-
output.writeln();
|
|
702
|
-
output.printSuccess(`Progress synced: ${result.progress}%`);
|
|
703
|
-
output.writeln(output.dim(` Persisted to .monomind/metrics/v1-progress.json`));
|
|
704
|
-
output.writeln(output.dim(` Last updated: ${result.lastUpdated}`));
|
|
705
|
-
return { success: true, data: result };
|
|
706
|
-
}
|
|
707
|
-
// Default: check progress
|
|
708
|
-
const spinner = output.createSpinner({ text: 'Checking v1 progress...' });
|
|
709
|
-
spinner.start();
|
|
710
|
-
const result = await callMCPTool('progress_check', { detailed });
|
|
711
|
-
spinner.stop();
|
|
712
|
-
if (ctx.flags.format === 'json') {
|
|
713
|
-
output.printJson(result);
|
|
714
|
-
return { success: true, data: result };
|
|
715
|
-
}
|
|
716
|
-
output.writeln();
|
|
717
|
-
const progressValue = result.overall ?? result.progress ?? 0;
|
|
718
|
-
// Create progress bar
|
|
719
|
-
const barWidth = 30;
|
|
720
|
-
const filled = Math.round((progressValue / 100) * barWidth);
|
|
721
|
-
const empty = barWidth - filled;
|
|
722
|
-
const bar = output.success('█'.repeat(filled)) + output.dim('░'.repeat(empty));
|
|
723
|
-
output.writeln(output.bold('v1 Implementation Progress'));
|
|
724
|
-
output.writeln();
|
|
725
|
-
output.writeln(`[${bar}] ${progressValue}%`);
|
|
726
|
-
output.writeln();
|
|
727
|
-
if (detailed && result.cli) {
|
|
728
|
-
output.writeln(output.highlight('CLI Commands:') + ` ${result.cli.progress}% (${result.cli.commands}/${result.cli.target})`);
|
|
729
|
-
output.writeln(output.highlight('MCP Tools:') + ` ${result.mcp?.progress ?? 0}% (${result.mcp?.tools ?? 0}/${result.mcp?.target ?? 0})`);
|
|
730
|
-
output.writeln(output.highlight('Hooks:') + ` ${result.hooks?.progress ?? 0}% (${result.hooks?.subcommands ?? 0}/${result.hooks?.target ?? 0})`);
|
|
731
|
-
output.writeln(output.highlight('Packages:') + ` ${result.packages?.progress ?? 0}% (${result.packages?.total ?? 0}/${result.packages?.target ?? 0})`);
|
|
732
|
-
output.writeln(output.highlight('DDD Structure:') + ` ${result.ddd?.progress ?? 0}% (${result.packages?.withDDD ?? 0}/${result.packages?.total ?? 0})`);
|
|
733
|
-
output.writeln();
|
|
734
|
-
if (result.codebase) {
|
|
735
|
-
output.writeln(output.dim(`Codebase: ${result.codebase.totalFiles} files, ${result.codebase.totalLines.toLocaleString()} lines`));
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
else if (result.breakdown) {
|
|
739
|
-
output.writeln('Breakdown:');
|
|
740
|
-
for (const [category, value] of Object.entries(result.breakdown)) {
|
|
741
|
-
output.writeln(` ${output.highlight(category)}: ${value}`);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
if (result.lastUpdated) {
|
|
745
|
-
output.writeln(output.dim(`Last updated: ${result.lastUpdated}`));
|
|
746
|
-
}
|
|
747
|
-
return { success: true, data: result };
|
|
748
|
-
}
|
|
749
|
-
catch (error) {
|
|
750
|
-
if (error instanceof MCPClientError) {
|
|
751
|
-
output.printError(`Progress check failed: ${error.message}`);
|
|
752
|
-
}
|
|
753
|
-
else {
|
|
754
|
-
output.printError(`Progress check failed: ${String(error)}`);
|
|
755
|
-
}
|
|
756
|
-
return { success: false, exitCode: 1 };
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
};
|
|
7
|
+
// Re-export commands from sub-modules so hooks.ts import stays unchanged
|
|
8
|
+
export { coverageRouteCommand } from './hooks-coverage-routing.js';
|
|
9
|
+
export { coverageSuggestCommand } from './hooks-coverage-routing.js';
|
|
10
|
+
export { coverageGapsCommand } from './hooks-coverage-gaps.js';
|
|
11
|
+
export { progressHookCommand } from './hooks-coverage-gaps.js';
|
|
760
12
|
// Statusline subcommand - generates dynamic status display
|
|
761
13
|
export const statuslineCommand = {
|
|
762
14
|
name: 'statusline',
|
|
@@ -790,7 +42,6 @@ export const statuslineCommand = {
|
|
|
790
42
|
const fs = await import('fs');
|
|
791
43
|
const path = await import('path');
|
|
792
44
|
const { execSync } = await import('child_process');
|
|
793
|
-
// Get learning stats from memory database
|
|
794
45
|
function getLearningStats() {
|
|
795
46
|
const memoryPaths = [
|
|
796
47
|
path.join(process.cwd(), '.swarm', 'memory.db'),
|
|
@@ -809,9 +60,7 @@ export const statuslineCommand = {
|
|
|
809
60
|
trajectories = Math.floor(patterns / 5);
|
|
810
61
|
break;
|
|
811
62
|
}
|
|
812
|
-
catch {
|
|
813
|
-
// Ignore
|
|
814
|
-
}
|
|
63
|
+
catch { /* ignore */ }
|
|
815
64
|
}
|
|
816
65
|
}
|
|
817
66
|
const sessionsPath = path.join(process.cwd(), '.claude', 'sessions');
|
|
@@ -820,13 +69,10 @@ export const statuslineCommand = {
|
|
|
820
69
|
const sessionFiles = fs.readdirSync(sessionsPath).filter((f) => f.endsWith('.json'));
|
|
821
70
|
sessions = Math.max(sessions, sessionFiles.length);
|
|
822
71
|
}
|
|
823
|
-
catch {
|
|
824
|
-
// Ignore
|
|
825
|
-
}
|
|
72
|
+
catch { /* ignore */ }
|
|
826
73
|
}
|
|
827
74
|
return { patterns, sessions, trajectories };
|
|
828
75
|
}
|
|
829
|
-
// Get v1 progress
|
|
830
76
|
function getv1Progress() {
|
|
831
77
|
const learning = getLearningStats();
|
|
832
78
|
let domainsCompleted = 0;
|
|
@@ -844,7 +90,6 @@ export const statuslineCommand = {
|
|
|
844
90
|
const dddProgress = Math.min(100, Math.floor((domainsCompleted / totalDomains) * 100));
|
|
845
91
|
return { domainsCompleted, totalDomains, dddProgress, patternsLearned: learning.patterns, sessionsCompleted: learning.sessions };
|
|
846
92
|
}
|
|
847
|
-
// Get security status
|
|
848
93
|
function getSecurityStatus() {
|
|
849
94
|
const scanResultsPath = path.join(process.cwd(), '.claude', 'security-scans');
|
|
850
95
|
let cvesFixed = 0;
|
|
@@ -854,9 +99,7 @@ export const statuslineCommand = {
|
|
|
854
99
|
const scans = fs.readdirSync(scanResultsPath).filter((f) => f.endsWith('.json'));
|
|
855
100
|
cvesFixed = Math.min(totalCves, scans.length);
|
|
856
101
|
}
|
|
857
|
-
catch {
|
|
858
|
-
// Ignore
|
|
859
|
-
}
|
|
102
|
+
catch { /* ignore */ }
|
|
860
103
|
}
|
|
861
104
|
const auditPath = path.join(process.cwd(), '.swarm', 'security');
|
|
862
105
|
if (fs.existsSync(auditPath)) {
|
|
@@ -864,14 +107,11 @@ export const statuslineCommand = {
|
|
|
864
107
|
const audits = fs.readdirSync(auditPath).filter((f) => f.includes('audit'));
|
|
865
108
|
cvesFixed = Math.min(totalCves, Math.max(cvesFixed, audits.length));
|
|
866
109
|
}
|
|
867
|
-
catch {
|
|
868
|
-
// Ignore
|
|
869
|
-
}
|
|
110
|
+
catch { /* ignore */ }
|
|
870
111
|
}
|
|
871
112
|
const status = cvesFixed >= totalCves ? 'CLEAN' : cvesFixed > 0 ? 'IN_PROGRESS' : 'PENDING';
|
|
872
113
|
return { status, cvesFixed, totalCves };
|
|
873
114
|
}
|
|
874
|
-
// Get swarm status
|
|
875
115
|
function getSwarmStatus() {
|
|
876
116
|
let activeAgents = 0;
|
|
877
117
|
let coordinationActive = false;
|
|
@@ -886,12 +126,9 @@ export const statuslineCommand = {
|
|
|
886
126
|
activeAgents = Math.max(0, isWindows ? raw : raw - 1);
|
|
887
127
|
coordinationActive = activeAgents > 0;
|
|
888
128
|
}
|
|
889
|
-
catch {
|
|
890
|
-
// Ignore
|
|
891
|
-
}
|
|
129
|
+
catch { /* ignore */ }
|
|
892
130
|
return { activeAgents, maxAgents, coordinationActive };
|
|
893
131
|
}
|
|
894
|
-
// Get system metrics
|
|
895
132
|
function getSystemMetrics() {
|
|
896
133
|
let memoryMB = 0;
|
|
897
134
|
let subAgents = 0;
|
|
@@ -899,12 +136,8 @@ export const statuslineCommand = {
|
|
|
899
136
|
try {
|
|
900
137
|
memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
901
138
|
}
|
|
902
|
-
catch {
|
|
903
|
-
// Ignore
|
|
904
|
-
}
|
|
905
|
-
// Calculate intelligence from multiple sources (matching statusline-generator.ts)
|
|
139
|
+
catch { /* ignore */ }
|
|
906
140
|
let intelligencePct = 0;
|
|
907
|
-
// 1. Check learning.json for REAL intelligence metrics first
|
|
908
141
|
const learningJsonPaths = [
|
|
909
142
|
path.join(process.cwd(), '.monomind', 'learning.json'),
|
|
910
143
|
path.join(process.cwd(), '.claude', '.monomind', 'learning.json'),
|
|
@@ -924,16 +157,11 @@ export const statuslineCommand = {
|
|
|
924
157
|
catch { /* ignore */ }
|
|
925
158
|
}
|
|
926
159
|
}
|
|
927
|
-
// 2. Fallback: calculate from patterns and vectors
|
|
928
160
|
if (intelligencePct === 0) {
|
|
929
|
-
|
|
930
|
-
// Will be updated later with vector count
|
|
931
|
-
intelligencePct = fromPatterns;
|
|
161
|
+
intelligencePct = learning.patterns > 0 ? Math.min(100, Math.floor(learning.patterns / 10)) : 0;
|
|
932
162
|
}
|
|
933
|
-
// 3. Fallback: calculate maturity score from project indicators
|
|
934
163
|
if (intelligencePct === 0) {
|
|
935
164
|
let maturityScore = 0;
|
|
936
|
-
// Check for key project files/dirs
|
|
937
165
|
if (fs.existsSync(path.join(process.cwd(), '.claude')))
|
|
938
166
|
maturityScore += 15;
|
|
939
167
|
if (fs.existsSync(path.join(process.cwd(), '.monomind')))
|
|
@@ -944,7 +172,6 @@ export const statuslineCommand = {
|
|
|
944
172
|
maturityScore += 10;
|
|
945
173
|
if (fs.existsSync(path.join(process.cwd(), '.swarm')))
|
|
946
174
|
maturityScore += 10;
|
|
947
|
-
// Check for test files
|
|
948
175
|
const testDirs = ['tests', '__tests__', 'test', 'v1/__tests__'];
|
|
949
176
|
for (const dir of testDirs) {
|
|
950
177
|
if (fs.existsSync(path.join(process.cwd(), dir))) {
|
|
@@ -952,7 +179,6 @@ export const statuslineCommand = {
|
|
|
952
179
|
break;
|
|
953
180
|
}
|
|
954
181
|
}
|
|
955
|
-
// Check for hooks config
|
|
956
182
|
if (fs.existsSync(path.join(process.cwd(), '.claude', 'settings.json')))
|
|
957
183
|
maturityScore += 10;
|
|
958
184
|
intelligencePct = Math.min(100, maturityScore);
|
|
@@ -960,30 +186,22 @@ export const statuslineCommand = {
|
|
|
960
186
|
const contextPct = Math.min(100, Math.floor(learning.sessions * 5));
|
|
961
187
|
return { memoryMB, contextPct, intelligencePct, subAgents };
|
|
962
188
|
}
|
|
963
|
-
// Get user info
|
|
964
189
|
function getUserInfo() {
|
|
965
190
|
let name = 'user';
|
|
966
191
|
let gitBranch = '';
|
|
967
192
|
const modelName = 'Opus 4.6 (1M context)';
|
|
968
193
|
const isWindows = process.platform === 'win32';
|
|
969
194
|
try {
|
|
970
|
-
const nameCmd = isWindows
|
|
971
|
-
|
|
972
|
-
: 'git config user.name 2>/dev/null || echo "user"';
|
|
973
|
-
const branchCmd = isWindows
|
|
974
|
-
? 'git branch --show-current 2>NUL || echo.'
|
|
975
|
-
: 'git branch --show-current 2>/dev/null || echo ""';
|
|
195
|
+
const nameCmd = isWindows ? 'git config user.name 2>NUL || echo user' : 'git config user.name 2>/dev/null || echo "user"';
|
|
196
|
+
const branchCmd = isWindows ? 'git branch --show-current 2>NUL || echo.' : 'git branch --show-current 2>/dev/null || echo ""';
|
|
976
197
|
name = execSync(nameCmd, { encoding: 'utf-8' }).trim();
|
|
977
198
|
gitBranch = execSync(branchCmd, { encoding: 'utf-8' }).trim();
|
|
978
199
|
if (gitBranch === '.')
|
|
979
200
|
gitBranch = '';
|
|
980
201
|
}
|
|
981
|
-
catch {
|
|
982
|
-
// Ignore
|
|
983
|
-
}
|
|
202
|
+
catch { /* ignore */ }
|
|
984
203
|
return { name, gitBranch, modelName };
|
|
985
204
|
}
|
|
986
|
-
// Collect all status
|
|
987
205
|
const progress = getv1Progress();
|
|
988
206
|
const security = getSecurityStatus();
|
|
989
207
|
const swarm = getSwarmStatus();
|
|
@@ -997,18 +215,15 @@ export const statuslineCommand = {
|
|
|
997
215
|
system,
|
|
998
216
|
timestamp: new Date().toISOString()
|
|
999
217
|
};
|
|
1000
|
-
// JSON output
|
|
1001
218
|
if (ctx.flags.json || ctx.flags.format === 'json') {
|
|
1002
219
|
output.printJson(statusData);
|
|
1003
220
|
return { success: true, data: statusData };
|
|
1004
221
|
}
|
|
1005
|
-
// Compact output
|
|
1006
222
|
if (ctx.flags.compact) {
|
|
1007
223
|
const line = `DDD:${progress.domainsCompleted}/${progress.totalDomains} CVE:${security.cvesFixed}/${security.totalCves} Swarm:${swarm.activeAgents}/${swarm.maxAgents} Ctx:${system.contextPct}% Int:${system.intelligencePct}%`;
|
|
1008
224
|
output.writeln(line);
|
|
1009
225
|
return { success: true, data: statusData };
|
|
1010
226
|
}
|
|
1011
|
-
// Full colored output
|
|
1012
227
|
const noColor = ctx.flags['no-color'] || ctx.flags.noColor;
|
|
1013
228
|
const c = noColor ? {
|
|
1014
229
|
reset: '', bold: '', dim: '', red: '', green: '', yellow: '', blue: '',
|
|
@@ -1021,13 +236,11 @@ export const statuslineCommand = {
|
|
|
1021
236
|
brightGreen: '\x1b[1;32m', brightYellow: '\x1b[1;33m', brightBlue: '\x1b[1;34m',
|
|
1022
237
|
brightPurple: '\x1b[1;35m', brightCyan: '\x1b[1;36m', brightWhite: '\x1b[1;37m'
|
|
1023
238
|
};
|
|
1024
|
-
// Progress bar helper
|
|
1025
239
|
const progressBar = (current, total) => {
|
|
1026
240
|
const filled = Math.round((current / total) * 5);
|
|
1027
241
|
const empty = 5 - filled;
|
|
1028
242
|
return '[' + '●'.repeat(filled) + '○'.repeat(empty) + ']';
|
|
1029
243
|
};
|
|
1030
|
-
// Generate lines
|
|
1031
244
|
let header = `${c.bold}${c.brightPurple}▊ Monomind ${c.reset}`;
|
|
1032
245
|
header += `${swarm.coordinationActive ? c.brightCyan : c.dim}● ${c.brightCyan}${user.name}${c.reset}`;
|
|
1033
246
|
if (user.gitBranch) {
|
|
@@ -1035,7 +248,6 @@ export const statuslineCommand = {
|
|
|
1035
248
|
}
|
|
1036
249
|
header += ` ${c.dim}│${c.reset} ${c.purple}${user.modelName}${c.reset}`;
|
|
1037
250
|
const separator = `${c.dim}─────────────────────────────────────────────────────${c.reset}`;
|
|
1038
|
-
// Get hooks stats
|
|
1039
251
|
const hooksStats = { enabled: 0, total: 17 };
|
|
1040
252
|
const settingsPath = path.join(process.cwd(), '.claude', 'settings.json');
|
|
1041
253
|
if (fs.existsSync(settingsPath)) {
|
|
@@ -1049,9 +261,7 @@ export const statuslineCommand = {
|
|
|
1049
261
|
}
|
|
1050
262
|
catch { /* ignore */ }
|
|
1051
263
|
}
|
|
1052
|
-
// Get memory backend stats
|
|
1053
264
|
const memoryStats = { vectorCount: 0, dbSizeKB: 0, hasHnsw: false };
|
|
1054
|
-
// Check for direct database files first
|
|
1055
265
|
const dbPaths = [
|
|
1056
266
|
path.join(process.cwd(), '.swarm', 'memory.db'),
|
|
1057
267
|
path.join(process.cwd(), '.monomind', 'memory.db'),
|
|
@@ -1073,13 +283,11 @@ export const statuslineCommand = {
|
|
|
1073
283
|
catch { /* ignore */ }
|
|
1074
284
|
}
|
|
1075
285
|
}
|
|
1076
|
-
// Check for LanceDB directories if no direct db found
|
|
1077
286
|
if (memoryStats.vectorCount === 0) {
|
|
1078
287
|
const lancedbDirs = [
|
|
1079
288
|
path.join(process.cwd(), ".monomind", "lancedb"),
|
|
1080
289
|
path.join(process.cwd(), ".swarm", "lancedb"),
|
|
1081
290
|
path.join(process.cwd(), "data", "lancedb"),
|
|
1082
|
-
path.join(process.cwd(), ".swarm", "lancedb"),
|
|
1083
291
|
];
|
|
1084
292
|
for (const dir of lancedbDirs) {
|
|
1085
293
|
if (fs.existsSync(dir)) {
|
|
@@ -1087,8 +295,7 @@ export const statuslineCommand = {
|
|
|
1087
295
|
const files = fs.readdirSync(dir);
|
|
1088
296
|
for (const f of files) {
|
|
1089
297
|
if (f.endsWith('.db') || f.endsWith('.sqlite')) {
|
|
1090
|
-
const
|
|
1091
|
-
const fileStat = fs.statSync(filePath);
|
|
298
|
+
const fileStat = fs.statSync(path.join(dir, f));
|
|
1092
299
|
memoryStats.dbSizeKB += Math.round(fileStat.size / 1024);
|
|
1093
300
|
}
|
|
1094
301
|
}
|
|
@@ -1101,7 +308,6 @@ export const statuslineCommand = {
|
|
|
1101
308
|
}
|
|
1102
309
|
}
|
|
1103
310
|
}
|
|
1104
|
-
// Check for HNSW index files
|
|
1105
311
|
const hnswPaths = [
|
|
1106
312
|
path.join(process.cwd(), '.monomind', 'hnsw'),
|
|
1107
313
|
path.join(process.cwd(), '.swarm', 'hnsw'),
|
|
@@ -1115,15 +321,13 @@ export const statuslineCommand = {
|
|
|
1115
321
|
const indexFile = hnswFiles.find(f => f.endsWith('.index'));
|
|
1116
322
|
if (indexFile) {
|
|
1117
323
|
const indexStat = fs.statSync(path.join(hnswPath, indexFile));
|
|
1118
|
-
|
|
1119
|
-
memoryStats.vectorCount = Math.max(memoryStats.vectorCount, hnswVectors);
|
|
324
|
+
memoryStats.vectorCount = Math.max(memoryStats.vectorCount, Math.floor(indexStat.size / 512));
|
|
1120
325
|
}
|
|
1121
326
|
}
|
|
1122
327
|
catch { /* ignore */ }
|
|
1123
328
|
break;
|
|
1124
329
|
}
|
|
1125
330
|
}
|
|
1126
|
-
// Check for vectors.json file
|
|
1127
331
|
const vectorsPath = path.join(process.cwd(), '.monomind', 'vectors.json');
|
|
1128
332
|
if (fs.existsSync(vectorsPath) && memoryStats.vectorCount === 0) {
|
|
1129
333
|
try {
|
|
@@ -1139,21 +343,18 @@ export const statuslineCommand = {
|
|
|
1139
343
|
}
|
|
1140
344
|
catch { /* ignore */ }
|
|
1141
345
|
}
|
|
1142
|
-
// Get test stats
|
|
1143
346
|
const testStats = { testFiles: 0, testCases: 0 };
|
|
1144
|
-
const
|
|
1145
|
-
for (const testPath of testPaths) {
|
|
347
|
+
for (const testPath of ['tests', '__tests__', 'test', 'spec']) {
|
|
1146
348
|
const fullPath = path.join(process.cwd(), testPath);
|
|
1147
349
|
if (fs.existsSync(fullPath)) {
|
|
1148
350
|
try {
|
|
1149
351
|
const files = fs.readdirSync(fullPath, { recursive: true });
|
|
1150
352
|
testStats.testFiles = files.filter((f) => /\.(test|spec)\.(ts|js|tsx|jsx)$/.test(f)).length;
|
|
1151
|
-
testStats.testCases = testStats.testFiles * 28;
|
|
353
|
+
testStats.testCases = testStats.testFiles * 28;
|
|
1152
354
|
}
|
|
1153
355
|
catch { /* ignore */ }
|
|
1154
356
|
}
|
|
1155
357
|
}
|
|
1156
|
-
// Get MCP stats
|
|
1157
358
|
const mcpStats = { enabled: 0, total: 0 };
|
|
1158
359
|
const mcpPath = path.join(process.cwd(), '.mcp.json');
|
|
1159
360
|
if (fs.existsSync(mcpPath)) {
|
|
@@ -1167,7 +368,6 @@ export const statuslineCommand = {
|
|
|
1167
368
|
catch { /* ignore */ }
|
|
1168
369
|
}
|
|
1169
370
|
const domainsColor = progress.domainsCompleted >= 3 ? c.brightGreen : progress.domainsCompleted > 0 ? c.yellow : c.red;
|
|
1170
|
-
// Dynamic perf indicator based on patterns/HNSW
|
|
1171
371
|
let perfIndicator = `${c.dim}⚡ HNSW: idle${c.reset}`;
|
|
1172
372
|
if (memoryStats.hasHnsw && memoryStats.vectorCount > 0) {
|
|
1173
373
|
perfIndicator = `${c.brightGreen}⚡ HNSW ${memoryStats.vectorCount.toLocaleString()} vec${c.reset}`;
|