Commit all workspace changes from current session
This commit is contained in:
@@ -0,0 +1,348 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Format Locker - Enforce Structured Output Templates
|
||||
*
|
||||
* Ensures responses follow prescribed formats based on workflow type
|
||||
*/
|
||||
|
||||
class FormatLocker {
|
||||
constructor(workflow) {
|
||||
this.workflow = workflow;
|
||||
this.templates = this.loadTemplates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load format templates for each workflow type
|
||||
*/
|
||||
loadTemplates() {
|
||||
return {
|
||||
coding: {
|
||||
requiredSections: ['Summary', 'Files Modified', 'Verification', 'Decisions Made'],
|
||||
optionalSections: ['Still Open', 'Testing Notes'],
|
||||
format: `
|
||||
## Summary
|
||||
[What changed - 1-2 sentences]
|
||||
|
||||
## Files Modified
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| file1.py | [description] |
|
||||
| file2.js | [description] |
|
||||
|
||||
## Verification
|
||||
- [ ] Tests pass: \`command\`
|
||||
- [ ] Lint passes: \`command\`
|
||||
- [ ] Manual verification: [how]
|
||||
|
||||
## Decisions Made
|
||||
- [Decision 1]
|
||||
- [Decision 2]
|
||||
|
||||
## Still Open
|
||||
- [ ] [if any]
|
||||
`.trim(),
|
||||
validators: {
|
||||
'Files Modified': (content) => content.includes('|') && content.includes('File'),
|
||||
'Verification': (content) => content.includes('- [ ]') || content.includes('- [x]'),
|
||||
'Decisions Made': (content) => content.startsWith('- ')
|
||||
}
|
||||
},
|
||||
|
||||
debug: {
|
||||
requiredSections: ['Problem', 'Cause', 'Fix', 'Validation'],
|
||||
optionalSections: ['Prevention', 'Impact'],
|
||||
format: `
|
||||
## Problem
|
||||
[Symptom - what was observed]
|
||||
|
||||
## Cause
|
||||
[Root cause - why it happened]
|
||||
|
||||
## Fix
|
||||
[What was changed to resolve it]
|
||||
|
||||
## Validation
|
||||
[How it was verified fixed]
|
||||
`.trim(),
|
||||
validators: {
|
||||
'Problem': (content) => content.length > 10,
|
||||
'Cause': (content) => content.length > 10,
|
||||
'Fix': (content) => content.length > 10,
|
||||
'Validation': (content) => content.length > 10
|
||||
}
|
||||
},
|
||||
|
||||
deploy: {
|
||||
requiredSections: ['Deployment Summary', 'Pre-Deployment', 'Changes Applied', 'Verification'],
|
||||
optionalSections: ['Rollback', 'Post-Deployment Notes'],
|
||||
format: `
|
||||
## Deployment Summary
|
||||
[What and where]
|
||||
|
||||
## Pre-Deployment
|
||||
- [x] Backup created
|
||||
- [x] Tests pass
|
||||
- [x] Rollback plan documented
|
||||
|
||||
## Changes Applied
|
||||
| Step | Command | Status |
|
||||
|------|---------|--------|
|
||||
| 1 | [command] | ✅ |
|
||||
| 2 | [command] | ✅ |
|
||||
|
||||
## Verification
|
||||
- [x] Service responding: [url]
|
||||
- [x] Logs normal: [check]
|
||||
- [x] Smoke test: [result]
|
||||
`.trim(),
|
||||
validators: {
|
||||
'Pre-Deployment': (content) => content.includes('- [x]') || content.includes('- [ ]'),
|
||||
'Changes Applied': (content) => content.includes('|'),
|
||||
'Verification': (content) => content.includes('- [x]') || content.includes('- [ ]')
|
||||
}
|
||||
},
|
||||
|
||||
audit: {
|
||||
requiredSections: ['Scope', 'Findings', 'Recommendations'],
|
||||
optionalSections: ['Risk Assessment', 'Priority Matrix', 'Action Items'],
|
||||
format: `
|
||||
## Scope
|
||||
[What was audited]
|
||||
|
||||
## Findings
|
||||
| Item | Status | Severity |
|
||||
|------|--------|----------|
|
||||
| Finding 1 | ✅/⚠️/❌ | Low/Med/High |
|
||||
| Finding 2 | ✅/⚠️/❌ | Low/Med/High |
|
||||
|
||||
## Recommendations
|
||||
1. **[Priority]** [Action to take]
|
||||
2. **[Priority]** [Action to take]
|
||||
`.trim(),
|
||||
validators: {
|
||||
'Findings': (content) => content.includes('|'),
|
||||
'Recommendations': (content) => /^\d+\./.test(content)
|
||||
}
|
||||
},
|
||||
|
||||
planning: {
|
||||
requiredSections: ['Goal', 'Approach', 'Breakdown', 'Timeline'],
|
||||
optionalSections: ['Dependencies', 'Risks', 'Acceptance Criteria'],
|
||||
format: `
|
||||
## Goal
|
||||
[What we're trying to achieve]
|
||||
|
||||
## Approach
|
||||
[High-level strategy]
|
||||
|
||||
## Breakdown
|
||||
| Task | Owner | Estimate |
|
||||
|------|-------|----------|
|
||||
| Task 1 | [agent] | [time] |
|
||||
| Task 2 | [agent] | [time] |
|
||||
|
||||
## Timeline
|
||||
- Phase 1: [duration] - [deliverable]
|
||||
- Phase 2: [duration] - [deliverable]
|
||||
`.trim(),
|
||||
validators: {
|
||||
'Breakdown': (content) => content.includes('|'),
|
||||
'Timeline': (content) => content.includes('- Phase')
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template for current workflow
|
||||
*/
|
||||
getTemplate() {
|
||||
return this.templates[this.workflow] || this.templates['coding'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that response follows required format
|
||||
*/
|
||||
validateFormat(response) {
|
||||
const template = this.getTemplate();
|
||||
const results = {
|
||||
passed: true,
|
||||
missingSections: [],
|
||||
malformedSections: [],
|
||||
suggestions: []
|
||||
};
|
||||
|
||||
// Check for required sections
|
||||
for (const section of template.requiredSections) {
|
||||
const sectionPattern = new RegExp(`##\\s*${section.replace(/\s+/g, '\\s+')}`, 'i');
|
||||
if (!sectionPattern.test(response)) {
|
||||
results.missingSections.push(section);
|
||||
results.passed = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract and validate section contents
|
||||
const sections = this.extractSections(response);
|
||||
for (const [name, content] of Object.entries(sections)) {
|
||||
if (template.validators[name]) {
|
||||
const isValid = template.validators[name](content);
|
||||
if (!isValid) {
|
||||
results.malformedSections.push(name);
|
||||
results.passed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate suggestions
|
||||
if (results.missingSections.length > 0) {
|
||||
results.suggestions.push(`Add missing sections: ${results.missingSections.join(', ')}`);
|
||||
}
|
||||
if (results.malformedSections.length > 0) {
|
||||
results.suggestions.push(`Fix format in sections: ${results.malformedSections.join(', ')}`);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract sections from markdown response
|
||||
*/
|
||||
extractSections(response) {
|
||||
const sections = {};
|
||||
const sectionRegex = /^##\s+(.+)$/gm;
|
||||
let match;
|
||||
|
||||
const sectionNames = [];
|
||||
const sectionPositions = [];
|
||||
|
||||
while ((match = sectionRegex.exec(response)) !== null) {
|
||||
sectionNames.push(match[1].trim());
|
||||
sectionPositions.push(match.index);
|
||||
}
|
||||
|
||||
// Extract content between sections
|
||||
for (let i = 0; i < sectionNames.length; i++) {
|
||||
const startPos = sectionPositions[i] + sectionNames[i].length + 3; // +3 for "## "
|
||||
const endPos = i < sectionNames.length - 1 ? sectionPositions[i + 1] : response.length;
|
||||
sections[sectionNames[i]] = response.substring(startPos, endPos).trim();
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply format template to content
|
||||
*/
|
||||
applyTemplate(content, customData = {}) {
|
||||
const template = this.getTemplate();
|
||||
let formatted = template.format;
|
||||
|
||||
// Replace placeholders with actual content
|
||||
for (const [key, value] of Object.entries(content)) {
|
||||
const placeholder = `[${key}]`;
|
||||
if (formatted.includes(placeholder)) {
|
||||
formatted = formatted.replace(placeholder, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace custom data
|
||||
for (const [key, value] of Object.entries(customData)) {
|
||||
const placeholder = `{${key}}`;
|
||||
if (formatted.includes(placeholder)) {
|
||||
formatted = formatted.replace(new RegExp(`\\{${key}\\}`, 'g'), value);
|
||||
}
|
||||
}
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a response to ensure format compliance
|
||||
*/
|
||||
enforceFormat(response, autoFix = true) {
|
||||
const validation = this.validateFormat(response);
|
||||
|
||||
if (validation.passed) {
|
||||
return {
|
||||
response: response,
|
||||
status: 'passed',
|
||||
fixes: []
|
||||
};
|
||||
}
|
||||
|
||||
if (!autoFix) {
|
||||
return {
|
||||
response: response,
|
||||
status: 'failed',
|
||||
errors: validation,
|
||||
suggestion: this.getTemplate().format
|
||||
};
|
||||
}
|
||||
|
||||
// Auto-fix: add missing sections
|
||||
let fixed = response;
|
||||
const sections = this.extractSections(response);
|
||||
const template = this.getTemplate();
|
||||
|
||||
for (const section of validation.missingSections) {
|
||||
// Add missing section at end
|
||||
fixed += `\n\n## ${section}\n[To be completed]`;
|
||||
}
|
||||
|
||||
return {
|
||||
response: fixed,
|
||||
status: 'fixed',
|
||||
fixes: validation.suggestions
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get format requirements as system prompt addition
|
||||
*/
|
||||
getFormatPrompt() {
|
||||
const template = this.getTemplate();
|
||||
|
||||
return `
|
||||
## Output Format Requirements
|
||||
|
||||
You MUST follow this structure exactly:
|
||||
|
||||
${template.format}
|
||||
|
||||
### Required Sections
|
||||
${template.requiredSections.map(s => `- ${s}`).join('\n')}
|
||||
|
||||
### Rules
|
||||
- All required sections must be present
|
||||
- Use proper markdown tables where shown
|
||||
- Include checkboxes for verification items
|
||||
- Be specific, not vague
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use
|
||||
module.exports = FormatLocker;
|
||||
|
||||
// CLI usage
|
||||
if (require.main === module) {
|
||||
const workflow = process.argv[2] || 'coding';
|
||||
const responseFile = process.argv[3];
|
||||
|
||||
const locker = new FormatLocker(workflow);
|
||||
|
||||
console.log('=== Format Template ===');
|
||||
console.log(locker.getTemplate().format);
|
||||
|
||||
if (responseFile) {
|
||||
const fs = require('fs');
|
||||
const response = fs.readFileSync(responseFile, 'utf8');
|
||||
|
||||
console.log('\n=== Validation ===');
|
||||
const result = locker.enforceFormat(response, true);
|
||||
console.log('Status:', result.status);
|
||||
if (result.fixes) {
|
||||
console.log('Fixes:', result.fixes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Orchestrator - Main Integration Point
|
||||
*
|
||||
* Ties together: Pipeline → Router → LLM → Validator → Format Locker
|
||||
*/
|
||||
|
||||
const ContextPipeline = require('./pipeline');
|
||||
const WorkflowRouter = require('./workflow-router');
|
||||
const ResponseValidator = require('./validator');
|
||||
const FormatLocker = require('./format-locker');
|
||||
|
||||
class AgentOrchestrator {
|
||||
constructor(options = {}) {
|
||||
this.workspace = options.workspace || '/home/jcbeasley/.openclaw/workspace';
|
||||
this.verbose = options.verbose || false;
|
||||
this.autoFix = options.autoFix !== false;
|
||||
|
||||
this.pipeline = new ContextPipeline();
|
||||
this.router = new WorkflowRouter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point - process user request through full pipeline
|
||||
*/
|
||||
async process(userInput, context = {}) {
|
||||
const startTime = Date.now();
|
||||
|
||||
this.log('=== Starting Orchestration ===');
|
||||
this.log(`Input: ${userInput.substring(0, 100)}...`);
|
||||
|
||||
try {
|
||||
// Step 1: Build context
|
||||
this.log('Step 1: Building context...');
|
||||
const contextPacket = await this.buildContext(userInput, context);
|
||||
|
||||
// Step 2: Classify intent and route
|
||||
this.log('Step 2: Classifying intent...');
|
||||
const route = this.router.route(userInput, context);
|
||||
|
||||
// Step 3: Prepare system prompt
|
||||
this.log(`Step 3: Workflow = ${route.workflow} (${(route.confidence * 100).toFixed(1)}% confidence)`);
|
||||
const systemPrompt = this.buildSystemPrompt(route, contextPacket);
|
||||
|
||||
// Step 4: Get response (this would call LLM)
|
||||
// For now, return the prompt for testing
|
||||
const response = {
|
||||
systemPrompt: systemPrompt,
|
||||
workflow: route.workflow,
|
||||
confidence: route.confidence,
|
||||
context: contextPacket
|
||||
};
|
||||
|
||||
// Step 5: Validate response
|
||||
this.log('Step 4: Validating...');
|
||||
const validation = this.validateResponse(response.systemPrompt, route.workflow);
|
||||
|
||||
// Step 6: Enforce format
|
||||
this.log('Step 5: Enforcing format...');
|
||||
const formatted = this.enforceFormat(response.systemPrompt, route.workflow);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
this.log(`=== Complete in ${duration}ms ===`);
|
||||
|
||||
return {
|
||||
...response,
|
||||
validation: validation,
|
||||
format: formatted,
|
||||
metadata: {
|
||||
duration: duration,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Orchestration error:', error);
|
||||
return {
|
||||
error: error.message,
|
||||
input: userInput,
|
||||
metadata: {
|
||||
duration: Date.now() - startTime,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build context packet from all sources
|
||||
*/
|
||||
async buildContext(userInput, context) {
|
||||
// Load rules
|
||||
await this.pipeline.loadRules();
|
||||
|
||||
// Load preferences
|
||||
await this.pipeline.loadPreferences();
|
||||
|
||||
// Load relevant memory
|
||||
await this.pipeline.loadRelevantMemory(userInput, 5);
|
||||
|
||||
// Build final packet
|
||||
const packet = this.pipeline.buildPacket(userInput);
|
||||
|
||||
// Add session context
|
||||
packet.project = context.project || process.env.CURRENT_PROJECT;
|
||||
packet.user = context.user || 'JC';
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build combined system prompt
|
||||
*/
|
||||
buildSystemPrompt(route, contextPacket) {
|
||||
const parts = [
|
||||
'# System Instructions',
|
||||
'',
|
||||
contextPacket.system,
|
||||
'',
|
||||
'---',
|
||||
'',
|
||||
'# Workflow Mode',
|
||||
route.systemPrompt,
|
||||
'',
|
||||
'---',
|
||||
'',
|
||||
'# Current Context',
|
||||
`Project: ${contextPacket.project || 'None'}`,
|
||||
`User: ${contextPacket.user || 'Unknown'}`,
|
||||
'',
|
||||
'---',
|
||||
'',
|
||||
'# Memory',
|
||||
contextPacket.memory.substring(0, 1000) // Truncate for brevity
|
||||
];
|
||||
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate response against rules
|
||||
*/
|
||||
validateResponse(response, workflow) {
|
||||
const validator = new ResponseValidator(workflow);
|
||||
return validator.validate(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce format compliance
|
||||
*/
|
||||
enforceFormat(response, workflow) {
|
||||
const locker = new FormatLocker(workflow);
|
||||
return locker.enforceFormat(response, this.autoFix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get format prompt for LLM
|
||||
*/
|
||||
getFormatPrompt(workflow) {
|
||||
const locker = new FormatLocker(workflow);
|
||||
return locker.getFormatPrompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Log with verbosity control
|
||||
*/
|
||||
log(message) {
|
||||
if (this.verbose) {
|
||||
console.log(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick classification without full processing
|
||||
*/
|
||||
classify(userInput) {
|
||||
return this.router.classifyIntent(userInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get workflow config
|
||||
*/
|
||||
getWorkflow(name) {
|
||||
return this.router.getWorkflow(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use
|
||||
module.exports = AgentOrchestrator;
|
||||
|
||||
// CLI usage
|
||||
if (require.main === module) {
|
||||
const userInput = process.argv[2] || 'implement a new feature';
|
||||
const verbose = process.argv.includes('--verbose') || process.argv.includes('-v');
|
||||
|
||||
const orchestrator = new AgentOrchestrator({ verbose: verbose });
|
||||
|
||||
orchestrator.process(userInput, { project: process.env.CURRENT_PROJECT })
|
||||
.then(result => {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Workflow Router - Intent Classification and Routing
|
||||
*
|
||||
* Before responding, classify intent and route to fixed workflow template
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
class WorkflowRouter {
|
||||
constructor() {
|
||||
this.workflows = new Map();
|
||||
this.loadWorkflows();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all workflow definitions from workflows/ directory
|
||||
*/
|
||||
loadWorkflows() {
|
||||
const workflowsDir = path.join(__dirname, '..', 'workflows');
|
||||
|
||||
const workflowFiles = [
|
||||
'coding.md',
|
||||
'debug.md',
|
||||
'deploy.md',
|
||||
'audit.md',
|
||||
'planning.md'
|
||||
];
|
||||
|
||||
for (const file of workflowFiles) {
|
||||
const workflowName = path.basename(file, '.md');
|
||||
const filePath = path.join(workflowsDir, file);
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
this.workflows.set(workflowName, this.parseWorkflow(content));
|
||||
} catch (err) {
|
||||
console.warn(`Workflow ${file} not found, using defaults`);
|
||||
this.workflows.set(workflowName, this.getDefaultWorkflow(workflowName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse workflow markdown into structured object
|
||||
*/
|
||||
parseWorkflow(content) {
|
||||
const workflow = {
|
||||
name: '',
|
||||
triggers: [],
|
||||
outputFormat: '',
|
||||
toolRules: [],
|
||||
constraints: [],
|
||||
requiredSections: []
|
||||
};
|
||||
|
||||
// Extract name from first header
|
||||
const nameMatch = content.match(/^#\s+Workflow:\s*(.+)$/m);
|
||||
if (nameMatch) workflow.name = nameMatch[1].trim();
|
||||
|
||||
// Extract triggers
|
||||
const triggersMatch = content.match(/\*\*Triggers:\*\*\s*(.+)/);
|
||||
if (triggersMatch) {
|
||||
workflow.triggers = triggersMatch[1].split(',').map(t => t.trim().toLowerCase());
|
||||
}
|
||||
|
||||
// Extract output format (code block after "Fixed Output Format")
|
||||
const formatMatch = content.match(/## Fixed Output Format\s*```markdown\s*([\s\S]*?)```/);
|
||||
if (formatMatch) workflow.outputFormat = formatMatch[1].trim();
|
||||
|
||||
// Extract tool rules
|
||||
const rulesSection = content.match(/## Tool Access Rules\s*([\s\S]*?)(?=##|$)/);
|
||||
if (rulesSection) {
|
||||
workflow.toolRules = rulesSection[1]
|
||||
.split('\n')
|
||||
.filter(line => /^\d+\./.test(line))
|
||||
.map(line => line.replace(/^\d+\.\s*/, '').trim());
|
||||
}
|
||||
|
||||
// Extract constraints
|
||||
const constraintsSection = content.match(/## Constraints\s*([\s\S]*?)(?=##|$)/);
|
||||
if (constraintsSection) {
|
||||
workflow.constraints = constraintsSection[1]
|
||||
.split('\n')
|
||||
.filter(line => /^(NO|YES):/.test(line))
|
||||
.map(line => line.trim());
|
||||
}
|
||||
|
||||
// Extract required sections from format template
|
||||
const sectionMatches = workflow.outputFormat.match(/^##\s+(.+)$/gm);
|
||||
if (sectionMatches) {
|
||||
workflow.requiredSections = sectionMatches.map(s => s.replace(/^##\s*/, '').trim());
|
||||
}
|
||||
|
||||
return workflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify user intent and return matching workflow
|
||||
*/
|
||||
classifyIntent(userInput) {
|
||||
const input = userInput.toLowerCase();
|
||||
|
||||
// Intent patterns with confidence scoring
|
||||
const intentPatterns = {
|
||||
'coding': {
|
||||
patterns: [
|
||||
/\b(code|implement|build|create|write|develop|feature|fix bug|add\s+\w+\s+to)\b/,
|
||||
/\b(refactor|optimize|improve|clean up)\b/,
|
||||
/\b(pull request|pr|commit|merge|branch)\b/,
|
||||
/\b(api|endpoint|route|handler|controller|model)\b/
|
||||
],
|
||||
weight: 1.0
|
||||
},
|
||||
'debug': {
|
||||
patterns: [
|
||||
/\b(debug|fix|broken|error|bug|issue|problem|crash|fails?|not working)\b/,
|
||||
/\b(troubleshoot|diagnose|investigate|trace|root cause)\b/,
|
||||
/\b(exception|stack trace|log|error message)\b/
|
||||
],
|
||||
weight: 1.0
|
||||
},
|
||||
'deploy': {
|
||||
patterns: [
|
||||
/\b(deploy|release|push to|go live|production|staging)\b/,
|
||||
/\b(docker|container|build|image|registry)\b/,
|
||||
/\b(rollback|revert|restore|emergency)\b/,
|
||||
/\b(install|upgrade|update|migrate)\b/
|
||||
],
|
||||
weight: 1.0
|
||||
},
|
||||
'audit': {
|
||||
patterns: [
|
||||
/\b(audit|review|assess|inventory|check|scan|inspect)\b/,
|
||||
/\b(security|vulnerability|compliance|policy)\b/,
|
||||
/\b(status|health|state|report|summary)\b/,
|
||||
/\b(workspace|files|structure|organization)\b/
|
||||
],
|
||||
weight: 1.0
|
||||
},
|
||||
'planning': {
|
||||
patterns: [
|
||||
/\b(plan|design|architecture|strategy|roadmap|timeline)\b/,
|
||||
/\b(requirement|spec|proposal|approach)\b/,
|
||||
/\b(breakdown|estimate|scope|prioritize)\b/
|
||||
],
|
||||
weight: 0.8
|
||||
}
|
||||
};
|
||||
|
||||
let bestMatch = null;
|
||||
let bestScore = 0;
|
||||
|
||||
for (const [workflowName, config] of Object.entries(intentPatterns)) {
|
||||
let score = 0;
|
||||
let matches = 0;
|
||||
|
||||
for (const pattern of config.patterns) {
|
||||
if (pattern.test(input)) {
|
||||
matches++;
|
||||
score += config.weight;
|
||||
}
|
||||
}
|
||||
|
||||
// Boost score for multiple matches
|
||||
if (matches > 1) score *= 1.2;
|
||||
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
bestMatch = workflowName;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to coding if no strong match
|
||||
if (bestScore < 0.5 || !bestMatch) {
|
||||
bestMatch = 'coding';
|
||||
bestScore = 0.3;
|
||||
}
|
||||
|
||||
return {
|
||||
workflow: bestMatch,
|
||||
confidence: Math.min(bestScore / 2, 1.0), // Normalize
|
||||
allScores: this.getAllScores(intentPatterns, input)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get confidence scores for all workflows
|
||||
*/
|
||||
getAllScores(patterns, input) {
|
||||
const scores = {};
|
||||
for (const [name, config] of Object.entries(patterns)) {
|
||||
let score = 0;
|
||||
for (const pattern of config.patterns) {
|
||||
if (pattern.test(input)) score += config.weight;
|
||||
}
|
||||
scores[name] = score;
|
||||
}
|
||||
return scores;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get workflow by name
|
||||
*/
|
||||
getWorkflow(name) {
|
||||
return this.workflows.get(name) || this.getDefaultWorkflow(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default workflow template
|
||||
*/
|
||||
getDefaultWorkflow(name) {
|
||||
return {
|
||||
name: name || 'default',
|
||||
triggers: ['general'],
|
||||
outputFormat: '## Summary\n[Response]\n\n## Details\n[Details]',
|
||||
toolRules: ['Delegate when possible', 'Verify before reporting'],
|
||||
constraints: ['NO: Assumptions without verification', 'YES: Clear status updates'],
|
||||
requiredSections: ['Summary', 'Details']
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build context-aware system prompt for the classified workflow
|
||||
*/
|
||||
buildSystemPrompt(workflowName, context = {}) {
|
||||
const workflow = this.getWorkflow(workflowName);
|
||||
|
||||
const parts = [
|
||||
`You are in ${workflow.name} workflow mode.`,
|
||||
'',
|
||||
'## Required Output Sections',
|
||||
...workflow.requiredSections.map(s => `- ${s}`),
|
||||
'',
|
||||
'## Tool Access Rules',
|
||||
...workflow.toolRules.map((rule, i) => `${i + 1}. ${rule}`),
|
||||
'',
|
||||
'## Constraints',
|
||||
...workflow.constraints,
|
||||
'',
|
||||
'## Format Template',
|
||||
'```markdown',
|
||||
workflow.outputFormat,
|
||||
'```'
|
||||
];
|
||||
|
||||
if (context.project) {
|
||||
parts.push('', `## Current Project: ${context.project}`);
|
||||
}
|
||||
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point: classify and route
|
||||
*/
|
||||
route(userInput, context = {}) {
|
||||
const classification = this.classifyIntent(userInput);
|
||||
const workflow = this.getWorkflow(classification.workflow);
|
||||
const systemPrompt = this.buildSystemPrompt(classification.workflow, context);
|
||||
|
||||
return {
|
||||
workflow: classification.workflow,
|
||||
confidence: classification.confidence,
|
||||
workflowConfig: workflow,
|
||||
systemPrompt: systemPrompt,
|
||||
context: context
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use
|
||||
module.exports = WorkflowRouter;
|
||||
|
||||
// CLI usage
|
||||
if (require.main === module) {
|
||||
const router = new WorkflowRouter();
|
||||
const userInput = process.argv[2] || 'implement a new feature';
|
||||
|
||||
const result = router.route(userInput, { project: process.env.CURRENT_PROJECT });
|
||||
|
||||
console.log('=== Workflow Classification ===');
|
||||
console.log(`Workflow: ${result.workflow}`);
|
||||
console.log(`Confidence: ${(result.confidence * 100).toFixed(1)}%`);
|
||||
console.log('\n=== System Prompt ===');
|
||||
console.log(result.systemPrompt);
|
||||
}
|
||||
Reference in New Issue
Block a user