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.
Files changed (174) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +199 -0
  3. package/agents/reviewer.agent.md +50 -0
  4. package/agents/worker.agent.md +41 -0
  5. package/config/default-agents.yaml +56 -0
  6. package/config/pm-agent.yaml +36 -0
  7. package/config/quality-gates.yaml +88 -0
  8. package/config/repair-agent.yaml +34 -0
  9. package/config/semgrep-rules/complexity-mismatch.yaml +6 -0
  10. package/config/semgrep-rules/exception-swallowing.yaml +13 -0
  11. package/config/semgrep-rules/hardcoded-answer.yaml +6 -0
  12. package/config/semgrep-rules/mock-mutation.yaml +6 -0
  13. package/config/semgrep-rules/test-modification.yaml +6 -0
  14. package/config/user-agents.yaml +23 -0
  15. package/dist/src/adapters/adapter-factory.js +29 -0
  16. package/dist/src/adapters/agent-adapter.js +32 -0
  17. package/dist/src/adapters/claude-code-adapter.js +126 -0
  18. package/dist/src/adapters/claude-code-teams.js +119 -0
  19. package/dist/src/adapters/codex-adapter.js +118 -0
  20. package/dist/src/adapters/copilot-adapter.js +224 -0
  21. package/dist/src/adapters/fatal-error-classifier.js +88 -0
  22. package/dist/src/adapters/index.js +44 -0
  23. package/dist/src/adapters/persistent-session.js +204 -0
  24. package/dist/src/adapters/process-supervisor.js +199 -0
  25. package/dist/src/agents-exporter.js +398 -0
  26. package/dist/src/analytics-log.js +161 -0
  27. package/dist/src/baseline-scanner.js +65 -0
  28. package/dist/src/bootstrap-evidence.js +88 -0
  29. package/dist/src/bootstrap-orchestrator.js +205 -0
  30. package/dist/src/bootstrap-types.js +5 -0
  31. package/dist/src/branch-merger.js +427 -0
  32. package/dist/src/cli/attest-handlers.js +44 -0
  33. package/dist/src/cli/cost-prompt.js +51 -0
  34. package/dist/src/cli/demo-handlers.js +274 -0
  35. package/dist/src/cli/flags.js +152 -0
  36. package/dist/src/cli/index.js +57 -0
  37. package/dist/src/cli/live-status.js +186 -0
  38. package/dist/src/cli/misc-handlers.js +242 -0
  39. package/dist/src/cli/plan-handlers.js +350 -0
  40. package/dist/src/cli/share-handlers.js +163 -0
  41. package/dist/src/cli/status-handlers.js +457 -0
  42. package/dist/src/cli/swarm-handlers.js +638 -0
  43. package/dist/src/cli/usage.js +72 -0
  44. package/dist/src/cli.js +217 -0
  45. package/dist/src/commit-pattern-detector.js +143 -0
  46. package/dist/src/commit-quality-analyzer.js +38 -0
  47. package/dist/src/config-loader.js +339 -0
  48. package/dist/src/context-broker.js +358 -0
  49. package/dist/src/copilot-cli-wrapper.js +258 -0
  50. package/dist/src/cost-estimator.js +241 -0
  51. package/dist/src/defaults.js +18 -0
  52. package/dist/src/demo-mode.js +180 -0
  53. package/dist/src/deployment-handler.js +78 -0
  54. package/dist/src/deployment-manager.js +228 -0
  55. package/dist/src/execution-queue.js +196 -0
  56. package/dist/src/external-tool-manager.js +184 -0
  57. package/dist/src/gate-prompt-builder.js +117 -0
  58. package/dist/src/gate-remediation.js +173 -0
  59. package/dist/src/github-issues-ingester.js +152 -0
  60. package/dist/src/github-mcp-integrator.js +164 -0
  61. package/dist/src/hook-generator.js +229 -0
  62. package/dist/src/index.js +48 -0
  63. package/dist/src/knowledge-base.js +300 -0
  64. package/dist/src/logger.js +111 -0
  65. package/dist/src/meta-analyzer.js +208 -0
  66. package/dist/src/metrics-collector.js +209 -0
  67. package/dist/src/metrics-types.js +5 -0
  68. package/dist/src/multi-repo-coordinator.js +179 -0
  69. package/dist/src/orchestrator/async-meta-analysis.js +84 -0
  70. package/dist/src/orchestrator/fatal-run-error.js +43 -0
  71. package/dist/src/orchestrator/final-gates-remediation.js +258 -0
  72. package/dist/src/orchestrator/git-state-utils.js +119 -0
  73. package/dist/src/orchestrator/pause-controller.js +55 -0
  74. package/dist/src/orchestrator/replan-runner.js +258 -0
  75. package/dist/src/orchestrator/step-executor.js +391 -0
  76. package/dist/src/orchestrator/wave-scheduler-loop.js +204 -0
  77. package/dist/src/owasp-mapper.js +153 -0
  78. package/dist/src/owasp-report-renderer.js +39 -0
  79. package/dist/src/plan-files.js +142 -0
  80. package/dist/src/plan-generator.js +1207 -0
  81. package/dist/src/pm-agent.js +296 -0
  82. package/dist/src/post-run-reporter.js +203 -0
  83. package/dist/src/pr-automation.js +103 -0
  84. package/dist/src/pr-manager.js +318 -0
  85. package/dist/src/presenter/index.js +209 -0
  86. package/dist/src/prompt-builder.js +173 -0
  87. package/dist/src/quality-gates/config-loader.js +196 -0
  88. package/dist/src/quality-gates/default-config.js +243 -0
  89. package/dist/src/quality-gates/file-utils.js +152 -0
  90. package/dist/src/quality-gates/gate-runner.js +171 -0
  91. package/dist/src/quality-gates/gates/accessibility.js +355 -0
  92. package/dist/src/quality-gates/gates/duplicate-blocks.js +144 -0
  93. package/dist/src/quality-gates/gates/hardcoded-config.js +52 -0
  94. package/dist/src/quality-gates/gates/readme-claims.js +62 -0
  95. package/dist/src/quality-gates/gates/runtime-checks.js +265 -0
  96. package/dist/src/quality-gates/gates/scaffold-defaults.js +108 -0
  97. package/dist/src/quality-gates/gates/test-coverage.js +240 -0
  98. package/dist/src/quality-gates/gates/test-file-protection.js +85 -0
  99. package/dist/src/quality-gates/gates/test-isolation.js +74 -0
  100. package/dist/src/quality-gates/index.js +17 -0
  101. package/dist/src/quality-gates/registry.js +205 -0
  102. package/dist/src/quality-gates/types.js +2 -0
  103. package/dist/src/quick-fix-mode.js +302 -0
  104. package/dist/src/recipe-loader.js +122 -0
  105. package/dist/src/repair-agent.js +396 -0
  106. package/dist/src/repo-analyzer.js +342 -0
  107. package/dist/src/report-generator.js +146 -0
  108. package/dist/src/report-renderer.js +90 -0
  109. package/dist/src/requirement-filter.js +52 -0
  110. package/dist/src/sarif-formatter.js +140 -0
  111. package/dist/src/scheduling/dependency-analyzer.js +178 -0
  112. package/dist/src/scheduling/work-stealing-queue.js +123 -0
  113. package/dist/src/secret-redactor.js +90 -0
  114. package/dist/src/session-executor.js +587 -0
  115. package/dist/src/session-manager.js +221 -0
  116. package/dist/src/share/transcript-verification.js +128 -0
  117. package/dist/src/share-parser.js +477 -0
  118. package/dist/src/spinner.js +146 -0
  119. package/dist/src/step-runner.js +365 -0
  120. package/dist/src/swarm-orchestrator.js +686 -0
  121. package/dist/src/task-classifier.js +134 -0
  122. package/dist/src/test-command-discovery.js +123 -0
  123. package/dist/src/text-similarity.js +24 -0
  124. package/dist/src/tier-maps.js +158 -0
  125. package/dist/src/types.js +5 -0
  126. package/dist/src/url-shortener.js +135 -0
  127. package/dist/src/verification/attestation.js +166 -0
  128. package/dist/src/verification/cheat-detector.js +240 -0
  129. package/dist/src/verification/command-runner.js +70 -0
  130. package/dist/src/verification/composite-score.js +157 -0
  131. package/dist/src/verification/cosign-attestation.js +137 -0
  132. package/dist/src/verification/diff-analysis.js +89 -0
  133. package/dist/src/verification/differential-gate.js +173 -0
  134. package/dist/src/verification/index.js +41 -0
  135. package/dist/src/verification/mutation-gate.js +242 -0
  136. package/dist/src/verification/property-gate.js +201 -0
  137. package/dist/src/verification/test-synthesizer.js +243 -0
  138. package/dist/src/verifier/outcome-checks.js +396 -0
  139. package/dist/src/verifier/transcript-checks.js +207 -0
  140. package/dist/src/verifier/verification-reporters.js +148 -0
  141. package/dist/src/verifier-engine.js +491 -0
  142. package/dist/src/wave-resizer.js +131 -0
  143. package/dist/src/wave-scheduler.js +44 -0
  144. package/dist/src/worktree-manager.js +368 -0
  145. package/dist/src/worktree-reserved-paths.js +97 -0
  146. package/package.json +80 -0
  147. package/plugin/agents/.gitkeep +0 -0
  148. package/plugin/agents/backend_master.agent.md +72 -0
  149. package/plugin/agents/devops_pro.agent.md +45 -0
  150. package/plugin/agents/frontend_expert.agent.md +59 -0
  151. package/plugin/agents/integrator_finalizer.agent.md +44 -0
  152. package/plugin/agents/meta_reviewer.agent.md +44 -0
  153. package/plugin/agents/security_auditor.agent.md +43 -0
  154. package/plugin/agents/tester_elite.agent.md +43 -0
  155. package/plugin/hooks/hooks.json +19 -0
  156. package/plugin/hooks/post-tool-use.json +9 -0
  157. package/plugin/hooks/pre-tool-use.json +9 -0
  158. package/plugin/plugin.json +47 -0
  159. package/plugin/skills/swarm-gates/SKILL.md +53 -0
  160. package/plugin/skills/swarm-orchestrate/SKILL.md +34 -0
  161. package/plugin/skills/swarm-verify/SKILL.md +44 -0
  162. package/templates/README.md +35 -0
  163. package/templates/cli-tool.json +31 -0
  164. package/templates/fullstack.json +52 -0
  165. package/templates/library.json +38 -0
  166. package/templates/react-app.json +45 -0
  167. package/templates/recipes/add-api-docs.json +35 -0
  168. package/templates/recipes/add-auth.json +47 -0
  169. package/templates/recipes/add-ci.json +39 -0
  170. package/templates/recipes/add-tests.json +39 -0
  171. package/templates/recipes/migrate-to-ts.json +42 -0
  172. package/templates/recipes/refactor-modularize.json +44 -0
  173. package/templates/recipes/security-audit.json +35 -0
  174. 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;