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.
- package/.blueprint/agents/AGENT_BA_CASS.md +50 -25
- package/.blueprint/agents/AGENT_DEVELOPER_CODEY.md +60 -69
- package/.blueprint/agents/AGENT_SPECIFICATION_ALEX.md +45 -0
- package/.blueprint/agents/AGENT_TESTER_NIGEL.md +72 -105
- package/.blueprint/features/feature_adaptive-retry/FEATURE_SPEC.md +239 -0
- package/.blueprint/features/feature_adaptive-retry/IMPLEMENTATION_PLAN.md +48 -0
- package/.blueprint/features/feature_adaptive-retry/story-prompt-modification.md +85 -0
- package/.blueprint/features/feature_adaptive-retry/story-retry-config.md +89 -0
- package/.blueprint/features/feature_adaptive-retry/story-should-retry.md +98 -0
- package/.blueprint/features/feature_adaptive-retry/story-strategy-recommendation.md +85 -0
- package/.blueprint/features/feature_agent-guardrails/FEATURE_SPEC.md +328 -0
- package/.blueprint/features/feature_agent-guardrails/IMPLEMENTATION_PLAN.md +90 -0
- package/.blueprint/features/feature_agent-guardrails/story-citation-requirements.md +50 -0
- package/.blueprint/features/feature_agent-guardrails/story-confidentiality.md +50 -0
- package/.blueprint/features/feature_agent-guardrails/story-escalation-protocol.md +55 -0
- package/.blueprint/features/feature_agent-guardrails/story-source-restrictions.md +50 -0
- package/.blueprint/features/feature_feedback-loop/FEATURE_SPEC.md +347 -0
- package/.blueprint/features/feature_feedback-loop/IMPLEMENTATION_PLAN.md +71 -0
- package/.blueprint/features/feature_feedback-loop/story-feedback-collection.md +63 -0
- package/.blueprint/features/feature_feedback-loop/story-feedback-config.md +61 -0
- package/.blueprint/features/feature_feedback-loop/story-feedback-insights.md +63 -0
- package/.blueprint/features/feature_feedback-loop/story-quality-gates.md +57 -0
- package/.blueprint/features/feature_pipeline-history/FEATURE_SPEC.md +239 -0
- package/.blueprint/features/feature_pipeline-history/IMPLEMENTATION_PLAN.md +71 -0
- package/.blueprint/features/feature_pipeline-history/story-clear-history.md +73 -0
- package/.blueprint/features/feature_pipeline-history/story-display-history.md +75 -0
- package/.blueprint/features/feature_pipeline-history/story-record-execution.md +76 -0
- package/.blueprint/features/feature_pipeline-history/story-show-statistics.md +85 -0
- package/.blueprint/features/feature_pipeline-insights/FEATURE_SPEC.md +288 -0
- package/.blueprint/features/feature_pipeline-insights/IMPLEMENTATION_PLAN.md +65 -0
- package/.blueprint/features/feature_pipeline-insights/story-anomaly-detection.md +71 -0
- package/.blueprint/features/feature_pipeline-insights/story-bottleneck-analysis.md +75 -0
- package/.blueprint/features/feature_pipeline-insights/story-failure-patterns.md +75 -0
- package/.blueprint/features/feature_pipeline-insights/story-json-output.md +75 -0
- package/.blueprint/features/feature_pipeline-insights/story-trend-analysis.md +78 -0
- package/.blueprint/features/feature_validate-command/FEATURE_SPEC.md +209 -0
- package/.blueprint/features/feature_validate-command/IMPLEMENTATION_PLAN.md +59 -0
- package/.blueprint/features/feature_validate-command/story-failure-output.md +61 -0
- package/.blueprint/features/feature_validate-command/story-node-version-check.md +52 -0
- package/.blueprint/features/feature_validate-command/story-run-validation.md +59 -0
- package/.blueprint/features/feature_validate-command/story-success-output.md +50 -0
- package/.blueprint/system_specification/SYSTEM_SPEC.md +248 -0
- package/README.md +174 -40
- package/SKILL.md +399 -74
- package/bin/cli.js +128 -20
- package/package.json +1 -1
- package/src/feedback.js +171 -0
- package/src/history.js +306 -0
- package/src/index.js +57 -2
- package/src/init.js +2 -6
- package/src/insights.js +504 -0
- package/src/retry.js +274 -0
- package/src/update.js +10 -2
- package/src/validate.js +172 -0
- 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
|
-
|
|
140
|
+
orchestr8 - Multi-agent workflow framework
|
|
49
141
|
|
|
50
|
-
Usage:
|
|
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
|
-
|
|
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
|
|
63
|
-
npx
|
|
64
|
-
npx
|
|
65
|
-
npx
|
|
66
|
-
npx
|
|
67
|
-
npx
|
|
68
|
-
npx
|
|
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
package/src/feedback.js
ADDED
|
@@ -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
|
+
};
|