murmur8 4.2.0 → 4.3.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/.blueprint/agents/AGENT_SPECIFICATION_ALEX.md +33 -3
- package/.blueprint/features/feature_config-factory/FEATURE_SPEC.md +138 -0
- package/.blueprint/features/feature_config-factory/IMPLEMENTATION_PLAN.md +187 -0
- package/.blueprint/features/feature_config-factory/handoff-nigel.md +57 -0
- package/.blueprint/features/feature_cost-tracking/FEATURE_SPEC.md +216 -0
- package/.blueprint/features/feature_cost-tracking/IMPLEMENTATION_PLAN.md +50 -0
- package/.blueprint/features/feature_diff-preview/FEATURE_SPEC.md +182 -0
- package/.blueprint/features/feature_diff-preview/IMPLEMENTATION_PLAN.md +42 -0
- package/.blueprint/features/feature_extract-prompt-util/FEATURE_SPEC.md +42 -0
- package/.blueprint/features/feature_fix-status-icons/FEATURE_SPEC.md +37 -0
- package/.blueprint/features/feature_murm-subagent/FEATURE_SPEC.md +137 -0
- package/.blueprint/features/feature_murm-subagent/SKILL_CHANGES.md +345 -0
- package/.blueprint/features/feature_split-cli-commands/FEATURE_SPEC.md +125 -0
- package/.blueprint/features/feature_split-cli-commands/IMPLEMENTATION_PLAN.md +119 -0
- package/.blueprint/features/feature_split-cli-commands/handoff-nigel.md +45 -0
- package/.blueprint/features/feature_theme-adoption/FEATURE_SPEC.md +143 -0
- package/.blueprint/features/feature_theme-adoption/IMPLEMENTATION_PLAN.md +68 -0
- package/.blueprint/features/feature_theme-adoption/handoff-nigel.md +35 -0
- package/.blueprint/templates/BACKLOG_TEMPLATE.md +46 -0
- package/README.md +79 -12
- package/SKILL.md +377 -3
- package/bin/cli.js +20 -411
- package/package.json +1 -1
- package/src/commands/cost-config.js +28 -0
- package/src/commands/feedback-config.js +32 -0
- package/src/commands/help.js +86 -0
- package/src/commands/history.js +42 -0
- package/src/commands/init.js +12 -0
- package/src/commands/insights.js +23 -0
- package/src/commands/murm-config.js +52 -0
- package/src/commands/murm.js +109 -0
- package/src/commands/queue.js +19 -0
- package/src/commands/retry-config.js +28 -0
- package/src/commands/stack-config.js +32 -0
- package/src/commands/update.js +12 -0
- package/src/commands/utils.js +25 -0
- package/src/commands/validate.js +15 -0
- package/src/config-factory.js +190 -0
- package/src/cost.js +122 -0
- package/src/diff-preview.js +165 -0
- package/src/feedback.js +5 -2
- package/src/history.js +51 -3
- package/src/index.js +25 -0
- package/src/init.js +1 -15
- package/src/insights.js +19 -16
- package/src/retry.js +5 -2
- package/src/stack.js +4 -1
- package/src/theme.js +6 -5
- package/src/update.js +2 -15
- package/src/utils.js +26 -0
- package/src/validate.js +5 -12
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function parseGitStatus(porcelainOutput) {
|
|
4
|
+
const added = [];
|
|
5
|
+
const modified = [];
|
|
6
|
+
const deleted = [];
|
|
7
|
+
|
|
8
|
+
if (!porcelainOutput || porcelainOutput.trim() === '') {
|
|
9
|
+
return { added, modified, deleted, total: 0 };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const lines = porcelainOutput.trim().split('\n');
|
|
13
|
+
for (const line of lines) {
|
|
14
|
+
if (line.length < 3) continue;
|
|
15
|
+
|
|
16
|
+
const statusCode = line.substring(0, 2);
|
|
17
|
+
const filePath = line.substring(3);
|
|
18
|
+
|
|
19
|
+
if (statusCode === 'A ' || statusCode === '??') {
|
|
20
|
+
added.push(filePath);
|
|
21
|
+
} else if (statusCode === 'M ' || statusCode === ' M') {
|
|
22
|
+
modified.push(filePath);
|
|
23
|
+
} else if (statusCode === 'D ' || statusCode === ' D') {
|
|
24
|
+
deleted.push(filePath);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
added,
|
|
30
|
+
modified,
|
|
31
|
+
deleted,
|
|
32
|
+
total: added.length + modified.length + deleted.length
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function formatDiffSummary(changes, slug) {
|
|
37
|
+
const lines = [];
|
|
38
|
+
const MAX_FILES = 20;
|
|
39
|
+
|
|
40
|
+
lines.push(formatFeatureHeader(slug));
|
|
41
|
+
lines.push('');
|
|
42
|
+
|
|
43
|
+
const addedLabel = `Added (${changes.added.length} file${changes.added.length === 1 ? '' : 's'})`;
|
|
44
|
+
lines.push(addedLabel);
|
|
45
|
+
if (changes.added.length === 0) {
|
|
46
|
+
lines.push(' (none)');
|
|
47
|
+
} else {
|
|
48
|
+
const displayFiles = changes.added.slice(0, MAX_FILES);
|
|
49
|
+
for (const file of displayFiles) {
|
|
50
|
+
lines.push(` + ${file}`);
|
|
51
|
+
}
|
|
52
|
+
if (changes.added.length > MAX_FILES) {
|
|
53
|
+
lines.push(` ... and ${changes.added.length - MAX_FILES} more`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
lines.push('');
|
|
57
|
+
|
|
58
|
+
const modifiedLabel = `Modified (${changes.modified.length} file${changes.modified.length === 1 ? '' : 's'})`;
|
|
59
|
+
lines.push(modifiedLabel);
|
|
60
|
+
if (changes.modified.length === 0) {
|
|
61
|
+
lines.push(' (none)');
|
|
62
|
+
} else {
|
|
63
|
+
const displayFiles = changes.modified.slice(0, MAX_FILES);
|
|
64
|
+
for (const file of displayFiles) {
|
|
65
|
+
lines.push(` ~ ${file}`);
|
|
66
|
+
}
|
|
67
|
+
if (changes.modified.length > MAX_FILES) {
|
|
68
|
+
lines.push(` ... and ${changes.modified.length - MAX_FILES} more`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
lines.push('');
|
|
72
|
+
|
|
73
|
+
const deletedLabel = `Deleted (${changes.deleted.length} file${changes.deleted.length === 1 ? '' : 's'})`;
|
|
74
|
+
lines.push(deletedLabel);
|
|
75
|
+
if (changes.deleted.length === 0) {
|
|
76
|
+
lines.push(' (none)');
|
|
77
|
+
} else {
|
|
78
|
+
const displayFiles = changes.deleted.slice(0, MAX_FILES);
|
|
79
|
+
for (const file of displayFiles) {
|
|
80
|
+
lines.push(` - ${file}`);
|
|
81
|
+
}
|
|
82
|
+
if (changes.deleted.length > MAX_FILES) {
|
|
83
|
+
lines.push(` ... and ${changes.deleted.length - MAX_FILES} more`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
lines.push('');
|
|
87
|
+
|
|
88
|
+
lines.push(`Total: ${changes.total} files changed`);
|
|
89
|
+
|
|
90
|
+
return lines.join('\n');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function formatFeatureHeader(slug) {
|
|
94
|
+
return `--- Changes to commit for feature: ${slug} ---`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function hasChanges(changes) {
|
|
98
|
+
return changes.total > 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function shouldSkipPreview({ noCommit, noDiffPreview, yes, hasChanges }) {
|
|
102
|
+
if (noCommit) return true;
|
|
103
|
+
if (noDiffPreview) return true;
|
|
104
|
+
if (yes) return true;
|
|
105
|
+
if (!hasChanges) return true;
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function parseUserChoice(input) {
|
|
110
|
+
if (!input || typeof input !== 'string') return null;
|
|
111
|
+
const normalized = input.toLowerCase().trim();
|
|
112
|
+
if (normalized === 'c') return 'commit';
|
|
113
|
+
if (normalized === 'a') return 'abort';
|
|
114
|
+
if (normalized === 'd') return 'diff';
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function createAbortResult(slug) {
|
|
119
|
+
return {
|
|
120
|
+
exitCode: 0,
|
|
121
|
+
reason: 'user-aborted',
|
|
122
|
+
slug
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function truncateDiff(diffOutput, threshold) {
|
|
127
|
+
if (!diffOutput) return diffOutput;
|
|
128
|
+
const lines = diffOutput.split('\n');
|
|
129
|
+
if (lines.length <= threshold) {
|
|
130
|
+
return diffOutput;
|
|
131
|
+
}
|
|
132
|
+
const truncated = lines.slice(0, threshold);
|
|
133
|
+
const remaining = lines.length - threshold;
|
|
134
|
+
truncated.push(`... ${remaining} more lines`);
|
|
135
|
+
return truncated.join('\n');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getPreviewState() {
|
|
139
|
+
return 'awaiting-commit-review';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function markWorktreeAborted(worktree) {
|
|
143
|
+
return {
|
|
144
|
+
...worktree,
|
|
145
|
+
status: 'user-aborted'
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getPromptText() {
|
|
150
|
+
return '[c]ommit / [a]bort / [d]iff';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
parseGitStatus,
|
|
155
|
+
formatDiffSummary,
|
|
156
|
+
formatFeatureHeader,
|
|
157
|
+
hasChanges,
|
|
158
|
+
shouldSkipPreview,
|
|
159
|
+
parseUserChoice,
|
|
160
|
+
createAbortResult,
|
|
161
|
+
truncateDiff,
|
|
162
|
+
getPreviewState,
|
|
163
|
+
markWorktreeAborted,
|
|
164
|
+
getPromptText
|
|
165
|
+
};
|
package/src/feedback.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { colorize } = require('./theme');
|
|
3
4
|
|
|
4
5
|
const CONFIG_FILE = '.claude/feedback-config.json';
|
|
5
6
|
|
|
@@ -169,10 +170,12 @@ function setConfigValue(key, value) {
|
|
|
169
170
|
*/
|
|
170
171
|
function displayConfig() {
|
|
171
172
|
const config = readConfig();
|
|
172
|
-
|
|
173
|
+
const useColor = process.stdout.isTTY;
|
|
174
|
+
|
|
175
|
+
console.log('\n' + colorize('Feedback Configuration', 'cyan', useColor) + '\n');
|
|
173
176
|
console.log(` Min rating threshold: ${config.minRatingThreshold}`);
|
|
174
177
|
console.log(` Enabled: ${config.enabled}`);
|
|
175
|
-
console.log('\n Issue Mappings:');
|
|
178
|
+
console.log('\n ' + colorize('Issue Mappings:', 'cyan', useColor));
|
|
176
179
|
for (const [issue, strategy] of Object.entries(config.issueMappings)) {
|
|
177
180
|
console.log(` ${issue.padEnd(24)}: ${strategy}`);
|
|
178
181
|
}
|
package/src/history.js
CHANGED
|
@@ -195,8 +195,14 @@ function formatDate(isoString) {
|
|
|
195
195
|
return date.toISOString().replace('T', ' ').slice(0, 19);
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
function formatCostValue(cost) {
|
|
199
|
+
if (cost === null || cost === undefined) return 'N/A';
|
|
200
|
+
return `$${cost.toFixed(3)}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
198
203
|
function displayHistory(options = {}) {
|
|
199
204
|
const showAll = options.all || false;
|
|
205
|
+
const showCost = options.cost || false;
|
|
200
206
|
const useColor = options.color !== false && process.stdout.isTTY;
|
|
201
207
|
|
|
202
208
|
const history = readHistoryFile();
|
|
@@ -220,7 +226,11 @@ function displayHistory(options = {}) {
|
|
|
220
226
|
const showing = entries.length;
|
|
221
227
|
|
|
222
228
|
console.log(`\nPipeline History (showing ${showing} of ${total} runs)\n`);
|
|
223
|
-
|
|
229
|
+
if (showCost) {
|
|
230
|
+
console.log(' SLUG STATUS DURATION TOTAL COST');
|
|
231
|
+
} else {
|
|
232
|
+
console.log(' SLUG STATUS DATE DURATION');
|
|
233
|
+
}
|
|
224
234
|
|
|
225
235
|
for (const entry of entries) {
|
|
226
236
|
const slug = entry.slug.padEnd(18);
|
|
@@ -243,7 +253,12 @@ function displayHistory(options = {}) {
|
|
|
243
253
|
suffix = ` (paused at: ${entry.pausedAfter})`;
|
|
244
254
|
}
|
|
245
255
|
|
|
246
|
-
|
|
256
|
+
if (showCost) {
|
|
257
|
+
const costDisplay = formatCostValue(entry.totalCost).padEnd(10);
|
|
258
|
+
console.log(` ${slug} ${status} ${duration.padEnd(9)} ${costDisplay}${suffix}`);
|
|
259
|
+
} else {
|
|
260
|
+
console.log(` ${slug} ${status} ${date} ${duration}${suffix}`);
|
|
261
|
+
}
|
|
247
262
|
}
|
|
248
263
|
|
|
249
264
|
if (!showAll && total > 10) {
|
|
@@ -252,7 +267,8 @@ function displayHistory(options = {}) {
|
|
|
252
267
|
console.log(`Run 'murmur8 history --stats' for aggregate statistics.`);
|
|
253
268
|
}
|
|
254
269
|
|
|
255
|
-
function showStats() {
|
|
270
|
+
function showStats(options = {}) {
|
|
271
|
+
const showCost = options.cost || false;
|
|
256
272
|
const history = readHistoryFile();
|
|
257
273
|
|
|
258
274
|
if (history.error === 'corrupted') {
|
|
@@ -285,6 +301,38 @@ function showStats() {
|
|
|
285
301
|
console.log(` Avg pipeline duration ${formatDuration(avgTotal)}`);
|
|
286
302
|
}
|
|
287
303
|
|
|
304
|
+
if (showCost) {
|
|
305
|
+
const runsWithCost = history.filter(e => e.totalCost !== undefined);
|
|
306
|
+
if (runsWithCost.length > 0) {
|
|
307
|
+
const totalCostAll = runsWithCost.reduce((sum, e) => sum + e.totalCost, 0);
|
|
308
|
+
const avgCost = totalCostAll / runsWithCost.length;
|
|
309
|
+
console.log(` Avg cost per run ${formatCostValue(avgCost)}`);
|
|
310
|
+
console.log(` Total cost (all runs) ${formatCostValue(totalCostAll)}`);
|
|
311
|
+
|
|
312
|
+
const stages = ['alex', 'cass', 'nigel', 'codey-plan', 'codey-implement'];
|
|
313
|
+
const stageCosts = {};
|
|
314
|
+
for (const stage of stages) {
|
|
315
|
+
stageCosts[stage] = 0;
|
|
316
|
+
}
|
|
317
|
+
for (const entry of runsWithCost) {
|
|
318
|
+
if (entry.stages) {
|
|
319
|
+
for (const stage of stages) {
|
|
320
|
+
if (entry.stages[stage] && entry.stages[stage].cost !== undefined) {
|
|
321
|
+
stageCosts[stage] += entry.stages[stage].cost;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const maxCost = Math.max(...Object.values(stageCosts));
|
|
327
|
+
const mostExpensive = Object.entries(stageCosts)
|
|
328
|
+
.filter(([, cost]) => cost === maxCost)
|
|
329
|
+
.map(([stage]) => stage);
|
|
330
|
+
if (mostExpensive.length > 0 && maxCost > 0) {
|
|
331
|
+
console.log(` Most expensive stage ${mostExpensive[0]} (${formatCostValue(maxCost)} total)`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
288
336
|
const stages = ['alex', 'cass', 'nigel', 'codey-plan', 'codey-implement'];
|
|
289
337
|
const stageStats = {};
|
|
290
338
|
const failureCounts = {};
|
package/src/index.js
CHANGED
|
@@ -81,6 +81,19 @@ const {
|
|
|
81
81
|
detectStackConfig,
|
|
82
82
|
displayStackConfig
|
|
83
83
|
} = require('./stack');
|
|
84
|
+
const {
|
|
85
|
+
parseGitStatus,
|
|
86
|
+
formatDiffSummary,
|
|
87
|
+
formatFeatureHeader,
|
|
88
|
+
hasChanges,
|
|
89
|
+
shouldSkipPreview,
|
|
90
|
+
parseUserChoice,
|
|
91
|
+
createAbortResult,
|
|
92
|
+
truncateDiff,
|
|
93
|
+
getPreviewState,
|
|
94
|
+
markWorktreeAborted,
|
|
95
|
+
getPromptText
|
|
96
|
+
} = require('./diff-preview');
|
|
84
97
|
const tools = require('./tools');
|
|
85
98
|
const theme = require('./theme');
|
|
86
99
|
|
|
@@ -149,6 +162,18 @@ module.exports = {
|
|
|
149
162
|
tools,
|
|
150
163
|
// Theme module (murmuration visual theming)
|
|
151
164
|
theme,
|
|
165
|
+
// Diff preview exports
|
|
166
|
+
parseGitStatus,
|
|
167
|
+
formatDiffSummary,
|
|
168
|
+
formatFeatureHeader,
|
|
169
|
+
hasChanges,
|
|
170
|
+
shouldSkipPreview,
|
|
171
|
+
parseUserChoice,
|
|
172
|
+
createAbortResult,
|
|
173
|
+
truncateDiff,
|
|
174
|
+
getPreviewState,
|
|
175
|
+
markWorktreeAborted,
|
|
176
|
+
getPromptText,
|
|
152
177
|
// Interactive mode exports
|
|
153
178
|
parseInteractiveFlags,
|
|
154
179
|
shouldEnterInteractiveMode,
|
package/src/init.js
CHANGED
|
@@ -1,26 +1,12 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const readline = require('readline');
|
|
4
3
|
|
|
5
4
|
const { detectStackConfig, writeStackConfig, CONFIG_FILE: STACK_CONFIG_FILE } = require('./stack');
|
|
5
|
+
const { prompt } = require('./utils');
|
|
6
6
|
|
|
7
7
|
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
8
8
|
const TARGET_DIR = process.cwd();
|
|
9
9
|
|
|
10
|
-
async function prompt(question) {
|
|
11
|
-
const rl = readline.createInterface({
|
|
12
|
-
input: process.stdin,
|
|
13
|
-
output: process.stdout
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
return new Promise((resolve) => {
|
|
17
|
-
rl.question(question, (answer) => {
|
|
18
|
-
rl.close();
|
|
19
|
-
resolve(answer.toLowerCase().trim());
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
10
|
function copyDir(src, dest) {
|
|
25
11
|
fs.mkdirSync(dest, { recursive: true });
|
|
26
12
|
|
package/src/insights.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { readHistoryFile, formatDuration } = require('./history');
|
|
2
|
+
const { colorize } = require('./theme');
|
|
2
3
|
|
|
3
4
|
const STAGES = ['alex', 'cass', 'nigel', 'codey-plan', 'codey-implement'];
|
|
4
5
|
|
|
@@ -229,8 +230,8 @@ function analyzeTrends(history) {
|
|
|
229
230
|
};
|
|
230
231
|
}
|
|
231
232
|
|
|
232
|
-
function formatTextOutput(analysis, sections) {
|
|
233
|
-
const lines = ['\
|
|
233
|
+
function formatTextOutput(analysis, sections, useColor = false) {
|
|
234
|
+
const lines = ['\n' + colorize('Pipeline Insights', 'cyan', useColor) + '\n'];
|
|
234
235
|
|
|
235
236
|
const showAll = sections.length === 0;
|
|
236
237
|
const showBottlenecks = showAll || sections.includes('bottlenecks');
|
|
@@ -239,24 +240,24 @@ function formatTextOutput(analysis, sections) {
|
|
|
239
240
|
const showTrends = showAll || sections.includes('trends');
|
|
240
241
|
|
|
241
242
|
if (showBottlenecks) {
|
|
242
|
-
lines.push('BOTTLENECK ANALYSIS');
|
|
243
|
+
lines.push(colorize('BOTTLENECK ANALYSIS', 'cyan', useColor));
|
|
243
244
|
if (analysis.bottlenecks.insufficientData) {
|
|
244
245
|
lines.push(` ${analysis.bottlenecks.message}`);
|
|
245
246
|
} else {
|
|
246
247
|
lines.push(` Slowest stage: ${analysis.bottlenecks.bottleneckStage} (${analysis.bottlenecks.percentage}% of pipeline)`);
|
|
247
248
|
lines.push(` Average duration: ${formatDuration(analysis.bottlenecks.avgDurationMs)}`);
|
|
248
249
|
if (analysis.bottlenecks.isBottleneck) {
|
|
249
|
-
lines.push(' Status: BOTTLENECK DETECTED');
|
|
250
|
+
lines.push(' Status: ' + colorize('BOTTLENECK DETECTED', 'yellow', useColor));
|
|
250
251
|
}
|
|
251
252
|
if (analysis.bottlenecks.recommendation) {
|
|
252
|
-
lines.push(` Recommendation: ${analysis.bottlenecks.recommendation}`);
|
|
253
|
+
lines.push(` Recommendation: ${colorize(analysis.bottlenecks.recommendation, 'yellow', useColor)}`);
|
|
253
254
|
}
|
|
254
255
|
}
|
|
255
256
|
lines.push('');
|
|
256
257
|
}
|
|
257
258
|
|
|
258
259
|
if (showFailures) {
|
|
259
|
-
lines.push('FAILURE PATTERNS');
|
|
260
|
+
lines.push(colorize('FAILURE PATTERNS', 'cyan', useColor));
|
|
260
261
|
if (analysis.failures.noFailures) {
|
|
261
262
|
lines.push(` ${analysis.failures.message}`);
|
|
262
263
|
} else {
|
|
@@ -269,14 +270,14 @@ function formatTextOutput(analysis, sections) {
|
|
|
269
270
|
}
|
|
270
271
|
}
|
|
271
272
|
if (analysis.failures.recommendation) {
|
|
272
|
-
lines.push(` Recommendation: ${analysis.failures.recommendation}`);
|
|
273
|
+
lines.push(` Recommendation: ${colorize(analysis.failures.recommendation, 'yellow', useColor)}`);
|
|
273
274
|
}
|
|
274
275
|
}
|
|
275
276
|
lines.push('');
|
|
276
277
|
}
|
|
277
278
|
|
|
278
279
|
if (showAnomalies) {
|
|
279
|
-
lines.push('ANOMALY DETECTION');
|
|
280
|
+
lines.push(colorize('ANOMALY DETECTION', 'cyan', useColor));
|
|
280
281
|
if (analysis.anomalies.insufficientData) {
|
|
281
282
|
lines.push(` ${analysis.anomalies.message}`);
|
|
282
283
|
} else if (analysis.anomalies.noAnomalies) {
|
|
@@ -287,14 +288,14 @@ function formatTextOutput(analysis, sections) {
|
|
|
287
288
|
lines.push(` - ${a.slug}/${a.stage}: ${formatDuration(a.actual)} (expected ~${formatDuration(a.expected)}, ${a.deviation}x stddev)`);
|
|
288
289
|
}
|
|
289
290
|
if (analysis.anomalies.recommendation) {
|
|
290
|
-
lines.push(` Recommendation: ${analysis.anomalies.recommendation}`);
|
|
291
|
+
lines.push(` Recommendation: ${colorize(analysis.anomalies.recommendation, 'yellow', useColor)}`);
|
|
291
292
|
}
|
|
292
293
|
}
|
|
293
294
|
lines.push('');
|
|
294
295
|
}
|
|
295
296
|
|
|
296
297
|
if (showTrends) {
|
|
297
|
-
lines.push('TREND ANALYSIS');
|
|
298
|
+
lines.push(colorize('TREND ANALYSIS', 'cyan', useColor));
|
|
298
299
|
if (analysis.trends.insufficientData) {
|
|
299
300
|
lines.push(` ${analysis.trends.message}`);
|
|
300
301
|
} else {
|
|
@@ -303,7 +304,7 @@ function formatTextOutput(analysis, sections) {
|
|
|
303
304
|
lines.push(` Success rate: ${sr.trend} (${sr.change > 0 ? '+' : ''}${sr.change}%)`);
|
|
304
305
|
lines.push(` Duration: ${dr.trend} (${dr.change > 0 ? '+' : ''}${dr.change}%)`);
|
|
305
306
|
if (analysis.trends.recommendation) {
|
|
306
|
-
lines.push(` Recommendation: ${analysis.trends.recommendation}`);
|
|
307
|
+
lines.push(` Recommendation: ${colorize(analysis.trends.recommendation, 'yellow', useColor)}`);
|
|
307
308
|
}
|
|
308
309
|
}
|
|
309
310
|
lines.push('');
|
|
@@ -334,6 +335,7 @@ function formatJsonOutput(analysis, sections) {
|
|
|
334
335
|
|
|
335
336
|
function displayInsights(options = {}) {
|
|
336
337
|
const history = readHistoryFile();
|
|
338
|
+
const useColor = options.color !== false && process.stdout.isTTY;
|
|
337
339
|
|
|
338
340
|
if (history.error === 'corrupted') {
|
|
339
341
|
console.log("Warning: History file is corrupted. Run 'murmur8 history clear' to reset.");
|
|
@@ -359,7 +361,7 @@ function displayInsights(options = {}) {
|
|
|
359
361
|
if (options.json) {
|
|
360
362
|
console.log(formatJsonOutput(analysis, sections));
|
|
361
363
|
} else {
|
|
362
|
-
console.log(formatTextOutput(analysis, sections));
|
|
364
|
+
console.log(formatTextOutput(analysis, sections, useColor));
|
|
363
365
|
}
|
|
364
366
|
}
|
|
365
367
|
|
|
@@ -437,6 +439,7 @@ function recommendThreshold(history) {
|
|
|
437
439
|
*/
|
|
438
440
|
function displayFeedbackInsights(options = {}) {
|
|
439
441
|
const history = readHistoryFile();
|
|
442
|
+
const useColor = options.color !== false && process.stdout.isTTY;
|
|
440
443
|
|
|
441
444
|
if (history.error === 'corrupted') {
|
|
442
445
|
console.log("Warning: History file is corrupted.");
|
|
@@ -448,10 +451,10 @@ function displayFeedbackInsights(options = {}) {
|
|
|
448
451
|
return;
|
|
449
452
|
}
|
|
450
453
|
|
|
451
|
-
console.log('\
|
|
454
|
+
console.log('\n' + colorize('Feedback Insights', 'cyan', useColor) + '\n');
|
|
452
455
|
|
|
453
456
|
// Agent calibration
|
|
454
|
-
console.log('AGENT CALIBRATION');
|
|
457
|
+
console.log(colorize('AGENT CALIBRATION', 'cyan', useColor));
|
|
455
458
|
for (const agent of ['alex', 'cass', 'nigel']) {
|
|
456
459
|
const calibration = calculateCalibration(agent, history);
|
|
457
460
|
if (calibration === null) {
|
|
@@ -466,7 +469,7 @@ function displayFeedbackInsights(options = {}) {
|
|
|
466
469
|
// Issue correlations
|
|
467
470
|
const correlations = correlateIssues(history);
|
|
468
471
|
if (Object.keys(correlations).length > 0) {
|
|
469
|
-
console.log('ISSUE CORRELATIONS');
|
|
472
|
+
console.log(colorize('ISSUE CORRELATIONS', 'cyan', useColor));
|
|
470
473
|
const sorted = Object.entries(correlations)
|
|
471
474
|
.sort(([, a], [, b]) => b - a);
|
|
472
475
|
for (const [issue, corr] of sorted) {
|
|
@@ -482,7 +485,7 @@ function displayFeedbackInsights(options = {}) {
|
|
|
482
485
|
);
|
|
483
486
|
if (entriesWithFeedback.length >= 10) {
|
|
484
487
|
const recommended = recommendThreshold(history);
|
|
485
|
-
console.log('RECOMMENDATIONS');
|
|
488
|
+
console.log(colorize('RECOMMENDATIONS', 'cyan', useColor));
|
|
486
489
|
console.log(` Suggested minRatingThreshold: ${recommended}`);
|
|
487
490
|
console.log('');
|
|
488
491
|
}
|
package/src/retry.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { readHistoryFile } = require('./history');
|
|
4
|
+
const { colorize } = require('./theme');
|
|
4
5
|
|
|
5
6
|
const CONFIG_FILE = '.claude/retry-config.json';
|
|
6
7
|
|
|
@@ -179,11 +180,13 @@ function shouldRetry(stage, featureSlug, history, config, attemptCount) {
|
|
|
179
180
|
*/
|
|
180
181
|
function displayConfig() {
|
|
181
182
|
const config = readConfig();
|
|
182
|
-
|
|
183
|
+
const useColor = process.stdout.isTTY;
|
|
184
|
+
|
|
185
|
+
console.log('\n' + colorize('Retry Configuration', 'cyan', useColor) + '\n');
|
|
183
186
|
console.log(` Max retries: ${config.maxRetries}`);
|
|
184
187
|
console.log(` Window size: ${config.windowSize}`);
|
|
185
188
|
console.log(` High failure threshold: ${config.highFailureThreshold}`);
|
|
186
|
-
console.log('\n Stage Strategies:');
|
|
189
|
+
console.log('\n ' + colorize('Stage Strategies:', 'cyan', useColor));
|
|
187
190
|
for (const [stage, strategies] of Object.entries(config.strategies)) {
|
|
188
191
|
console.log(` ${stage.padEnd(16)}: ${strategies.join(' -> ')}`);
|
|
189
192
|
}
|
package/src/stack.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { colorize } = require('./theme');
|
|
3
4
|
|
|
4
5
|
const CONFIG_FILE = '.claude/stack-config.json';
|
|
5
6
|
|
|
@@ -296,7 +297,9 @@ function detectStackConfig(projectDir) {
|
|
|
296
297
|
*/
|
|
297
298
|
function displayStackConfig() {
|
|
298
299
|
const config = readStackConfig();
|
|
299
|
-
|
|
300
|
+
const useColor = process.stdout.isTTY;
|
|
301
|
+
|
|
302
|
+
console.log('\n' + colorize('Stack Configuration', 'cyan', useColor) + '\n');
|
|
300
303
|
console.log(` language: ${config.language || '(not set)'}`);
|
|
301
304
|
console.log(` runtime: ${config.runtime || '(not set)'}`);
|
|
302
305
|
console.log(` packageManager: ${config.packageManager || '(not set)'}`);
|
package/src/theme.js
CHANGED
|
@@ -43,14 +43,15 @@ const STAGE_NAMES = {
|
|
|
43
43
|
// --- Status Icons (replacing emoji) ---
|
|
44
44
|
|
|
45
45
|
const STATUS_ICONS = {
|
|
46
|
-
'
|
|
46
|
+
'murm_queued': '\u00b7', // ·
|
|
47
47
|
'worktree_created': '\u25cb', // ○
|
|
48
|
-
'
|
|
48
|
+
'murm_running': '\u25d4', // ◔
|
|
49
49
|
'merge_pending': '\u25d1', // ◑
|
|
50
|
-
'
|
|
51
|
-
'
|
|
50
|
+
'murm_complete': '\u2713', // ✓
|
|
51
|
+
'murm_failed': '\u2717', // ✗
|
|
52
52
|
'merge_conflict': '\u26a0', // ⚠
|
|
53
|
-
'aborted': '\u25a0'
|
|
53
|
+
'aborted': '\u25a0', // ■
|
|
54
|
+
'user-aborted': '\u25a1' // □
|
|
54
55
|
};
|
|
55
56
|
|
|
56
57
|
// --- Spinner ---
|
package/src/update.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
const { prompt } = require('./utils');
|
|
4
5
|
|
|
5
6
|
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
6
7
|
const TARGET_DIR = process.cwd();
|
|
@@ -18,20 +19,6 @@ const UPDATABLE = [
|
|
|
18
19
|
'ways_of_working'
|
|
19
20
|
];
|
|
20
21
|
|
|
21
|
-
async function prompt(question) {
|
|
22
|
-
const rl = readline.createInterface({
|
|
23
|
-
input: process.stdin,
|
|
24
|
-
output: process.stdout
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
return new Promise((resolve) => {
|
|
28
|
-
rl.question(question, (answer) => {
|
|
29
|
-
rl.close();
|
|
30
|
-
resolve(answer.toLowerCase().trim());
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
22
|
function copyDir(src, dest) {
|
|
36
23
|
fs.mkdirSync(dest, { recursive: true });
|
|
37
24
|
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Prompts the user with a question and returns the answer.
|
|
7
|
+
* @param {string} question - The question to ask
|
|
8
|
+
* @returns {Promise<string>} The answer, lowercased and trimmed
|
|
9
|
+
*/
|
|
10
|
+
async function prompt(question) {
|
|
11
|
+
const rl = readline.createInterface({
|
|
12
|
+
input: process.stdin,
|
|
13
|
+
output: process.stdout
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
rl.question(question, (answer) => {
|
|
18
|
+
rl.close();
|
|
19
|
+
resolve(answer.toLowerCase().trim());
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
prompt
|
|
26
|
+
};
|
package/src/validate.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { colorize } = require('./theme');
|
|
3
4
|
|
|
4
5
|
const REQUIRED_DIRS = ['.blueprint', '.business_context', '.claude/commands'];
|
|
5
6
|
const AGENT_FILES = [
|
|
@@ -135,12 +136,8 @@ async function validate() {
|
|
|
135
136
|
function formatOutput(result, useColor = false) {
|
|
136
137
|
const lines = [];
|
|
137
138
|
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
const reset = useColor ? '\x1b[0m' : '';
|
|
141
|
-
|
|
142
|
-
const passIndicator = useColor ? `${green}\u2713${reset}` : '[PASS]';
|
|
143
|
-
const failIndicator = useColor ? `${red}\u2717${reset}` : '[FAIL]';
|
|
139
|
+
const passIndicator = useColor ? colorize('\u2713', 'green', useColor) : '[PASS]';
|
|
140
|
+
const failIndicator = useColor ? colorize('\u2717', 'red', useColor) : '[FAIL]';
|
|
144
141
|
|
|
145
142
|
for (const check of result.checks) {
|
|
146
143
|
const indicator = check.passed ? passIndicator : failIndicator;
|
|
@@ -152,14 +149,10 @@ function formatOutput(result, useColor = false) {
|
|
|
152
149
|
|
|
153
150
|
lines.push('');
|
|
154
151
|
if (result.success) {
|
|
155
|
-
lines.push(useColor
|
|
156
|
-
? `${green}All checks passed. Project is ready.${reset}`
|
|
157
|
-
: 'All checks passed. Project is ready.');
|
|
152
|
+
lines.push(colorize('All checks passed. Project is ready.', 'green', useColor));
|
|
158
153
|
} else {
|
|
159
154
|
const failedCount = result.checks.filter(c => !c.passed).length;
|
|
160
|
-
lines.push(useColor
|
|
161
|
-
? `${red}${failedCount} check(s) failed.${reset}`
|
|
162
|
-
: `${failedCount} check(s) failed.`);
|
|
155
|
+
lines.push(colorize(`${failedCount} check(s) failed.`, 'red', useColor));
|
|
163
156
|
}
|
|
164
157
|
|
|
165
158
|
return lines.join('\n');
|