murmur8 3.5.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 (120) hide show
  1. package/.blueprint/agents/AGENT_BA_CASS.md +239 -0
  2. package/.blueprint/agents/AGENT_DEVELOPER_CODEY.md +308 -0
  3. package/.blueprint/agents/AGENT_SPECIFICATION_ALEX.md +183 -0
  4. package/.blueprint/agents/AGENT_TESTER_NIGEL.md +159 -0
  5. package/.blueprint/agents/GUARDRAILS.md +83 -0
  6. package/.blueprint/agents/TEAM_MANIFESTO.md +91 -0
  7. package/.blueprint/features/.gitkeep +0 -0
  8. package/.blueprint/features/feature_adaptive-retry/FEATURE_SPEC.md +239 -0
  9. package/.blueprint/features/feature_adaptive-retry/IMPLEMENTATION_PLAN.md +48 -0
  10. package/.blueprint/features/feature_adaptive-retry/story-prompt-modification.md +85 -0
  11. package/.blueprint/features/feature_adaptive-retry/story-retry-config.md +89 -0
  12. package/.blueprint/features/feature_adaptive-retry/story-should-retry.md +98 -0
  13. package/.blueprint/features/feature_adaptive-retry/story-strategy-recommendation.md +85 -0
  14. package/.blueprint/features/feature_agent-guardrails/FEATURE_SPEC.md +328 -0
  15. package/.blueprint/features/feature_agent-guardrails/IMPLEMENTATION_PLAN.md +90 -0
  16. package/.blueprint/features/feature_agent-guardrails/story-citation-requirements.md +50 -0
  17. package/.blueprint/features/feature_agent-guardrails/story-confidentiality.md +50 -0
  18. package/.blueprint/features/feature_agent-guardrails/story-escalation-protocol.md +55 -0
  19. package/.blueprint/features/feature_agent-guardrails/story-source-restrictions.md +50 -0
  20. package/.blueprint/features/feature_compressed-feedback/FEATURE_SPEC.md +136 -0
  21. package/.blueprint/features/feature_compressed-feedback/IMPLEMENTATION_PLAN.md +40 -0
  22. package/.blueprint/features/feature_feedback-loop/FEATURE_SPEC.md +347 -0
  23. package/.blueprint/features/feature_feedback-loop/IMPLEMENTATION_PLAN.md +71 -0
  24. package/.blueprint/features/feature_feedback-loop/story-feedback-collection.md +63 -0
  25. package/.blueprint/features/feature_feedback-loop/story-feedback-config.md +61 -0
  26. package/.blueprint/features/feature_feedback-loop/story-feedback-insights.md +63 -0
  27. package/.blueprint/features/feature_feedback-loop/story-quality-gates.md +57 -0
  28. package/.blueprint/features/feature_interactive-alex/FEATURE_SPEC.md +263 -0
  29. package/.blueprint/features/feature_interactive-alex/IMPLEMENTATION_PLAN.md +69 -0
  30. package/.blueprint/features/feature_interactive-alex/handoff-alex.md +19 -0
  31. package/.blueprint/features/feature_interactive-alex/handoff-cass.md +21 -0
  32. package/.blueprint/features/feature_interactive-alex/handoff-nigel.md +19 -0
  33. package/.blueprint/features/feature_interactive-alex/story-flag-routing.md +54 -0
  34. package/.blueprint/features/feature_interactive-alex/story-iterative-drafting.md +65 -0
  35. package/.blueprint/features/feature_interactive-alex/story-pipeline-integration.md +66 -0
  36. package/.blueprint/features/feature_interactive-alex/story-session-lifecycle.md +75 -0
  37. package/.blueprint/features/feature_interactive-alex/story-system-spec-creation.md +57 -0
  38. package/.blueprint/features/feature_lazy-business-context/FEATURE_SPEC.md +140 -0
  39. package/.blueprint/features/feature_lazy-business-context/IMPLEMENTATION_PLAN.md +54 -0
  40. package/.blueprint/features/feature_model-native-features/FEATURE_SPEC.md +174 -0
  41. package/.blueprint/features/feature_model-native-features/IMPLEMENTATION_PLAN.md +45 -0
  42. package/.blueprint/features/feature_parallel-abort/FEATURE_SPEC.md +117 -0
  43. package/.blueprint/features/feature_parallel-confirm/FEATURE_SPEC.md +90 -0
  44. package/.blueprint/features/feature_parallel-features/FEATURE_SPEC.md +291 -0
  45. package/.blueprint/features/feature_parallel-features/IMPLEMENTATION_PLAN.md +73 -0
  46. package/.blueprint/features/feature_parallel-lock/FEATURE_SPEC.md +119 -0
  47. package/.blueprint/features/feature_parallel-logging/FEATURE_SPEC.md +105 -0
  48. package/.blueprint/features/feature_parallel-preflight/FEATURE_SPEC.md +141 -0
  49. package/.blueprint/features/feature_pipeline-history/FEATURE_SPEC.md +239 -0
  50. package/.blueprint/features/feature_pipeline-history/IMPLEMENTATION_PLAN.md +71 -0
  51. package/.blueprint/features/feature_pipeline-history/story-clear-history.md +73 -0
  52. package/.blueprint/features/feature_pipeline-history/story-display-history.md +75 -0
  53. package/.blueprint/features/feature_pipeline-history/story-record-execution.md +76 -0
  54. package/.blueprint/features/feature_pipeline-history/story-show-statistics.md +85 -0
  55. package/.blueprint/features/feature_pipeline-insights/FEATURE_SPEC.md +288 -0
  56. package/.blueprint/features/feature_pipeline-insights/IMPLEMENTATION_PLAN.md +65 -0
  57. package/.blueprint/features/feature_pipeline-insights/story-anomaly-detection.md +71 -0
  58. package/.blueprint/features/feature_pipeline-insights/story-bottleneck-analysis.md +75 -0
  59. package/.blueprint/features/feature_pipeline-insights/story-failure-patterns.md +75 -0
  60. package/.blueprint/features/feature_pipeline-insights/story-json-output.md +75 -0
  61. package/.blueprint/features/feature_pipeline-insights/story-trend-analysis.md +78 -0
  62. package/.blueprint/features/feature_shared-guardrails/FEATURE_SPEC.md +119 -0
  63. package/.blueprint/features/feature_shared-guardrails/IMPLEMENTATION_PLAN.md +34 -0
  64. package/.blueprint/features/feature_shared-guardrails/story-extract-guardrails.md +60 -0
  65. package/.blueprint/features/feature_shared-guardrails/story-update-init-commands.md +63 -0
  66. package/.blueprint/features/feature_slim-agent-prompts/FEATURE_SPEC.md +145 -0
  67. package/.blueprint/features/feature_slim-agent-prompts/IMPLEMENTATION_PLAN.md +87 -0
  68. package/.blueprint/features/feature_slim-agent-prompts/story-create-runtime-prompt-template.md +59 -0
  69. package/.blueprint/features/feature_slim-agent-prompts/story-create-slim-agent-prompts.md +65 -0
  70. package/.blueprint/features/feature_slim-agent-prompts/story-skill-integration.md +53 -0
  71. package/.blueprint/features/feature_smart-story-routing/FEATURE_SPEC.md +147 -0
  72. package/.blueprint/features/feature_smart-story-routing/IMPLEMENTATION_PLAN.md +73 -0
  73. package/.blueprint/features/feature_template-extraction/FEATURE_SPEC.md +134 -0
  74. package/.blueprint/features/feature_template-extraction/IMPLEMENTATION_PLAN.md +46 -0
  75. package/.blueprint/features/feature_upstream-summaries/FEATURE_SPEC.md +150 -0
  76. package/.blueprint/features/feature_upstream-summaries/IMPLEMENTATION_PLAN.md +70 -0
  77. package/.blueprint/features/feature_validate-command/FEATURE_SPEC.md +209 -0
  78. package/.blueprint/features/feature_validate-command/IMPLEMENTATION_PLAN.md +59 -0
  79. package/.blueprint/features/feature_validate-command/story-failure-output.md +61 -0
  80. package/.blueprint/features/feature_validate-command/story-node-version-check.md +52 -0
  81. package/.blueprint/features/feature_validate-command/story-run-validation.md +59 -0
  82. package/.blueprint/features/feature_validate-command/story-success-output.md +50 -0
  83. package/.blueprint/prompts/TEMPLATE.md +65 -0
  84. package/.blueprint/prompts/alex-runtime.md +49 -0
  85. package/.blueprint/prompts/cass-runtime.md +46 -0
  86. package/.blueprint/prompts/codey-implement-runtime.md +52 -0
  87. package/.blueprint/prompts/codey-plan-runtime.md +47 -0
  88. package/.blueprint/prompts/nigel-runtime.md +47 -0
  89. package/.blueprint/system_specification/.gitkeep +0 -0
  90. package/.blueprint/system_specification/SYSTEM_SPEC.md +248 -0
  91. package/.blueprint/templates/FEATURE_SPEC.md +125 -0
  92. package/.blueprint/templates/STORY_TEMPLATE.md +96 -0
  93. package/.blueprint/templates/SYSTEM_SPEC.md +128 -0
  94. package/.blueprint/templates/TEST_TEMPLATE.md +76 -0
  95. package/.blueprint/ways_of_working/DEVELOPMENT_RITUAL.md +178 -0
  96. package/.business_context/README.md +27 -0
  97. package/LICENSE +21 -0
  98. package/README.md +564 -0
  99. package/SKILL.md +840 -0
  100. package/bin/cli.js +388 -0
  101. package/package.json +36 -0
  102. package/src/business-context.js +91 -0
  103. package/src/classifier.js +173 -0
  104. package/src/feedback.js +201 -0
  105. package/src/handoff.js +148 -0
  106. package/src/history.js +306 -0
  107. package/src/index.js +170 -0
  108. package/src/init.js +139 -0
  109. package/src/insights.js +504 -0
  110. package/src/interactive.js +338 -0
  111. package/src/orchestrator.js +217 -0
  112. package/src/parallel.js +1544 -0
  113. package/src/retry.js +274 -0
  114. package/src/stack.js +320 -0
  115. package/src/tools/index.js +27 -0
  116. package/src/tools/prompts.js +45 -0
  117. package/src/tools/schemas.js +38 -0
  118. package/src/tools/validation.js +83 -0
  119. package/src/update.js +112 -0
  120. package/src/validate.js +172 -0
@@ -0,0 +1,201 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const CONFIG_FILE = '.claude/feedback-config.json';
5
+
6
+ /**
7
+ * Normalizes abbreviated keys to full names.
8
+ * Converts "rec" to "recommendation" while preserving existing full key.
9
+ * @param {Object} feedback - Raw feedback object
10
+ * @returns {Object} - Normalized feedback object
11
+ */
12
+ function normalizeFeedbackKeys(feedback) {
13
+ const normalized = { ...feedback };
14
+ if ('rec' in normalized && !('recommendation' in normalized)) {
15
+ normalized.recommendation = normalized.rec;
16
+ delete normalized.rec;
17
+ }
18
+ return normalized;
19
+ }
20
+
21
+ /**
22
+ * Parses FEEDBACK: JSON from agent output text.
23
+ * @param {string} output - Raw agent output
24
+ * @returns {Object|null} - Parsed feedback or null if not found/invalid
25
+ */
26
+ function parseFeedbackFromOutput(output) {
27
+ const match = output.match(/FEEDBACK:\s*(\{[^}]+\})/);
28
+ if (!match) return null;
29
+ try {
30
+ return JSON.parse(match[1]);
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Returns the default feedback configuration.
38
+ * Per FEATURE_SPEC.md defaults.
39
+ */
40
+ function getDefaultConfig() {
41
+ return {
42
+ minRatingThreshold: 3.0,
43
+ enabled: true,
44
+ issueMappings: {
45
+ 'missing-error-handling': 'add-context',
46
+ 'unclear-scope': 'simplify-prompt',
47
+ 'too-complex': 'simplify-prompt',
48
+ 'too-many-stories': 'reduce-stories',
49
+ 'untestable-criteria': 'simplify-tests',
50
+ 'missing-edge-cases': 'add-context'
51
+ }
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Ensures the .claude directory exists.
57
+ */
58
+ function ensureConfigDir() {
59
+ const dir = path.dirname(CONFIG_FILE);
60
+ if (!fs.existsSync(dir)) {
61
+ fs.mkdirSync(dir, { recursive: true });
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Reads the feedback config from file.
67
+ * Returns defaults if file is missing or corrupted.
68
+ */
69
+ function readConfig() {
70
+ ensureConfigDir();
71
+ if (!fs.existsSync(CONFIG_FILE)) {
72
+ return getDefaultConfig();
73
+ }
74
+ try {
75
+ const content = fs.readFileSync(CONFIG_FILE, 'utf8');
76
+ return JSON.parse(content);
77
+ } catch (err) {
78
+ return getDefaultConfig();
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Writes the feedback config to file.
84
+ */
85
+ function writeConfig(config) {
86
+ ensureConfigDir();
87
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
88
+ }
89
+
90
+ /**
91
+ * Validates a feedback object against the schema.
92
+ * Per FEATURE_SPEC.md:Rule 1.
93
+ * Accepts both "rec" and "recommendation" keys for recommendation field.
94
+ * @param {object} feedback - Feedback object to validate
95
+ * @returns {object} { valid: boolean, errors: string[] }
96
+ */
97
+ function validateFeedback(feedback) {
98
+ const errors = [];
99
+
100
+ // Rating validation
101
+ if (typeof feedback.rating !== 'number' || !Number.isInteger(feedback.rating)) {
102
+ errors.push('rating must be an integer');
103
+ } else if (feedback.rating < 1 || feedback.rating > 5) {
104
+ errors.push('rating must be between 1 and 5');
105
+ }
106
+
107
+ // Issues validation
108
+ if (!Array.isArray(feedback.issues)) {
109
+ errors.push('issues must be an array');
110
+ } else if (!feedback.issues.every(i => typeof i === 'string')) {
111
+ errors.push('issues must be an array of strings');
112
+ }
113
+
114
+ // Recommendation validation - accept both "rec" and "recommendation" keys
115
+ const rec = feedback.recommendation || feedback.rec;
116
+ const validRecs = ['proceed', 'pause', 'revise'];
117
+ if (!validRecs.includes(rec)) {
118
+ errors.push(`recommendation must be one of: ${validRecs.join(', ')}`);
119
+ }
120
+
121
+ return { valid: errors.length === 0, errors };
122
+ }
123
+
124
+ /**
125
+ * Determines whether the pipeline should pause based on feedback.
126
+ * Per FEATURE_SPEC.md:Rule 2.
127
+ * @param {object} feedback - Validated feedback object
128
+ * @param {object} config - Feedback configuration
129
+ * @returns {boolean} True if pipeline should pause
130
+ */
131
+ function shouldPause(feedback, config) {
132
+ return feedback.rating < config.minRatingThreshold ||
133
+ feedback.recommendation === 'pause';
134
+ }
135
+
136
+ /**
137
+ * Validates and sets a config value.
138
+ * @param {string} key - Config key
139
+ * @param {string} value - New value (will be parsed)
140
+ */
141
+ function setConfigValue(key, value) {
142
+ const config = readConfig();
143
+
144
+ if (key === 'minRatingThreshold') {
145
+ const numValue = parseFloat(value);
146
+ if (isNaN(numValue) || numValue < 1.0 || numValue > 5.0) {
147
+ throw new Error(
148
+ 'minRatingThreshold must be a number between 1.0 and 5.0'
149
+ );
150
+ }
151
+ config.minRatingThreshold = numValue;
152
+ } else if (key === 'enabled') {
153
+ if (value !== 'true' && value !== 'false') {
154
+ throw new Error('enabled must be true or false');
155
+ }
156
+ config.enabled = value === 'true';
157
+ } else {
158
+ throw new Error(
159
+ `Unknown config key: ${key}. Valid keys: minRatingThreshold, enabled`
160
+ );
161
+ }
162
+
163
+ writeConfig(config);
164
+ console.log(`Set ${key} = ${config[key]}`);
165
+ }
166
+
167
+ /**
168
+ * Displays the current feedback configuration.
169
+ */
170
+ function displayConfig() {
171
+ const config = readConfig();
172
+ console.log('\nFeedback Configuration\n');
173
+ console.log(` Min rating threshold: ${config.minRatingThreshold}`);
174
+ console.log(` Enabled: ${config.enabled}`);
175
+ console.log('\n Issue Mappings:');
176
+ for (const [issue, strategy] of Object.entries(config.issueMappings)) {
177
+ console.log(` ${issue.padEnd(24)}: ${strategy}`);
178
+ }
179
+ console.log('');
180
+ }
181
+
182
+ /**
183
+ * Resets feedback config to defaults.
184
+ */
185
+ function resetConfig() {
186
+ writeConfig(getDefaultConfig());
187
+ }
188
+
189
+ module.exports = {
190
+ CONFIG_FILE,
191
+ getDefaultConfig,
192
+ readConfig,
193
+ writeConfig,
194
+ normalizeFeedbackKeys,
195
+ parseFeedbackFromOutput,
196
+ validateFeedback,
197
+ shouldPause,
198
+ setConfigValue,
199
+ displayConfig,
200
+ resetConfig
201
+ };
package/src/handoff.js ADDED
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Handoff summary helper functions.
3
+ * Parses and validates handoff summary format for agent-to-agent communication.
4
+ */
5
+
6
+ /**
7
+ * Parses a handoff summary and extracts key fields.
8
+ * @param {string} content - The handoff summary markdown content
9
+ * @returns {object} Parsed summary fields
10
+ */
11
+ function parseHandoffSummary(content) {
12
+ return {
13
+ hasHeading: /^## Handoff Summary/m.test(content),
14
+ forField: content.match(/\*\*For:\*\*\s*(.+)/)?.[1]?.trim(),
15
+ featureField: content.match(/\*\*Feature:\*\*\s*(.+)/)?.[1]?.trim(),
16
+ hasKeyDecisions: /### Key Decisions/m.test(content),
17
+ hasFilesCreated: /### Files Created/m.test(content),
18
+ hasOpenQuestions: /### Open Questions/m.test(content),
19
+ hasCriticalContext: /### Critical Context/m.test(content),
20
+ lineCount: content.split('\n').length
21
+ };
22
+ }
23
+
24
+ /**
25
+ * Extracts a named section from the summary.
26
+ * @param {string} content - The handoff summary markdown content
27
+ * @param {string} sectionName - Name of the section (e.g., 'Key Decisions')
28
+ * @returns {string} The section content, or empty string if not found
29
+ */
30
+ function extractSection(content, sectionName) {
31
+ const regex = new RegExp(`### ${sectionName}\\n([\\s\\S]*?)(?=\\n###|$)`);
32
+ const match = content.match(regex);
33
+ return match ? match[1].trim() : '';
34
+ }
35
+
36
+ /**
37
+ * Counts bullet items in a section.
38
+ * @param {string} section - Section content
39
+ * @returns {number} Number of bullet items
40
+ */
41
+ function countBulletItems(section) {
42
+ return section.split('\n').filter(line => /^[-*]\s/.test(line)).length;
43
+ }
44
+
45
+ /**
46
+ * Extracts file paths from a section.
47
+ * @param {string} section - Section content
48
+ * @returns {string[]} Array of file paths
49
+ */
50
+ function extractFilePaths(section) {
51
+ const lines = section.split('\n').filter(line => /^[-*]\s/.test(line));
52
+ return lines.map(line => line.replace(/^[-*]\s+/, '').trim());
53
+ }
54
+
55
+ /**
56
+ * Validates a handoff summary against format rules.
57
+ * @param {string} content - The handoff summary markdown content
58
+ * @returns {object} { valid: boolean, errors: string[] }
59
+ */
60
+ function validateHandoffSummary(content) {
61
+ const errors = [];
62
+ const parsed = parseHandoffSummary(content);
63
+
64
+ if (!parsed.hasHeading) {
65
+ errors.push('Missing ## Handoff Summary heading');
66
+ }
67
+
68
+ if (!parsed.forField) {
69
+ errors.push('Missing **For:** field');
70
+ }
71
+
72
+ if (!parsed.featureField) {
73
+ errors.push('Missing **Feature:** field');
74
+ }
75
+
76
+ if (!parsed.hasKeyDecisions) {
77
+ errors.push('Missing ### Key Decisions section');
78
+ }
79
+
80
+ if (!parsed.hasFilesCreated) {
81
+ errors.push('Missing ### Files Created section');
82
+ }
83
+
84
+ if (!parsed.hasOpenQuestions) {
85
+ errors.push('Missing ### Open Questions section');
86
+ }
87
+
88
+ if (!parsed.hasCriticalContext) {
89
+ errors.push('Missing ### Critical Context section');
90
+ }
91
+
92
+ if (parsed.lineCount >= 30) {
93
+ errors.push(`Summary exceeds 30 lines (found ${parsed.lineCount})`);
94
+ }
95
+
96
+ // Validate Key Decisions bullet count
97
+ const keyDecisions = extractSection(content, 'Key Decisions');
98
+ const bulletCount = countBulletItems(keyDecisions);
99
+ if (bulletCount < 1 || bulletCount > 5) {
100
+ errors.push(`Key Decisions should have 1-5 items (found ${bulletCount})`);
101
+ }
102
+
103
+ return { valid: errors.length === 0, errors };
104
+ }
105
+
106
+ /**
107
+ * Returns the handoff file path for an agent.
108
+ * @param {string} featureDir - Feature directory path
109
+ * @param {string} agent - Agent name (alex, cass, nigel)
110
+ * @returns {string} Full path to handoff file
111
+ */
112
+ function getHandoffPath(featureDir, agent) {
113
+ return `${featureDir}/handoff-${agent.toLowerCase()}.md`;
114
+ }
115
+
116
+ /**
117
+ * Generates a handoff summary template.
118
+ * @param {string} forAgent - Target agent name
119
+ * @param {string} featureSlug - Feature slug
120
+ * @returns {string} Template markdown content
121
+ */
122
+ function getHandoffTemplate(forAgent, featureSlug) {
123
+ return `## Handoff Summary
124
+ **For:** ${forAgent}
125
+ **Feature:** ${featureSlug}
126
+
127
+ ### Key Decisions
128
+ -
129
+
130
+ ### Files Created
131
+ -
132
+
133
+ ### Open Questions
134
+ - None
135
+
136
+ ### Critical Context
137
+ `;
138
+ }
139
+
140
+ module.exports = {
141
+ parseHandoffSummary,
142
+ extractSection,
143
+ countBulletItems,
144
+ extractFilePaths,
145
+ validateHandoffSummary,
146
+ getHandoffPath,
147
+ getHandoffTemplate
148
+ };
package/src/history.js ADDED
@@ -0,0 +1,306 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const readline = require('readline');
4
+
5
+ const HISTORY_FILE = '.claude/pipeline-history.json';
6
+
7
+ function ensureHistoryDir() {
8
+ const dir = path.dirname(HISTORY_FILE);
9
+ if (!fs.existsSync(dir)) {
10
+ fs.mkdirSync(dir, { recursive: true });
11
+ }
12
+ }
13
+
14
+ function readHistoryFile() {
15
+ ensureHistoryDir();
16
+ if (!fs.existsSync(HISTORY_FILE)) {
17
+ return [];
18
+ }
19
+ try {
20
+ const content = fs.readFileSync(HISTORY_FILE, 'utf8');
21
+ return JSON.parse(content);
22
+ } catch (err) {
23
+ return { error: 'corrupted' };
24
+ }
25
+ }
26
+
27
+ function writeHistoryFile(entries) {
28
+ ensureHistoryDir();
29
+ fs.writeFileSync(HISTORY_FILE, JSON.stringify(entries, null, 2));
30
+ }
31
+
32
+ function recordHistory(entry) {
33
+ try {
34
+ const history = readHistoryFile();
35
+ if (history.error) {
36
+ console.warn('Warning: History file is corrupted, cannot record entry.');
37
+ return false;
38
+ }
39
+ history.push(entry);
40
+ writeHistoryFile(history);
41
+ return true;
42
+ } catch (err) {
43
+ console.warn(`Warning: Failed to record history: ${err.message}`);
44
+ return false;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Stores feedback for a specific stage in a feature's history entry.
50
+ * Per FEATURE_SPEC.md - feedback is stored at stages[stage].feedback
51
+ * @param {string} slug - Feature slug
52
+ * @param {string} stage - Stage name (alex, cass, nigel, etc.)
53
+ * @param {object} feedback - Feedback object to store
54
+ * @returns {boolean} True if stored successfully
55
+ */
56
+ function storeStageFeedback(slug, stage, feedback) {
57
+ try {
58
+ const history = readHistoryFile();
59
+ if (history.error) {
60
+ console.warn('Warning: History file is corrupted, cannot store feedback.');
61
+ return false;
62
+ }
63
+
64
+ // Find the most recent entry for this slug
65
+ const entry = history.findLast(e => e.slug === slug);
66
+ if (!entry) {
67
+ console.warn(`Warning: No history entry found for slug: ${slug}`);
68
+ return false;
69
+ }
70
+
71
+ // Ensure stages object exists
72
+ if (!entry.stages) {
73
+ entry.stages = {};
74
+ }
75
+
76
+ // Ensure stage object exists
77
+ if (!entry.stages[stage]) {
78
+ entry.stages[stage] = {};
79
+ }
80
+
81
+ // Store feedback
82
+ entry.stages[stage].feedback = feedback;
83
+
84
+ writeHistoryFile(history);
85
+ return true;
86
+ } catch (err) {
87
+ console.warn(`Warning: Failed to store feedback: ${err.message}`);
88
+ return false;
89
+ }
90
+ }
91
+
92
+ function formatDuration(ms) {
93
+ const seconds = Math.floor(ms / 1000);
94
+ const minutes = Math.floor(seconds / 60);
95
+ const secs = seconds % 60;
96
+ if (minutes === 0) {
97
+ return `${secs}s`;
98
+ }
99
+ return `${minutes}m ${secs}s`;
100
+ }
101
+
102
+ function formatDate(isoString) {
103
+ const date = new Date(isoString);
104
+ return date.toISOString().replace('T', ' ').slice(0, 19);
105
+ }
106
+
107
+ function colorize(text, color, useColor) {
108
+ if (!useColor) return text;
109
+ const colors = {
110
+ green: '\x1b[32m',
111
+ red: '\x1b[31m',
112
+ yellow: '\x1b[33m',
113
+ reset: '\x1b[0m'
114
+ };
115
+ return `${colors[color] || ''}${text}${colors.reset}`;
116
+ }
117
+
118
+ function displayHistory(options = {}) {
119
+ const showAll = options.all || false;
120
+ const useColor = options.color !== false && process.stdout.isTTY;
121
+
122
+ const history = readHistoryFile();
123
+
124
+ if (history.error === 'corrupted') {
125
+ console.log("Warning: History file is corrupted. Run 'murmur8 history clear' to reset.");
126
+ return;
127
+ }
128
+
129
+ if (!history || history.length === 0) {
130
+ console.log('No pipeline history found.');
131
+ return;
132
+ }
133
+
134
+ const sorted = [...history].sort((a, b) =>
135
+ new Date(b.completedAt) - new Date(a.completedAt)
136
+ );
137
+
138
+ const entries = showAll ? sorted : sorted.slice(0, 10);
139
+ const total = history.length;
140
+ const showing = entries.length;
141
+
142
+ console.log(`\nPipeline History (showing ${showing} of ${total} runs)\n`);
143
+ console.log(' SLUG STATUS DATE DURATION');
144
+
145
+ for (const entry of entries) {
146
+ const slug = entry.slug.padEnd(18);
147
+ let status = entry.status.padEnd(8);
148
+ const date = formatDate(entry.completedAt);
149
+ const duration = formatDuration(entry.totalDurationMs);
150
+
151
+ if (entry.status === 'success') {
152
+ status = colorize(status, 'green', useColor);
153
+ } else if (entry.status === 'failed') {
154
+ status = colorize(status, 'red', useColor);
155
+ } else if (entry.status === 'paused') {
156
+ status = colorize(status, 'yellow', useColor);
157
+ }
158
+
159
+ let suffix = '';
160
+ if (entry.status === 'failed' && entry.failedStage) {
161
+ suffix = ` (failed at: ${entry.failedStage})`;
162
+ } else if (entry.status === 'paused' && entry.pausedAfter) {
163
+ suffix = ` (paused at: ${entry.pausedAfter})`;
164
+ }
165
+
166
+ console.log(` ${slug} ${status} ${date} ${duration}${suffix}`);
167
+ }
168
+
169
+ if (!showAll && total > 10) {
170
+ console.log(`\nRun 'murmur8 history --all' to see all entries.`);
171
+ }
172
+ console.log(`Run 'murmur8 history --stats' for aggregate statistics.`);
173
+ }
174
+
175
+ function showStats() {
176
+ const history = readHistoryFile();
177
+
178
+ if (history.error === 'corrupted') {
179
+ console.log("Warning: History file is corrupted. Run 'murmur8 history clear' to reset.");
180
+ return;
181
+ }
182
+
183
+ if (!history || history.length === 0) {
184
+ console.log('Insufficient data for statistics. Complete at least one pipeline run.');
185
+ return;
186
+ }
187
+
188
+ const total = history.length;
189
+ const successRuns = history.filter(e => e.status === 'success');
190
+ const failedRuns = history.filter(e => e.status === 'failed');
191
+ const pausedRuns = history.filter(e => e.status === 'paused');
192
+
193
+ const successCount = successRuns.length;
194
+ const successRate = Math.round((successCount / total) * 100);
195
+
196
+ console.log(`\nPipeline Statistics (based on ${total} runs)\n`);
197
+ console.log(' METRIC VALUE');
198
+ console.log(` Success rate ${successRate}% (${successCount}/${total} runs)`);
199
+ console.log(` Total runs ${total} (${successCount} success, ${failedRuns.length} failed, ${pausedRuns.length} paused)`);
200
+
201
+ if (successRuns.length > 0) {
202
+ const avgTotal = Math.round(
203
+ successRuns.reduce((sum, e) => sum + e.totalDurationMs, 0) / successRuns.length
204
+ );
205
+ console.log(` Avg pipeline duration ${formatDuration(avgTotal)}`);
206
+ }
207
+
208
+ const stages = ['alex', 'cass', 'nigel', 'codey-plan', 'codey-implement'];
209
+ const stageStats = {};
210
+ const failureCounts = {};
211
+
212
+ for (const stage of stages) {
213
+ stageStats[stage] = { durations: [], failures: 0 };
214
+ }
215
+
216
+ for (const entry of history) {
217
+ if (entry.stages) {
218
+ for (const stage of stages) {
219
+ if (entry.stages[stage] && entry.stages[stage].durationMs) {
220
+ stageStats[stage].durations.push(entry.stages[stage].durationMs);
221
+ }
222
+ }
223
+ }
224
+ if (entry.status === 'failed' && entry.failedStage) {
225
+ failureCounts[entry.failedStage] = (failureCounts[entry.failedStage] || 0) + 1;
226
+ }
227
+ }
228
+
229
+ console.log('\n STAGE AVG DURATION FAILURES');
230
+ for (const stage of stages) {
231
+ const stats = stageStats[stage];
232
+ const avgDuration = stats.durations.length > 0
233
+ ? formatDuration(Math.round(stats.durations.reduce((a, b) => a + b, 0) / stats.durations.length))
234
+ : 'N/A';
235
+ const failures = failureCounts[stage] || 0;
236
+ console.log(` ${stage.padEnd(16)} ${avgDuration.padEnd(14)} ${failures}`);
237
+ }
238
+
239
+ if (failedRuns.length === 0) {
240
+ console.log('\n No failures recorded');
241
+ } else {
242
+ const maxFailures = Math.max(...Object.values(failureCounts));
243
+ const topFailures = Object.entries(failureCounts)
244
+ .filter(([, count]) => count === maxFailures)
245
+ .map(([stage]) => stage);
246
+
247
+ if (topFailures.length === 1) {
248
+ console.log(`\n Most common failure: ${topFailures[0]} (${maxFailures} failures)`);
249
+ } else {
250
+ console.log(`\n Most common failures: ${topFailures.join(', ')} (${maxFailures} each)`);
251
+ }
252
+ }
253
+ }
254
+
255
+ async function clearHistory(options = {}) {
256
+ const force = options.force || false;
257
+
258
+ const history = readHistoryFile();
259
+
260
+ if (history.error === 'corrupted') {
261
+ writeHistoryFile([]);
262
+ console.log('History file was corrupted. File has been reset.');
263
+ return;
264
+ }
265
+
266
+ if (!history || history.length === 0) {
267
+ console.log('No history to clear.');
268
+ return;
269
+ }
270
+
271
+ const count = history.length;
272
+
273
+ if (!force) {
274
+ const rl = readline.createInterface({
275
+ input: process.stdin,
276
+ output: process.stdout
277
+ });
278
+
279
+ const answer = await new Promise((resolve) => {
280
+ rl.question(`This will delete all ${count} history entries. Continue? (y/N) `, (ans) => {
281
+ rl.close();
282
+ resolve(ans.toLowerCase().trim());
283
+ });
284
+ });
285
+
286
+ if (answer !== 'y' && answer !== 'yes') {
287
+ console.log('Clear cancelled. History unchanged.');
288
+ return;
289
+ }
290
+ }
291
+
292
+ writeHistoryFile([]);
293
+ console.log(`Pipeline history cleared. ${count} entries removed.`);
294
+ }
295
+
296
+ module.exports = {
297
+ HISTORY_FILE,
298
+ readHistoryFile,
299
+ writeHistoryFile,
300
+ recordHistory,
301
+ storeStageFeedback,
302
+ displayHistory,
303
+ showStats,
304
+ clearHistory,
305
+ formatDuration
306
+ };