orchestr8 2.4.0 → 2.6.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 (55) hide show
  1. package/.blueprint/agents/AGENT_BA_CASS.md +50 -25
  2. package/.blueprint/agents/AGENT_DEVELOPER_CODEY.md +60 -69
  3. package/.blueprint/agents/AGENT_SPECIFICATION_ALEX.md +45 -0
  4. package/.blueprint/agents/AGENT_TESTER_NIGEL.md +72 -105
  5. package/.blueprint/features/feature_adaptive-retry/FEATURE_SPEC.md +239 -0
  6. package/.blueprint/features/feature_adaptive-retry/IMPLEMENTATION_PLAN.md +48 -0
  7. package/.blueprint/features/feature_adaptive-retry/story-prompt-modification.md +85 -0
  8. package/.blueprint/features/feature_adaptive-retry/story-retry-config.md +89 -0
  9. package/.blueprint/features/feature_adaptive-retry/story-should-retry.md +98 -0
  10. package/.blueprint/features/feature_adaptive-retry/story-strategy-recommendation.md +85 -0
  11. package/.blueprint/features/feature_agent-guardrails/FEATURE_SPEC.md +328 -0
  12. package/.blueprint/features/feature_agent-guardrails/IMPLEMENTATION_PLAN.md +90 -0
  13. package/.blueprint/features/feature_agent-guardrails/story-citation-requirements.md +50 -0
  14. package/.blueprint/features/feature_agent-guardrails/story-confidentiality.md +50 -0
  15. package/.blueprint/features/feature_agent-guardrails/story-escalation-protocol.md +55 -0
  16. package/.blueprint/features/feature_agent-guardrails/story-source-restrictions.md +50 -0
  17. package/.blueprint/features/feature_feedback-loop/FEATURE_SPEC.md +347 -0
  18. package/.blueprint/features/feature_feedback-loop/IMPLEMENTATION_PLAN.md +71 -0
  19. package/.blueprint/features/feature_feedback-loop/story-feedback-collection.md +63 -0
  20. package/.blueprint/features/feature_feedback-loop/story-feedback-config.md +61 -0
  21. package/.blueprint/features/feature_feedback-loop/story-feedback-insights.md +63 -0
  22. package/.blueprint/features/feature_feedback-loop/story-quality-gates.md +57 -0
  23. package/.blueprint/features/feature_pipeline-history/FEATURE_SPEC.md +239 -0
  24. package/.blueprint/features/feature_pipeline-history/IMPLEMENTATION_PLAN.md +71 -0
  25. package/.blueprint/features/feature_pipeline-history/story-clear-history.md +73 -0
  26. package/.blueprint/features/feature_pipeline-history/story-display-history.md +75 -0
  27. package/.blueprint/features/feature_pipeline-history/story-record-execution.md +76 -0
  28. package/.blueprint/features/feature_pipeline-history/story-show-statistics.md +85 -0
  29. package/.blueprint/features/feature_pipeline-insights/FEATURE_SPEC.md +288 -0
  30. package/.blueprint/features/feature_pipeline-insights/IMPLEMENTATION_PLAN.md +65 -0
  31. package/.blueprint/features/feature_pipeline-insights/story-anomaly-detection.md +71 -0
  32. package/.blueprint/features/feature_pipeline-insights/story-bottleneck-analysis.md +75 -0
  33. package/.blueprint/features/feature_pipeline-insights/story-failure-patterns.md +75 -0
  34. package/.blueprint/features/feature_pipeline-insights/story-json-output.md +75 -0
  35. package/.blueprint/features/feature_pipeline-insights/story-trend-analysis.md +78 -0
  36. package/.blueprint/features/feature_validate-command/FEATURE_SPEC.md +209 -0
  37. package/.blueprint/features/feature_validate-command/IMPLEMENTATION_PLAN.md +59 -0
  38. package/.blueprint/features/feature_validate-command/story-failure-output.md +61 -0
  39. package/.blueprint/features/feature_validate-command/story-node-version-check.md +52 -0
  40. package/.blueprint/features/feature_validate-command/story-run-validation.md +59 -0
  41. package/.blueprint/features/feature_validate-command/story-success-output.md +50 -0
  42. package/.blueprint/system_specification/SYSTEM_SPEC.md +248 -0
  43. package/README.md +174 -40
  44. package/SKILL.md +399 -74
  45. package/bin/cli.js +128 -20
  46. package/package.json +1 -1
  47. package/src/feedback.js +171 -0
  48. package/src/history.js +306 -0
  49. package/src/index.js +57 -2
  50. package/src/init.js +2 -6
  51. package/src/insights.js +504 -0
  52. package/src/retry.js +274 -0
  53. package/src/update.js +10 -2
  54. package/src/validate.js +172 -0
  55. package/src/skills.js +0 -93
package/bin/cli.js CHANGED
@@ -2,13 +2,36 @@
2
2
 
3
3
  const { init } = require('../src/init');
4
4
  const { update } = require('../src/update');
5
- const { addSkills, listSkills } = require('../src/skills');
6
5
  const { displayQueue, resetQueue } = require('../src/orchestrator');
6
+ const { validate, formatOutput } = require('../src/validate');
7
+ const { displayHistory, showStats, clearHistory } = require('../src/history');
8
+ const { displayInsights } = require('../src/insights');
9
+ const { displayConfig, setConfigValue, resetConfig } = require('../src/retry');
10
+ const {
11
+ displayConfig: displayFeedbackConfig,
12
+ setConfigValue: setFeedbackConfigValue,
13
+ resetConfig: resetFeedbackConfig
14
+ } = require('../src/feedback');
15
+ const { displayFeedbackInsights } = require('../src/insights');
7
16
 
8
17
  const args = process.argv.slice(2);
9
18
  const command = args[0];
10
19
  const subArg = args[1];
11
20
 
21
+ function parseFlags(args) {
22
+ const flags = {};
23
+ for (const arg of args) {
24
+ if (arg === '--all') flags.all = true;
25
+ if (arg === '--stats') flags.stats = true;
26
+ if (arg === '--force') flags.force = true;
27
+ if (arg === '--bottlenecks') flags.bottlenecks = true;
28
+ if (arg === '--failures') flags.failures = true;
29
+ if (arg === '--json') flags.json = true;
30
+ if (arg === '--feedback') flags.feedback = true;
31
+ }
32
+ return flags;
33
+ }
34
+
12
35
  const commands = {
13
36
  init: {
14
37
  fn: init,
@@ -18,14 +41,6 @@ const commands = {
18
41
  fn: update,
19
42
  description: 'Update agents, templates, and rituals (preserves your content)'
20
43
  },
21
- 'add-skills': {
22
- fn: () => addSkills(subArg || 'all'),
23
- description: 'Install recommended skills for an agent (or all)'
24
- },
25
- skills: {
26
- fn: () => listSkills(subArg),
27
- description: 'List recommended skills for agents'
28
- },
29
44
  queue: {
30
45
  fn: () => {
31
46
  if (subArg === 'reset') {
@@ -37,6 +52,83 @@ const commands = {
37
52
  },
38
53
  description: 'Show queue status (use "reset" to clear)'
39
54
  },
55
+ validate: {
56
+ fn: async () => {
57
+ const result = await validate();
58
+ const useColor = process.stdout.isTTY || false;
59
+ console.log(formatOutput(result, useColor));
60
+ process.exit(result.exitCode);
61
+ },
62
+ description: 'Run pre-flight checks to validate project configuration'
63
+ },
64
+ history: {
65
+ fn: async () => {
66
+ const flags = parseFlags(args);
67
+ if (subArg === 'clear') {
68
+ await clearHistory({ force: flags.force });
69
+ } else if (flags.stats) {
70
+ showStats();
71
+ } else {
72
+ displayHistory({ all: flags.all });
73
+ }
74
+ },
75
+ description: 'View pipeline execution history'
76
+ },
77
+ insights: {
78
+ fn: () => {
79
+ const flags = parseFlags(args);
80
+ if (flags.feedback) {
81
+ displayFeedbackInsights({ json: flags.json });
82
+ } else {
83
+ displayInsights({
84
+ bottlenecks: flags.bottlenecks,
85
+ failures: flags.failures,
86
+ json: flags.json
87
+ });
88
+ }
89
+ },
90
+ description: 'Analyze pipeline history for bottlenecks, failures, and trends'
91
+ },
92
+ 'retry-config': {
93
+ fn: () => {
94
+ if (subArg === 'set') {
95
+ const key = args[2];
96
+ const value = args[3];
97
+ if (!key || !value) {
98
+ console.error('Usage: retry-config set <key> <value>');
99
+ console.error('Valid keys: maxRetries, windowSize, highFailureThreshold');
100
+ process.exit(1);
101
+ }
102
+ setConfigValue(key, value);
103
+ } else if (subArg === 'reset') {
104
+ resetConfig();
105
+ console.log('Retry configuration reset to defaults.');
106
+ } else {
107
+ displayConfig();
108
+ }
109
+ },
110
+ description: 'Manage retry configuration for adaptive retry logic'
111
+ },
112
+ 'feedback-config': {
113
+ fn: () => {
114
+ if (subArg === 'set') {
115
+ const key = args[2];
116
+ const value = args[3];
117
+ if (!key || !value) {
118
+ console.error('Usage: feedback-config set <key> <value>');
119
+ console.error('Valid keys: minRatingThreshold, enabled');
120
+ process.exit(1);
121
+ }
122
+ setFeedbackConfigValue(key, value);
123
+ } else if (subArg === 'reset') {
124
+ resetFeedbackConfig();
125
+ console.log('Feedback configuration reset to defaults.');
126
+ } else {
127
+ displayFeedbackConfig();
128
+ }
129
+ },
130
+ description: 'Manage feedback loop configuration'
131
+ },
40
132
  help: {
41
133
  fn: showHelp,
42
134
  description: 'Show this help message'
@@ -45,27 +137,43 @@ const commands = {
45
137
 
46
138
  function showHelp() {
47
139
  console.log(`
48
- agent-workflow - Multi-agent workflow framework
140
+ orchestr8 - Multi-agent workflow framework
49
141
 
50
- Usage: agent-workflow <command> [options]
142
+ Usage: orchestr8 <command> [options]
51
143
 
52
144
  Commands:
53
145
  init Initialize .blueprint directory in current project
54
146
  update Update agents, templates, and rituals (preserves your content)
55
- add-skills [agent] Install recommended skills for an agent (alex, cass, nigel, codey, all)
56
- skills [agent] List recommended skills for agents
147
+ validate Run pre-flight checks to validate project configuration
57
148
  queue Show current queue state for /implement-feature pipeline
58
149
  queue reset Clear the queue and reset all state
150
+ history View recent pipeline runs (last 10 by default)
151
+ history --all View all pipeline runs
152
+ history --stats View aggregate statistics
153
+ history clear Clear all pipeline history (with confirmation)
154
+ history clear --force Clear all pipeline history (no confirmation)
155
+ insights Analyze pipeline for bottlenecks, failures, and trends
156
+ insights --bottlenecks Show only bottleneck analysis
157
+ insights --failures Show only failure patterns
158
+ insights --feedback Show feedback loop insights (calibration, correlations)
159
+ insights --json Output analysis as JSON
160
+ retry-config View current retry configuration
161
+ retry-config set <key> <value> Modify a config value (maxRetries, windowSize, highFailureThreshold)
162
+ retry-config reset Reset retry configuration to defaults
163
+ feedback-config View current feedback loop configuration
164
+ feedback-config set <key> <value> Modify a config value (minRatingThreshold, enabled)
165
+ feedback-config reset Reset feedback configuration to defaults
59
166
  help Show this help message
60
167
 
61
168
  Examples:
62
- npx agent-workflow init
63
- npx agent-workflow update
64
- npx agent-workflow add-skills all
65
- npx agent-workflow add-skills codey
66
- npx agent-workflow skills
67
- npx agent-workflow queue
68
- npx agent-workflow queue reset
169
+ npx orchestr8 init
170
+ npx orchestr8 update
171
+ npx orchestr8 validate
172
+ npx orchestr8 queue
173
+ npx orchestr8 history
174
+ npx orchestr8 history --stats
175
+ npx orchestr8 insights --feedback
176
+ npx orchestr8 feedback-config
69
177
  `);
70
178
  }
71
179
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestr8",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "description": "Multi-agent workflow framework for automated feature development",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -0,0 +1,171 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const CONFIG_FILE = '.claude/feedback-config.json';
5
+
6
+ /**
7
+ * Returns the default feedback configuration.
8
+ * Per FEATURE_SPEC.md defaults.
9
+ */
10
+ function getDefaultConfig() {
11
+ return {
12
+ minRatingThreshold: 3.0,
13
+ enabled: true,
14
+ issueMappings: {
15
+ 'missing-error-handling': 'add-context',
16
+ 'unclear-scope': 'simplify-prompt',
17
+ 'too-complex': 'simplify-prompt',
18
+ 'too-many-stories': 'reduce-stories',
19
+ 'untestable-criteria': 'simplify-tests',
20
+ 'missing-edge-cases': 'add-context'
21
+ }
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Ensures the .claude directory exists.
27
+ */
28
+ function ensureConfigDir() {
29
+ const dir = path.dirname(CONFIG_FILE);
30
+ if (!fs.existsSync(dir)) {
31
+ fs.mkdirSync(dir, { recursive: true });
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Reads the feedback config from file.
37
+ * Returns defaults if file is missing or corrupted.
38
+ */
39
+ function readConfig() {
40
+ ensureConfigDir();
41
+ if (!fs.existsSync(CONFIG_FILE)) {
42
+ return getDefaultConfig();
43
+ }
44
+ try {
45
+ const content = fs.readFileSync(CONFIG_FILE, 'utf8');
46
+ return JSON.parse(content);
47
+ } catch (err) {
48
+ return getDefaultConfig();
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Writes the feedback config to file.
54
+ */
55
+ function writeConfig(config) {
56
+ ensureConfigDir();
57
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
58
+ }
59
+
60
+ /**
61
+ * Validates a feedback object against the schema.
62
+ * Per FEATURE_SPEC.md:Rule 1.
63
+ * @param {object} feedback - Feedback object to validate
64
+ * @returns {object} { valid: boolean, errors: string[] }
65
+ */
66
+ function validateFeedback(feedback) {
67
+ const errors = [];
68
+
69
+ if (!['alex', 'cass', 'nigel'].includes(feedback.about)) {
70
+ errors.push('Invalid "about" field');
71
+ }
72
+
73
+ if (typeof feedback.rating !== 'number' ||
74
+ feedback.rating < 1 ||
75
+ feedback.rating > 5) {
76
+ errors.push('Invalid "rating" field');
77
+ }
78
+
79
+ if (typeof feedback.confidence !== 'number' ||
80
+ feedback.confidence < 0 ||
81
+ feedback.confidence > 1) {
82
+ errors.push('Invalid "confidence" field');
83
+ }
84
+
85
+ if (!Array.isArray(feedback.issues)) {
86
+ errors.push('Invalid "issues" field');
87
+ }
88
+
89
+ if (!['proceed', 'pause', 'revise'].includes(feedback.recommendation)) {
90
+ errors.push('Invalid "recommendation" field');
91
+ }
92
+
93
+ return { valid: errors.length === 0, errors };
94
+ }
95
+
96
+ /**
97
+ * Determines whether the pipeline should pause based on feedback.
98
+ * Per FEATURE_SPEC.md:Rule 2.
99
+ * @param {object} feedback - Validated feedback object
100
+ * @param {object} config - Feedback configuration
101
+ * @returns {boolean} True if pipeline should pause
102
+ */
103
+ function shouldPause(feedback, config) {
104
+ return feedback.rating < config.minRatingThreshold ||
105
+ feedback.recommendation === 'pause';
106
+ }
107
+
108
+ /**
109
+ * Validates and sets a config value.
110
+ * @param {string} key - Config key
111
+ * @param {string} value - New value (will be parsed)
112
+ */
113
+ function setConfigValue(key, value) {
114
+ const config = readConfig();
115
+
116
+ if (key === 'minRatingThreshold') {
117
+ const numValue = parseFloat(value);
118
+ if (isNaN(numValue) || numValue < 1.0 || numValue > 5.0) {
119
+ throw new Error(
120
+ 'minRatingThreshold must be a number between 1.0 and 5.0'
121
+ );
122
+ }
123
+ config.minRatingThreshold = numValue;
124
+ } else if (key === 'enabled') {
125
+ if (value !== 'true' && value !== 'false') {
126
+ throw new Error('enabled must be true or false');
127
+ }
128
+ config.enabled = value === 'true';
129
+ } else {
130
+ throw new Error(
131
+ `Unknown config key: ${key}. Valid keys: minRatingThreshold, enabled`
132
+ );
133
+ }
134
+
135
+ writeConfig(config);
136
+ console.log(`Set ${key} = ${config[key]}`);
137
+ }
138
+
139
+ /**
140
+ * Displays the current feedback configuration.
141
+ */
142
+ function displayConfig() {
143
+ const config = readConfig();
144
+ console.log('\nFeedback Configuration\n');
145
+ console.log(` Min rating threshold: ${config.minRatingThreshold}`);
146
+ console.log(` Enabled: ${config.enabled}`);
147
+ console.log('\n Issue Mappings:');
148
+ for (const [issue, strategy] of Object.entries(config.issueMappings)) {
149
+ console.log(` ${issue.padEnd(24)}: ${strategy}`);
150
+ }
151
+ console.log('');
152
+ }
153
+
154
+ /**
155
+ * Resets feedback config to defaults.
156
+ */
157
+ function resetConfig() {
158
+ writeConfig(getDefaultConfig());
159
+ }
160
+
161
+ module.exports = {
162
+ CONFIG_FILE,
163
+ getDefaultConfig,
164
+ readConfig,
165
+ writeConfig,
166
+ validateFeedback,
167
+ shouldPause,
168
+ setConfigValue,
169
+ displayConfig,
170
+ resetConfig
171
+ };
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 'orchestr8 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 'orchestr8 history --all' to see all entries.`);
171
+ }
172
+ console.log(`Run 'orchestr8 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 'orchestr8 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
+ };