orchestr8 2.5.0 → 2.6.1
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 +42 -19
- package/.blueprint/agents/AGENT_DEVELOPER_CODEY.md +42 -38
- package/.blueprint/agents/AGENT_SPECIFICATION_ALEX.md +45 -0
- package/.blueprint/agents/AGENT_TESTER_NIGEL.md +42 -21
- 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 +182 -38
- package/SKILL.md +333 -23
- package/bin/cli.js +128 -20
- package/package.json +2 -2
- 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/validate.js +172 -0
- package/src/skills.js +0 -93
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orchestr8",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"description": "Multi-agent workflow framework for automated feature development",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"author": "NewmanJustice",
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
|
-
"url": "git+https://github.com/NewmanJustice/
|
|
23
|
+
"url": "git+https://github.com/NewmanJustice/orchestr8.git"
|
|
24
24
|
},
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"engines": {
|
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
|
+
};
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
const { init } = require('./init');
|
|
2
2
|
const { update } = require('./update');
|
|
3
|
-
const {
|
|
3
|
+
const { validate, formatOutput, checkNodeVersion } = require('./validate');
|
|
4
|
+
const { recordHistory, displayHistory, showStats, clearHistory, storeStageFeedback } = require('./history');
|
|
5
|
+
const {
|
|
6
|
+
readConfig,
|
|
7
|
+
writeConfig,
|
|
8
|
+
resetConfig,
|
|
9
|
+
calculateFailureRate,
|
|
10
|
+
recommendStrategy,
|
|
11
|
+
applyStrategy,
|
|
12
|
+
shouldRetry,
|
|
13
|
+
mapIssuesToStrategies
|
|
14
|
+
} = require('./retry');
|
|
15
|
+
const {
|
|
16
|
+
validateFeedback,
|
|
17
|
+
shouldPause,
|
|
18
|
+
getDefaultConfig: getFeedbackDefaultConfig,
|
|
19
|
+
readConfig: readFeedbackConfig,
|
|
20
|
+
writeConfig: writeFeedbackConfig
|
|
21
|
+
} = require('./feedback');
|
|
22
|
+
const {
|
|
23
|
+
calculateCalibration,
|
|
24
|
+
correlateIssues,
|
|
25
|
+
recommendThreshold,
|
|
26
|
+
displayFeedbackInsights
|
|
27
|
+
} = require('./insights');
|
|
4
28
|
|
|
5
|
-
module.exports = {
|
|
29
|
+
module.exports = {
|
|
30
|
+
init,
|
|
31
|
+
update,
|
|
32
|
+
validate,
|
|
33
|
+
formatOutput,
|
|
34
|
+
checkNodeVersion,
|
|
35
|
+
recordHistory,
|
|
36
|
+
displayHistory,
|
|
37
|
+
showStats,
|
|
38
|
+
clearHistory,
|
|
39
|
+
storeStageFeedback,
|
|
40
|
+
// Retry module exports
|
|
41
|
+
readConfig,
|
|
42
|
+
writeConfig,
|
|
43
|
+
resetConfig,
|
|
44
|
+
calculateFailureRate,
|
|
45
|
+
recommendStrategy,
|
|
46
|
+
applyStrategy,
|
|
47
|
+
shouldRetry,
|
|
48
|
+
mapIssuesToStrategies,
|
|
49
|
+
// Feedback module exports
|
|
50
|
+
validateFeedback,
|
|
51
|
+
shouldPause,
|
|
52
|
+
getFeedbackDefaultConfig,
|
|
53
|
+
readFeedbackConfig,
|
|
54
|
+
writeFeedbackConfig,
|
|
55
|
+
// Feedback insights exports
|
|
56
|
+
calculateCalibration,
|
|
57
|
+
correlateIssues,
|
|
58
|
+
recommendThreshold,
|
|
59
|
+
displayFeedbackInsights
|
|
60
|
+
};
|
package/src/init.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const readline = require('readline');
|
|
4
|
-
const { addSkills } = require('./skills');
|
|
5
4
|
|
|
6
5
|
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
7
6
|
const TARGET_DIR = process.cwd();
|
|
@@ -41,7 +40,8 @@ function updateGitignore() {
|
|
|
41
40
|
const gitignorePath = path.join(TARGET_DIR, '.gitignore');
|
|
42
41
|
const entriesToAdd = [
|
|
43
42
|
'# agent-workflow',
|
|
44
|
-
'.claude/implement-queue.json'
|
|
43
|
+
'.claude/implement-queue.json',
|
|
44
|
+
'.claude/pipeline-history.json'
|
|
45
45
|
];
|
|
46
46
|
|
|
47
47
|
let content = '';
|
|
@@ -109,10 +109,6 @@ async function init() {
|
|
|
109
109
|
// Update .gitignore
|
|
110
110
|
updateGitignore();
|
|
111
111
|
|
|
112
|
-
// Install agent skills
|
|
113
|
-
console.log('\nInstalling agent skills...');
|
|
114
|
-
await addSkills('all');
|
|
115
|
-
|
|
116
112
|
console.log(`
|
|
117
113
|
orchestr8 initialized successfully!
|
|
118
114
|
|