murmur8 4.2.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.blueprint/agents/AGENT_SPECIFICATION_ALEX.md +33 -3
  2. package/.blueprint/features/feature_config-factory/FEATURE_SPEC.md +138 -0
  3. package/.blueprint/features/feature_config-factory/IMPLEMENTATION_PLAN.md +187 -0
  4. package/.blueprint/features/feature_config-factory/handoff-nigel.md +57 -0
  5. package/.blueprint/features/feature_extract-prompt-util/FEATURE_SPEC.md +42 -0
  6. package/.blueprint/features/feature_fix-status-icons/FEATURE_SPEC.md +37 -0
  7. package/.blueprint/features/feature_murm-subagent/FEATURE_SPEC.md +137 -0
  8. package/.blueprint/features/feature_murm-subagent/SKILL_CHANGES.md +345 -0
  9. package/.blueprint/features/feature_split-cli-commands/FEATURE_SPEC.md +125 -0
  10. package/.blueprint/features/feature_split-cli-commands/IMPLEMENTATION_PLAN.md +119 -0
  11. package/.blueprint/features/feature_split-cli-commands/handoff-nigel.md +45 -0
  12. package/.blueprint/features/feature_theme-adoption/FEATURE_SPEC.md +143 -0
  13. package/.blueprint/features/feature_theme-adoption/IMPLEMENTATION_PLAN.md +68 -0
  14. package/.blueprint/features/feature_theme-adoption/handoff-nigel.md +35 -0
  15. package/.blueprint/templates/BACKLOG_TEMPLATE.md +46 -0
  16. package/README.md +19 -10
  17. package/SKILL.md +377 -3
  18. package/bin/cli.js +20 -411
  19. package/package.json +1 -1
  20. package/src/commands/feedback-config.js +32 -0
  21. package/src/commands/help.js +81 -0
  22. package/src/commands/history.js +42 -0
  23. package/src/commands/init.js +12 -0
  24. package/src/commands/insights.js +23 -0
  25. package/src/commands/murm-config.js +52 -0
  26. package/src/commands/murm.js +109 -0
  27. package/src/commands/queue.js +19 -0
  28. package/src/commands/retry-config.js +28 -0
  29. package/src/commands/stack-config.js +32 -0
  30. package/src/commands/update.js +12 -0
  31. package/src/commands/utils.js +24 -0
  32. package/src/commands/validate.js +15 -0
  33. package/src/config-factory.js +190 -0
  34. package/src/feedback.js +5 -2
  35. package/src/init.js +1 -15
  36. package/src/insights.js +19 -16
  37. package/src/retry.js +5 -2
  38. package/src/stack.js +4 -1
  39. package/src/theme.js +4 -4
  40. package/src/update.js +2 -15
  41. package/src/utils.js +26 -0
  42. package/src/validate.js +5 -12
@@ -0,0 +1,52 @@
1
+ /**
2
+ * murm-config command - View or modify murmuration pipeline configuration
3
+ */
4
+ const {
5
+ readMurmConfig,
6
+ writeMurmConfig,
7
+ getDefaultMurmConfig
8
+ } = require('../murm');
9
+
10
+ const description = 'View or modify murmuration pipeline configuration';
11
+ const aliases = ['parallel-config'];
12
+
13
+ async function run(args) {
14
+ const subArg = args[1];
15
+
16
+ if (subArg === 'set') {
17
+ const key = args[2];
18
+ const value = args[3];
19
+ if (!key || !value) {
20
+ console.error('Usage: murm-config set <key> <value>');
21
+ console.error('Valid keys: cli, skill, skillFlags, worktreeDir, maxConcurrency, queueFile');
22
+ process.exit(1);
23
+ }
24
+ const config = readMurmConfig();
25
+ if (key === 'maxConcurrency') {
26
+ config[key] = parseInt(value, 10);
27
+ } else {
28
+ config[key] = value;
29
+ }
30
+ writeMurmConfig(config);
31
+ console.log(`Set ${key} = ${value}`);
32
+ } else if (subArg === 'reset') {
33
+ writeMurmConfig(getDefaultMurmConfig());
34
+ console.log('Murmuration configuration reset to defaults.');
35
+ } else {
36
+ const config = readMurmConfig();
37
+ console.log('Murmuration Configuration\n');
38
+ console.log(` cli: ${config.cli}`);
39
+ console.log(` skill: ${config.skill}`);
40
+ console.log(` skillFlags: ${config.skillFlags}`);
41
+ console.log(` worktreeDir: ${config.worktreeDir}`);
42
+ console.log(` maxConcurrency: ${config.maxConcurrency}`);
43
+ console.log(` maxFeatures: ${config.maxFeatures}`);
44
+ console.log(` timeout: ${config.timeout} min`);
45
+ console.log(` minDiskSpaceMB: ${config.minDiskSpaceMB}`);
46
+ console.log(` queueFile: ${config.queueFile}`);
47
+ console.log('\nTo change: murmur8 murm-config set <key> <value>');
48
+ console.log('Run pipelines: murmur8 murm <slug1> <slug2> ...');
49
+ }
50
+ }
51
+
52
+ module.exports = { run, description, aliases };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * murm command - Run multiple feature pipelines in parallel using git worktrees
3
+ */
4
+ const {
5
+ formatStatus,
6
+ runMurm,
7
+ loadQueue,
8
+ cleanupWorktrees,
9
+ abortMurm,
10
+ getLockInfo,
11
+ getDetailedStatus,
12
+ formatDetailedStatus,
13
+ rollbackMurm
14
+ } = require('../murm');
15
+
16
+ const description = 'Run multiple feature pipelines in parallel using git worktrees';
17
+ const aliases = ['parallel', 'murmuration'];
18
+
19
+ async function run(args) {
20
+ const subArg = args[1];
21
+
22
+ if (subArg === 'status') {
23
+ const detailed = args.includes('--detailed') || args.includes('-d');
24
+ const lock = getLockInfo();
25
+
26
+ if (detailed) {
27
+ const details = getDetailedStatus();
28
+ console.log(formatDetailedStatus(details));
29
+ } else {
30
+ const queue = loadQueue();
31
+
32
+ if (!queue.features || queue.features.length === 0) {
33
+ if (lock) {
34
+ console.log(`Murmuration execution in progress (PID: ${lock.pid})`);
35
+ console.log(`Started: ${lock.startedAt}`);
36
+ console.log(`Features: ${lock.features.join(', ')}`);
37
+ } else {
38
+ console.log('No murmuration pipelines active.');
39
+ }
40
+ return;
41
+ }
42
+
43
+ console.log('Murmuration Pipeline Status\n');
44
+ console.log(formatStatus(queue.features));
45
+ const summary = {
46
+ running: queue.features.filter(f => f.status === 'murm_running').length,
47
+ pending: queue.features.filter(f => f.status === 'murm_queued').length,
48
+ completed: queue.features.filter(f => f.status === 'murm_complete').length,
49
+ failed: queue.features.filter(f => f.status === 'murm_failed').length,
50
+ conflicts: queue.features.filter(f => f.status === 'merge_conflict').length
51
+ };
52
+ console.log(`\nRunning: ${summary.running} | Pending: ${summary.pending} | Completed: ${summary.completed} | Failed: ${summary.failed} | Conflicts: ${summary.conflicts}`);
53
+
54
+ // Show log paths for running/failed
55
+ const withLogs = queue.features.filter(f =>
56
+ f.logPath && (f.status === 'murm_running' || f.status === 'murm_failed')
57
+ );
58
+ if (withLogs.length > 0) {
59
+ console.log('\nLog files:');
60
+ withLogs.forEach(f => console.log(` ${f.slug}: ${f.logPath}`));
61
+ }
62
+
63
+ console.log('\nTip: Use --detailed for progress bars');
64
+ }
65
+ } else if (subArg === 'rollback') {
66
+ const dryRunFlag = args.includes('--dry-run');
67
+ const forceFlag = args.includes('--force');
68
+ await rollbackMurm({ dryRun: dryRunFlag, force: forceFlag });
69
+ } else if (subArg === 'cleanup') {
70
+ const cleaned = await cleanupWorktrees();
71
+ console.log(`Cleaned ${cleaned} worktree(s).`);
72
+ } else if (subArg === 'abort') {
73
+ const cleanupFlag = args.includes('--cleanup');
74
+ await abortMurm({ cleanup: cleanupFlag });
75
+ } else {
76
+ const slugs = args.slice(1).filter(a => !a.startsWith('--') && !a.startsWith('-'));
77
+ if (slugs.length === 0) {
78
+ console.error('Usage: murmur8 murm <slug1> <slug2> ... [options]');
79
+ console.error('\nOptions:');
80
+ console.error(' --dry-run Preview execution plan without running');
81
+ console.error(' --yes, -y Skip confirmation prompt');
82
+ console.error(' --force Override existing lock');
83
+ console.error(' --verbose Stream output to console (not just logs)');
84
+ console.error(' --skip-preflight Skip feature validation checks');
85
+ console.error(' --max-concurrency=N Set max parallel pipelines (default: 3)');
86
+ console.error('\nSubcommands:');
87
+ console.error(' murm status Show status of all pipelines');
88
+ console.error(' murm abort Stop all running pipelines');
89
+ console.error(' murm cleanup Remove completed/aborted worktrees');
90
+ process.exit(1);
91
+ }
92
+
93
+ const maxFlag = args.find(a => a.startsWith('--max-concurrency='));
94
+ const options = {
95
+ dryRun: args.includes('--dry-run'),
96
+ yes: args.includes('--yes') || args.includes('-y'),
97
+ force: args.includes('--force'),
98
+ verbose: args.includes('--verbose'),
99
+ skipPreflight: args.includes('--skip-preflight')
100
+ };
101
+ if (maxFlag) {
102
+ options.maxConcurrency = parseInt(maxFlag.split('=')[1], 10);
103
+ }
104
+ const result = await runMurm(slugs, options);
105
+ process.exit(result.success ? 0 : 1);
106
+ }
107
+ }
108
+
109
+ module.exports = { run, description, aliases };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * queue command - Show queue status or reset
3
+ */
4
+ const { displayQueue, resetQueue } = require('../orchestrator');
5
+
6
+ const description = 'Show queue status (use "reset" to clear)';
7
+
8
+ async function run(args) {
9
+ const subArg = args[1];
10
+
11
+ if (subArg === 'reset') {
12
+ resetQueue();
13
+ console.log('Queue has been reset.');
14
+ } else {
15
+ displayQueue();
16
+ }
17
+ }
18
+
19
+ module.exports = { run, description };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * retry-config command - Manage retry configuration for adaptive retry logic
3
+ */
4
+ const { displayConfig, setConfigValue, resetConfig } = require('../retry');
5
+
6
+ const description = 'Manage retry configuration for adaptive retry logic';
7
+
8
+ async function run(args) {
9
+ const subArg = args[1];
10
+
11
+ if (subArg === 'set') {
12
+ const key = args[2];
13
+ const value = args[3];
14
+ if (!key || !value) {
15
+ console.error('Usage: retry-config set <key> <value>');
16
+ console.error('Valid keys: maxRetries, windowSize, highFailureThreshold');
17
+ process.exit(1);
18
+ }
19
+ setConfigValue(key, value);
20
+ } else if (subArg === 'reset') {
21
+ resetConfig();
22
+ console.log('Retry configuration reset to defaults.');
23
+ } else {
24
+ displayConfig();
25
+ }
26
+ }
27
+
28
+ module.exports = { run, description };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * stack-config command - View or modify project tech stack configuration
3
+ */
4
+ const {
5
+ displayStackConfig,
6
+ setStackConfigValue,
7
+ resetStackConfig
8
+ } = require('../stack');
9
+
10
+ const description = 'View or modify project tech stack configuration';
11
+
12
+ async function run(args) {
13
+ const subArg = args[1];
14
+
15
+ if (subArg === 'set') {
16
+ const key = args[2];
17
+ const value = args[3];
18
+ if (!key || !value) {
19
+ console.error('Usage: stack-config set <key> <value>');
20
+ console.error('Valid keys: language, runtime, packageManager, frameworks, testRunner, testCommand, linter, tools');
21
+ process.exit(1);
22
+ }
23
+ setStackConfigValue(key, value);
24
+ } else if (subArg === 'reset') {
25
+ resetStackConfig();
26
+ console.log('Stack configuration reset to defaults.');
27
+ } else {
28
+ displayStackConfig();
29
+ }
30
+ }
31
+
32
+ module.exports = { run, description };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * update command - Update agents, templates, and rituals (preserves your content)
3
+ */
4
+ const { update } = require('../update');
5
+
6
+ const description = 'Update agents, templates, and rituals (preserves your content)';
7
+
8
+ async function run(args) {
9
+ await update();
10
+ }
11
+
12
+ module.exports = { run, description };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared utilities for CLI commands
3
+ */
4
+
5
+ /**
6
+ * Parse common flags from command line arguments
7
+ * @param {string[]} args - Command line arguments
8
+ * @returns {Object} Parsed flags
9
+ */
10
+ function parseFlags(args) {
11
+ const flags = {};
12
+ for (const arg of args) {
13
+ if (arg === '--all') flags.all = true;
14
+ if (arg === '--stats') flags.stats = true;
15
+ if (arg === '--force') flags.force = true;
16
+ if (arg === '--bottlenecks') flags.bottlenecks = true;
17
+ if (arg === '--failures') flags.failures = true;
18
+ if (arg === '--json') flags.json = true;
19
+ if (arg === '--feedback') flags.feedback = true;
20
+ }
21
+ return flags;
22
+ }
23
+
24
+ module.exports = { parseFlags };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * validate command - Run pre-flight checks to validate project configuration
3
+ */
4
+ const { validate, formatOutput } = require('../validate');
5
+
6
+ const description = 'Run pre-flight checks to validate project configuration';
7
+
8
+ async function run(args) {
9
+ const result = await validate();
10
+ const useColor = process.stdout.isTTY || false;
11
+ console.log(formatOutput(result, useColor));
12
+ process.exit(result.exitCode);
13
+ }
14
+
15
+ module.exports = { run, description };
@@ -0,0 +1,190 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ /**
7
+ * Factory function to create a standardized config module.
8
+ *
9
+ * @param {Object} options - Configuration options
10
+ * @param {string} options.name - Display name for the config (e.g., 'Retry', 'Feedback')
11
+ * @param {string} options.file - Path to config file (e.g., '.claude/retry-config.json')
12
+ * @param {Object} options.defaults - Default configuration values
13
+ * @param {Object} [options.validators] - Map of key -> validator function
14
+ * Validator returns true for valid, or error string for invalid
15
+ * @param {Object} [options.formatters] - Map of key -> display formatter function
16
+ * @param {string[]} [options.arrayKeys] - Keys that accept JSON array values
17
+ * @returns {Object} Config module with standard methods
18
+ */
19
+ function createConfigModule(options) {
20
+ const {
21
+ name,
22
+ file,
23
+ defaults,
24
+ validators = {},
25
+ formatters = {},
26
+ arrayKeys = []
27
+ } = options;
28
+
29
+ /**
30
+ * Ensures the config directory exists.
31
+ */
32
+ function ensureConfigDir() {
33
+ const dir = path.dirname(file);
34
+ if (!fs.existsSync(dir)) {
35
+ fs.mkdirSync(dir, { recursive: true });
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Returns a copy of the default configuration.
41
+ */
42
+ function getDefault() {
43
+ return JSON.parse(JSON.stringify(defaults));
44
+ }
45
+
46
+ /**
47
+ * Reads the config from file.
48
+ * Returns defaults if file is missing or corrupted.
49
+ * Merges missing keys from defaults.
50
+ */
51
+ function read() {
52
+ ensureConfigDir();
53
+ if (!fs.existsSync(file)) {
54
+ return getDefault();
55
+ }
56
+ try {
57
+ const content = fs.readFileSync(file, 'utf8');
58
+ const parsed = JSON.parse(content);
59
+ // Merge missing keys from defaults
60
+ const merged = { ...getDefault(), ...parsed };
61
+ return merged;
62
+ } catch (err) {
63
+ // Graceful degradation: return defaults on parse error
64
+ return getDefault();
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Writes the config to file.
70
+ * Creates directory if needed.
71
+ */
72
+ function write(config) {
73
+ ensureConfigDir();
74
+ fs.writeFileSync(file, JSON.stringify(config, null, 2));
75
+ }
76
+
77
+ /**
78
+ * Resets config to defaults by writing default config to file.
79
+ */
80
+ function reset() {
81
+ write(getDefault());
82
+ }
83
+
84
+ /**
85
+ * Sets a config value by key.
86
+ * @param {string} key - Config key
87
+ * @param {string} value - New value (will be parsed appropriately)
88
+ */
89
+ function setValue(key, value) {
90
+ const validKeys = Object.keys(defaults);
91
+
92
+ if (!validKeys.includes(key)) {
93
+ throw new Error(
94
+ `Unknown config key: ${key}. Valid keys: ${validKeys.join(', ')}`
95
+ );
96
+ }
97
+
98
+ let parsed = value;
99
+
100
+ // Handle array keys
101
+ if (arrayKeys.includes(key)) {
102
+ try {
103
+ parsed = JSON.parse(value);
104
+ if (!Array.isArray(parsed)) {
105
+ throw new Error(`${key} must be a JSON array, e.g. '["a","b"]'`);
106
+ }
107
+ } catch (err) {
108
+ if (err.message.includes('must be a JSON array')) throw err;
109
+ throw new Error(`${key} must be a valid JSON array, e.g. '["a","b"]'`);
110
+ }
111
+ } else {
112
+ // Type coercion based on default value type
113
+ const defaultType = typeof defaults[key];
114
+ if (defaultType === 'number') {
115
+ parsed = parseFloat(value);
116
+ if (isNaN(parsed)) {
117
+ throw new Error(`Invalid value for ${key}: ${value}. Must be a number.`);
118
+ }
119
+ } else if (defaultType === 'boolean') {
120
+ if (value !== 'true' && value !== 'false') {
121
+ throw new Error(`Invalid value for ${key}: ${value}. Must be true or false.`);
122
+ }
123
+ parsed = value === 'true';
124
+ }
125
+ }
126
+
127
+ // Run custom validator if provided
128
+ if (validators[key]) {
129
+ const result = validators[key](parsed);
130
+ if (result !== true) {
131
+ throw new Error(`Invalid value for ${key}: ${value}. ${result}`);
132
+ }
133
+ }
134
+
135
+ const config = read();
136
+ config[key] = parsed;
137
+ write(config);
138
+
139
+ // Log confirmation
140
+ const display = Array.isArray(config[key]) ? JSON.stringify(config[key]) : config[key];
141
+ console.log(`Set ${key} = ${display}`);
142
+ }
143
+
144
+ /**
145
+ * Displays the current configuration.
146
+ */
147
+ function display() {
148
+ const config = read();
149
+ console.log(`\n${name} Configuration\n`);
150
+
151
+ for (const [key, value] of Object.entries(config)) {
152
+ let displayValue;
153
+
154
+ // Use custom formatter if available
155
+ if (formatters[key]) {
156
+ displayValue = formatters[key](value);
157
+ } else if (Array.isArray(value)) {
158
+ displayValue = value.length > 0 ? value.join(', ') : '(not set)';
159
+ } else if (typeof value === 'object' && value !== null) {
160
+ // For nested objects, format each entry
161
+ console.log(`\n ${key}:`);
162
+ for (const [subKey, subValue] of Object.entries(value)) {
163
+ const subDisplay = Array.isArray(subValue) ? subValue.join(' -> ') : subValue;
164
+ console.log(` ${subKey.padEnd(20)}: ${subDisplay}`);
165
+ }
166
+ continue;
167
+ } else if (value === '' || value === null || value === undefined) {
168
+ displayValue = '(not set)';
169
+ } else {
170
+ displayValue = String(value);
171
+ }
172
+
173
+ console.log(` ${key.padEnd(20)}: ${displayValue}`);
174
+ }
175
+
176
+ console.log('');
177
+ }
178
+
179
+ return {
180
+ CONFIG_FILE: file,
181
+ getDefault,
182
+ read,
183
+ write,
184
+ reset,
185
+ setValue,
186
+ display
187
+ };
188
+ }
189
+
190
+ module.exports = { createConfigModule };
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
- console.log('\nFeedback Configuration\n');
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/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 = ['\nPipeline Insights\n'];
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('\nFeedback Insights\n');
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
  }