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.
- package/.blueprint/agents/AGENT_BA_CASS.md +239 -0
- package/.blueprint/agents/AGENT_DEVELOPER_CODEY.md +308 -0
- package/.blueprint/agents/AGENT_SPECIFICATION_ALEX.md +183 -0
- package/.blueprint/agents/AGENT_TESTER_NIGEL.md +159 -0
- package/.blueprint/agents/GUARDRAILS.md +83 -0
- package/.blueprint/agents/TEAM_MANIFESTO.md +91 -0
- package/.blueprint/features/.gitkeep +0 -0
- 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_compressed-feedback/FEATURE_SPEC.md +136 -0
- package/.blueprint/features/feature_compressed-feedback/IMPLEMENTATION_PLAN.md +40 -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_interactive-alex/FEATURE_SPEC.md +263 -0
- package/.blueprint/features/feature_interactive-alex/IMPLEMENTATION_PLAN.md +69 -0
- package/.blueprint/features/feature_interactive-alex/handoff-alex.md +19 -0
- package/.blueprint/features/feature_interactive-alex/handoff-cass.md +21 -0
- package/.blueprint/features/feature_interactive-alex/handoff-nigel.md +19 -0
- package/.blueprint/features/feature_interactive-alex/story-flag-routing.md +54 -0
- package/.blueprint/features/feature_interactive-alex/story-iterative-drafting.md +65 -0
- package/.blueprint/features/feature_interactive-alex/story-pipeline-integration.md +66 -0
- package/.blueprint/features/feature_interactive-alex/story-session-lifecycle.md +75 -0
- package/.blueprint/features/feature_interactive-alex/story-system-spec-creation.md +57 -0
- package/.blueprint/features/feature_lazy-business-context/FEATURE_SPEC.md +140 -0
- package/.blueprint/features/feature_lazy-business-context/IMPLEMENTATION_PLAN.md +54 -0
- package/.blueprint/features/feature_model-native-features/FEATURE_SPEC.md +174 -0
- package/.blueprint/features/feature_model-native-features/IMPLEMENTATION_PLAN.md +45 -0
- package/.blueprint/features/feature_parallel-abort/FEATURE_SPEC.md +117 -0
- package/.blueprint/features/feature_parallel-confirm/FEATURE_SPEC.md +90 -0
- package/.blueprint/features/feature_parallel-features/FEATURE_SPEC.md +291 -0
- package/.blueprint/features/feature_parallel-features/IMPLEMENTATION_PLAN.md +73 -0
- package/.blueprint/features/feature_parallel-lock/FEATURE_SPEC.md +119 -0
- package/.blueprint/features/feature_parallel-logging/FEATURE_SPEC.md +105 -0
- package/.blueprint/features/feature_parallel-preflight/FEATURE_SPEC.md +141 -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_shared-guardrails/FEATURE_SPEC.md +119 -0
- package/.blueprint/features/feature_shared-guardrails/IMPLEMENTATION_PLAN.md +34 -0
- package/.blueprint/features/feature_shared-guardrails/story-extract-guardrails.md +60 -0
- package/.blueprint/features/feature_shared-guardrails/story-update-init-commands.md +63 -0
- package/.blueprint/features/feature_slim-agent-prompts/FEATURE_SPEC.md +145 -0
- package/.blueprint/features/feature_slim-agent-prompts/IMPLEMENTATION_PLAN.md +87 -0
- package/.blueprint/features/feature_slim-agent-prompts/story-create-runtime-prompt-template.md +59 -0
- package/.blueprint/features/feature_slim-agent-prompts/story-create-slim-agent-prompts.md +65 -0
- package/.blueprint/features/feature_slim-agent-prompts/story-skill-integration.md +53 -0
- package/.blueprint/features/feature_smart-story-routing/FEATURE_SPEC.md +147 -0
- package/.blueprint/features/feature_smart-story-routing/IMPLEMENTATION_PLAN.md +73 -0
- package/.blueprint/features/feature_template-extraction/FEATURE_SPEC.md +134 -0
- package/.blueprint/features/feature_template-extraction/IMPLEMENTATION_PLAN.md +46 -0
- package/.blueprint/features/feature_upstream-summaries/FEATURE_SPEC.md +150 -0
- package/.blueprint/features/feature_upstream-summaries/IMPLEMENTATION_PLAN.md +70 -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/prompts/TEMPLATE.md +65 -0
- package/.blueprint/prompts/alex-runtime.md +49 -0
- package/.blueprint/prompts/cass-runtime.md +46 -0
- package/.blueprint/prompts/codey-implement-runtime.md +52 -0
- package/.blueprint/prompts/codey-plan-runtime.md +47 -0
- package/.blueprint/prompts/nigel-runtime.md +47 -0
- package/.blueprint/system_specification/.gitkeep +0 -0
- package/.blueprint/system_specification/SYSTEM_SPEC.md +248 -0
- package/.blueprint/templates/FEATURE_SPEC.md +125 -0
- package/.blueprint/templates/STORY_TEMPLATE.md +96 -0
- package/.blueprint/templates/SYSTEM_SPEC.md +128 -0
- package/.blueprint/templates/TEST_TEMPLATE.md +76 -0
- package/.blueprint/ways_of_working/DEVELOPMENT_RITUAL.md +178 -0
- package/.business_context/README.md +27 -0
- package/LICENSE +21 -0
- package/README.md +564 -0
- package/SKILL.md +840 -0
- package/bin/cli.js +388 -0
- package/package.json +36 -0
- package/src/business-context.js +91 -0
- package/src/classifier.js +173 -0
- package/src/feedback.js +201 -0
- package/src/handoff.js +148 -0
- package/src/history.js +306 -0
- package/src/index.js +170 -0
- package/src/init.js +139 -0
- package/src/insights.js +504 -0
- package/src/interactive.js +338 -0
- package/src/orchestrator.js +217 -0
- package/src/parallel.js +1544 -0
- package/src/retry.js +274 -0
- package/src/stack.js +320 -0
- package/src/tools/index.js +27 -0
- package/src/tools/prompts.js +45 -0
- package/src/tools/schemas.js +38 -0
- package/src/tools/validation.js +83 -0
- package/src/update.js +112 -0
- package/src/validate.js +172 -0
package/src/retry.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { readHistoryFile } = require('./history');
|
|
4
|
+
|
|
5
|
+
const CONFIG_FILE = '.claude/retry-config.json';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns the default retry configuration.
|
|
9
|
+
* Per FEATURE_SPEC.md defaults.
|
|
10
|
+
*/
|
|
11
|
+
function getDefaultConfig() {
|
|
12
|
+
return {
|
|
13
|
+
maxRetries: 3,
|
|
14
|
+
windowSize: 10,
|
|
15
|
+
highFailureThreshold: 0.2,
|
|
16
|
+
strategies: {
|
|
17
|
+
alex: ['simplify-prompt', 'add-context'],
|
|
18
|
+
cass: ['reduce-stories', 'simplify-prompt'],
|
|
19
|
+
nigel: ['simplify-tests', 'add-context'],
|
|
20
|
+
'codey-plan': ['add-context', 'simplify-prompt'],
|
|
21
|
+
'codey-implement': ['incremental', 'rollback']
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Ensures the .claude directory exists.
|
|
28
|
+
*/
|
|
29
|
+
function ensureConfigDir() {
|
|
30
|
+
const dir = path.dirname(CONFIG_FILE);
|
|
31
|
+
if (!fs.existsSync(dir)) {
|
|
32
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Reads the retry config from file.
|
|
38
|
+
* Returns defaults if file is missing or corrupted.
|
|
39
|
+
*/
|
|
40
|
+
function readConfig() {
|
|
41
|
+
ensureConfigDir();
|
|
42
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
43
|
+
return getDefaultConfig();
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const content = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
47
|
+
return JSON.parse(content);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
// Graceful degradation: return defaults on parse error
|
|
50
|
+
return getDefaultConfig();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Writes the retry config to file.
|
|
56
|
+
* Creates .claude directory if needed.
|
|
57
|
+
*/
|
|
58
|
+
function writeConfig(config) {
|
|
59
|
+
ensureConfigDir();
|
|
60
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resets config to defaults by writing default config to file.
|
|
65
|
+
*/
|
|
66
|
+
function resetConfig() {
|
|
67
|
+
writeConfig(getDefaultConfig());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Calculates failure rate for a stage over a sliding window.
|
|
72
|
+
* @param {string} stage - The pipeline stage name
|
|
73
|
+
* @param {Array} history - Array of history entries
|
|
74
|
+
* @param {number} windowSize - Number of recent entries to consider
|
|
75
|
+
* @returns {number} Failure rate between 0 and 1
|
|
76
|
+
*/
|
|
77
|
+
function calculateFailureRate(stage, history, windowSize = 10) {
|
|
78
|
+
// Handle corrupted history gracefully
|
|
79
|
+
if (!Array.isArray(history) || history.length === 0) {
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
const recent = history.slice(-windowSize);
|
|
83
|
+
if (recent.length === 0) return 0;
|
|
84
|
+
const failedAtStage = recent.filter(
|
|
85
|
+
e => e.status === 'failed' && e.failedStage === stage
|
|
86
|
+
).length;
|
|
87
|
+
return failedAtStage / recent.length;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Recommends a retry strategy based on attempt count and failure rate.
|
|
92
|
+
* @param {string} stage - The pipeline stage name
|
|
93
|
+
* @param {number} attemptCount - Current attempt number (1-based)
|
|
94
|
+
* @param {number} failureRate - Calculated failure rate (0-1)
|
|
95
|
+
* @param {object} config - Retry configuration object
|
|
96
|
+
* @returns {string} Strategy name or 'abort-recommended'
|
|
97
|
+
*/
|
|
98
|
+
function recommendStrategy(stage, attemptCount, failureRate, config) {
|
|
99
|
+
if (attemptCount > config.maxRetries) {
|
|
100
|
+
return 'abort-recommended';
|
|
101
|
+
}
|
|
102
|
+
if (failureRate > config.highFailureThreshold) {
|
|
103
|
+
const strategies = config.strategies[stage] || [];
|
|
104
|
+
const idx = Math.min(attemptCount - 1, strategies.length - 1);
|
|
105
|
+
return strategies[idx] || 'retry';
|
|
106
|
+
}
|
|
107
|
+
return 'retry';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Applies a strategy by modifying the prompt.
|
|
112
|
+
* @param {string} strategy - The strategy name
|
|
113
|
+
* @param {string} originalPrompt - The original prompt text
|
|
114
|
+
* @returns {string} Modified prompt or original if no modification needed
|
|
115
|
+
*/
|
|
116
|
+
function applyStrategy(strategy, originalPrompt) {
|
|
117
|
+
const modifications = {
|
|
118
|
+
'retry': null,
|
|
119
|
+
'simplify-prompt': 'Focus on core requirements only. Skip edge cases and optional sections.',
|
|
120
|
+
'reduce-stories': 'Write only the 2-3 most critical user stories. Defer others to follow-up.',
|
|
121
|
+
'simplify-tests': 'Write only happy-path tests for each AC. Skip edge cases.',
|
|
122
|
+
'add-context': '[Context from previous stage prepended]',
|
|
123
|
+
'incremental': 'Implement one test at a time. Stop and report after each.',
|
|
124
|
+
'rollback': 'git checkout -- .'
|
|
125
|
+
};
|
|
126
|
+
const mod = modifications[strategy];
|
|
127
|
+
return mod ? `${originalPrompt}\n\n${mod}` : originalPrompt;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Orchestrator entry point: determines if/how to retry a failed stage.
|
|
132
|
+
* @param {string} stage - The pipeline stage that failed
|
|
133
|
+
* @param {string} featureSlug - The feature being implemented
|
|
134
|
+
* @param {Array|null} history - History entries (pass null to read from file)
|
|
135
|
+
* @param {object|null} config - Config object (pass null to read from file)
|
|
136
|
+
* @param {number} attemptCount - Current attempt number (1-based)
|
|
137
|
+
* @returns {object} Recommendation object with strategy and metadata
|
|
138
|
+
*/
|
|
139
|
+
function shouldRetry(stage, featureSlug, history, config, attemptCount) {
|
|
140
|
+
// Read history from file if not provided
|
|
141
|
+
let historyData = history;
|
|
142
|
+
if (historyData === null || historyData === undefined) {
|
|
143
|
+
historyData = readHistoryFile();
|
|
144
|
+
// Handle corrupted history
|
|
145
|
+
if (historyData && historyData.error === 'corrupted') {
|
|
146
|
+
historyData = [];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Read config from file if not provided
|
|
151
|
+
const configData = config || readConfig();
|
|
152
|
+
|
|
153
|
+
const failureRate = calculateFailureRate(
|
|
154
|
+
stage,
|
|
155
|
+
historyData,
|
|
156
|
+
configData.windowSize
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const strategy = recommendStrategy(
|
|
160
|
+
stage,
|
|
161
|
+
attemptCount,
|
|
162
|
+
failureRate,
|
|
163
|
+
configData
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
stage,
|
|
168
|
+
featureSlug,
|
|
169
|
+
attemptCount,
|
|
170
|
+
failureRate,
|
|
171
|
+
strategy,
|
|
172
|
+
shouldRetry: strategy !== 'abort-recommended',
|
|
173
|
+
maxRetries: configData.maxRetries
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Displays the current retry configuration.
|
|
179
|
+
*/
|
|
180
|
+
function displayConfig() {
|
|
181
|
+
const config = readConfig();
|
|
182
|
+
console.log('\nRetry Configuration\n');
|
|
183
|
+
console.log(` Max retries: ${config.maxRetries}`);
|
|
184
|
+
console.log(` Window size: ${config.windowSize}`);
|
|
185
|
+
console.log(` High failure threshold: ${config.highFailureThreshold}`);
|
|
186
|
+
console.log('\n Stage Strategies:');
|
|
187
|
+
for (const [stage, strategies] of Object.entries(config.strategies)) {
|
|
188
|
+
console.log(` ${stage.padEnd(16)}: ${strategies.join(' -> ')}`);
|
|
189
|
+
}
|
|
190
|
+
console.log('');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Sets a config value by key.
|
|
195
|
+
* @param {string} key - Config key (maxRetries, windowSize, highFailureThreshold)
|
|
196
|
+
* @param {string} value - New value (will be parsed appropriately)
|
|
197
|
+
*/
|
|
198
|
+
function setConfigValue(key, value) {
|
|
199
|
+
const config = readConfig();
|
|
200
|
+
const numericKeys = ['maxRetries', 'windowSize', 'highFailureThreshold'];
|
|
201
|
+
|
|
202
|
+
if (!numericKeys.includes(key)) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`Unknown config key: ${key}. Valid keys: ${numericKeys.join(', ')}`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const numValue = parseFloat(value);
|
|
209
|
+
if (isNaN(numValue)) {
|
|
210
|
+
throw new Error(`Invalid value for ${key}: ${value}. Must be a number.`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Validate specific constraints
|
|
214
|
+
if (key === 'maxRetries' && (numValue < 0 || !Number.isInteger(numValue))) {
|
|
215
|
+
throw new Error('maxRetries must be a non-negative integer.');
|
|
216
|
+
}
|
|
217
|
+
if (key === 'windowSize' && (numValue < 1 || !Number.isInteger(numValue))) {
|
|
218
|
+
throw new Error('windowSize must be a positive integer.');
|
|
219
|
+
}
|
|
220
|
+
if (key === 'highFailureThreshold' && (numValue < 0 || numValue > 1)) {
|
|
221
|
+
throw new Error('highFailureThreshold must be between 0 and 1.');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
config[key] = numValue;
|
|
225
|
+
writeConfig(config);
|
|
226
|
+
console.log(`Set ${key} = ${numValue}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Maps feedback issues to retry strategies using issue mappings config.
|
|
231
|
+
* Per FEATURE_SPEC.md:Rule 3.
|
|
232
|
+
* @param {Array} issues - Array of issue codes from feedback
|
|
233
|
+
* @param {object} config - Config with issueMappings (from feedback config)
|
|
234
|
+
* @returns {Array} Array of recommended strategies
|
|
235
|
+
*/
|
|
236
|
+
function mapIssuesToStrategies(issues, config) {
|
|
237
|
+
if (!issues || !Array.isArray(issues) || issues.length === 0) {
|
|
238
|
+
return ['retry'];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const mappings = config?.issueMappings || {
|
|
242
|
+
'missing-error-handling': 'add-context',
|
|
243
|
+
'unclear-scope': 'simplify-prompt',
|
|
244
|
+
'too-complex': 'simplify-prompt',
|
|
245
|
+
'too-many-stories': 'reduce-stories',
|
|
246
|
+
'untestable-criteria': 'simplify-tests',
|
|
247
|
+
'missing-edge-cases': 'add-context'
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const strategies = [];
|
|
251
|
+
for (const issue of issues) {
|
|
252
|
+
const strategy = mappings[issue];
|
|
253
|
+
if (strategy && !strategies.includes(strategy)) {
|
|
254
|
+
strategies.push(strategy);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return strategies.length > 0 ? strategies : ['retry'];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
module.exports = {
|
|
262
|
+
CONFIG_FILE,
|
|
263
|
+
getDefaultConfig,
|
|
264
|
+
readConfig,
|
|
265
|
+
writeConfig,
|
|
266
|
+
resetConfig,
|
|
267
|
+
calculateFailureRate,
|
|
268
|
+
recommendStrategy,
|
|
269
|
+
applyStrategy,
|
|
270
|
+
shouldRetry,
|
|
271
|
+
displayConfig,
|
|
272
|
+
setConfigValue,
|
|
273
|
+
mapIssuesToStrategies
|
|
274
|
+
};
|
package/src/stack.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const CONFIG_FILE = '.claude/stack-config.json';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns the default (empty) stack configuration.
|
|
8
|
+
*/
|
|
9
|
+
function getDefaultStackConfig() {
|
|
10
|
+
return {
|
|
11
|
+
language: '',
|
|
12
|
+
runtime: '',
|
|
13
|
+
packageManager: '',
|
|
14
|
+
frameworks: [],
|
|
15
|
+
testRunner: '',
|
|
16
|
+
testCommand: '',
|
|
17
|
+
linter: '',
|
|
18
|
+
tools: []
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Ensures the .claude directory exists.
|
|
24
|
+
*/
|
|
25
|
+
function ensureConfigDir() {
|
|
26
|
+
const dir = path.dirname(CONFIG_FILE);
|
|
27
|
+
if (!fs.existsSync(dir)) {
|
|
28
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Reads the stack config from file.
|
|
34
|
+
* Returns defaults if file is missing or corrupted.
|
|
35
|
+
*/
|
|
36
|
+
function readStackConfig() {
|
|
37
|
+
ensureConfigDir();
|
|
38
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
39
|
+
return getDefaultStackConfig();
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const content = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
43
|
+
return JSON.parse(content);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
return getDefaultStackConfig();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Writes the stack config to file.
|
|
51
|
+
*/
|
|
52
|
+
function writeStackConfig(config) {
|
|
53
|
+
ensureConfigDir();
|
|
54
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resets stack config to defaults by writing empty config to file.
|
|
59
|
+
*/
|
|
60
|
+
function resetStackConfig() {
|
|
61
|
+
writeStackConfig(getDefaultStackConfig());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const VALID_KEYS = [
|
|
65
|
+
'language', 'runtime', 'packageManager',
|
|
66
|
+
'frameworks', 'testRunner', 'testCommand',
|
|
67
|
+
'linter', 'tools'
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const ARRAY_KEYS = ['frameworks', 'tools'];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Sets a config value by key.
|
|
74
|
+
* Array keys (frameworks, tools) accept JSON array strings.
|
|
75
|
+
* @param {string} key - Config key
|
|
76
|
+
* @param {string} value - New value (string or JSON array string)
|
|
77
|
+
*/
|
|
78
|
+
function setStackConfigValue(key, value) {
|
|
79
|
+
if (!VALID_KEYS.includes(key)) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Unknown config key: ${key}. Valid keys: ${VALID_KEYS.join(', ')}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const config = readStackConfig();
|
|
86
|
+
|
|
87
|
+
if (ARRAY_KEYS.includes(key)) {
|
|
88
|
+
// Try parsing as JSON array
|
|
89
|
+
try {
|
|
90
|
+
const parsed = JSON.parse(value);
|
|
91
|
+
if (!Array.isArray(parsed)) {
|
|
92
|
+
throw new Error(`${key} must be a JSON array, e.g. '["express","react"]'`);
|
|
93
|
+
}
|
|
94
|
+
config[key] = parsed;
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if (err.message.includes('must be a JSON array')) throw err;
|
|
97
|
+
throw new Error(`${key} must be a valid JSON array, e.g. '["express","react"]'`);
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
config[key] = value;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
writeStackConfig(config);
|
|
104
|
+
const display = Array.isArray(config[key]) ? JSON.stringify(config[key]) : config[key];
|
|
105
|
+
console.log(`Set ${key} = ${display}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Auto-detect tech stack from project files.
|
|
110
|
+
* Scans for manifest files and infers configuration.
|
|
111
|
+
* @param {string} projectDir - Directory to scan (defaults to cwd)
|
|
112
|
+
* @returns {object} Detected stack config
|
|
113
|
+
*/
|
|
114
|
+
function detectStackConfig(projectDir) {
|
|
115
|
+
const dir = projectDir || process.cwd();
|
|
116
|
+
const config = getDefaultStackConfig();
|
|
117
|
+
|
|
118
|
+
const exists = (file) => fs.existsSync(path.join(dir, file));
|
|
119
|
+
const readJSON = (file) => {
|
|
120
|
+
try {
|
|
121
|
+
return JSON.parse(fs.readFileSync(path.join(dir, file), 'utf8'));
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const readText = (file) => {
|
|
127
|
+
try {
|
|
128
|
+
return fs.readFileSync(path.join(dir, file), 'utf8');
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Node.js / JavaScript detection
|
|
135
|
+
if (exists('package.json')) {
|
|
136
|
+
config.language = 'JavaScript';
|
|
137
|
+
config.runtime = 'Node.js';
|
|
138
|
+
|
|
139
|
+
const pkg = readJSON('package.json');
|
|
140
|
+
if (pkg) {
|
|
141
|
+
// Runtime version from engines
|
|
142
|
+
if (pkg.engines && pkg.engines.node) {
|
|
143
|
+
config.runtime = `Node.js ${pkg.engines.node}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const deps = pkg.dependencies || {};
|
|
147
|
+
const devDeps = pkg.devDependencies || {};
|
|
148
|
+
const allDeps = { ...deps, ...devDeps };
|
|
149
|
+
|
|
150
|
+
// Frameworks
|
|
151
|
+
const frameworkNames = [
|
|
152
|
+
'express', 'fastify', 'koa', 'react', 'next',
|
|
153
|
+
'vue', 'angular', 'hapi', 'nest', '@hapi/hapi', '@nestjs/core'
|
|
154
|
+
];
|
|
155
|
+
const detected = [];
|
|
156
|
+
for (const fw of frameworkNames) {
|
|
157
|
+
if (fw in deps || fw in devDeps) {
|
|
158
|
+
// Normalize package names to friendly names
|
|
159
|
+
if (fw === '@hapi/hapi') detected.push('hapi');
|
|
160
|
+
else if (fw === '@nestjs/core') detected.push('nest');
|
|
161
|
+
else detected.push(fw);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (detected.length > 0) config.frameworks = detected;
|
|
165
|
+
|
|
166
|
+
// Test runner
|
|
167
|
+
const testRunners = ['jest', 'mocha', 'vitest', 'ava'];
|
|
168
|
+
for (const tr of testRunners) {
|
|
169
|
+
if (tr in devDeps || tr in deps) {
|
|
170
|
+
config.testRunner = tr;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Test command from scripts
|
|
176
|
+
if (pkg.scripts && pkg.scripts.test) {
|
|
177
|
+
config.testCommand = pkg.scripts.test;
|
|
178
|
+
// Also try to detect test runner from test script if not found in deps
|
|
179
|
+
if (!config.testRunner) {
|
|
180
|
+
for (const tr of testRunners) {
|
|
181
|
+
if (pkg.scripts.test.includes(tr)) {
|
|
182
|
+
config.testRunner = tr;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (!config.testCommand) {
|
|
189
|
+
config.testCommand = 'npm test';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Linter
|
|
193
|
+
const linters = ['eslint', 'biome', 'oxlint', '@biomejs/biome'];
|
|
194
|
+
for (const l of linters) {
|
|
195
|
+
if (l in devDeps || l in deps) {
|
|
196
|
+
if (l === '@biomejs/biome') config.linter = 'biome';
|
|
197
|
+
else config.linter = l;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Tools
|
|
203
|
+
const toolNames = ['nodemon', 'supertest', 'prettier', 'typescript'];
|
|
204
|
+
const detectedTools = [];
|
|
205
|
+
for (const t of toolNames) {
|
|
206
|
+
if (t in devDeps || t in deps) {
|
|
207
|
+
detectedTools.push(t);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (detectedTools.length > 0) config.tools = detectedTools;
|
|
211
|
+
|
|
212
|
+
// Package manager from lockfiles
|
|
213
|
+
if (exists('yarn.lock')) {
|
|
214
|
+
config.packageManager = 'yarn';
|
|
215
|
+
} else if (exists('pnpm-lock.yaml')) {
|
|
216
|
+
config.packageManager = 'pnpm';
|
|
217
|
+
} else {
|
|
218
|
+
config.packageManager = 'npm';
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// TypeScript overrides JavaScript
|
|
224
|
+
if (exists('tsconfig.json')) {
|
|
225
|
+
config.language = 'TypeScript';
|
|
226
|
+
if (!config.runtime) config.runtime = 'Node.js';
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Python detection
|
|
230
|
+
if (exists('pyproject.toml') || exists('requirements.txt')) {
|
|
231
|
+
config.language = 'Python';
|
|
232
|
+
config.runtime = 'Python 3.x';
|
|
233
|
+
|
|
234
|
+
const pyproject = readText('pyproject.toml');
|
|
235
|
+
if (pyproject) {
|
|
236
|
+
// Test runner
|
|
237
|
+
if (pyproject.includes('pytest')) config.testRunner = 'pytest';
|
|
238
|
+
else if (pyproject.includes('unittest')) config.testRunner = 'unittest';
|
|
239
|
+
|
|
240
|
+
// Linter/tools
|
|
241
|
+
if (pyproject.includes('ruff')) config.linter = 'ruff';
|
|
242
|
+
else if (pyproject.includes('flake8')) config.linter = 'flake8';
|
|
243
|
+
|
|
244
|
+
const pyTools = [];
|
|
245
|
+
if (pyproject.includes('black')) pyTools.push('black');
|
|
246
|
+
if (pyproject.includes('mypy')) pyTools.push('mypy');
|
|
247
|
+
if (pyTools.length > 0) config.tools = pyTools;
|
|
248
|
+
|
|
249
|
+
// Frameworks
|
|
250
|
+
const pyFrameworks = [];
|
|
251
|
+
if (pyproject.includes('django')) pyFrameworks.push('django');
|
|
252
|
+
if (pyproject.includes('flask')) pyFrameworks.push('flask');
|
|
253
|
+
if (pyproject.includes('fastapi')) pyFrameworks.push('fastapi');
|
|
254
|
+
if (pyFrameworks.length > 0) config.frameworks = pyFrameworks;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Package manager
|
|
258
|
+
if (exists('poetry.lock')) config.packageManager = 'poetry';
|
|
259
|
+
else if (exists('Pipfile.lock') || exists('Pipfile')) config.packageManager = 'pipenv';
|
|
260
|
+
else config.packageManager = 'pip';
|
|
261
|
+
|
|
262
|
+
if (!config.testCommand && config.testRunner) {
|
|
263
|
+
config.testCommand = config.testRunner === 'pytest' ? 'pytest' : 'python -m unittest';
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Go detection
|
|
268
|
+
if (exists('go.mod')) {
|
|
269
|
+
config.language = 'Go';
|
|
270
|
+
config.runtime = 'Go';
|
|
271
|
+
config.testRunner = 'go test';
|
|
272
|
+
config.testCommand = 'go test ./...';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Rust detection
|
|
276
|
+
if (exists('Cargo.toml')) {
|
|
277
|
+
config.language = 'Rust';
|
|
278
|
+
config.runtime = 'Rust';
|
|
279
|
+
config.packageManager = 'cargo';
|
|
280
|
+
config.testRunner = 'cargo test';
|
|
281
|
+
config.testCommand = 'cargo test';
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Ruby detection
|
|
285
|
+
if (exists('Gemfile')) {
|
|
286
|
+
config.language = 'Ruby';
|
|
287
|
+
config.runtime = 'Ruby';
|
|
288
|
+
config.packageManager = 'bundler';
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return config;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Displays the current stack configuration.
|
|
296
|
+
*/
|
|
297
|
+
function displayStackConfig() {
|
|
298
|
+
const config = readStackConfig();
|
|
299
|
+
console.log('\nStack Configuration\n');
|
|
300
|
+
console.log(` language: ${config.language || '(not set)'}`);
|
|
301
|
+
console.log(` runtime: ${config.runtime || '(not set)'}`);
|
|
302
|
+
console.log(` packageManager: ${config.packageManager || '(not set)'}`);
|
|
303
|
+
console.log(` frameworks: ${config.frameworks.length > 0 ? config.frameworks.join(', ') : '(not set)'}`);
|
|
304
|
+
console.log(` testRunner: ${config.testRunner || '(not set)'}`);
|
|
305
|
+
console.log(` testCommand: ${config.testCommand || '(not set)'}`);
|
|
306
|
+
console.log(` linter: ${config.linter || '(not set)'}`);
|
|
307
|
+
console.log(` tools: ${config.tools.length > 0 ? config.tools.join(', ') : '(not set)'}`);
|
|
308
|
+
console.log('\nTo change: murmur8 stack-config set <key> <value>');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
module.exports = {
|
|
312
|
+
CONFIG_FILE,
|
|
313
|
+
getDefaultStackConfig,
|
|
314
|
+
readStackConfig,
|
|
315
|
+
writeStackConfig,
|
|
316
|
+
resetStackConfig,
|
|
317
|
+
setStackConfigValue,
|
|
318
|
+
detectStackConfig,
|
|
319
|
+
displayStackConfig
|
|
320
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools Module - Model Native Features
|
|
3
|
+
* Exports tool schemas, validation, and prompt utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { FEEDBACK_TOOL_SCHEMA, HANDOFF_TOOL_SCHEMA } = require('./schemas');
|
|
7
|
+
const { validateToolInput, normalizeFeedbackInput } = require('./validation');
|
|
8
|
+
const {
|
|
9
|
+
buildPromptMessages,
|
|
10
|
+
identifyCacheableContent,
|
|
11
|
+
SYSTEM_PROMPT_TEMPLATE,
|
|
12
|
+
USER_PROMPT_TEMPLATE
|
|
13
|
+
} = require('./prompts');
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
// Schemas
|
|
17
|
+
FEEDBACK_TOOL_SCHEMA,
|
|
18
|
+
HANDOFF_TOOL_SCHEMA,
|
|
19
|
+
// Validation
|
|
20
|
+
validateToolInput,
|
|
21
|
+
normalizeFeedbackInput,
|
|
22
|
+
// Prompts
|
|
23
|
+
buildPromptMessages,
|
|
24
|
+
identifyCacheableContent,
|
|
25
|
+
SYSTEM_PROMPT_TEMPLATE,
|
|
26
|
+
USER_PROMPT_TEMPLATE
|
|
27
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Structure Utilities
|
|
3
|
+
* Helpers for system/user prompt separation and caching
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const SYSTEM_PROMPT_TEMPLATE = {
|
|
7
|
+
role: 'system',
|
|
8
|
+
content: '[AGENT_SPEC]\n[GUARDRAILS]\n[TEMPLATES]',
|
|
9
|
+
cache_control: { type: 'ephemeral' }
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const USER_PROMPT_TEMPLATE = {
|
|
13
|
+
role: 'user',
|
|
14
|
+
content: '[TASK_INSTRUCTIONS]\n[INPUTS]\n[OUTPUTS]'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build prompt messages with system and user separation
|
|
19
|
+
* @param {string} staticContent - Content for system prompt (agent specs, guardrails)
|
|
20
|
+
* @param {string} dynamicContent - Content for user prompt (task instructions, inputs)
|
|
21
|
+
* @returns {Array<Object>} Array of message objects
|
|
22
|
+
*/
|
|
23
|
+
function buildPromptMessages(staticContent, dynamicContent) {
|
|
24
|
+
return [
|
|
25
|
+
{ ...SYSTEM_PROMPT_TEMPLATE, content: staticContent },
|
|
26
|
+
{ ...USER_PROMPT_TEMPLATE, content: dynamicContent }
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Identify if content should be cached (static, reusable content)
|
|
32
|
+
* @param {string} content - Content to analyze
|
|
33
|
+
* @returns {boolean} True if content is cacheable
|
|
34
|
+
*/
|
|
35
|
+
function identifyCacheableContent(content) {
|
|
36
|
+
const cacheablePatterns = ['AGENT_', 'GUARDRAIL', 'TEMPLATE', 'SPEC.md'];
|
|
37
|
+
return cacheablePatterns.some(p => content.includes(p));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
SYSTEM_PROMPT_TEMPLATE,
|
|
42
|
+
USER_PROMPT_TEMPLATE,
|
|
43
|
+
buildPromptMessages,
|
|
44
|
+
identifyCacheableContent
|
|
45
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Schema Definitions
|
|
3
|
+
* Reusable schema constants for Claude tool use
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const FEEDBACK_TOOL_SCHEMA = {
|
|
7
|
+
name: 'submit_feedback',
|
|
8
|
+
description: 'Submit quality rating for prior stage',
|
|
9
|
+
input_schema: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
rating: { type: 'number', minimum: 1, maximum: 5 },
|
|
13
|
+
issues: { type: 'array', items: { type: 'string' } },
|
|
14
|
+
recommendation: { enum: ['proceed', 'pause', 'revise'] }
|
|
15
|
+
},
|
|
16
|
+
required: ['rating', 'issues', 'recommendation']
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const HANDOFF_TOOL_SCHEMA = {
|
|
21
|
+
name: 'submit_handoff',
|
|
22
|
+
description: 'Submit summary for next agent',
|
|
23
|
+
input_schema: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
from_agent: { type: 'string', enum: ['alex', 'cass', 'nigel', 'codey'] },
|
|
27
|
+
to_agent: { type: 'string', enum: ['alex', 'cass', 'nigel', 'codey'] },
|
|
28
|
+
summary: { type: 'string', maxLength: 500 },
|
|
29
|
+
artifacts: { type: 'array', items: { type: 'string' } }
|
|
30
|
+
},
|
|
31
|
+
required: ['from_agent', 'to_agent', 'summary']
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
FEEDBACK_TOOL_SCHEMA,
|
|
37
|
+
HANDOFF_TOOL_SCHEMA
|
|
38
|
+
};
|