orchestr8 2.6.1 → 2.7.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 +2 -112
- package/.blueprint/agents/AGENT_DEVELOPER_CODEY.md +1 -40
- package/.blueprint/agents/AGENT_SPECIFICATION_ALEX.md +1 -40
- package/.blueprint/agents/AGENT_TESTER_NIGEL.md +3 -51
- package/.blueprint/agents/GUARDRAILS.md +42 -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_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_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/prompts/TEMPLATE.md +65 -0
- package/.blueprint/prompts/alex-runtime.md +48 -0
- package/.blueprint/prompts/cass-runtime.md +45 -0
- package/.blueprint/prompts/codey-implement-runtime.md +50 -0
- package/.blueprint/prompts/codey-plan-runtime.md +46 -0
- package/.blueprint/prompts/nigel-runtime.md +46 -0
- package/.blueprint/templates/STORY_TEMPLATE.md +96 -0
- package/.blueprint/templates/TEST_TEMPLATE.md +76 -0
- package/README.md +82 -18
- package/SKILL.md +180 -80
- package/package.json +1 -1
- package/src/business-context.js +91 -0
- package/src/classifier.js +173 -0
- package/src/feedback.js +47 -17
- package/src/handoff.js +148 -0
- package/src/index.js +51 -1
- 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
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Story Routing - Feature Classifier Module
|
|
3
|
+
*
|
|
4
|
+
* Classifies features as "technical" or "user-facing" to determine
|
|
5
|
+
* whether the Cass (story writing) stage should be included in the pipeline.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Technical keywords indicate infrastructure/internal work
|
|
9
|
+
const TECHNICAL_KEYWORDS = [
|
|
10
|
+
'refactor',
|
|
11
|
+
'token',
|
|
12
|
+
'performance',
|
|
13
|
+
'module',
|
|
14
|
+
'internal',
|
|
15
|
+
'infrastructure',
|
|
16
|
+
'optimization',
|
|
17
|
+
'extract',
|
|
18
|
+
'compress',
|
|
19
|
+
'cache',
|
|
20
|
+
'schema',
|
|
21
|
+
'validation',
|
|
22
|
+
'helper',
|
|
23
|
+
'utility',
|
|
24
|
+
'config'
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
// User-facing keywords indicate customer-visible features
|
|
28
|
+
const USER_FACING_KEYWORDS = [
|
|
29
|
+
'user',
|
|
30
|
+
'customer',
|
|
31
|
+
'ui',
|
|
32
|
+
'screen',
|
|
33
|
+
'journey',
|
|
34
|
+
'flow',
|
|
35
|
+
'experience',
|
|
36
|
+
'interface',
|
|
37
|
+
'form',
|
|
38
|
+
'button',
|
|
39
|
+
'login',
|
|
40
|
+
'signup',
|
|
41
|
+
'dashboard',
|
|
42
|
+
'notification',
|
|
43
|
+
'email'
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Classify a feature specification as technical or user-facing
|
|
48
|
+
* @param {string} content - The feature specification content
|
|
49
|
+
* @returns {Object} Classification result with type, counts, and reason
|
|
50
|
+
*/
|
|
51
|
+
function classifyFeature(content) {
|
|
52
|
+
const lowerContent = (content || '').toLowerCase();
|
|
53
|
+
|
|
54
|
+
let technicalCount = 0;
|
|
55
|
+
let userFacingCount = 0;
|
|
56
|
+
const technicalMatches = [];
|
|
57
|
+
const userFacingMatches = [];
|
|
58
|
+
|
|
59
|
+
// Count technical keyword matches
|
|
60
|
+
for (const keyword of TECHNICAL_KEYWORDS) {
|
|
61
|
+
const regex = new RegExp(`\\b${keyword}\\b`, 'gi');
|
|
62
|
+
const matches = lowerContent.match(regex);
|
|
63
|
+
if (matches) {
|
|
64
|
+
technicalCount += matches.length;
|
|
65
|
+
technicalMatches.push(keyword);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Count user-facing keyword matches
|
|
70
|
+
for (const keyword of USER_FACING_KEYWORDS) {
|
|
71
|
+
const regex = new RegExp(`\\b${keyword}\\b`, 'gi');
|
|
72
|
+
const matches = lowerContent.match(regex);
|
|
73
|
+
if (matches) {
|
|
74
|
+
userFacingCount += matches.length;
|
|
75
|
+
userFacingMatches.push(keyword);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Determine type - tie goes to user-facing (conservative default)
|
|
80
|
+
const type = technicalCount > userFacingCount ? 'technical' : 'user-facing';
|
|
81
|
+
|
|
82
|
+
// Build reason string
|
|
83
|
+
let reason;
|
|
84
|
+
if (technicalCount === 0 && userFacingCount === 0) {
|
|
85
|
+
reason = 'No keywords found, defaulting to user-facing';
|
|
86
|
+
} else if (technicalCount > userFacingCount) {
|
|
87
|
+
reason = `Technical keywords (${technicalMatches.join(', ')}) outweigh user-facing`;
|
|
88
|
+
} else if (userFacingCount > technicalCount) {
|
|
89
|
+
reason = `User-facing keywords (${userFacingMatches.join(', ')}) outweigh technical`;
|
|
90
|
+
} else {
|
|
91
|
+
reason = 'Tie between technical and user-facing, defaulting to user-facing';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
type,
|
|
96
|
+
technicalCount,
|
|
97
|
+
userFacingCount,
|
|
98
|
+
reason
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parse story-related flags from command arguments
|
|
104
|
+
* @param {string[]} args - Array of command arguments
|
|
105
|
+
* @returns {Object} Parsed flags with override value
|
|
106
|
+
*/
|
|
107
|
+
function parseStoryFlags(args) {
|
|
108
|
+
const argList = args || [];
|
|
109
|
+
|
|
110
|
+
if (argList.includes('--with-stories')) {
|
|
111
|
+
return { override: 'include' };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (argList.includes('--skip-stories')) {
|
|
115
|
+
return { override: 'skip' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { override: null };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Determine whether stories should be included in the pipeline
|
|
123
|
+
* @param {string} featureType - 'technical' or 'user-facing'
|
|
124
|
+
* @param {string|null} override - 'include', 'skip', or null
|
|
125
|
+
* @returns {boolean} Whether to include stories in the pipeline
|
|
126
|
+
*/
|
|
127
|
+
function shouldIncludeStories(featureType, override) {
|
|
128
|
+
// Override takes precedence
|
|
129
|
+
if (override === 'include') {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
if (override === 'skip') {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Default behavior based on classification
|
|
137
|
+
return featureType === 'user-facing';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Build queue state object with classification data
|
|
142
|
+
* @param {string} slug - Feature slug
|
|
143
|
+
* @param {string} featureType - 'technical' or 'user-facing'
|
|
144
|
+
* @param {boolean} includeStories - Whether stories are included
|
|
145
|
+
* @returns {Object} Queue state object with featureType and skippedCass fields
|
|
146
|
+
*/
|
|
147
|
+
function buildClassifiedQueueState(slug, featureType, includeStories) {
|
|
148
|
+
return {
|
|
149
|
+
slug,
|
|
150
|
+
featureType,
|
|
151
|
+
skippedCass: !includeStories
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Log classification result to console
|
|
157
|
+
* @param {Object} result - Classification result from classifyFeature
|
|
158
|
+
*/
|
|
159
|
+
function logClassification(result) {
|
|
160
|
+
console.log(`Feature classified as ${result.type}: ${result.reason}`);
|
|
161
|
+
console.log(` Technical indicators: ${result.technicalCount}`);
|
|
162
|
+
console.log(` User-facing indicators: ${result.userFacingCount}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = {
|
|
166
|
+
TECHNICAL_KEYWORDS,
|
|
167
|
+
USER_FACING_KEYWORDS,
|
|
168
|
+
classifyFeature,
|
|
169
|
+
parseStoryFlags,
|
|
170
|
+
shouldIncludeStories,
|
|
171
|
+
buildClassifiedQueueState,
|
|
172
|
+
logClassification
|
|
173
|
+
};
|
package/src/feedback.js
CHANGED
|
@@ -3,6 +3,36 @@ const path = require('path');
|
|
|
3
3
|
|
|
4
4
|
const CONFIG_FILE = '.claude/feedback-config.json';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Normalizes abbreviated keys to full names.
|
|
8
|
+
* Converts "rec" to "recommendation" while preserving existing full key.
|
|
9
|
+
* @param {Object} feedback - Raw feedback object
|
|
10
|
+
* @returns {Object} - Normalized feedback object
|
|
11
|
+
*/
|
|
12
|
+
function normalizeFeedbackKeys(feedback) {
|
|
13
|
+
const normalized = { ...feedback };
|
|
14
|
+
if ('rec' in normalized && !('recommendation' in normalized)) {
|
|
15
|
+
normalized.recommendation = normalized.rec;
|
|
16
|
+
delete normalized.rec;
|
|
17
|
+
}
|
|
18
|
+
return normalized;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parses FEEDBACK: JSON from agent output text.
|
|
23
|
+
* @param {string} output - Raw agent output
|
|
24
|
+
* @returns {Object|null} - Parsed feedback or null if not found/invalid
|
|
25
|
+
*/
|
|
26
|
+
function parseFeedbackFromOutput(output) {
|
|
27
|
+
const match = output.match(/FEEDBACK:\s*(\{[^}]+\})/);
|
|
28
|
+
if (!match) return null;
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(match[1]);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
6
36
|
/**
|
|
7
37
|
* Returns the default feedback configuration.
|
|
8
38
|
* Per FEATURE_SPEC.md defaults.
|
|
@@ -60,34 +90,32 @@ function writeConfig(config) {
|
|
|
60
90
|
/**
|
|
61
91
|
* Validates a feedback object against the schema.
|
|
62
92
|
* Per FEATURE_SPEC.md:Rule 1.
|
|
93
|
+
* Accepts both "rec" and "recommendation" keys for recommendation field.
|
|
63
94
|
* @param {object} feedback - Feedback object to validate
|
|
64
95
|
* @returns {object} { valid: boolean, errors: string[] }
|
|
65
96
|
*/
|
|
66
97
|
function validateFeedback(feedback) {
|
|
67
98
|
const errors = [];
|
|
68
99
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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');
|
|
100
|
+
// Rating validation
|
|
101
|
+
if (typeof feedback.rating !== 'number' || !Number.isInteger(feedback.rating)) {
|
|
102
|
+
errors.push('rating must be an integer');
|
|
103
|
+
} else if (feedback.rating < 1 || feedback.rating > 5) {
|
|
104
|
+
errors.push('rating must be between 1 and 5');
|
|
83
105
|
}
|
|
84
106
|
|
|
107
|
+
// Issues validation
|
|
85
108
|
if (!Array.isArray(feedback.issues)) {
|
|
86
|
-
errors.push('
|
|
109
|
+
errors.push('issues must be an array');
|
|
110
|
+
} else if (!feedback.issues.every(i => typeof i === 'string')) {
|
|
111
|
+
errors.push('issues must be an array of strings');
|
|
87
112
|
}
|
|
88
113
|
|
|
89
|
-
|
|
90
|
-
|
|
114
|
+
// Recommendation validation - accept both "rec" and "recommendation" keys
|
|
115
|
+
const rec = feedback.recommendation || feedback.rec;
|
|
116
|
+
const validRecs = ['proceed', 'pause', 'revise'];
|
|
117
|
+
if (!validRecs.includes(rec)) {
|
|
118
|
+
errors.push(`recommendation must be one of: ${validRecs.join(', ')}`);
|
|
91
119
|
}
|
|
92
120
|
|
|
93
121
|
return { valid: errors.length === 0, errors };
|
|
@@ -163,6 +191,8 @@ module.exports = {
|
|
|
163
191
|
getDefaultConfig,
|
|
164
192
|
readConfig,
|
|
165
193
|
writeConfig,
|
|
194
|
+
normalizeFeedbackKeys,
|
|
195
|
+
parseFeedbackFromOutput,
|
|
166
196
|
validateFeedback,
|
|
167
197
|
shouldPause,
|
|
168
198
|
setConfigValue,
|
package/src/handoff.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handoff summary helper functions.
|
|
3
|
+
* Parses and validates handoff summary format for agent-to-agent communication.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parses a handoff summary and extracts key fields.
|
|
8
|
+
* @param {string} content - The handoff summary markdown content
|
|
9
|
+
* @returns {object} Parsed summary fields
|
|
10
|
+
*/
|
|
11
|
+
function parseHandoffSummary(content) {
|
|
12
|
+
return {
|
|
13
|
+
hasHeading: /^## Handoff Summary/m.test(content),
|
|
14
|
+
forField: content.match(/\*\*For:\*\*\s*(.+)/)?.[1]?.trim(),
|
|
15
|
+
featureField: content.match(/\*\*Feature:\*\*\s*(.+)/)?.[1]?.trim(),
|
|
16
|
+
hasKeyDecisions: /### Key Decisions/m.test(content),
|
|
17
|
+
hasFilesCreated: /### Files Created/m.test(content),
|
|
18
|
+
hasOpenQuestions: /### Open Questions/m.test(content),
|
|
19
|
+
hasCriticalContext: /### Critical Context/m.test(content),
|
|
20
|
+
lineCount: content.split('\n').length
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Extracts a named section from the summary.
|
|
26
|
+
* @param {string} content - The handoff summary markdown content
|
|
27
|
+
* @param {string} sectionName - Name of the section (e.g., 'Key Decisions')
|
|
28
|
+
* @returns {string} The section content, or empty string if not found
|
|
29
|
+
*/
|
|
30
|
+
function extractSection(content, sectionName) {
|
|
31
|
+
const regex = new RegExp(`### ${sectionName}\\n([\\s\\S]*?)(?=\\n###|$)`);
|
|
32
|
+
const match = content.match(regex);
|
|
33
|
+
return match ? match[1].trim() : '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Counts bullet items in a section.
|
|
38
|
+
* @param {string} section - Section content
|
|
39
|
+
* @returns {number} Number of bullet items
|
|
40
|
+
*/
|
|
41
|
+
function countBulletItems(section) {
|
|
42
|
+
return section.split('\n').filter(line => /^[-*]\s/.test(line)).length;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Extracts file paths from a section.
|
|
47
|
+
* @param {string} section - Section content
|
|
48
|
+
* @returns {string[]} Array of file paths
|
|
49
|
+
*/
|
|
50
|
+
function extractFilePaths(section) {
|
|
51
|
+
const lines = section.split('\n').filter(line => /^[-*]\s/.test(line));
|
|
52
|
+
return lines.map(line => line.replace(/^[-*]\s+/, '').trim());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validates a handoff summary against format rules.
|
|
57
|
+
* @param {string} content - The handoff summary markdown content
|
|
58
|
+
* @returns {object} { valid: boolean, errors: string[] }
|
|
59
|
+
*/
|
|
60
|
+
function validateHandoffSummary(content) {
|
|
61
|
+
const errors = [];
|
|
62
|
+
const parsed = parseHandoffSummary(content);
|
|
63
|
+
|
|
64
|
+
if (!parsed.hasHeading) {
|
|
65
|
+
errors.push('Missing ## Handoff Summary heading');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!parsed.forField) {
|
|
69
|
+
errors.push('Missing **For:** field');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!parsed.featureField) {
|
|
73
|
+
errors.push('Missing **Feature:** field');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!parsed.hasKeyDecisions) {
|
|
77
|
+
errors.push('Missing ### Key Decisions section');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!parsed.hasFilesCreated) {
|
|
81
|
+
errors.push('Missing ### Files Created section');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!parsed.hasOpenQuestions) {
|
|
85
|
+
errors.push('Missing ### Open Questions section');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!parsed.hasCriticalContext) {
|
|
89
|
+
errors.push('Missing ### Critical Context section');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (parsed.lineCount >= 30) {
|
|
93
|
+
errors.push(`Summary exceeds 30 lines (found ${parsed.lineCount})`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Validate Key Decisions bullet count
|
|
97
|
+
const keyDecisions = extractSection(content, 'Key Decisions');
|
|
98
|
+
const bulletCount = countBulletItems(keyDecisions);
|
|
99
|
+
if (bulletCount < 1 || bulletCount > 5) {
|
|
100
|
+
errors.push(`Key Decisions should have 1-5 items (found ${bulletCount})`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { valid: errors.length === 0, errors };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Returns the handoff file path for an agent.
|
|
108
|
+
* @param {string} featureDir - Feature directory path
|
|
109
|
+
* @param {string} agent - Agent name (alex, cass, nigel)
|
|
110
|
+
* @returns {string} Full path to handoff file
|
|
111
|
+
*/
|
|
112
|
+
function getHandoffPath(featureDir, agent) {
|
|
113
|
+
return `${featureDir}/handoff-${agent.toLowerCase()}.md`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Generates a handoff summary template.
|
|
118
|
+
* @param {string} forAgent - Target agent name
|
|
119
|
+
* @param {string} featureSlug - Feature slug
|
|
120
|
+
* @returns {string} Template markdown content
|
|
121
|
+
*/
|
|
122
|
+
function getHandoffTemplate(forAgent, featureSlug) {
|
|
123
|
+
return `## Handoff Summary
|
|
124
|
+
**For:** ${forAgent}
|
|
125
|
+
**Feature:** ${featureSlug}
|
|
126
|
+
|
|
127
|
+
### Key Decisions
|
|
128
|
+
-
|
|
129
|
+
|
|
130
|
+
### Files Created
|
|
131
|
+
-
|
|
132
|
+
|
|
133
|
+
### Open Questions
|
|
134
|
+
- None
|
|
135
|
+
|
|
136
|
+
### Critical Context
|
|
137
|
+
`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = {
|
|
141
|
+
parseHandoffSummary,
|
|
142
|
+
extractSection,
|
|
143
|
+
countBulletItems,
|
|
144
|
+
extractFilePaths,
|
|
145
|
+
validateHandoffSummary,
|
|
146
|
+
getHandoffPath,
|
|
147
|
+
getHandoffTemplate
|
|
148
|
+
};
|
package/src/index.js
CHANGED
|
@@ -25,6 +25,32 @@ const {
|
|
|
25
25
|
recommendThreshold,
|
|
26
26
|
displayFeedbackInsights
|
|
27
27
|
} = require('./insights');
|
|
28
|
+
const {
|
|
29
|
+
parseHandoffSummary,
|
|
30
|
+
extractSection,
|
|
31
|
+
countBulletItems,
|
|
32
|
+
extractFilePaths,
|
|
33
|
+
validateHandoffSummary,
|
|
34
|
+
getHandoffPath,
|
|
35
|
+
getHandoffTemplate
|
|
36
|
+
} = require('./handoff');
|
|
37
|
+
const {
|
|
38
|
+
needsBusinessContext,
|
|
39
|
+
parseIncludeBusinessContextFlag,
|
|
40
|
+
shouldIncludeBusinessContext,
|
|
41
|
+
buildQueueState,
|
|
42
|
+
generateBusinessContextDirective
|
|
43
|
+
} = require('./business-context');
|
|
44
|
+
const {
|
|
45
|
+
classifyFeature,
|
|
46
|
+
parseStoryFlags,
|
|
47
|
+
shouldIncludeStories,
|
|
48
|
+
buildClassifiedQueueState,
|
|
49
|
+
logClassification,
|
|
50
|
+
TECHNICAL_KEYWORDS,
|
|
51
|
+
USER_FACING_KEYWORDS
|
|
52
|
+
} = require('./classifier');
|
|
53
|
+
const tools = require('./tools');
|
|
28
54
|
|
|
29
55
|
module.exports = {
|
|
30
56
|
init,
|
|
@@ -56,5 +82,29 @@ module.exports = {
|
|
|
56
82
|
calculateCalibration,
|
|
57
83
|
correlateIssues,
|
|
58
84
|
recommendThreshold,
|
|
59
|
-
displayFeedbackInsights
|
|
85
|
+
displayFeedbackInsights,
|
|
86
|
+
// Handoff summary exports
|
|
87
|
+
parseHandoffSummary,
|
|
88
|
+
extractSection,
|
|
89
|
+
countBulletItems,
|
|
90
|
+
extractFilePaths,
|
|
91
|
+
validateHandoffSummary,
|
|
92
|
+
getHandoffPath,
|
|
93
|
+
getHandoffTemplate,
|
|
94
|
+
// Business context exports
|
|
95
|
+
needsBusinessContext,
|
|
96
|
+
parseIncludeBusinessContextFlag,
|
|
97
|
+
shouldIncludeBusinessContext,
|
|
98
|
+
buildQueueState,
|
|
99
|
+
generateBusinessContextDirective,
|
|
100
|
+
// Classifier module exports (smart story routing)
|
|
101
|
+
classifyFeature,
|
|
102
|
+
parseStoryFlags,
|
|
103
|
+
shouldIncludeStories,
|
|
104
|
+
buildClassifiedQueueState,
|
|
105
|
+
logClassification,
|
|
106
|
+
TECHNICAL_KEYWORDS,
|
|
107
|
+
USER_FACING_KEYWORDS,
|
|
108
|
+
// Tools module (model native features)
|
|
109
|
+
tools
|
|
60
110
|
};
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Input Validation Utilities
|
|
3
|
+
* Validates inputs against tool schema constraints
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validate tool input against a schema
|
|
8
|
+
* @param {Object} schema - Tool schema with input_schema
|
|
9
|
+
* @param {Object} input - Input to validate
|
|
10
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
11
|
+
*/
|
|
12
|
+
function validateToolInput(schema, input) {
|
|
13
|
+
const errors = [];
|
|
14
|
+
const props = schema.input_schema.properties;
|
|
15
|
+
const required = schema.input_schema.required || [];
|
|
16
|
+
|
|
17
|
+
// Check required fields
|
|
18
|
+
for (const field of required) {
|
|
19
|
+
if (!(field in input)) {
|
|
20
|
+
errors.push(`missing required field: ${field}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Validate each field
|
|
25
|
+
for (const [key, value] of Object.entries(input)) {
|
|
26
|
+
const propSchema = props[key];
|
|
27
|
+
if (!propSchema) continue;
|
|
28
|
+
|
|
29
|
+
// Type: number with bounds
|
|
30
|
+
if (propSchema.type === 'number') {
|
|
31
|
+
if (typeof value !== 'number') {
|
|
32
|
+
errors.push(`${key} must be number`);
|
|
33
|
+
} else {
|
|
34
|
+
if (propSchema.minimum !== undefined && value < propSchema.minimum) {
|
|
35
|
+
errors.push(`${key} below minimum ${propSchema.minimum}`);
|
|
36
|
+
}
|
|
37
|
+
if (propSchema.maximum !== undefined && value > propSchema.maximum) {
|
|
38
|
+
errors.push(`${key} above maximum ${propSchema.maximum}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Type: array
|
|
44
|
+
if (propSchema.type === 'array') {
|
|
45
|
+
if (!Array.isArray(value)) {
|
|
46
|
+
errors.push(`${key} must be array`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Type: string with maxLength
|
|
51
|
+
if (propSchema.type === 'string') {
|
|
52
|
+
if (typeof value !== 'string') {
|
|
53
|
+
errors.push(`${key} must be string`);
|
|
54
|
+
} else if (propSchema.maxLength && value.length > propSchema.maxLength) {
|
|
55
|
+
errors.push(`${key} exceeds maxLength ${propSchema.maxLength}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Enum constraint
|
|
60
|
+
if (propSchema.enum && !propSchema.enum.includes(value)) {
|
|
61
|
+
errors.push(`${key} must be one of: ${propSchema.enum.join(', ')}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { valid: errors.length === 0, errors };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Normalize feedback input to handle "rec" shorthand for "recommendation"
|
|
70
|
+
* @param {Object} input - Raw feedback input
|
|
71
|
+
* @returns {Object} - Normalized input
|
|
72
|
+
*/
|
|
73
|
+
function normalizeFeedbackInput(input) {
|
|
74
|
+
if (input.rec && !input.recommendation) {
|
|
75
|
+
return { ...input, recommendation: input.rec };
|
|
76
|
+
}
|
|
77
|
+
return input;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
validateToolInput,
|
|
82
|
+
normalizeFeedbackInput
|
|
83
|
+
};
|