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.
- 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_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 +19 -10
- package/SKILL.md +377 -3
- package/bin/cli.js +20 -411
- package/package.json +1 -1
- package/src/commands/feedback-config.js +32 -0
- package/src/commands/help.js +81 -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 +24 -0
- package/src/commands/validate.js +15 -0
- package/src/config-factory.js +190 -0
- package/src/feedback.js +5 -2
- 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 +4 -4
- package/src/update.js +2 -15
- package/src/utils.js +26 -0
- 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
|
-
|
|
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 = ['\
|
|
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
|
}
|