swarm-orchestrator 7.0.0-alpha.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/LICENSE +15 -0
- package/README.md +199 -0
- package/agents/reviewer.agent.md +50 -0
- package/agents/worker.agent.md +41 -0
- package/config/default-agents.yaml +56 -0
- package/config/pm-agent.yaml +36 -0
- package/config/quality-gates.yaml +88 -0
- package/config/repair-agent.yaml +34 -0
- package/config/semgrep-rules/complexity-mismatch.yaml +6 -0
- package/config/semgrep-rules/exception-swallowing.yaml +13 -0
- package/config/semgrep-rules/hardcoded-answer.yaml +6 -0
- package/config/semgrep-rules/mock-mutation.yaml +6 -0
- package/config/semgrep-rules/test-modification.yaml +6 -0
- package/config/user-agents.yaml +23 -0
- package/dist/src/adapters/adapter-factory.js +29 -0
- package/dist/src/adapters/agent-adapter.js +32 -0
- package/dist/src/adapters/claude-code-adapter.js +126 -0
- package/dist/src/adapters/claude-code-teams.js +119 -0
- package/dist/src/adapters/codex-adapter.js +118 -0
- package/dist/src/adapters/copilot-adapter.js +224 -0
- package/dist/src/adapters/fatal-error-classifier.js +88 -0
- package/dist/src/adapters/index.js +44 -0
- package/dist/src/adapters/persistent-session.js +204 -0
- package/dist/src/adapters/process-supervisor.js +199 -0
- package/dist/src/agents-exporter.js +398 -0
- package/dist/src/analytics-log.js +161 -0
- package/dist/src/baseline-scanner.js +65 -0
- package/dist/src/bootstrap-evidence.js +88 -0
- package/dist/src/bootstrap-orchestrator.js +205 -0
- package/dist/src/bootstrap-types.js +5 -0
- package/dist/src/branch-merger.js +427 -0
- package/dist/src/cli/attest-handlers.js +44 -0
- package/dist/src/cli/cost-prompt.js +51 -0
- package/dist/src/cli/demo-handlers.js +274 -0
- package/dist/src/cli/flags.js +152 -0
- package/dist/src/cli/index.js +57 -0
- package/dist/src/cli/live-status.js +186 -0
- package/dist/src/cli/misc-handlers.js +242 -0
- package/dist/src/cli/plan-handlers.js +350 -0
- package/dist/src/cli/share-handlers.js +163 -0
- package/dist/src/cli/status-handlers.js +457 -0
- package/dist/src/cli/swarm-handlers.js +638 -0
- package/dist/src/cli/usage.js +72 -0
- package/dist/src/cli.js +217 -0
- package/dist/src/commit-pattern-detector.js +143 -0
- package/dist/src/commit-quality-analyzer.js +38 -0
- package/dist/src/config-loader.js +339 -0
- package/dist/src/context-broker.js +358 -0
- package/dist/src/copilot-cli-wrapper.js +258 -0
- package/dist/src/cost-estimator.js +241 -0
- package/dist/src/defaults.js +18 -0
- package/dist/src/demo-mode.js +180 -0
- package/dist/src/deployment-handler.js +78 -0
- package/dist/src/deployment-manager.js +228 -0
- package/dist/src/execution-queue.js +196 -0
- package/dist/src/external-tool-manager.js +184 -0
- package/dist/src/gate-prompt-builder.js +117 -0
- package/dist/src/gate-remediation.js +173 -0
- package/dist/src/github-issues-ingester.js +152 -0
- package/dist/src/github-mcp-integrator.js +164 -0
- package/dist/src/hook-generator.js +229 -0
- package/dist/src/index.js +48 -0
- package/dist/src/knowledge-base.js +300 -0
- package/dist/src/logger.js +111 -0
- package/dist/src/meta-analyzer.js +208 -0
- package/dist/src/metrics-collector.js +209 -0
- package/dist/src/metrics-types.js +5 -0
- package/dist/src/multi-repo-coordinator.js +179 -0
- package/dist/src/orchestrator/async-meta-analysis.js +84 -0
- package/dist/src/orchestrator/fatal-run-error.js +43 -0
- package/dist/src/orchestrator/final-gates-remediation.js +258 -0
- package/dist/src/orchestrator/git-state-utils.js +119 -0
- package/dist/src/orchestrator/pause-controller.js +55 -0
- package/dist/src/orchestrator/replan-runner.js +258 -0
- package/dist/src/orchestrator/step-executor.js +391 -0
- package/dist/src/orchestrator/wave-scheduler-loop.js +204 -0
- package/dist/src/owasp-mapper.js +153 -0
- package/dist/src/owasp-report-renderer.js +39 -0
- package/dist/src/plan-files.js +142 -0
- package/dist/src/plan-generator.js +1207 -0
- package/dist/src/pm-agent.js +296 -0
- package/dist/src/post-run-reporter.js +203 -0
- package/dist/src/pr-automation.js +103 -0
- package/dist/src/pr-manager.js +318 -0
- package/dist/src/presenter/index.js +209 -0
- package/dist/src/prompt-builder.js +173 -0
- package/dist/src/quality-gates/config-loader.js +196 -0
- package/dist/src/quality-gates/default-config.js +243 -0
- package/dist/src/quality-gates/file-utils.js +152 -0
- package/dist/src/quality-gates/gate-runner.js +171 -0
- package/dist/src/quality-gates/gates/accessibility.js +355 -0
- package/dist/src/quality-gates/gates/duplicate-blocks.js +144 -0
- package/dist/src/quality-gates/gates/hardcoded-config.js +52 -0
- package/dist/src/quality-gates/gates/readme-claims.js +62 -0
- package/dist/src/quality-gates/gates/runtime-checks.js +265 -0
- package/dist/src/quality-gates/gates/scaffold-defaults.js +108 -0
- package/dist/src/quality-gates/gates/test-coverage.js +240 -0
- package/dist/src/quality-gates/gates/test-file-protection.js +85 -0
- package/dist/src/quality-gates/gates/test-isolation.js +74 -0
- package/dist/src/quality-gates/index.js +17 -0
- package/dist/src/quality-gates/registry.js +205 -0
- package/dist/src/quality-gates/types.js +2 -0
- package/dist/src/quick-fix-mode.js +302 -0
- package/dist/src/recipe-loader.js +122 -0
- package/dist/src/repair-agent.js +396 -0
- package/dist/src/repo-analyzer.js +342 -0
- package/dist/src/report-generator.js +146 -0
- package/dist/src/report-renderer.js +90 -0
- package/dist/src/requirement-filter.js +52 -0
- package/dist/src/sarif-formatter.js +140 -0
- package/dist/src/scheduling/dependency-analyzer.js +178 -0
- package/dist/src/scheduling/work-stealing-queue.js +123 -0
- package/dist/src/secret-redactor.js +90 -0
- package/dist/src/session-executor.js +587 -0
- package/dist/src/session-manager.js +221 -0
- package/dist/src/share/transcript-verification.js +128 -0
- package/dist/src/share-parser.js +477 -0
- package/dist/src/spinner.js +146 -0
- package/dist/src/step-runner.js +365 -0
- package/dist/src/swarm-orchestrator.js +686 -0
- package/dist/src/task-classifier.js +134 -0
- package/dist/src/test-command-discovery.js +123 -0
- package/dist/src/text-similarity.js +24 -0
- package/dist/src/tier-maps.js +158 -0
- package/dist/src/types.js +5 -0
- package/dist/src/url-shortener.js +135 -0
- package/dist/src/verification/attestation.js +166 -0
- package/dist/src/verification/cheat-detector.js +240 -0
- package/dist/src/verification/command-runner.js +70 -0
- package/dist/src/verification/composite-score.js +157 -0
- package/dist/src/verification/cosign-attestation.js +137 -0
- package/dist/src/verification/diff-analysis.js +89 -0
- package/dist/src/verification/differential-gate.js +173 -0
- package/dist/src/verification/index.js +41 -0
- package/dist/src/verification/mutation-gate.js +242 -0
- package/dist/src/verification/property-gate.js +201 -0
- package/dist/src/verification/test-synthesizer.js +243 -0
- package/dist/src/verifier/outcome-checks.js +396 -0
- package/dist/src/verifier/transcript-checks.js +207 -0
- package/dist/src/verifier/verification-reporters.js +148 -0
- package/dist/src/verifier-engine.js +491 -0
- package/dist/src/wave-resizer.js +131 -0
- package/dist/src/wave-scheduler.js +44 -0
- package/dist/src/worktree-manager.js +368 -0
- package/dist/src/worktree-reserved-paths.js +97 -0
- package/package.json +80 -0
- package/plugin/agents/.gitkeep +0 -0
- package/plugin/agents/backend_master.agent.md +72 -0
- package/plugin/agents/devops_pro.agent.md +45 -0
- package/plugin/agents/frontend_expert.agent.md +59 -0
- package/plugin/agents/integrator_finalizer.agent.md +44 -0
- package/plugin/agents/meta_reviewer.agent.md +44 -0
- package/plugin/agents/security_auditor.agent.md +43 -0
- package/plugin/agents/tester_elite.agent.md +43 -0
- package/plugin/hooks/hooks.json +19 -0
- package/plugin/hooks/post-tool-use.json +9 -0
- package/plugin/hooks/pre-tool-use.json +9 -0
- package/plugin/plugin.json +47 -0
- package/plugin/skills/swarm-gates/SKILL.md +53 -0
- package/plugin/skills/swarm-orchestrate/SKILL.md +34 -0
- package/plugin/skills/swarm-verify/SKILL.md +44 -0
- package/templates/README.md +35 -0
- package/templates/cli-tool.json +31 -0
- package/templates/fullstack.json +52 -0
- package/templates/library.json +38 -0
- package/templates/react-app.json +45 -0
- package/templates/recipes/add-api-docs.json +35 -0
- package/templates/recipes/add-auth.json +47 -0
- package/templates/recipes/add-ci.json +39 -0
- package/templates/recipes/add-tests.json +39 -0
- package/templates/recipes/migrate-to-ts.json +42 -0
- package/templates/recipes/refactor-modularize.json +44 -0
- package/templates/recipes/security-audit.json +35 -0
- package/templates/rest-api.json +38 -0
|
@@ -0,0 +1,1207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PlanGenerator = void 0;
|
|
4
|
+
const config_loader_1 = require("./config-loader");
|
|
5
|
+
const gate_prompt_builder_1 = require("./gate-prompt-builder");
|
|
6
|
+
const logger_1 = require("./logger");
|
|
7
|
+
const logger = (0, logger_1.getLogger)('plan-generator');
|
|
8
|
+
class PlanGenerator {
|
|
9
|
+
availableAgents;
|
|
10
|
+
gateConfig;
|
|
11
|
+
constructor(availableAgents, gateConfig) {
|
|
12
|
+
this.availableAgents = availableAgents;
|
|
13
|
+
this.gateConfig = gateConfig;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Creates an execution plan from a high-level goal.
|
|
17
|
+
* If userProvidedSteps is given, validates and uses them.
|
|
18
|
+
* Otherwise, generates intelligent default steps based on goal analysis.
|
|
19
|
+
*
|
|
20
|
+
* **Layer boundary on `goal` vs `agentGuidance`.** The classifier
|
|
21
|
+
* (detectGoalType + assignAgent) uses ONLY the raw task intent. Callers
|
|
22
|
+
* that need to inject guidance the executing agent should see (e.g.
|
|
23
|
+
* "do not edit test files" constraints from a benchmark harness) pass
|
|
24
|
+
* that text through `options.agentGuidance` — it prepends to each
|
|
25
|
+
* step's task string so agents see it, but never reaches the
|
|
26
|
+
* classifier. This isolates goal-interpretation from agent-execution
|
|
27
|
+
* guidance; without the split, preamble text about "tests" or
|
|
28
|
+
* "security" misdirects the classifier into picking the wrong primary
|
|
29
|
+
* agent. See issue #27 (Fix 1) for the sympy-12481 failure mode this
|
|
30
|
+
* prevents.
|
|
31
|
+
*/
|
|
32
|
+
createPlan(goal, userProvidedSteps, options) {
|
|
33
|
+
if (!goal || goal.trim() === '') {
|
|
34
|
+
throw new Error('Goal cannot be empty');
|
|
35
|
+
}
|
|
36
|
+
const rawSteps = userProvidedSteps || this.generateIntelligentSteps(goal);
|
|
37
|
+
const steps = options?.agentGuidance
|
|
38
|
+
? this.applyAgentGuidance(rawSteps, options.agentGuidance)
|
|
39
|
+
: rawSteps;
|
|
40
|
+
// validate that all assigned agents exist
|
|
41
|
+
this.validateAgentAssignments(steps);
|
|
42
|
+
// validate dependencies
|
|
43
|
+
this.validateDependencies(steps);
|
|
44
|
+
return {
|
|
45
|
+
goal: goal.trim(),
|
|
46
|
+
createdAt: new Date().toISOString(),
|
|
47
|
+
steps,
|
|
48
|
+
metadata: {
|
|
49
|
+
totalSteps: steps.length,
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Prepend guidance text to every step's task so the executing agent sees
|
|
55
|
+
* it. Runs AFTER classification so the guidance never influences agent
|
|
56
|
+
* selection or step-shape decisions.
|
|
57
|
+
*/
|
|
58
|
+
applyAgentGuidance(steps, guidance) {
|
|
59
|
+
const trimmed = guidance.trim();
|
|
60
|
+
if (!trimmed)
|
|
61
|
+
return steps;
|
|
62
|
+
return steps.map(step => ({
|
|
63
|
+
...step,
|
|
64
|
+
task: `${trimmed}\n\n${step.task}`,
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Generate Copilot CLI prompt for plan creation
|
|
69
|
+
* User pastes this into Copilot CLI, gets JSON back, then imports via `plan import`
|
|
70
|
+
*/
|
|
71
|
+
generateCopilotPlanningPrompt(goal) {
|
|
72
|
+
const agentList = this.availableAgents
|
|
73
|
+
.map(a => ` - ${a.name}: ${a.purpose}`)
|
|
74
|
+
.join('\n');
|
|
75
|
+
return `You are a software project planning expert. Generate a detailed, realistic execution plan for the following goal.
|
|
76
|
+
|
|
77
|
+
GOAL: ${goal}
|
|
78
|
+
|
|
79
|
+
Available agents (assign ONE agent per step):
|
|
80
|
+
${agentList}
|
|
81
|
+
|
|
82
|
+
CRITICAL: Output ONLY valid JSON matching this exact schema, no explanation before or after:
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
"goal": "${goal.replace(/"/g, '\\"')}",
|
|
86
|
+
"createdAt": "${new Date().toISOString()}",
|
|
87
|
+
"steps": [
|
|
88
|
+
{
|
|
89
|
+
"stepNumber": 1,
|
|
90
|
+
"agentName": "AgentName",
|
|
91
|
+
"task": "Specific, actionable task description",
|
|
92
|
+
"dependencies": [],
|
|
93
|
+
"expectedOutputs": ["Output 1", "Output 2"]
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"metadata": {
|
|
97
|
+
"totalSteps": 4
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
Requirements:
|
|
102
|
+
1. Create 4-8 realistic steps (not too few, not too many)
|
|
103
|
+
2. Assign appropriate agent to each step based on task domain
|
|
104
|
+
3. Use dependencies array to create a valid DAG (no cycles, only reference earlier steps)
|
|
105
|
+
4. MAXIMIZE PARALLELISM: steps that can run independently should have the same or no dependencies. Only add a dependency when a step truly needs another step's output.
|
|
106
|
+
5. Each task must be specific and actionable (not vague like "do everything")
|
|
107
|
+
6. expectedOutputs should list concrete artifacts (files, test results, PRs, etc.)
|
|
108
|
+
7. Consider typical software workflow: design/implement in parallel where possible, then test, then integrate
|
|
109
|
+
8. Use agentName 'worker' for all implementation steps
|
|
110
|
+
9. Use agentName 'reviewer' for all verification, review, and integration steps
|
|
111
|
+
10. Always include a testing step (agentName: 'worker', task focused on tests)
|
|
112
|
+
11. Final step should use agentName 'reviewer' for quality verification and integration review
|
|
113
|
+
|
|
114
|
+
OUTPUT ONLY THE JSON, NOTHING ELSE.`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Create plan from bootstrap analysis
|
|
118
|
+
* Uses annotated steps with source evidence
|
|
119
|
+
*/
|
|
120
|
+
createBootstrapPlan(goal, annotatedSteps) {
|
|
121
|
+
// Validate agent assignments
|
|
122
|
+
const regularSteps = annotatedSteps.map(s => ({
|
|
123
|
+
stepNumber: s.stepNumber,
|
|
124
|
+
agentName: s.agentName,
|
|
125
|
+
task: s.task,
|
|
126
|
+
dependencies: s.dependencies,
|
|
127
|
+
expectedOutputs: s.expectedOutputs
|
|
128
|
+
}));
|
|
129
|
+
this.validateAgentAssignments(regularSteps);
|
|
130
|
+
this.validateDependencies(regularSteps);
|
|
131
|
+
return {
|
|
132
|
+
goal: goal.trim(),
|
|
133
|
+
createdAt: new Date().toISOString(),
|
|
134
|
+
steps: annotatedSteps,
|
|
135
|
+
metadata: {
|
|
136
|
+
totalSteps: annotatedSteps.length
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Parse Copilot-generated plan from /share transcript
|
|
142
|
+
* Extracts JSON from transcript and validates against schema
|
|
143
|
+
*/
|
|
144
|
+
parseCopilotPlanFromTranscript(transcriptContent) {
|
|
145
|
+
// extract JSON from transcript (might be in code block or plain text)
|
|
146
|
+
const jsonMatch = transcriptContent.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/) ||
|
|
147
|
+
transcriptContent.match(/(\{[\s\S]*"goal"[\s\S]*"steps"[\s\S]*\})/);
|
|
148
|
+
if (!jsonMatch || !jsonMatch[1]) {
|
|
149
|
+
throw new Error('No valid JSON plan found in transcript. Ensure Copilot output contains a complete JSON object.');
|
|
150
|
+
}
|
|
151
|
+
let plan;
|
|
152
|
+
try {
|
|
153
|
+
plan = JSON.parse(jsonMatch[1]);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
throw new Error(`Invalid JSON in transcript: ${error instanceof Error ? error.message : 'parse error'}`, { cause: error });
|
|
157
|
+
}
|
|
158
|
+
// validate schema
|
|
159
|
+
this.validatePlanSchema(plan);
|
|
160
|
+
return plan;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Validate plan structure matches expected schema
|
|
164
|
+
*/
|
|
165
|
+
validatePlanSchema(plan) {
|
|
166
|
+
if (!plan || typeof plan !== 'object') {
|
|
167
|
+
throw new Error('Plan must be an object');
|
|
168
|
+
}
|
|
169
|
+
const p = plan;
|
|
170
|
+
if (!p.goal || typeof p.goal !== 'string') {
|
|
171
|
+
throw new Error('Plan must have a goal string');
|
|
172
|
+
}
|
|
173
|
+
if (!p.createdAt || typeof p.createdAt !== 'string') {
|
|
174
|
+
throw new Error('Plan must have a createdAt timestamp');
|
|
175
|
+
}
|
|
176
|
+
if (!Array.isArray(p.steps)) {
|
|
177
|
+
throw new Error('Plan must have a steps array');
|
|
178
|
+
}
|
|
179
|
+
if (p.steps.length === 0) {
|
|
180
|
+
throw new Error('Plan must have at least one step');
|
|
181
|
+
}
|
|
182
|
+
if (p.steps.length > 20) {
|
|
183
|
+
throw new Error('Plan has too many steps (max 20)');
|
|
184
|
+
}
|
|
185
|
+
p.steps.forEach((step, index) => {
|
|
186
|
+
if (typeof step.stepNumber !== 'number') {
|
|
187
|
+
throw new Error(`Step ${index}: stepNumber must be a number`);
|
|
188
|
+
}
|
|
189
|
+
if (!step.agentName || typeof step.agentName !== 'string') {
|
|
190
|
+
throw new Error(`Step ${index}: agentName must be a non-empty string`);
|
|
191
|
+
}
|
|
192
|
+
if (!step.task || typeof step.task !== 'string') {
|
|
193
|
+
throw new Error(`Step ${index}: task must be a non-empty string`);
|
|
194
|
+
}
|
|
195
|
+
if (!Array.isArray(step.dependencies)) {
|
|
196
|
+
throw new Error(`Step ${index}: dependencies must be an array`);
|
|
197
|
+
}
|
|
198
|
+
if (!Array.isArray(step.expectedOutputs)) {
|
|
199
|
+
throw new Error(`Step ${index}: expectedOutputs must be an array`);
|
|
200
|
+
}
|
|
201
|
+
if (step.expectedOutputs.length === 0) {
|
|
202
|
+
throw new Error(`Step ${index}: expectedOutputs cannot be empty`);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
if (!p.metadata || typeof p.metadata !== 'object') {
|
|
206
|
+
p.metadata = { totalSteps: p.steps.length };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Acceptance criteria that get appended to the primary build step for each goal type.
|
|
211
|
+
* These force the agent to address quality dimensions that AI code gen typically skips.
|
|
212
|
+
*/
|
|
213
|
+
getAcceptanceCriteria(goalType) {
|
|
214
|
+
const shared = [
|
|
215
|
+
'Code must read as human-written: no over-commented obvious logic, no generic variable names, no boilerplate filler.',
|
|
216
|
+
'README must only contain sections relevant to this project. Do not add troubleshooting, FAQ, or contributing sections unless the project warrants them.',
|
|
217
|
+
];
|
|
218
|
+
const byType = {
|
|
219
|
+
'web-app': [
|
|
220
|
+
'Semantic HTML: use appropriate elements (nav, main, article, button) not generic divs.',
|
|
221
|
+
'Accessible: ARIA labels on interactive elements, keyboard navigable, :focus-visible styles on all clickable elements.',
|
|
222
|
+
'Responsive: layout works from 320px to 1920px without horizontal overflow. Use relative units, clamp(), or media queries.',
|
|
223
|
+
'No default/placeholder content: real page title, real meta description, favicon link if appropriate.',
|
|
224
|
+
'Include <meta name="description"> and <meta name="theme-color"> in the HTML head.',
|
|
225
|
+
'Add @media (prefers-reduced-motion: reduce) to disable all animations and set transition durations to 0s.',
|
|
226
|
+
'Define CSS custom properties on :root for all colors, spacing, font sizes. Never use raw hex/rgb in rules; always reference a custom property.',
|
|
227
|
+
'Add @media (prefers-color-scheme: dark) that overrides the :root custom properties for full dark mode support.',
|
|
228
|
+
'Separate state management from presentation. Business logic (game rules, validation, computation) must live in a standalone module testable without the DOM.',
|
|
229
|
+
'Wrap all JS in an IIFE or module scope. No bare globals in script scope. Separate pure logic from DOM reads.',
|
|
230
|
+
'Functions that do pure computation must accept values as parameters, not reach into the DOM internally.',
|
|
231
|
+
'Persist counters, preferences, or user state to localStorage when losing them on page reload would be annoying.',
|
|
232
|
+
'Provide an audio signal (Web Audio API beep, no audio file needed) for events that use only visual notification, so background tabs get feedback.',
|
|
233
|
+
'Never reference files, images, fonts, or icons that do not exist in the repo.',
|
|
234
|
+
],
|
|
235
|
+
'api': [
|
|
236
|
+
'All error responses return specific, actionable messages with relevant values (not "something went wrong").',
|
|
237
|
+
'Input validation on all endpoints with proper 4xx status codes.',
|
|
238
|
+
'No hardcoded config: base URLs, ports, timeouts, secrets in env vars or config module.',
|
|
239
|
+
'Request/response types fully defined (TypeScript interfaces or JSON schema).',
|
|
240
|
+
],
|
|
241
|
+
'cli-tool': [
|
|
242
|
+
'Helpful --help output with examples for each command.',
|
|
243
|
+
'Specific error messages with context when commands fail (what failed, why, what to try).',
|
|
244
|
+
'Exit codes: 0 for success, non-zero for failure.',
|
|
245
|
+
],
|
|
246
|
+
'library': [
|
|
247
|
+
'Public API is minimal and well-typed. No internal implementation details exposed.',
|
|
248
|
+
'JSDoc on all exported functions with parameter descriptions and return type.',
|
|
249
|
+
'At least one usage example per major feature in README.',
|
|
250
|
+
],
|
|
251
|
+
'infrastructure': [
|
|
252
|
+
'All secrets and credentials use environment variables or secrets manager references.',
|
|
253
|
+
'Resources tagged with project name and environment.',
|
|
254
|
+
'Rollback procedure documented.',
|
|
255
|
+
],
|
|
256
|
+
'data-pipeline': [
|
|
257
|
+
'Input validation and schema checks at pipeline entry point.',
|
|
258
|
+
'Error handling with specific context for each transform stage.',
|
|
259
|
+
'Idempotent: safe to re-run without duplicating data.',
|
|
260
|
+
],
|
|
261
|
+
'mobile-app': [
|
|
262
|
+
'Touch targets minimum 44x44 points.',
|
|
263
|
+
'Accessible: labels on all interactive elements, VoiceOver/TalkBack compatible.',
|
|
264
|
+
'Handle offline state gracefully with clear user feedback.',
|
|
265
|
+
],
|
|
266
|
+
'generic': [
|
|
267
|
+
'Separate pure logic from I/O. Business rules must be testable without side effects.',
|
|
268
|
+
'Error messages must be specific and actionable with relevant values.',
|
|
269
|
+
'If the project produces HTML, use semantic elements (nav, main, header, footer, button) not generic divs.',
|
|
270
|
+
'If the project produces CSS, define colors and spacing as CSS custom properties on :root.',
|
|
271
|
+
'If the project produces HTML, include <meta name="description"> and a meaningful <title>.',
|
|
272
|
+
'Never reference files, images, fonts, or icons that do not exist in the repo.',
|
|
273
|
+
],
|
|
274
|
+
'bug-fix': [
|
|
275
|
+
'Diagnose the root cause before editing. State your hypothesis, then verify it against the reported failure — do not patch symptoms.',
|
|
276
|
+
'Apply the smallest change that resolves the reported failure. Refactors and unrelated improvements do not belong in a bug fix.',
|
|
277
|
+
'Preserve observable behavior for every code path the fix does not target. A bug fix that changes behavior elsewhere is a regression.',
|
|
278
|
+
'Error messages and exception types must match what downstream callers already depend on, unless the issue explicitly asks to change them.',
|
|
279
|
+
],
|
|
280
|
+
'contract-change': [
|
|
281
|
+
'Apply the contract change and every caller/test update in this single step. The post-step verifier runs tests against the combined state — an impl change without its matching test updates will fail verification and force a rollback.',
|
|
282
|
+
'Enumerate every call site before editing. A caller the agent missed is a broken build; use the repo\'s own tools (grep, TypeScript references) to find them all.',
|
|
283
|
+
'Update tests alongside the impl so existing tests that exercised the pre-change contract become tests of the post-change contract. Adding entirely new tests is fine; leaving stale tests in place is not.',
|
|
284
|
+
'Do not introduce unrelated refactors while touching each file. The only changes that belong in this step are the ones the contract change requires.',
|
|
285
|
+
],
|
|
286
|
+
};
|
|
287
|
+
const criteria = [...shared, ...(byType[goalType] || [])];
|
|
288
|
+
return criteria.map(c => '- ' + c).join('\n');
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Integrator review criteria appended to the final integration/review step.
|
|
292
|
+
* Turns the integrator from a rubber-stamp documenter into an active reviewer.
|
|
293
|
+
*/
|
|
294
|
+
getIntegratorReviewCriteria() {
|
|
295
|
+
return [
|
|
296
|
+
'Review all code from prior steps for quality issues before documenting:',
|
|
297
|
+
'- Remove AI-typical patterns: over-commenting, generic variable names, templated README sections that do not apply, placeholder content.',
|
|
298
|
+
'- Fix package.json metadata: fill author with a relevant value or remove the field entirely. Remove empty keywords array. Remove main field if the project is browser-only with no Node entry point. Description must be accurate.',
|
|
299
|
+
'- Verify no phantom file references: scan for src=, href=, icon: attributes that point to files not in the repo (e.g. favicon.ico that was never created).',
|
|
300
|
+
'- Verify README claims match what is actually implemented. Remove any claims about features that do not exist.',
|
|
301
|
+
'- If test scripts exist in package.json, README must include test instructions (npm install && npm test).',
|
|
302
|
+
'- Check for missing error handling, hardcoded values, or copy-pasted logic that should be extracted.',
|
|
303
|
+
'- If CSS has animations or custom properties: verify @media (prefers-reduced-motion) and @media (prefers-color-scheme: dark) exist.',
|
|
304
|
+
'- Verify JS state is encapsulated (IIFE or module), no bare globals in script scope.',
|
|
305
|
+
'- Verify architecture: business logic (game rules, validation, data transforms) lives in its own module, separate from DOM/UI code, and is testable without a browser.',
|
|
306
|
+
'- Verify tests import and exercise the real module exports, not reimplemented copies of the logic.',
|
|
307
|
+
'- Verify semantic HTML: headings, nav, main, button, etc. not div-with-role or span-as-button.',
|
|
308
|
+
'- Strip any boilerplate that does not serve this specific project.',
|
|
309
|
+
'Then write concise, accurate documentation. Only include sections the project actually needs.',
|
|
310
|
+
].join('\n');
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Generate intelligent default steps based on goal analysis
|
|
314
|
+
* This is the enhanced fallback for instant usability without Copilot CLI
|
|
315
|
+
*/
|
|
316
|
+
generateIntelligentSteps(goal) {
|
|
317
|
+
const goalType = this.detectGoalType(goal);
|
|
318
|
+
const steps = [];
|
|
319
|
+
const stepNumber = 1;
|
|
320
|
+
// determine project phases based on goal type
|
|
321
|
+
switch (goalType) {
|
|
322
|
+
case 'api':
|
|
323
|
+
steps.push(...this.generateApiSteps(goal, stepNumber));
|
|
324
|
+
break;
|
|
325
|
+
case 'web-app':
|
|
326
|
+
steps.push(...this.generateWebAppSteps(goal, stepNumber));
|
|
327
|
+
break;
|
|
328
|
+
case 'cli-tool':
|
|
329
|
+
steps.push(...this.generateCliToolSteps(goal, stepNumber));
|
|
330
|
+
break;
|
|
331
|
+
case 'library':
|
|
332
|
+
steps.push(...this.generateLibrarySteps(goal, stepNumber));
|
|
333
|
+
break;
|
|
334
|
+
case 'infrastructure':
|
|
335
|
+
steps.push(...this.generateInfrastructureSteps(goal, stepNumber));
|
|
336
|
+
break;
|
|
337
|
+
case 'data-pipeline':
|
|
338
|
+
steps.push(...this.generateDataPipelineSteps(goal, stepNumber));
|
|
339
|
+
break;
|
|
340
|
+
case 'mobile-app':
|
|
341
|
+
steps.push(...this.generateMobileAppSteps(goal, stepNumber));
|
|
342
|
+
break;
|
|
343
|
+
case 'bug-fix':
|
|
344
|
+
steps.push(...this.generateBugFixSteps(goal, stepNumber));
|
|
345
|
+
break;
|
|
346
|
+
case 'contract-change':
|
|
347
|
+
steps.push(...this.generateContractChangeSteps(goal, stepNumber));
|
|
348
|
+
break;
|
|
349
|
+
default:
|
|
350
|
+
steps.push(...this.generateGenericSteps(goal, stepNumber));
|
|
351
|
+
}
|
|
352
|
+
this.applyGateRequirements(steps, goal, goalType);
|
|
353
|
+
return steps;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Append gate-derived requirements to step prompts when gate config is available.
|
|
357
|
+
* Also injects a test step if testCoverage is enabled and no worker test step exists —
|
|
358
|
+
* EXCEPT for contract-change plans, where tests are updated in the same step as the
|
|
359
|
+
* impl change by template design (see generateContractChangeSteps). Injecting a
|
|
360
|
+
* separate test step on a contract-change plan would re-introduce the exact
|
|
361
|
+
* impl-vs-test split that #27 Fix 3 exists to prevent.
|
|
362
|
+
*/
|
|
363
|
+
applyGateRequirements(steps, goal, goalType) {
|
|
364
|
+
if (!this.gateConfig)
|
|
365
|
+
return;
|
|
366
|
+
for (const step of steps) {
|
|
367
|
+
const suffix = (0, gate_prompt_builder_1.getGateRequirements)(this.gateConfig, step.agentName);
|
|
368
|
+
if (suffix) {
|
|
369
|
+
step.task += suffix;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// Inject a test step when testCoverage is enabled and no test step exists.
|
|
373
|
+
// Contract-change plans bundle tests into their impl step; a separate
|
|
374
|
+
// worker test step here would re-create the broken mid-plan `npm test`
|
|
375
|
+
// against a half-updated state. Trivial single-file tasks also bundle
|
|
376
|
+
// tests into the one step their template emits.
|
|
377
|
+
if (goalType === 'contract-change')
|
|
378
|
+
return;
|
|
379
|
+
if (goalType === 'generic' && this.isTrivialTask(goal))
|
|
380
|
+
return;
|
|
381
|
+
if ((0, gate_prompt_builder_1.requiresTestStep)(this.gateConfig)) {
|
|
382
|
+
const hasTestStep = steps.some(s => s.agentName === 'worker' && /\btest/i.test(s.task));
|
|
383
|
+
if (!hasTestStep) {
|
|
384
|
+
const maxStep = steps.reduce((max, s) => Math.max(max, s.stepNumber), 0);
|
|
385
|
+
const codeStepNumbers = steps
|
|
386
|
+
.filter(s => s.agentName !== 'reviewer')
|
|
387
|
+
.map(s => s.stepNumber);
|
|
388
|
+
const testStep = {
|
|
389
|
+
stepNumber: maxStep + 1,
|
|
390
|
+
agentName: 'worker',
|
|
391
|
+
task: `Write comprehensive tests for: ${goal}` +
|
|
392
|
+
(0, gate_prompt_builder_1.getGateRequirements)(this.gateConfig, 'worker'),
|
|
393
|
+
dependencies: codeStepNumbers,
|
|
394
|
+
expectedOutputs: ['Unit tests', 'Integration tests', 'Test coverage report'],
|
|
395
|
+
};
|
|
396
|
+
steps.push(testStep);
|
|
397
|
+
// Update reviewer step dependencies to include the new test step
|
|
398
|
+
for (const step of steps) {
|
|
399
|
+
if (step.agentName === 'reviewer' && !step.dependencies.includes(testStep.stepNumber)) {
|
|
400
|
+
step.dependencies.push(testStep.stepNumber);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Classify a raw goal string. Public for test — consumers in production
|
|
408
|
+
* code should call `createPlan` and read `plan.metadata` or inspect step
|
|
409
|
+
* shape instead of depending on this classification directly, so the
|
|
410
|
+
* classifier's keyword/structural heuristics remain an implementation
|
|
411
|
+
* detail of the planner. The test suite uses this directly to assert
|
|
412
|
+
* classification decisions in isolation from template behavior, per the
|
|
413
|
+
* #30 review on separating classifier correctness from template
|
|
414
|
+
* correctness.
|
|
415
|
+
*
|
|
416
|
+
* @internal
|
|
417
|
+
*/
|
|
418
|
+
classifyGoal(goal) {
|
|
419
|
+
return this.detectGoalType(goal);
|
|
420
|
+
}
|
|
421
|
+
detectGoalType(goal) {
|
|
422
|
+
// CLASSIFIER DISPATCH ORDER — do not reorder without re-reading this.
|
|
423
|
+
//
|
|
424
|
+
// Structural discriminators (contract-change, bug-fix) run BEFORE the
|
|
425
|
+
// keyword-based domain classifiers (api, library, frontend, etc.).
|
|
426
|
+
// Domain keywords are commonly present in goals that are actually
|
|
427
|
+
// coordinated-change or bug-report tasks — "update the `foo` library
|
|
428
|
+
// to ..." mentions "library" but should never route to the rigid
|
|
429
|
+
// library template because its step shape is wrong for a contract
|
|
430
|
+
// change. Centralizing the structural discrimination at the top of
|
|
431
|
+
// the chain means the decision lives in one place, audit-able and
|
|
432
|
+
// non-duplicated across templates. See #27 Fixes 2 and 3 for the
|
|
433
|
+
// two failure modes the ordering prevents.
|
|
434
|
+
//
|
|
435
|
+
// Precedence when a goal has BOTH structural shapes (≥2 backticks +
|
|
436
|
+
// failure verb AND ≥2 change verbs — e.g., "fix this: update foo,
|
|
437
|
+
// rename bar, modify baz"): contract-change wins because it runs
|
|
438
|
+
// first. That's the intended precedence — a goal with multi-target
|
|
439
|
+
// imperative change verbs is better served by the bundled impl+tests
|
|
440
|
+
// shape than by the bug-fix 3-step shape, even when it opens with a
|
|
441
|
+
// failure description. If a counter-example surfaces where this
|
|
442
|
+
// precedence produces the wrong plan shape, that's the evidence to
|
|
443
|
+
// revisit here.
|
|
444
|
+
const goalLower = goal.toLowerCase();
|
|
445
|
+
// contract-change: coordinated modification of existing code (impl +
|
|
446
|
+
// callers + tests). Must run BEFORE the domain classifiers (api, library,
|
|
447
|
+
// etc.) — see ordering note above.
|
|
448
|
+
if (this.hasContractChangeShape(goal)) {
|
|
449
|
+
return 'contract-change';
|
|
450
|
+
}
|
|
451
|
+
if (goalLower.match(/\b(rest|api|endpoint|graphql|microservice|backend|server|middleware)\b/)) {
|
|
452
|
+
return 'api';
|
|
453
|
+
}
|
|
454
|
+
if (goalLower.match(/\b(web app|website|frontend|react|vue|angular|next\.js|dashboard|browser.based|game|interactive|single.page|landing.page)\b/)) {
|
|
455
|
+
return 'web-app';
|
|
456
|
+
}
|
|
457
|
+
// Secondary signal: goal mentions HTML+CSS+JS file combo
|
|
458
|
+
if (goalLower.match(/\b(index\.html|html.+css|css.+js|\.html.+\.js)\b/)) {
|
|
459
|
+
return 'web-app';
|
|
460
|
+
}
|
|
461
|
+
if (goalLower.match(/\b(cli|command.line|terminal|console tool|script)\b/)) {
|
|
462
|
+
return 'cli-tool';
|
|
463
|
+
}
|
|
464
|
+
if (goalLower.match(/\b(library|package|module|sdk|npm package|component library)\b/)) {
|
|
465
|
+
return 'library';
|
|
466
|
+
}
|
|
467
|
+
if (goalLower.match(/\b(deploy|infrastructure|ci\/cd|docker|kubernetes|terraform|cloud)\b/)) {
|
|
468
|
+
return 'infrastructure';
|
|
469
|
+
}
|
|
470
|
+
if (goalLower.match(/\b(etl|pipeline|data processing|analytics|streaming)\b/)) {
|
|
471
|
+
return 'data-pipeline';
|
|
472
|
+
}
|
|
473
|
+
if (goalLower.match(/\b(mobile|ios|android|react native|flutter)\b/)) {
|
|
474
|
+
return 'mobile-app';
|
|
475
|
+
}
|
|
476
|
+
// bug-fix: the task operates on pre-existing state with an observed
|
|
477
|
+
// failure. Structural discriminator (not keyword matching on "fix" /
|
|
478
|
+
// "bug" / "broken" — those produce false positives on greenfield goals
|
|
479
|
+
// like "fix common patterns" and false negatives on issue bodies that
|
|
480
|
+
// describe failure without using trigger words). Two structural signals:
|
|
481
|
+
// 1. backtick-wrapped code references — symbols, methods, expressions
|
|
482
|
+
// — indicating the task already knows about specific identifiers
|
|
483
|
+
// in the existing codebase
|
|
484
|
+
// 2. present-tense failure verbs describing current broken behavior
|
|
485
|
+
// (fails/raises/throws/errors/crashes/returns X instead of Y)
|
|
486
|
+
// Both must be present for a goal to count as a bug report shape. See
|
|
487
|
+
// issue #27 Fix 2.
|
|
488
|
+
if (this.hasBugReportShape(goal)) {
|
|
489
|
+
return 'bug-fix';
|
|
490
|
+
}
|
|
491
|
+
return 'generic';
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Structural discriminator for contract-change goals: coordinated
|
|
495
|
+
* modifications to existing code across impl + callers + tests, with
|
|
496
|
+
* explicit "update X, update Y" language. Two signals required:
|
|
497
|
+
* 1. At least two backtick-wrapped code references (existing symbols,
|
|
498
|
+
* files, or APIs the task names). Same floor as hasBugReportShape
|
|
499
|
+
* to keep the greenfield false-positive rate low.
|
|
500
|
+
* 2. At least two imperative change verbs — "update X" / "rename X" /
|
|
501
|
+
* "modify X" / "change X" — which signals the author expects work
|
|
502
|
+
* distributed across multiple artifacts rather than a single new
|
|
503
|
+
* build.
|
|
504
|
+
*
|
|
505
|
+
* The second signal is what distinguishes contract-change from bug-fix:
|
|
506
|
+
* bug-fix describes OBSERVED failure; contract-change prescribes
|
|
507
|
+
* INTENDED multi-artifact change. Both can match the same existing code
|
|
508
|
+
* backtick-density signal, but only one uses multi-target update verbs.
|
|
509
|
+
*
|
|
510
|
+
* Kept deliberately narrow. A single "update X" clause isn't enough —
|
|
511
|
+
* "fix this: update index.js" is closer to bug-fix shape. Two separate
|
|
512
|
+
* update clauses ("update X ... update Y") is what marks the
|
|
513
|
+
* coordinated-change intent that the contract-change template exists
|
|
514
|
+
* to handle.
|
|
515
|
+
*
|
|
516
|
+
* See issue #27 Fix 3.
|
|
517
|
+
*/
|
|
518
|
+
hasContractChangeShape(goal) {
|
|
519
|
+
const backtickMatches = goal.match(/`[^`\n]+`/g) ?? [];
|
|
520
|
+
if (backtickMatches.length < 2)
|
|
521
|
+
return false;
|
|
522
|
+
// Count distinct imperative change clauses. `\b(update|rename|...)\s+\S+`
|
|
523
|
+
// matches "update X" / "rename X" etc.; we require ≥2 so a lone
|
|
524
|
+
// "update index.js" in a bug-fix description doesn't misroute.
|
|
525
|
+
const changeVerbRe = /\b(update|rename|modify|change|convert)\s+[^\s.,;]+/gi;
|
|
526
|
+
const verbCount = (goal.match(changeVerbRe) ?? []).length;
|
|
527
|
+
return verbCount >= 2;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Structural discriminator for bug-report-shaped goals. Implementation
|
|
531
|
+
* detail of detectGoalType — exposed as its own method so the classifier
|
|
532
|
+
* contract stays testable in isolation.
|
|
533
|
+
*/
|
|
534
|
+
hasBugReportShape(goal) {
|
|
535
|
+
// 1. Backtick density — at least two distinct backtick-wrapped references.
|
|
536
|
+
// Single backticks around one term (`API` in "Build an API") are common
|
|
537
|
+
// in imperative goals too, so we require at least two.
|
|
538
|
+
const backtickMatches = goal.match(/`[^`\n]+`/g) ?? [];
|
|
539
|
+
if (backtickMatches.length < 2)
|
|
540
|
+
return false;
|
|
541
|
+
// 2. Present-tense failure verb describing current broken behavior.
|
|
542
|
+
// Explicitly not matching "fix" / "bug" / "broken" as keywords — those
|
|
543
|
+
// describe the PROPOSED WORK, not the OBSERVED FAILURE, and produce
|
|
544
|
+
// false positives on greenfield goals like "fix common patterns".
|
|
545
|
+
const failurePattern = new RegExp([
|
|
546
|
+
'\\b(fails?|failing|raises?|raising)\\b',
|
|
547
|
+
'\\b(throws?|throwing|errors?|erroring)\\b',
|
|
548
|
+
'\\b(crashes?|crashing|hangs?|hanging)\\b',
|
|
549
|
+
'\\b(leaks?|leaking)\\b',
|
|
550
|
+
'\\b(returns?\\s+\\S+\\s+instead\\b)',
|
|
551
|
+
'(does\\s?n[’\']?t\\s+(work|match|behave|handle))',
|
|
552
|
+
'(is\\s+incorrect|is\\s+wrong|incorrectly\\s+\\w+)',
|
|
553
|
+
].join('|'), 'i');
|
|
554
|
+
return failurePattern.test(goal);
|
|
555
|
+
}
|
|
556
|
+
generateApiSteps(goal, startNumber) {
|
|
557
|
+
const criteria = this.getAcceptanceCriteria('api');
|
|
558
|
+
const reviewCriteria = this.getIntegratorReviewCriteria();
|
|
559
|
+
return [
|
|
560
|
+
{
|
|
561
|
+
stepNumber: startNumber,
|
|
562
|
+
agentName: 'worker',
|
|
563
|
+
task: `Design and implement API routes, controllers, and data models for: ${goal}\n\nAcceptance criteria:\n${criteria}`,
|
|
564
|
+
dependencies: [],
|
|
565
|
+
expectedOutputs: ['API endpoint definitions', 'Request/response schemas', 'Database models']
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
stepNumber: startNumber + 1,
|
|
569
|
+
agentName: 'worker',
|
|
570
|
+
task: 'Setup project configuration, environment handling, and containerization',
|
|
571
|
+
dependencies: [],
|
|
572
|
+
expectedOutputs: ['Docker configuration', 'Environment config', 'Package scripts']
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
stepNumber: startNumber + 2,
|
|
576
|
+
agentName: 'worker',
|
|
577
|
+
task: 'Implement input validation, error handling middleware, and security hardening',
|
|
578
|
+
dependencies: [startNumber],
|
|
579
|
+
expectedOutputs: ['Input validation middleware', 'Error handling', 'Security headers']
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
stepNumber: startNumber + 3,
|
|
583
|
+
agentName: 'worker',
|
|
584
|
+
task: 'Create comprehensive API test suite with unit and integration tests',
|
|
585
|
+
dependencies: [startNumber],
|
|
586
|
+
expectedOutputs: ['Unit tests', 'Integration tests', 'Test coverage report']
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
stepNumber: startNumber + 4,
|
|
590
|
+
agentName: 'reviewer',
|
|
591
|
+
task: `Review API implementation, verify integration, and write accurate documentation.\n\n${reviewCriteria}`,
|
|
592
|
+
dependencies: [startNumber + 1, startNumber + 2, startNumber + 3],
|
|
593
|
+
expectedOutputs: ['Cleaned-up code', 'Accurate API documentation', 'Quality review notes']
|
|
594
|
+
}
|
|
595
|
+
];
|
|
596
|
+
}
|
|
597
|
+
generateWebAppSteps(goal, startNumber) {
|
|
598
|
+
const criteria = this.getAcceptanceCriteria('web-app');
|
|
599
|
+
const reviewCriteria = this.getIntegratorReviewCriteria();
|
|
600
|
+
// Simple web apps (vanilla HTML/CSS/JS, no framework, no backend) get a
|
|
601
|
+
// lean 2-step plan: the frontend expert builds everything with tests,
|
|
602
|
+
// and the integrator reviews. Burning 5 premium requests on backend/devops
|
|
603
|
+
// steps that produce nothing useful wastes time and money.
|
|
604
|
+
if (this.isSimpleProject(goal)) {
|
|
605
|
+
return [
|
|
606
|
+
{
|
|
607
|
+
stepNumber: startNumber,
|
|
608
|
+
agentName: 'worker',
|
|
609
|
+
task: `Build the complete application with tests for: ${goal}\n\nAcceptance criteria:\n${criteria}\n\nYou must also write tests. Include a test file and a package.json with a working npm test script.`,
|
|
610
|
+
dependencies: [],
|
|
611
|
+
expectedOutputs: ['UI components', 'Styling', 'Accessible markup', 'Tests', 'Working functionality']
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
stepNumber: startNumber + 1,
|
|
615
|
+
agentName: 'reviewer',
|
|
616
|
+
task: `Review all code for quality, then write accurate documentation.\n\n${reviewCriteria}`,
|
|
617
|
+
dependencies: [startNumber],
|
|
618
|
+
expectedOutputs: ['Cleaned-up code', 'Accurate documentation', 'Quality review notes']
|
|
619
|
+
}
|
|
620
|
+
];
|
|
621
|
+
}
|
|
622
|
+
return [
|
|
623
|
+
{
|
|
624
|
+
stepNumber: startNumber,
|
|
625
|
+
agentName: 'worker',
|
|
626
|
+
task: `Build UI components and pages for: ${goal}\n\nAcceptance criteria:\n${criteria}`,
|
|
627
|
+
dependencies: [],
|
|
628
|
+
expectedOutputs: ['UI components', 'Page layouts', 'Styling', 'Accessible markup']
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
stepNumber: startNumber + 1,
|
|
632
|
+
agentName: 'worker',
|
|
633
|
+
task: 'Implement backend API and data layer',
|
|
634
|
+
dependencies: [],
|
|
635
|
+
expectedOutputs: ['API endpoints', 'Database schema', 'Business logic']
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
stepNumber: startNumber + 2,
|
|
639
|
+
agentName: 'worker',
|
|
640
|
+
task: 'Create frontend and integration tests',
|
|
641
|
+
dependencies: [startNumber, startNumber + 1],
|
|
642
|
+
expectedOutputs: ['Component tests', 'E2E tests', 'Test coverage']
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
stepNumber: startNumber + 3,
|
|
646
|
+
agentName: 'worker',
|
|
647
|
+
task: 'Setup CI/CD pipeline and deployment',
|
|
648
|
+
dependencies: [startNumber + 2],
|
|
649
|
+
expectedOutputs: ['CI workflow', 'Build pipeline', 'Deployment config']
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
stepNumber: startNumber + 4,
|
|
653
|
+
agentName: 'reviewer',
|
|
654
|
+
task: `Final integration review, cleanup, and documentation.\n\n${reviewCriteria}`,
|
|
655
|
+
dependencies: [startNumber + 3],
|
|
656
|
+
expectedOutputs: ['Cleaned-up code', 'Accurate documentation', 'Quality review notes']
|
|
657
|
+
}
|
|
658
|
+
];
|
|
659
|
+
}
|
|
660
|
+
generateCliToolSteps(goal, startNumber) {
|
|
661
|
+
const criteria = this.getAcceptanceCriteria('cli-tool');
|
|
662
|
+
const reviewCriteria = this.getIntegratorReviewCriteria();
|
|
663
|
+
return [
|
|
664
|
+
{
|
|
665
|
+
stepNumber: startNumber,
|
|
666
|
+
agentName: 'worker',
|
|
667
|
+
task: `Implement CLI core functionality for: ${goal}\n\nAcceptance criteria:\n${criteria}`,
|
|
668
|
+
dependencies: [],
|
|
669
|
+
expectedOutputs: ['CLI commands', 'Argument parsing', 'Core logic']
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
stepNumber: startNumber + 1,
|
|
673
|
+
agentName: 'worker',
|
|
674
|
+
task: 'Create CLI tests and validation',
|
|
675
|
+
dependencies: [startNumber],
|
|
676
|
+
expectedOutputs: ['Unit tests', 'CLI integration tests', 'Test coverage']
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
stepNumber: startNumber + 2,
|
|
680
|
+
agentName: 'reviewer',
|
|
681
|
+
task: `Review implementation, add accurate documentation and examples.\n\n${reviewCriteria}`,
|
|
682
|
+
dependencies: [startNumber],
|
|
683
|
+
expectedOutputs: ['Cleaned-up code', 'README with examples', 'Quality review notes']
|
|
684
|
+
}
|
|
685
|
+
];
|
|
686
|
+
}
|
|
687
|
+
generateLibrarySteps(goal, startNumber) {
|
|
688
|
+
const criteria = this.getAcceptanceCriteria('library');
|
|
689
|
+
const reviewCriteria = this.getIntegratorReviewCriteria();
|
|
690
|
+
return [
|
|
691
|
+
{
|
|
692
|
+
stepNumber: startNumber,
|
|
693
|
+
agentName: 'worker',
|
|
694
|
+
task: `Implement library core API for: ${goal}\n\nAcceptance criteria:\n${criteria}`,
|
|
695
|
+
dependencies: [],
|
|
696
|
+
expectedOutputs: ['Public API', 'Type definitions', 'Core implementation']
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
stepNumber: startNumber + 1,
|
|
700
|
+
agentName: 'worker',
|
|
701
|
+
task: 'Create comprehensive test suite',
|
|
702
|
+
dependencies: [startNumber],
|
|
703
|
+
expectedOutputs: ['Unit tests', 'Integration tests', 'Coverage report']
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
stepNumber: startNumber + 2,
|
|
707
|
+
agentName: 'reviewer',
|
|
708
|
+
task: `Review implementation, write accurate documentation with usage examples.\n\n${reviewCriteria}`,
|
|
709
|
+
dependencies: [startNumber],
|
|
710
|
+
expectedOutputs: ['Cleaned-up code', 'API documentation with examples', 'Quality review notes']
|
|
711
|
+
}
|
|
712
|
+
];
|
|
713
|
+
}
|
|
714
|
+
generateInfrastructureSteps(goal, startNumber) {
|
|
715
|
+
const criteria = this.getAcceptanceCriteria('infrastructure');
|
|
716
|
+
const reviewCriteria = this.getIntegratorReviewCriteria();
|
|
717
|
+
return [
|
|
718
|
+
{
|
|
719
|
+
stepNumber: startNumber,
|
|
720
|
+
agentName: 'worker',
|
|
721
|
+
task: `Design and implement infrastructure for: ${goal}\n\nAcceptance criteria:\n${criteria}`,
|
|
722
|
+
dependencies: [],
|
|
723
|
+
expectedOutputs: ['Infrastructure as code', 'Configuration files', 'Deployment scripts']
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
stepNumber: startNumber + 1,
|
|
727
|
+
agentName: 'worker',
|
|
728
|
+
task: 'Review security configuration and access controls',
|
|
729
|
+
dependencies: [startNumber],
|
|
730
|
+
expectedOutputs: ['Security audit', 'Access policies', 'Secrets management']
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
stepNumber: startNumber + 2,
|
|
734
|
+
agentName: 'worker',
|
|
735
|
+
task: 'Create infrastructure tests and validation',
|
|
736
|
+
dependencies: [startNumber],
|
|
737
|
+
expectedOutputs: ['Infrastructure tests', 'Validation scripts', 'Test results']
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
stepNumber: startNumber + 3,
|
|
741
|
+
agentName: 'reviewer',
|
|
742
|
+
task: `Verify deployment, review configs, and write accurate runbooks.\n\n${reviewCriteria}`,
|
|
743
|
+
dependencies: [startNumber + 1, startNumber + 2],
|
|
744
|
+
expectedOutputs: ['Deployment verification', 'Accurate runbooks', 'Quality review notes']
|
|
745
|
+
}
|
|
746
|
+
];
|
|
747
|
+
}
|
|
748
|
+
generateDataPipelineSteps(goal, startNumber) {
|
|
749
|
+
const criteria = this.getAcceptanceCriteria('data-pipeline');
|
|
750
|
+
const reviewCriteria = this.getIntegratorReviewCriteria();
|
|
751
|
+
return [
|
|
752
|
+
{
|
|
753
|
+
stepNumber: startNumber,
|
|
754
|
+
agentName: 'worker',
|
|
755
|
+
task: `Implement data pipeline for: ${goal}\n\nAcceptance criteria:\n${criteria}`,
|
|
756
|
+
dependencies: [],
|
|
757
|
+
expectedOutputs: ['Pipeline code', 'Data transformations', 'Storage layer']
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
stepNumber: startNumber + 1,
|
|
761
|
+
agentName: 'worker',
|
|
762
|
+
task: 'Create data validation and pipeline tests',
|
|
763
|
+
dependencies: [startNumber],
|
|
764
|
+
expectedOutputs: ['Data quality tests', 'Pipeline tests', 'Test coverage']
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
stepNumber: startNumber + 2,
|
|
768
|
+
agentName: 'worker',
|
|
769
|
+
task: 'Setup pipeline orchestration and monitoring',
|
|
770
|
+
dependencies: [startNumber],
|
|
771
|
+
expectedOutputs: ['Orchestration config', 'Monitoring setup', 'Alerting']
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
stepNumber: startNumber + 3,
|
|
775
|
+
agentName: 'reviewer',
|
|
776
|
+
task: `Verify end-to-end pipeline and write accurate documentation.\n\n${reviewCriteria}`,
|
|
777
|
+
dependencies: [startNumber + 1, startNumber + 2],
|
|
778
|
+
expectedOutputs: ['Pipeline verification', 'Accurate documentation', 'Quality review notes']
|
|
779
|
+
}
|
|
780
|
+
];
|
|
781
|
+
}
|
|
782
|
+
generateMobileAppSteps(goal, startNumber) {
|
|
783
|
+
const criteria = this.getAcceptanceCriteria('mobile-app');
|
|
784
|
+
const reviewCriteria = this.getIntegratorReviewCriteria();
|
|
785
|
+
return [
|
|
786
|
+
{
|
|
787
|
+
stepNumber: startNumber,
|
|
788
|
+
agentName: 'worker',
|
|
789
|
+
task: `Build mobile UI and screens for: ${goal}\n\nAcceptance criteria:\n${criteria}`,
|
|
790
|
+
dependencies: [],
|
|
791
|
+
expectedOutputs: ['Mobile screens', 'Navigation', 'Accessible UI components']
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
stepNumber: startNumber + 1,
|
|
795
|
+
agentName: 'worker',
|
|
796
|
+
task: 'Implement mobile backend and API integration',
|
|
797
|
+
dependencies: [],
|
|
798
|
+
expectedOutputs: ['API client', 'State management', 'Backend integration']
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
stepNumber: startNumber + 2,
|
|
802
|
+
agentName: 'worker',
|
|
803
|
+
task: 'Create mobile app tests',
|
|
804
|
+
dependencies: [startNumber, startNumber + 1],
|
|
805
|
+
expectedOutputs: ['Component tests', 'Integration tests', 'Test coverage']
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
stepNumber: startNumber + 3,
|
|
809
|
+
agentName: 'reviewer',
|
|
810
|
+
task: `Review app quality, prepare store metadata, and write accurate documentation.\n\n${reviewCriteria}`,
|
|
811
|
+
dependencies: [startNumber + 2],
|
|
812
|
+
expectedOutputs: ['Cleaned-up code', 'App metadata', 'Quality review notes']
|
|
813
|
+
}
|
|
814
|
+
];
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Template for goals that describe a failing behavior in pre-existing code
|
|
818
|
+
* and ask for it to be corrected. Structural shape:
|
|
819
|
+
* 1. worker — locate root cause and apply a minimal fix
|
|
820
|
+
* 2. worker — verify the fix with a regression test
|
|
821
|
+
* 3. reviewer — review scope and quality
|
|
822
|
+
*
|
|
823
|
+
* The primary step is an implementation step by construction. That's the
|
|
824
|
+
* invariant the bug-fix template exists to guarantee: a bug report will
|
|
825
|
+
* never produce a plan without an implementation step, which was exactly
|
|
826
|
+
* the sympy-12481 failure mode in Phase 4a smoke3.
|
|
827
|
+
*
|
|
828
|
+
* **Worker as fixed primary — deferred decision.** The current
|
|
829
|
+
* benchmark corpus (SWE-bench Verified) is backend-dominant: the 500
|
|
830
|
+
* Verified instances are drawn from sympy, sphinx, django, scikit-learn,
|
|
831
|
+
* matplotlib, astropy, xarray, pytest, pylint, requests — all Python
|
|
832
|
+
* libraries and frameworks whose issues are predominantly source-code
|
|
833
|
+
* bugs in logic/data/API layers. The worker handles all of these.
|
|
834
|
+
*
|
|
835
|
+
* The trigger for adding domain-specific routing: observe a bug-report
|
|
836
|
+
* goal in a future corpus where the worker-primary plan produces a
|
|
837
|
+
* broken result specifically because the task requires specialized
|
|
838
|
+
* knowledge the worker lacks. Until that evidence surfaces, the single
|
|
839
|
+
* worker role keeps the template simple and correct for the dominant
|
|
840
|
+
* case. See #27.
|
|
841
|
+
*/
|
|
842
|
+
generateBugFixSteps(goal, startNumber) {
|
|
843
|
+
const criteria = this.getAcceptanceCriteria('bug-fix');
|
|
844
|
+
const reviewCriteria = this.getIntegratorReviewCriteria();
|
|
845
|
+
return [
|
|
846
|
+
{
|
|
847
|
+
stepNumber: startNumber,
|
|
848
|
+
agentName: 'worker',
|
|
849
|
+
task: `Diagnose and fix the reported bug.\n\nReported issue:\n${goal}\n\n` +
|
|
850
|
+
`Acceptance criteria:\n${criteria}`,
|
|
851
|
+
dependencies: [],
|
|
852
|
+
expectedOutputs: [
|
|
853
|
+
'Root cause identification',
|
|
854
|
+
'Minimal source-code fix targeting the reported failure',
|
|
855
|
+
'Brief justification of why the fix addresses the root cause, not a symptom',
|
|
856
|
+
],
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
stepNumber: startNumber + 1,
|
|
860
|
+
agentName: 'worker',
|
|
861
|
+
task: `Write a regression test that reproduces the reported failure and ` +
|
|
862
|
+
`now passes with the fix applied. Do not modify pre-existing test ` +
|
|
863
|
+
`assertions — add new coverage targeted at the bug.\n\n` +
|
|
864
|
+
`Reported issue (for context):\n${goal}`,
|
|
865
|
+
dependencies: [startNumber],
|
|
866
|
+
expectedOutputs: [
|
|
867
|
+
'Regression test that would have failed pre-fix',
|
|
868
|
+
'Confirmation existing tests still pass',
|
|
869
|
+
],
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
stepNumber: startNumber + 2,
|
|
873
|
+
agentName: 'reviewer',
|
|
874
|
+
task: `Review the fix's scope, quality, and documentation. Confirm the ` +
|
|
875
|
+
`change does not drift beyond the reported failure and that any ` +
|
|
876
|
+
`public API or error message changes are documented.\n\n${reviewCriteria}`,
|
|
877
|
+
dependencies: [startNumber, startNumber + 1],
|
|
878
|
+
expectedOutputs: [
|
|
879
|
+
'Scope review notes',
|
|
880
|
+
'Documentation updates if public surface changed',
|
|
881
|
+
'Quality review notes',
|
|
882
|
+
],
|
|
883
|
+
},
|
|
884
|
+
];
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Template for coordinated modifications to an existing codebase — the
|
|
888
|
+
* "contract change" shape. Structural invariant of this template:
|
|
889
|
+
*
|
|
890
|
+
* The impl change, the caller updates, and the test updates all land
|
|
891
|
+
* in ONE step. There is NO separate test-update step
|
|
892
|
+
* between the implementation change and the reviewer step.
|
|
893
|
+
*
|
|
894
|
+
* Rationale (#27 Fix 3 root-cause analysis). The library template and
|
|
895
|
+
* bug-fix template both place worker (implement) → worker (tests) as two
|
|
896
|
+
* separate steps. Between them, the per-step verifier runs `npm test`.
|
|
897
|
+
* For a contract-change task, step 1 lands the impl change that
|
|
898
|
+
* intentionally breaks the pre-existing tests (they assert the OLD
|
|
899
|
+
* contract); `npm test` fails; verifier rolls step 1 back; step 2
|
|
900
|
+
* never runs. This template removes that failure mode by bundling
|
|
901
|
+
* impl + callers + tests into one step whose post-step verification
|
|
902
|
+
* sees a coherent post-contract-change state.
|
|
903
|
+
*
|
|
904
|
+
* Verifier contract is unchanged. The orchestrator's per-step
|
|
905
|
+
* verification discipline is what catches agent lies; weakening it to
|
|
906
|
+
* "defer `npm test` to end-of-plan" would defeat that discipline. The
|
|
907
|
+
* plan generator's job is to shape plans the per-step verifier can
|
|
908
|
+
* successfully evaluate.
|
|
909
|
+
*
|
|
910
|
+
* Why only 2 steps. For a bundled change, there's no coherent handoff
|
|
911
|
+
* to a second implementation step. Reviewer stays as the
|
|
912
|
+
* scope/quality reviewer.
|
|
913
|
+
*/
|
|
914
|
+
generateContractChangeSteps(goal, startNumber) {
|
|
915
|
+
const criteria = this.getAcceptanceCriteria('contract-change');
|
|
916
|
+
const reviewCriteria = this.getIntegratorReviewCriteria();
|
|
917
|
+
return [
|
|
918
|
+
{
|
|
919
|
+
stepNumber: startNumber,
|
|
920
|
+
agentName: 'worker',
|
|
921
|
+
task: `Apply the following contract change as a single atomic step. ` +
|
|
922
|
+
`This step must update the implementation, every call site, AND ` +
|
|
923
|
+
`every affected test so the test suite passes against the new ` +
|
|
924
|
+
`contract when the step completes. Updating impl + callers while ` +
|
|
925
|
+
`leaving pre-existing tests unchanged is a failure mode of this ` +
|
|
926
|
+
`step: pre-existing tests assert the OLD contract and will fail ` +
|
|
927
|
+
`verification against the NEW impl. Updating tests is not ` +
|
|
928
|
+
`optional; it is part of the atomic bundle.\n\n` +
|
|
929
|
+
`Contract change:\n${goal}\n\n` +
|
|
930
|
+
`Acceptance criteria:\n${criteria}`,
|
|
931
|
+
dependencies: [],
|
|
932
|
+
expectedOutputs: [
|
|
933
|
+
'Updated implementation',
|
|
934
|
+
'All call sites updated',
|
|
935
|
+
'Tests updated to exercise the post-change contract',
|
|
936
|
+
'Docs/examples updated where the contract surfaces publicly',
|
|
937
|
+
],
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
stepNumber: startNumber + 1,
|
|
941
|
+
agentName: 'reviewer',
|
|
942
|
+
task: `Review the contract change's scope, quality, and documentation. ` +
|
|
943
|
+
`Confirm every call site was updated (no stale pre-change callers) ` +
|
|
944
|
+
`and the test suite now exercises the post-change contract rather ` +
|
|
945
|
+
`than asserting on the old one.\n\n${reviewCriteria}`,
|
|
946
|
+
dependencies: [startNumber],
|
|
947
|
+
expectedOutputs: [
|
|
948
|
+
'Scope review notes',
|
|
949
|
+
'Caller-update completeness check',
|
|
950
|
+
'Quality review notes',
|
|
951
|
+
],
|
|
952
|
+
},
|
|
953
|
+
];
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Detect whether the goal describes a trivial single-file modification —
|
|
957
|
+
* something like "add X to index.js", "export Y from utils.ts", "rename foo
|
|
958
|
+
* in bar.py". These tasks are fully completed by one agent in one pass;
|
|
959
|
+
* the generic 3-step template (worker → worker (tests) → reviewer)
|
|
960
|
+
* would wait on 2 more agent invocations that re-do Step 1's work.
|
|
961
|
+
*
|
|
962
|
+
* Three signals required so greenfield goals don't misroute:
|
|
963
|
+
* 1. An additive/modifying verb ("add", "export", "rename", ...).
|
|
964
|
+
* 2. A single code-file reference (foo.js / utils/bar.ts / etc.).
|
|
965
|
+
* 3. The goal stays short (≤20 words) — multi-requirement goals that
|
|
966
|
+
* mention one file in passing are still multi-step work.
|
|
967
|
+
*/
|
|
968
|
+
isTrivialTask(goal) {
|
|
969
|
+
const lower = goal.toLowerCase();
|
|
970
|
+
const hasTrivialVerb = /\b(add|append|export|rename|remove|replace|inline|extract)\b/.test(lower);
|
|
971
|
+
if (!hasTrivialVerb)
|
|
972
|
+
return false;
|
|
973
|
+
const fileRefs = lower.match(/\b[\w./-]+\.(?:js|mjs|cjs|jsx|ts|tsx|py|rb|go|rs|java|kt|php|swift|cs|sh|css|scss|html|json|yaml|yml|md)\b/g) ?? [];
|
|
974
|
+
if (fileRefs.length < 1 || fileRefs.length > 2)
|
|
975
|
+
return false;
|
|
976
|
+
const wordCount = goal.trim().split(/\s+/).length;
|
|
977
|
+
if (wordCount > 20)
|
|
978
|
+
return false;
|
|
979
|
+
return true;
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Detect whether the goal describes a small, self-contained project that
|
|
983
|
+
* doesn't need a separate testing step. Indicators: explicit file list,
|
|
984
|
+
* "no framework", "no build step", or very short goal with few deliverables.
|
|
985
|
+
*/
|
|
986
|
+
isSimpleProject(goal) {
|
|
987
|
+
const lower = goal.toLowerCase();
|
|
988
|
+
// Explicit signals that the project is small/self-contained
|
|
989
|
+
const simpleSignals = [
|
|
990
|
+
/no (build step|framework|dependencies)/,
|
|
991
|
+
/just (index\.html|html|css|js)/,
|
|
992
|
+
/single.page/,
|
|
993
|
+
/vanilla (js|javascript|html|css)/,
|
|
994
|
+
/no.frameworks/,
|
|
995
|
+
];
|
|
996
|
+
const fileListMention = (lower.match(/\b(index\.html|style\.css|app\.js|main\.js|script\.js)\b/g) || []).length;
|
|
997
|
+
const hasSimpleSignal = simpleSignals.some(re => re.test(lower));
|
|
998
|
+
// Small file count + simple signal = skip separate test step
|
|
999
|
+
return hasSimpleSignal || fileListMention >= 2;
|
|
1000
|
+
}
|
|
1001
|
+
generateGenericSteps(goal, startNumber) {
|
|
1002
|
+
const primaryAgent = this.assignAgent(goal);
|
|
1003
|
+
const criteria = this.getAcceptanceCriteria('generic');
|
|
1004
|
+
const reviewCriteria = this.getIntegratorReviewCriteria();
|
|
1005
|
+
// Trivial single-file modifications collapse to one step. The default
|
|
1006
|
+
// 3-step generic template paid a full extra Codex invocation per added
|
|
1007
|
+
// step for work the primary agent already does in-pass (impl + tests).
|
|
1008
|
+
if (this.isTrivialTask(goal)) {
|
|
1009
|
+
return [
|
|
1010
|
+
{
|
|
1011
|
+
stepNumber: startNumber,
|
|
1012
|
+
agentName: primaryAgent,
|
|
1013
|
+
task: `Implement with tests for: ${goal}\n\nAcceptance criteria:\n${criteria}\n\nWrite or update tests in the same commit; the project's test command (npm test, pytest, go test, ...) must pass before you finish.`,
|
|
1014
|
+
dependencies: [],
|
|
1015
|
+
expectedOutputs: ['Implementation', 'Tests', 'Working functionality'],
|
|
1016
|
+
},
|
|
1017
|
+
];
|
|
1018
|
+
}
|
|
1019
|
+
// For simple projects (few files, no framework, no build step), skip
|
|
1020
|
+
// the dedicated test step. The primary worker's acceptance criteria
|
|
1021
|
+
// already require tests, and the reviewer verifies test coverage.
|
|
1022
|
+
if (this.isSimpleProject(goal)) {
|
|
1023
|
+
return [
|
|
1024
|
+
{
|
|
1025
|
+
stepNumber: startNumber,
|
|
1026
|
+
agentName: primaryAgent,
|
|
1027
|
+
task: `Implement core functionality with tests for: ${goal}\n\nAcceptance criteria:\n${criteria}\n\nYou must also write tests. Include a test file and a package.json with a working npm test script.`,
|
|
1028
|
+
dependencies: [],
|
|
1029
|
+
expectedOutputs: ['Implementation', 'Core files', 'Tests', 'Working functionality']
|
|
1030
|
+
},
|
|
1031
|
+
{
|
|
1032
|
+
stepNumber: startNumber + 1,
|
|
1033
|
+
agentName: 'reviewer',
|
|
1034
|
+
task: `Review all code for quality, then write accurate documentation.\n\n${reviewCriteria}`,
|
|
1035
|
+
dependencies: [startNumber],
|
|
1036
|
+
expectedOutputs: ['Cleaned-up code', 'Accurate documentation', 'Quality review notes']
|
|
1037
|
+
}
|
|
1038
|
+
];
|
|
1039
|
+
}
|
|
1040
|
+
return [
|
|
1041
|
+
{
|
|
1042
|
+
stepNumber: startNumber,
|
|
1043
|
+
agentName: primaryAgent,
|
|
1044
|
+
task: `Implement core functionality for: ${goal}\n\nAcceptance criteria:\n${criteria}`,
|
|
1045
|
+
dependencies: [],
|
|
1046
|
+
expectedOutputs: ['Implementation', 'Core files', 'Working functionality']
|
|
1047
|
+
},
|
|
1048
|
+
{
|
|
1049
|
+
stepNumber: startNumber + 1,
|
|
1050
|
+
agentName: 'worker',
|
|
1051
|
+
task: 'Create tests and verify functionality',
|
|
1052
|
+
dependencies: [startNumber],
|
|
1053
|
+
expectedOutputs: ['Tests', 'Test results', 'Coverage report']
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
stepNumber: startNumber + 2,
|
|
1057
|
+
agentName: 'reviewer',
|
|
1058
|
+
task: `Review all code for quality, then write accurate documentation.\n\n${reviewCriteria}`,
|
|
1059
|
+
dependencies: [startNumber + 1],
|
|
1060
|
+
expectedOutputs: ['Cleaned-up code', 'Accurate documentation', 'Quality review notes']
|
|
1061
|
+
}
|
|
1062
|
+
];
|
|
1063
|
+
}
|
|
1064
|
+
validateAgentAssignments(steps) {
|
|
1065
|
+
const agentNames = new Set(this.availableAgents.map(a => a.name));
|
|
1066
|
+
const normalizedAgentNames = new Set(this.availableAgents.map(a => config_loader_1.ConfigLoader.normalizeAgentName(a.name)));
|
|
1067
|
+
for (const step of steps) {
|
|
1068
|
+
if (!agentNames.has(step.agentName) && !normalizedAgentNames.has(config_loader_1.ConfigLoader.normalizeAgentName(step.agentName))) {
|
|
1069
|
+
throw new Error(`Step ${step.stepNumber} assigns unknown agent: ${step.agentName}. ` +
|
|
1070
|
+
`Available agents: ${Array.from(agentNames).join(', ')}`);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
validateDependencies(steps) {
|
|
1075
|
+
const stepNumbers = new Set(steps.map(s => s.stepNumber));
|
|
1076
|
+
for (const step of steps) {
|
|
1077
|
+
for (const dep of step.dependencies) {
|
|
1078
|
+
if (!stepNumbers.has(dep)) {
|
|
1079
|
+
throw new Error(`Step ${step.stepNumber} has invalid dependency: step ${dep} does not exist`);
|
|
1080
|
+
}
|
|
1081
|
+
if (dep >= step.stepNumber) {
|
|
1082
|
+
throw new Error(`Step ${step.stepNumber} has invalid dependency: step ${dep} must come before this step`);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Assigns the role for generic task steps. In v7, all implementation work
|
|
1089
|
+
* is done by the worker role regardless of domain keywords. Domain
|
|
1090
|
+
* specialization (security policy, accessibility review) is expressed as
|
|
1091
|
+
* a reviewer policy field, not as separate agent names.
|
|
1092
|
+
*
|
|
1093
|
+
* @param task - The task text (unused in v7; retained for call-site compatibility).
|
|
1094
|
+
* @returns Always 'worker'.
|
|
1095
|
+
*/
|
|
1096
|
+
assignAgent(_task) {
|
|
1097
|
+
return 'worker';
|
|
1098
|
+
}
|
|
1099
|
+
getExecutionOrder(plan) {
|
|
1100
|
+
// Simple topological sort for step execution order
|
|
1101
|
+
const steps = plan.steps;
|
|
1102
|
+
const executed = new Set();
|
|
1103
|
+
const order = [];
|
|
1104
|
+
while (order.length < steps.length) {
|
|
1105
|
+
let foundStep = false;
|
|
1106
|
+
for (const step of steps) {
|
|
1107
|
+
if (executed.has(step.stepNumber)) {
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
const depsReady = step.dependencies.every(dep => executed.has(dep));
|
|
1111
|
+
if (depsReady) {
|
|
1112
|
+
order.push(step.stepNumber);
|
|
1113
|
+
executed.add(step.stepNumber);
|
|
1114
|
+
foundStep = true;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (!foundStep && order.length < steps.length) {
|
|
1118
|
+
throw new Error('Circular dependency detected in plan');
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
return order;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* revise plan based on replan payload from reviewer
|
|
1125
|
+
* preserves completed steps, marks retries with suffix, appends new steps
|
|
1126
|
+
*/
|
|
1127
|
+
revisePlan(plan, replanPayload, completedSteps) {
|
|
1128
|
+
const revisedSteps = [];
|
|
1129
|
+
// copy all existing steps (completed ones stay as-is)
|
|
1130
|
+
for (const step of plan.steps) {
|
|
1131
|
+
revisedSteps.push({ ...step });
|
|
1132
|
+
}
|
|
1133
|
+
// track highest step number
|
|
1134
|
+
let maxStepNumber = Math.max(...plan.steps.map(s => s.stepNumber));
|
|
1135
|
+
// mark retry steps with updated task description
|
|
1136
|
+
// actual retry branches use suffix like step-3-retry1
|
|
1137
|
+
for (const retryStepNum of replanPayload.retrySteps) {
|
|
1138
|
+
const existing = revisedSteps.find(s => s.stepNumber === retryStepNum);
|
|
1139
|
+
if (existing && !completedSteps.includes(retryStepNum)) {
|
|
1140
|
+
// prepend retry indicator to task
|
|
1141
|
+
if (!existing.task.startsWith('[RETRY]')) {
|
|
1142
|
+
existing.task = `[RETRY] ${existing.task}`;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
// append new steps if any
|
|
1147
|
+
if (replanPayload.addSteps && replanPayload.addSteps.length > 0) {
|
|
1148
|
+
for (const addReq of replanPayload.addSteps) {
|
|
1149
|
+
// validate agent exists — use normalized name comparison so snake_case
|
|
1150
|
+
// variants (e.g. 'worker', 'reviewer') match YAML agent names case-insensitively
|
|
1151
|
+
const agentNames = new Set(this.availableAgents.map(a => a.name));
|
|
1152
|
+
const matchedAgent = agentNames.has(addReq.agent)
|
|
1153
|
+
? addReq.agent
|
|
1154
|
+
: this.availableAgents.find(a => config_loader_1.ConfigLoader.normalizeAgentName(a.name) === config_loader_1.ConfigLoader.normalizeAgentName(addReq.agent))?.name;
|
|
1155
|
+
if (!matchedAgent) {
|
|
1156
|
+
logger.warn(`replan: unknown agent "${addReq.agent}", skipping`);
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
maxStepNumber++;
|
|
1160
|
+
// Dependency resolution for replan-added steps:
|
|
1161
|
+
// 1. If the caller specified afterStep explicitly, use it.
|
|
1162
|
+
// 2. Otherwise, anchor to the highest completed step. The previous
|
|
1163
|
+
// "last existing step" fallback was the codex-quota smoke
|
|
1164
|
+
// regression: when every original step had failed (no
|
|
1165
|
+
// completed steps at all, last step in plan was failed), the
|
|
1166
|
+
// replan step inherited a dependency on that failed step,
|
|
1167
|
+
// which never satisfies in waitForDependencies and burned
|
|
1168
|
+
// DEFAULT_DEPENDENCY_WAIT_MS (10 min) per replan attempt.
|
|
1169
|
+
// 3. If no steps completed, the replan step has no dependency
|
|
1170
|
+
// and runs immediately — the safest fallback when prior
|
|
1171
|
+
// state is unusable.
|
|
1172
|
+
let dependencies;
|
|
1173
|
+
if (addReq.afterStep !== undefined) {
|
|
1174
|
+
dependencies = [addReq.afterStep];
|
|
1175
|
+
}
|
|
1176
|
+
else if (completedSteps.length > 0) {
|
|
1177
|
+
dependencies = [Math.max(...completedSteps)];
|
|
1178
|
+
}
|
|
1179
|
+
else {
|
|
1180
|
+
dependencies = [];
|
|
1181
|
+
}
|
|
1182
|
+
const newStep = {
|
|
1183
|
+
stepNumber: maxStepNumber,
|
|
1184
|
+
agentName: matchedAgent,
|
|
1185
|
+
task: addReq.task,
|
|
1186
|
+
dependencies,
|
|
1187
|
+
expectedOutputs: ['Replan-generated output']
|
|
1188
|
+
};
|
|
1189
|
+
revisedSteps.push(newStep);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
const metadata = {
|
|
1193
|
+
totalSteps: revisedSteps.length
|
|
1194
|
+
};
|
|
1195
|
+
if (plan.metadata?.estimatedDuration) {
|
|
1196
|
+
metadata.estimatedDuration = plan.metadata.estimatedDuration;
|
|
1197
|
+
}
|
|
1198
|
+
return {
|
|
1199
|
+
...plan,
|
|
1200
|
+
createdAt: new Date().toISOString(),
|
|
1201
|
+
steps: revisedSteps,
|
|
1202
|
+
metadata
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
exports.PlanGenerator = PlanGenerator;
|
|
1207
|
+
exports.default = PlanGenerator;
|