attocode 0.2.3 → 0.2.4

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 (327) hide show
  1. package/CHANGELOG.md +67 -1
  2. package/README.md +65 -5
  3. package/dist/src/adapters.d.ts.map +1 -1
  4. package/dist/src/adapters.js +15 -11
  5. package/dist/src/adapters.js.map +1 -1
  6. package/dist/src/agent.d.ts +38 -98
  7. package/dist/src/agent.d.ts.map +1 -1
  8. package/dist/src/agent.js +505 -2892
  9. package/dist/src/agent.js.map +1 -1
  10. package/dist/src/cli.d.ts.map +1 -1
  11. package/dist/src/cli.js +2 -1
  12. package/dist/src/cli.js.map +1 -1
  13. package/dist/src/commands/handler.d.ts.map +1 -1
  14. package/dist/src/commands/handler.js +11 -3
  15. package/dist/src/commands/handler.js.map +1 -1
  16. package/dist/src/commands/init-commands.d.ts.map +1 -1
  17. package/dist/src/commands/init-commands.js +16 -1
  18. package/dist/src/commands/init-commands.js.map +1 -1
  19. package/dist/src/commands/init.d.ts.map +1 -1
  20. package/dist/src/commands/init.js +31 -0
  21. package/dist/src/commands/init.js.map +1 -1
  22. package/dist/src/config/base-types.d.ts +45 -0
  23. package/dist/src/config/base-types.d.ts.map +1 -0
  24. package/dist/src/config/base-types.js +9 -0
  25. package/dist/src/config/base-types.js.map +1 -0
  26. package/dist/src/config/config-manager.d.ts +35 -0
  27. package/dist/src/config/config-manager.d.ts.map +1 -0
  28. package/dist/src/config/config-manager.js +108 -0
  29. package/dist/src/config/config-manager.js.map +1 -0
  30. package/dist/src/config/index.d.ts +4 -0
  31. package/dist/src/config/index.d.ts.map +1 -0
  32. package/dist/src/config/index.js +3 -0
  33. package/dist/src/config/index.js.map +1 -0
  34. package/dist/src/config/schema.d.ts +1546 -0
  35. package/dist/src/config/schema.d.ts.map +1 -0
  36. package/dist/src/config/schema.js +268 -0
  37. package/dist/src/config/schema.js.map +1 -0
  38. package/dist/src/config.d.ts +4 -1
  39. package/dist/src/config.d.ts.map +1 -1
  40. package/dist/src/config.js +8 -12
  41. package/dist/src/config.js.map +1 -1
  42. package/dist/src/core/agent-state-machine.d.ts +131 -0
  43. package/dist/src/core/agent-state-machine.d.ts.map +1 -0
  44. package/dist/src/core/agent-state-machine.js +302 -0
  45. package/dist/src/core/agent-state-machine.js.map +1 -0
  46. package/dist/src/core/base-manager.d.ts +79 -0
  47. package/dist/src/core/base-manager.d.ts.map +1 -0
  48. package/dist/src/core/base-manager.js +170 -0
  49. package/dist/src/core/base-manager.js.map +1 -0
  50. package/dist/src/core/completion-analyzer.d.ts +15 -0
  51. package/dist/src/core/completion-analyzer.d.ts.map +1 -0
  52. package/dist/src/core/completion-analyzer.js +53 -0
  53. package/dist/src/core/completion-analyzer.js.map +1 -0
  54. package/dist/src/core/execution-loop.d.ts +46 -0
  55. package/dist/src/core/execution-loop.d.ts.map +1 -0
  56. package/dist/src/core/execution-loop.js +1258 -0
  57. package/dist/src/core/execution-loop.js.map +1 -0
  58. package/dist/src/core/index.d.ts +7 -0
  59. package/dist/src/core/index.d.ts.map +1 -1
  60. package/dist/src/core/index.js +9 -0
  61. package/dist/src/core/index.js.map +1 -1
  62. package/dist/src/core/process-handlers.d.ts.map +1 -1
  63. package/dist/src/core/process-handlers.js +14 -0
  64. package/dist/src/core/process-handlers.js.map +1 -1
  65. package/dist/src/core/protocol/types.d.ts +12 -12
  66. package/dist/src/core/response-handler.d.ts +16 -0
  67. package/dist/src/core/response-handler.d.ts.map +1 -0
  68. package/dist/src/core/response-handler.js +234 -0
  69. package/dist/src/core/response-handler.js.map +1 -0
  70. package/dist/src/core/subagent-spawner.d.ts +43 -0
  71. package/dist/src/core/subagent-spawner.d.ts.map +1 -0
  72. package/dist/src/core/subagent-spawner.js +966 -0
  73. package/dist/src/core/subagent-spawner.js.map +1 -0
  74. package/dist/src/core/tool-executor.d.ts +59 -0
  75. package/dist/src/core/tool-executor.d.ts.map +1 -0
  76. package/dist/src/core/tool-executor.js +677 -0
  77. package/dist/src/core/tool-executor.js.map +1 -0
  78. package/dist/src/core/types.d.ts +133 -0
  79. package/dist/src/core/types.d.ts.map +1 -0
  80. package/dist/src/core/types.js +12 -0
  81. package/dist/src/core/types.js.map +1 -0
  82. package/dist/src/defaults.d.ts +2 -2
  83. package/dist/src/defaults.d.ts.map +1 -1
  84. package/dist/src/defaults.js +29 -1
  85. package/dist/src/defaults.js.map +1 -1
  86. package/dist/src/integrations/auto-compaction.d.ts.map +1 -1
  87. package/dist/src/integrations/auto-compaction.js +3 -2
  88. package/dist/src/integrations/auto-compaction.js.map +1 -1
  89. package/dist/src/integrations/budget-pool.d.ts +7 -0
  90. package/dist/src/integrations/budget-pool.d.ts.map +1 -1
  91. package/dist/src/integrations/budget-pool.js +43 -0
  92. package/dist/src/integrations/budget-pool.js.map +1 -1
  93. package/dist/src/integrations/codebase-ast.d.ts +52 -0
  94. package/dist/src/integrations/codebase-ast.d.ts.map +1 -0
  95. package/dist/src/integrations/codebase-ast.js +457 -0
  96. package/dist/src/integrations/codebase-ast.js.map +1 -0
  97. package/dist/src/integrations/codebase-context.d.ts +18 -0
  98. package/dist/src/integrations/codebase-context.d.ts.map +1 -1
  99. package/dist/src/integrations/codebase-context.js +197 -17
  100. package/dist/src/integrations/codebase-context.js.map +1 -1
  101. package/dist/src/integrations/compaction.d.ts.map +1 -1
  102. package/dist/src/integrations/compaction.js +14 -6
  103. package/dist/src/integrations/compaction.js.map +1 -1
  104. package/dist/src/integrations/context-engineering.d.ts +8 -0
  105. package/dist/src/integrations/context-engineering.d.ts.map +1 -1
  106. package/dist/src/integrations/context-engineering.js +19 -0
  107. package/dist/src/integrations/context-engineering.js.map +1 -1
  108. package/dist/src/integrations/economics.d.ts +25 -1
  109. package/dist/src/integrations/economics.d.ts.map +1 -1
  110. package/dist/src/integrations/economics.js +217 -38
  111. package/dist/src/integrations/economics.js.map +1 -1
  112. package/dist/src/integrations/edit-validator.d.ts +30 -0
  113. package/dist/src/integrations/edit-validator.d.ts.map +1 -0
  114. package/dist/src/integrations/edit-validator.js +85 -0
  115. package/dist/src/integrations/edit-validator.js.map +1 -0
  116. package/dist/src/integrations/file-cache.d.ts +7 -0
  117. package/dist/src/integrations/file-cache.d.ts.map +1 -1
  118. package/dist/src/integrations/file-cache.js +54 -0
  119. package/dist/src/integrations/file-cache.js.map +1 -1
  120. package/dist/src/integrations/health-check.d.ts.map +1 -1
  121. package/dist/src/integrations/health-check.js +3 -2
  122. package/dist/src/integrations/health-check.js.map +1 -1
  123. package/dist/src/integrations/hierarchical-config.d.ts +3 -0
  124. package/dist/src/integrations/hierarchical-config.d.ts.map +1 -1
  125. package/dist/src/integrations/hierarchical-config.js +3 -0
  126. package/dist/src/integrations/hierarchical-config.js.map +1 -1
  127. package/dist/src/integrations/hooks.d.ts +2 -0
  128. package/dist/src/integrations/hooks.d.ts.map +1 -1
  129. package/dist/src/integrations/hooks.js +99 -15
  130. package/dist/src/integrations/hooks.js.map +1 -1
  131. package/dist/src/integrations/index.d.ts +7 -0
  132. package/dist/src/integrations/index.d.ts.map +1 -1
  133. package/dist/src/integrations/index.js +9 -1
  134. package/dist/src/integrations/index.js.map +1 -1
  135. package/dist/src/integrations/logger.d.ts +104 -0
  136. package/dist/src/integrations/logger.d.ts.map +1 -0
  137. package/dist/src/integrations/logger.js +219 -0
  138. package/dist/src/integrations/logger.js.map +1 -0
  139. package/dist/src/integrations/lsp.d.ts.map +1 -1
  140. package/dist/src/integrations/lsp.js +5 -4
  141. package/dist/src/integrations/lsp.js.map +1 -1
  142. package/dist/src/integrations/mcp-client.d.ts.map +1 -1
  143. package/dist/src/integrations/mcp-client.js +8 -7
  144. package/dist/src/integrations/mcp-client.js.map +1 -1
  145. package/dist/src/integrations/observability.d.ts.map +1 -1
  146. package/dist/src/integrations/observability.js +5 -4
  147. package/dist/src/integrations/observability.js.map +1 -1
  148. package/dist/src/integrations/openrouter-pricing.d.ts.map +1 -1
  149. package/dist/src/integrations/openrouter-pricing.js +4 -3
  150. package/dist/src/integrations/openrouter-pricing.js.map +1 -1
  151. package/dist/src/integrations/persistence.d.ts.map +1 -1
  152. package/dist/src/integrations/persistence.js +5 -4
  153. package/dist/src/integrations/persistence.js.map +1 -1
  154. package/dist/src/integrations/planning.d.ts.map +1 -1
  155. package/dist/src/integrations/planning.js +5 -4
  156. package/dist/src/integrations/planning.js.map +1 -1
  157. package/dist/src/integrations/retry.d.ts +1 -0
  158. package/dist/src/integrations/retry.d.ts.map +1 -1
  159. package/dist/src/integrations/retry.js.map +1 -1
  160. package/dist/src/integrations/routing.d.ts.map +1 -1
  161. package/dist/src/integrations/routing.js +2 -1
  162. package/dist/src/integrations/routing.js.map +1 -1
  163. package/dist/src/integrations/safety.d.ts.map +1 -1
  164. package/dist/src/integrations/safety.js +13 -13
  165. package/dist/src/integrations/safety.js.map +1 -1
  166. package/dist/src/integrations/sandbox/docker.d.ts.map +1 -1
  167. package/dist/src/integrations/sandbox/docker.js +2 -1
  168. package/dist/src/integrations/sandbox/docker.js.map +1 -1
  169. package/dist/src/integrations/sandbox/index.d.ts.map +1 -1
  170. package/dist/src/integrations/sandbox/index.js +5 -4
  171. package/dist/src/integrations/sandbox/index.js.map +1 -1
  172. package/dist/src/integrations/session-store.d.ts +1 -0
  173. package/dist/src/integrations/session-store.d.ts.map +1 -1
  174. package/dist/src/integrations/session-store.js +1 -0
  175. package/dist/src/integrations/session-store.js.map +1 -1
  176. package/dist/src/integrations/shared-blackboard.d.ts +3 -0
  177. package/dist/src/integrations/shared-blackboard.d.ts.map +1 -1
  178. package/dist/src/integrations/shared-blackboard.js +47 -0
  179. package/dist/src/integrations/shared-blackboard.js.map +1 -1
  180. package/dist/src/integrations/smart-decomposer.d.ts +27 -0
  181. package/dist/src/integrations/smart-decomposer.d.ts.map +1 -1
  182. package/dist/src/integrations/smart-decomposer.js +414 -30
  183. package/dist/src/integrations/smart-decomposer.js.map +1 -1
  184. package/dist/src/integrations/sqlite-store.d.ts +2 -0
  185. package/dist/src/integrations/sqlite-store.d.ts.map +1 -1
  186. package/dist/src/integrations/sqlite-store.js +18 -6
  187. package/dist/src/integrations/sqlite-store.js.map +1 -1
  188. package/dist/src/integrations/swarm/failure-classifier.d.ts +11 -0
  189. package/dist/src/integrations/swarm/failure-classifier.d.ts.map +1 -0
  190. package/dist/src/integrations/swarm/failure-classifier.js +95 -0
  191. package/dist/src/integrations/swarm/failure-classifier.js.map +1 -0
  192. package/dist/src/integrations/swarm/model-selector.d.ts.map +1 -1
  193. package/dist/src/integrations/swarm/model-selector.js +2 -1
  194. package/dist/src/integrations/swarm/model-selector.js.map +1 -1
  195. package/dist/src/integrations/swarm/swarm-config-loader.d.ts +8 -0
  196. package/dist/src/integrations/swarm/swarm-config-loader.d.ts.map +1 -1
  197. package/dist/src/integrations/swarm/swarm-config-loader.js +95 -0
  198. package/dist/src/integrations/swarm/swarm-config-loader.js.map +1 -1
  199. package/dist/src/integrations/swarm/swarm-event-bridge.d.ts +74 -0
  200. package/dist/src/integrations/swarm/swarm-event-bridge.d.ts.map +1 -1
  201. package/dist/src/integrations/swarm/swarm-event-bridge.js +37 -0
  202. package/dist/src/integrations/swarm/swarm-event-bridge.js.map +1 -1
  203. package/dist/src/integrations/swarm/swarm-events.d.ts +3 -0
  204. package/dist/src/integrations/swarm/swarm-events.d.ts.map +1 -1
  205. package/dist/src/integrations/swarm/swarm-events.js +1 -1
  206. package/dist/src/integrations/swarm/swarm-events.js.map +1 -1
  207. package/dist/src/integrations/swarm/swarm-orchestrator.d.ts +23 -0
  208. package/dist/src/integrations/swarm/swarm-orchestrator.d.ts.map +1 -1
  209. package/dist/src/integrations/swarm/swarm-orchestrator.js +530 -55
  210. package/dist/src/integrations/swarm/swarm-orchestrator.js.map +1 -1
  211. package/dist/src/integrations/swarm/swarm-state-store.d.ts +4 -1
  212. package/dist/src/integrations/swarm/swarm-state-store.d.ts.map +1 -1
  213. package/dist/src/integrations/swarm/swarm-state-store.js +8 -1
  214. package/dist/src/integrations/swarm/swarm-state-store.js.map +1 -1
  215. package/dist/src/integrations/swarm/task-queue.d.ts +10 -0
  216. package/dist/src/integrations/swarm/task-queue.d.ts.map +1 -1
  217. package/dist/src/integrations/swarm/task-queue.js +36 -1
  218. package/dist/src/integrations/swarm/task-queue.js.map +1 -1
  219. package/dist/src/integrations/swarm/types.d.ts +41 -0
  220. package/dist/src/integrations/swarm/types.d.ts.map +1 -1
  221. package/dist/src/integrations/swarm/types.js +9 -0
  222. package/dist/src/integrations/swarm/types.js.map +1 -1
  223. package/dist/src/integrations/swarm/worker-pool.d.ts +12 -2
  224. package/dist/src/integrations/swarm/worker-pool.d.ts.map +1 -1
  225. package/dist/src/integrations/swarm/worker-pool.js +53 -4
  226. package/dist/src/integrations/swarm/worker-pool.js.map +1 -1
  227. package/dist/src/integrations/task-manager.d.ts +33 -1
  228. package/dist/src/integrations/task-manager.d.ts.map +1 -1
  229. package/dist/src/integrations/task-manager.js +78 -4
  230. package/dist/src/integrations/task-manager.js.map +1 -1
  231. package/dist/src/main.js +83 -32
  232. package/dist/src/main.js.map +1 -1
  233. package/dist/src/modes/repl.d.ts.map +1 -1
  234. package/dist/src/modes/repl.js +40 -8
  235. package/dist/src/modes/repl.js.map +1 -1
  236. package/dist/src/modes/tui.d.ts.map +1 -1
  237. package/dist/src/modes/tui.js +36 -6
  238. package/dist/src/modes/tui.js.map +1 -1
  239. package/dist/src/observability/tracer.d.ts.map +1 -1
  240. package/dist/src/observability/tracer.js +2 -1
  241. package/dist/src/observability/tracer.js.map +1 -1
  242. package/dist/src/persistence/schema.d.ts.map +1 -1
  243. package/dist/src/persistence/schema.js +11 -0
  244. package/dist/src/persistence/schema.js.map +1 -1
  245. package/dist/src/providers/adapters/anthropic.d.ts.map +1 -1
  246. package/dist/src/providers/adapters/anthropic.js +3 -2
  247. package/dist/src/providers/adapters/anthropic.js.map +1 -1
  248. package/dist/src/providers/adapters/openai.d.ts.map +1 -1
  249. package/dist/src/providers/adapters/openai.js +3 -2
  250. package/dist/src/providers/adapters/openai.js.map +1 -1
  251. package/dist/src/providers/adapters/openrouter.d.ts.map +1 -1
  252. package/dist/src/providers/adapters/openrouter.js +11 -11
  253. package/dist/src/providers/adapters/openrouter.js.map +1 -1
  254. package/dist/src/providers/circuit-breaker.d.ts +1 -0
  255. package/dist/src/providers/circuit-breaker.d.ts.map +1 -1
  256. package/dist/src/providers/circuit-breaker.js.map +1 -1
  257. package/dist/src/providers/provider.d.ts.map +1 -1
  258. package/dist/src/providers/provider.js +2 -1
  259. package/dist/src/providers/provider.js.map +1 -1
  260. package/dist/src/providers/resilient-provider.d.ts.map +1 -1
  261. package/dist/src/providers/resilient-provider.js +2 -1
  262. package/dist/src/providers/resilient-provider.js.map +1 -1
  263. package/dist/src/session-picker.d.ts.map +1 -1
  264. package/dist/src/session-picker.js +40 -5
  265. package/dist/src/session-picker.js.map +1 -1
  266. package/dist/src/shared/budget-tracker.d.ts +65 -0
  267. package/dist/src/shared/budget-tracker.d.ts.map +1 -0
  268. package/dist/src/shared/budget-tracker.js +128 -0
  269. package/dist/src/shared/budget-tracker.js.map +1 -0
  270. package/dist/src/shared/context-engine.d.ts +64 -0
  271. package/dist/src/shared/context-engine.d.ts.map +1 -0
  272. package/dist/src/shared/context-engine.js +117 -0
  273. package/dist/src/shared/context-engine.js.map +1 -0
  274. package/dist/src/shared/index.d.ts +12 -0
  275. package/dist/src/shared/index.d.ts.map +1 -0
  276. package/dist/src/shared/index.js +12 -0
  277. package/dist/src/shared/index.js.map +1 -0
  278. package/dist/src/shared/persistence.d.ts +57 -0
  279. package/dist/src/shared/persistence.d.ts.map +1 -0
  280. package/dist/src/shared/persistence.js +168 -0
  281. package/dist/src/shared/persistence.js.map +1 -0
  282. package/dist/src/shared/shared-context-state.d.ts +89 -0
  283. package/dist/src/shared/shared-context-state.d.ts.map +1 -0
  284. package/dist/src/shared/shared-context-state.js +175 -0
  285. package/dist/src/shared/shared-context-state.js.map +1 -0
  286. package/dist/src/shared/shared-economics-state.d.ts +61 -0
  287. package/dist/src/shared/shared-economics-state.d.ts.map +1 -0
  288. package/dist/src/shared/shared-economics-state.js +100 -0
  289. package/dist/src/shared/shared-economics-state.js.map +1 -0
  290. package/dist/src/tools/bash.d.ts +3 -3
  291. package/dist/src/tools/bash.d.ts.map +1 -1
  292. package/dist/src/tools/bash.js +2 -1
  293. package/dist/src/tools/bash.js.map +1 -1
  294. package/dist/src/tools/file.d.ts +3 -3
  295. package/dist/src/tools/permission.d.ts.map +1 -1
  296. package/dist/src/tools/permission.js +6 -5
  297. package/dist/src/tools/permission.js.map +1 -1
  298. package/dist/src/tools/types.d.ts +1 -0
  299. package/dist/src/tools/types.d.ts.map +1 -1
  300. package/dist/src/tools/types.js.map +1 -1
  301. package/dist/src/tracing/trace-collector.d.ts +125 -0
  302. package/dist/src/tracing/trace-collector.d.ts.map +1 -1
  303. package/dist/src/tracing/trace-collector.js +112 -5
  304. package/dist/src/tracing/trace-collector.js.map +1 -1
  305. package/dist/src/tracing/types.d.ts +96 -1
  306. package/dist/src/tracing/types.d.ts.map +1 -1
  307. package/dist/src/tracing/types.js.map +1 -1
  308. package/dist/src/tricks/failure-evidence.d.ts.map +1 -1
  309. package/dist/src/tricks/failure-evidence.js +2 -1
  310. package/dist/src/tricks/failure-evidence.js.map +1 -1
  311. package/dist/src/tui/app.d.ts +13 -0
  312. package/dist/src/tui/app.d.ts.map +1 -1
  313. package/dist/src/tui/app.js +129 -15
  314. package/dist/src/tui/app.js.map +1 -1
  315. package/dist/src/tui/components/ErrorBoundary.d.ts.map +1 -1
  316. package/dist/src/tui/components/ErrorBoundary.js +3 -2
  317. package/dist/src/tui/components/ErrorBoundary.js.map +1 -1
  318. package/dist/src/tui/event-display.d.ts.map +1 -1
  319. package/dist/src/tui/event-display.js +36 -62
  320. package/dist/src/tui/event-display.js.map +1 -1
  321. package/dist/src/tui/index.d.ts +4 -0
  322. package/dist/src/tui/index.d.ts.map +1 -1
  323. package/dist/src/tui/index.js +17 -0
  324. package/dist/src/tui/index.js.map +1 -1
  325. package/dist/src/types.d.ts +143 -1
  326. package/dist/src/types.d.ts.map +1 -1
  327. package/package.json +18 -3
@@ -27,6 +27,10 @@ import { createSwarmWorkerPool } from './worker-pool.js';
27
27
  import { evaluateWorkerOutput, runPreFlightChecks, checkArtifacts, checkArtifactsEnhanced, runConcreteChecks } from './swarm-quality-gate.js';
28
28
  import { ModelHealthTracker, selectAlternativeModel } from './model-selector.js';
29
29
  import { SwarmStateStore } from './swarm-state-store.js';
30
+ import { createSharedContextState } from '../../shared/shared-context-state.js';
31
+ import { createSharedEconomicsState } from '../../shared/shared-economics-state.js';
32
+ import { createSharedContextEngine } from '../../shared/context-engine.js';
33
+ import { classifySwarmFailure } from './failure-classifier.js';
30
34
  // ─── Hollow Completion Detection ──────────────────────────────────────────
31
35
  /**
32
36
  * V11: Hollow completion detection — catches empty completions AND "success" with failure language.
@@ -45,6 +49,35 @@ const BOILERPLATE_INDICATORS = [
45
49
  'the task has been completed', 'done', 'completed', 'finished',
46
50
  'no issues found', 'everything looks good', 'all tasks completed',
47
51
  ];
52
+ function hasFutureIntentLanguage(content) {
53
+ const trimmed = content.trim();
54
+ if (!trimmed)
55
+ return false;
56
+ const lower = trimmed.toLowerCase();
57
+ const completionSignals = /\b(done|completed|finished|created|saved|wrote|implemented|fixed|updated|added)\b/;
58
+ if (completionSignals.test(lower))
59
+ return false;
60
+ const futureIntentPatterns = [
61
+ /\b(i\s+will|i'll|let me)\s+(create|write|save|update|modify|fix|add|edit|implement|change|run|execute|build|continue)\b/,
62
+ /\b(i\s+need to|i\s+should|i\s+can)\s+(create|write|update|modify|fix|add|edit|implement|continue)\b/,
63
+ /\b(next step|remaining work|still need|to be done)\b/,
64
+ /\b(i am going to|i'm going to)\b/,
65
+ ];
66
+ return futureIntentPatterns.some(p => p.test(lower));
67
+ }
68
+ function repoLooksUnscaffolded(baseDir) {
69
+ try {
70
+ const packageJson = path.join(baseDir, 'package.json');
71
+ const srcDir = path.join(baseDir, 'src');
72
+ if (!fs.existsSync(packageJson) && !fs.existsSync(srcDir)) {
73
+ return true;
74
+ }
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ return false;
80
+ }
48
81
  export function isHollowCompletion(spawnResult, taskType, swarmConfig) {
49
82
  // Timeout uses toolCalls === -1, not hollow
50
83
  if ((spawnResult.metrics.toolCalls ?? 0) === -1)
@@ -86,6 +119,10 @@ export class SwarmOrchestrator {
86
119
  config;
87
120
  provider;
88
121
  blackboard;
122
+ // Phase 3.1+3.2: Shared state for cross-worker learning
123
+ sharedContextState;
124
+ sharedEconomicsState;
125
+ sharedContextEngine;
89
126
  taskQueue;
90
127
  budgetPool;
91
128
  workerPool;
@@ -149,15 +186,28 @@ export class SwarmOrchestrator {
149
186
  this.spawnAgentFn = spawnAgentFn;
150
187
  this.healthTracker = new ModelHealthTracker();
151
188
  this.adaptiveStaggerMs = this.getStaggerMs();
189
+ // Phase 3.1+3.2: Shared context & economics for cross-worker learning
190
+ this.sharedContextState = createSharedContextState({
191
+ staticPrefix: 'You are a swarm worker agent.',
192
+ maxFailures: 100,
193
+ maxReferences: 200,
194
+ });
195
+ this.sharedEconomicsState = createSharedEconomicsState({
196
+ globalDoomLoopThreshold: 10,
197
+ });
198
+ this.sharedContextEngine = createSharedContextEngine(this.sharedContextState, {
199
+ maxFailuresInPrompt: 5,
200
+ includeInsights: true,
201
+ });
152
202
  this.taskQueue = createSwarmTaskQueue();
153
203
  this.budgetPool = createSwarmBudgetPool(this.config);
154
- this.workerPool = createSwarmWorkerPool(this.config, agentRegistry, spawnAgentFn, this.budgetPool, this.healthTracker);
204
+ this.workerPool = createSwarmWorkerPool(this.config, agentRegistry, spawnAgentFn, this.budgetPool, this.healthTracker, this.sharedContextEngine);
155
205
  // Initialize state store if persistence enabled
156
206
  if (this.config.enablePersistence) {
157
207
  this.stateStore = new SwarmStateStore(this.config.stateDir ?? '.agent/swarm-state', this.config.resumeSessionId);
158
208
  }
159
209
  // C1: Build LLM decompose function with explicit JSON schema
160
- const llmDecompose = async (task, _context) => {
210
+ const llmDecompose = async (task, context) => {
161
211
  // V7: Dynamically build the allowed type list from built-in + user-defined types
162
212
  const builtinTypes = ['research', 'analysis', 'design', 'implement', 'test', 'refactor', 'review', 'document', 'integrate', 'deploy', 'merge'];
163
213
  const customTypes = Object.keys(this.config.taskTypes ?? {}).filter(t => !builtinTypes.includes(t));
@@ -179,6 +229,26 @@ export class SwarmOrchestrator {
179
229
  }).join('\n');
180
230
  customTypeSection = `\n\nCustom task types available:\n${descriptions}\nUse these when their description matches the subtask's purpose.`;
181
231
  }
232
+ // Build codebase context section from repo map if available
233
+ let codebaseSection = '';
234
+ if (context.repoMap) {
235
+ const map = context.repoMap;
236
+ const topFiles = Array.from(map.chunks.values())
237
+ .sort((a, b) => b.importance - a.importance)
238
+ .slice(0, 30)
239
+ .map(c => ` - ${c.filePath} (${c.type}, ${c.tokenCount} tokens, importance: ${c.importance.toFixed(2)})`);
240
+ codebaseSection = `
241
+
242
+ CODEBASE STRUCTURE (${map.chunks.size} files, ${map.totalTokens} total tokens):
243
+ Entry points: ${map.entryPoints.slice(0, 5).join(', ')}
244
+ Core modules: ${map.coreModules.slice(0, 5).join(', ')}
245
+ Key files:
246
+ ${topFiles.join('\n')}
247
+
248
+ CRITICAL: Your subtasks MUST reference actual files from this codebase.
249
+ Do NOT invent new project scaffolding or create files that don't relate to the existing codebase.
250
+ Decompose the work based on what ALREADY EXISTS in the project.`;
251
+ }
182
252
  const systemPrompt = `You are a task decomposition expert. Break down the given task into well-defined subtasks with clear dependencies.
183
253
 
184
254
  CRITICAL: Dependencies MUST use zero-based integer indices referring to other subtasks in the array.
@@ -197,7 +267,7 @@ Respond with valid JSON matching this exact schema:
197
267
  ],
198
268
  "strategy": "sequential" | "parallel" | "hierarchical" | "adaptive" | "pipeline",
199
269
  "reasoning": "Brief explanation of why this decomposition was chosen"
200
- }${customTypeSection}
270
+ }${customTypeSection}${codebaseSection}
201
271
 
202
272
  EXAMPLE 1 — Research task (3 parallel research + 1 merge):
203
273
  {
@@ -234,12 +304,52 @@ Rules:
234
304
  { role: 'user', content: task },
235
305
  ], {
236
306
  model: this.config.orchestratorModel,
237
- maxTokens: 4000,
307
+ maxTokens: 16000,
238
308
  temperature: 0.3,
239
309
  });
240
310
  this.trackOrchestratorUsage(response, 'decompose');
241
311
  // Use parseDecompositionResponse which handles markdown code blocks and edge cases
242
- return parseDecompositionResponse(response.content);
312
+ const result = parseDecompositionResponse(response.content);
313
+ // If decomposition returned 0 subtasks, log diagnostics and retry with explicit JSON instruction
314
+ if (result.subtasks.length === 0) {
315
+ const snippet = response.content?.slice(0, 500) ?? '(empty response)';
316
+ const parseError = result.parseError ?? 'unknown';
317
+ this.errors.push({
318
+ phase: 'decomposition',
319
+ message: `LLM returned no subtasks. Parse error: ${parseError}. Response preview: ${snippet}`,
320
+ recovered: true,
321
+ });
322
+ this.emit({
323
+ type: 'swarm.orchestrator.decision',
324
+ decision: {
325
+ timestamp: Date.now(),
326
+ phase: 'decomposition',
327
+ decision: `Empty decomposition — retrying with explicit JSON instruction`,
328
+ reasoning: `Parse error: ${parseError}. Response preview (first 500 chars): ${snippet}`,
329
+ },
330
+ });
331
+ // Retry with explicit JSON instruction — don't include previous truncated response (wastes input tokens)
332
+ const retryResponse = await this.provider.chat([
333
+ { role: 'system', content: systemPrompt },
334
+ { role: 'user', content: `${task}\n\nIMPORTANT: Your previous attempt was truncated or could not be parsed (${parseError}). Return ONLY a raw JSON object with NO markdown formatting, NO explanation text, NO code fences. The JSON must have a "subtasks" array with at least 2 entries matching the schema above. Keep subtask descriptions concise to avoid truncation.` },
335
+ ], {
336
+ model: this.config.orchestratorModel,
337
+ maxTokens: 16000,
338
+ temperature: 0.2,
339
+ });
340
+ this.trackOrchestratorUsage(retryResponse, 'decompose-retry');
341
+ const retryResult = parseDecompositionResponse(retryResponse.content);
342
+ if (retryResult.subtasks.length === 0) {
343
+ const retrySnippet = retryResponse.content?.slice(0, 500) ?? '(empty response)';
344
+ this.errors.push({
345
+ phase: 'decomposition',
346
+ message: `Retry also returned no subtasks. Response preview: ${retrySnippet}`,
347
+ recovered: false,
348
+ });
349
+ }
350
+ return retryResult;
351
+ }
352
+ return result;
243
353
  };
244
354
  // Configure decomposer for swarm use
245
355
  const decomposer = createSmartDecomposer({
@@ -257,6 +367,18 @@ Rules:
257
367
  getBudgetPool() {
258
368
  return this.budgetPool;
259
369
  }
370
+ /** Get shared context state for cross-worker failure learning. */
371
+ getSharedContextState() {
372
+ return this.sharedContextState;
373
+ }
374
+ /** Get shared economics state for cross-worker doom loop aggregation. */
375
+ getSharedEconomicsState() {
376
+ return this.sharedEconomicsState;
377
+ }
378
+ /** Get shared context engine for cross-worker failure learning. */
379
+ getSharedContextEngine() {
380
+ return this.sharedContextEngine;
381
+ }
260
382
  /**
261
383
  * Subscribe to swarm events.
262
384
  */
@@ -276,8 +398,12 @@ Rules:
276
398
  try {
277
399
  listener(event);
278
400
  }
279
- catch {
280
- // Don't let listener errors break the orchestrator
401
+ catch (err) {
402
+ // Don't let listener errors break the orchestrator, but log for debugging
403
+ const msg = err instanceof Error ? err.message : String(err);
404
+ if (process.env.DEBUG) {
405
+ console.error(`[SwarmOrchestrator] Listener error on ${event.type}: ${msg}`);
406
+ }
281
407
  }
282
408
  }
283
409
  }
@@ -287,8 +413,12 @@ Rules:
287
413
  trackOrchestratorUsage(response, purpose) {
288
414
  if (!response.usage)
289
415
  return;
290
- const tokens = response.usage.total_tokens ?? ((response.usage.prompt_tokens ?? 0) + (response.usage.completion_tokens ?? 0));
291
- const cost = tokens * 0.000015; // ~$15/M tokens average for orchestrator models
416
+ // Handle both raw API fields (total_tokens, prompt_tokens, completion_tokens)
417
+ // and ChatResponse fields (inputTokens, outputTokens)
418
+ const input = response.usage.prompt_tokens ?? response.usage.inputTokens ?? 0;
419
+ const output = response.usage.completion_tokens ?? response.usage.outputTokens ?? 0;
420
+ const tokens = response.usage.total_tokens ?? (input + output);
421
+ const cost = response.usage.cost ?? tokens * 0.000015; // ~$15/M tokens average for orchestrator models
292
422
  this.orchestratorTokens += tokens;
293
423
  this.orchestratorCost += cost;
294
424
  this.orchestratorCalls++;
@@ -325,10 +455,26 @@ Rules:
325
455
  // Phase 1: Decompose
326
456
  this.currentPhase = 'decomposing';
327
457
  this.emit({ type: 'swarm.phase.progress', phase: 'decomposing', message: 'Decomposing task into subtasks...' });
328
- let decomposition = await this.decompose(task);
329
- if (!decomposition) {
458
+ const decomposeOutcome = await this.decompose(task);
459
+ if (!decomposeOutcome.result) {
330
460
  this.currentPhase = 'failed';
331
- return this.buildErrorResult('Decomposition failed — task may be too simple for swarm mode');
461
+ return this.buildErrorResult(`Decomposition failed: ${decomposeOutcome.failureReason}`);
462
+ }
463
+ let decomposition = decomposeOutcome.result;
464
+ // If repository is mostly empty, force a scaffold-first dependency chain
465
+ // so implementation tasks don't immediately fail on missing files.
466
+ if (repoLooksUnscaffolded(this.config.facts?.workingDirectory ?? process.cwd())) {
467
+ const scaffoldTask = decomposition.subtasks.find(st => /\b(scaffold|bootstrap|initialize|setup|set up|project scaffold)\b/i.test(st.description));
468
+ if (scaffoldTask) {
469
+ for (const subtask of decomposition.subtasks) {
470
+ if (subtask.id === scaffoldTask.id)
471
+ continue;
472
+ if (!subtask.dependencies.includes(scaffoldTask.id)) {
473
+ subtask.dependencies.push(scaffoldTask.id);
474
+ }
475
+ }
476
+ this.logDecision('scaffold-first', `Repo appears unscaffolded; enforcing scaffold task ${scaffoldTask.id} as prerequisite`, '');
477
+ }
332
478
  }
333
479
  // F5: Validate decomposition — check for cycles, invalid deps, granularity
334
480
  const validation = validateDecomposition(decomposition);
@@ -338,11 +484,12 @@ Rules:
338
484
  if (!validation.valid) {
339
485
  this.logDecision('decomposition-validation', `Invalid decomposition: ${validation.issues.join('; ')}`, 'Retrying...');
340
486
  // Retry decomposition once with feedback
341
- decomposition = await this.decompose(`${task}\n\nIMPORTANT: Previous decomposition was invalid: ${validation.issues.join('. ')}. Fix these issues.`);
342
- if (!decomposition) {
487
+ const retryOutcome = await this.decompose(`${task}\n\nIMPORTANT: Previous decomposition was invalid: ${validation.issues.join('. ')}. Fix these issues.`);
488
+ if (!retryOutcome.result) {
343
489
  this.currentPhase = 'failed';
344
490
  return this.buildErrorResult(`Decomposition validation failed: ${validation.issues.join('; ')}`);
345
491
  }
492
+ decomposition = retryOutcome.result;
346
493
  const retryValidation = validateDecomposition(decomposition);
347
494
  if (!retryValidation.valid) {
348
495
  this.logDecision('decomposition-validation', `Retry still invalid: ${retryValidation.issues.join('; ')}`, 'Proceeding anyway');
@@ -463,9 +610,15 @@ Rules:
463
610
  this.checkpoint('final');
464
611
  const hasArtifacts = (this.artifactInventory?.totalFiles ?? 0) > 0;
465
612
  this.emit({ type: 'swarm.complete', stats: executionStats, errors: this.errors, artifactInventory: this.artifactInventory });
613
+ // Success requires completing at least 70% of tasks (not just > 0)
614
+ const completionRatio = executionStats.totalTasks > 0
615
+ ? executionStats.completedTasks / executionStats.totalTasks
616
+ : 0;
617
+ const isSuccess = completionRatio >= 0.7;
618
+ const isPartialSuccess = !isSuccess && executionStats.completedTasks > 0;
466
619
  return {
467
- success: executionStats.completedTasks > 0,
468
- partialSuccess: !executionStats.completedTasks && hasArtifacts,
620
+ success: isSuccess,
621
+ partialSuccess: isPartialSuccess || (!executionStats.completedTasks && hasArtifacts),
469
622
  partialFailure: executionStats.failedTasks > 0,
470
623
  synthesisResult: synthesisResult ?? undefined,
471
624
  artifactInventory: this.artifactInventory,
@@ -495,33 +648,234 @@ Rules:
495
648
  */
496
649
  async decompose(task) {
497
650
  try {
498
- const result = await this._decomposer.decompose(task);
651
+ const repoMap = this.config.codebaseContext?.getRepoMap() ?? undefined;
652
+ const result = await this._decomposer.decompose(task, {
653
+ repoMap,
654
+ });
499
655
  if (result.subtasks.length < 2) {
500
- // Too simple for swarm mode
501
- return null;
656
+ const reason = result.subtasks.length === 0
657
+ ? `Decomposition produced 0 subtasks (model: ${this.config.orchestratorModel}).`
658
+ : `Decomposition produced only ${result.subtasks.length} subtask — too few for swarm mode.`;
659
+ this.logDecision('decomposition', `Insufficient subtasks: ${result.subtasks.length}`, reason);
660
+ try {
661
+ const lastResortResult = await this.lastResortDecompose(task);
662
+ if (lastResortResult && lastResortResult.subtasks.length >= 2) {
663
+ this.logDecision('decomposition', `Last-resort decomposition succeeded: ${lastResortResult.subtasks.length} subtasks`, 'Recovered from insufficient primary decomposition');
664
+ return { result: lastResortResult };
665
+ }
666
+ }
667
+ catch (error) {
668
+ this.logDecision('decomposition', 'Last-resort decomposition failed after insufficient primary decomposition', error.message);
669
+ }
670
+ const fallback = this.buildEmergencyDecomposition(task, reason);
671
+ this.emit({
672
+ type: 'swarm.phase.progress',
673
+ phase: 'decomposing',
674
+ message: `Using emergency decomposition fallback (${this.classifyDecompositionFailure(reason)})`,
675
+ });
676
+ this.logDecision('decomposition', `Using emergency scaffold decomposition: ${fallback.subtasks.length} subtasks`, 'Swarm will continue with deterministic fallback tasks');
677
+ return { result: fallback };
502
678
  }
503
- // Reject heuristic fallback the generic 3-task chain is worse than aborting
679
+ // Non-LLM result means decomposer fell back to heuristic mode.
680
+ // Prefer a simplified LLM decomposition, but continue with heuristic fallback when needed.
504
681
  if (!result.metadata.llmAssisted) {
505
- this.logDecision('decomposition', 'Rejected heuristic fallback DAG', 'LLM decomposition failed after retries. Heuristic DAG is not useful.');
506
- return null;
682
+ this.logDecision('decomposition', 'Heuristic decomposition detected attempting last-resort simplified LLM decomposition', `Model: ${this.config.orchestratorModel}`);
683
+ try {
684
+ const lastResortResult = await this.lastResortDecompose(task);
685
+ if (lastResortResult && lastResortResult.subtasks.length >= 2) {
686
+ this.logDecision('decomposition', `Last-resort decomposition succeeded: ${lastResortResult.subtasks.length} subtasks`, 'Simplified prompt worked');
687
+ return { result: lastResortResult };
688
+ }
689
+ }
690
+ catch (error) {
691
+ this.logDecision('decomposition', 'Last-resort decomposition also failed', error.message);
692
+ }
693
+ this.logDecision('decomposition', `Continuing with heuristic decomposition: ${result.subtasks.length} subtasks`, 'Fallback is acceptable; do not abort swarm');
694
+ this.emit({
695
+ type: 'swarm.phase.progress',
696
+ phase: 'decomposing',
697
+ message: `Continuing with heuristic decomposition (${this.classifyDecompositionFailure('heuristic fallback')})`,
698
+ });
699
+ return { result };
507
700
  }
508
701
  // Flat-DAG detection: warn when all tasks land in wave 0 with no dependencies
509
702
  const hasAnyDependency = result.subtasks.some(s => s.dependencies.length > 0);
510
703
  if (!hasAnyDependency && result.subtasks.length >= 3) {
511
704
  this.logDecision('decomposition', `Flat DAG: ${result.subtasks.length} tasks, zero dependencies`, 'All tasks will execute in wave 0 without ordering');
512
705
  }
513
- return result;
706
+ return { result };
514
707
  }
515
708
  catch (error) {
709
+ const message = error.message;
516
710
  this.errors.push({
517
711
  phase: 'decomposition',
518
- message: error.message,
519
- recovered: false,
712
+ message,
713
+ recovered: true,
520
714
  });
521
- this.emit({ type: 'swarm.error', error: error.message, phase: 'decomposition' });
522
- return null;
715
+ const fallback = this.buildEmergencyDecomposition(task, `Decomposition threw an error: ${message}`);
716
+ this.emit({
717
+ type: 'swarm.phase.progress',
718
+ phase: 'decomposing',
719
+ message: `Decomposition fallback due to ${this.classifyDecompositionFailure(message)}`,
720
+ });
721
+ this.logDecision('decomposition', `Decomposition threw error; using emergency scaffold decomposition (${fallback.subtasks.length} subtasks)`, message);
722
+ return { result: fallback };
523
723
  }
524
724
  }
725
+ classifyDecompositionFailure(message) {
726
+ const m = message.toLowerCase();
727
+ if (m.includes('429') || m.includes('too many requests') || m.includes('rate limit')) {
728
+ return 'rate_limit';
729
+ }
730
+ if (m.includes('402') || m.includes('spend limit') || m.includes('key limit exceeded') || m.includes('insufficient credits')) {
731
+ return 'provider_budget_limit';
732
+ }
733
+ if (m.includes('parse') || m.includes('json') || m.includes('subtasks')) {
734
+ return 'parse_failure';
735
+ }
736
+ if (m.includes('invalid') || m.includes('validation')) {
737
+ return 'validation_failure';
738
+ }
739
+ return 'other';
740
+ }
741
+ /**
742
+ * Deterministic decomposition fallback when all LLM decomposition paths fail.
743
+ * Keeps swarm mode alive with visible scaffolding tasks instead of aborting.
744
+ */
745
+ buildEmergencyDecomposition(task, reason) {
746
+ const normalizer = createSmartDecomposer({ detectConflicts: true });
747
+ const taskLabel = task.trim().slice(0, 140) || 'requested task';
748
+ const repoMap = this.config.codebaseContext?.getRepoMap();
749
+ const topFiles = repoMap
750
+ ? Array.from(repoMap.chunks.values())
751
+ .sort((a, b) => b.importance - a.importance)
752
+ .slice(0, 10)
753
+ .map(c => c.filePath)
754
+ : [];
755
+ const subtasks = [
756
+ {
757
+ id: 'task-fb-0',
758
+ description: `Scaffold implementation plan and identify target files for: ${taskLabel}`,
759
+ status: 'ready',
760
+ dependencies: [],
761
+ complexity: 2,
762
+ type: 'design',
763
+ parallelizable: true,
764
+ relevantFiles: topFiles.slice(0, 5),
765
+ },
766
+ {
767
+ id: 'task-fb-1',
768
+ description: `Implement core code changes for: ${taskLabel}`,
769
+ status: 'blocked',
770
+ dependencies: ['task-fb-0'],
771
+ complexity: 5,
772
+ type: 'implement',
773
+ parallelizable: false,
774
+ relevantFiles: topFiles.slice(0, 8),
775
+ },
776
+ {
777
+ id: 'task-fb-2',
778
+ description: `Add or update tests and run validation for: ${taskLabel}`,
779
+ status: 'blocked',
780
+ dependencies: ['task-fb-1'],
781
+ complexity: 3,
782
+ type: 'test',
783
+ parallelizable: false,
784
+ relevantFiles: topFiles.slice(0, 8),
785
+ },
786
+ {
787
+ id: 'task-fb-3',
788
+ description: `Integrate results and produce final summary for: ${taskLabel}`,
789
+ status: 'blocked',
790
+ dependencies: ['task-fb-1', 'task-fb-2'],
791
+ complexity: 2,
792
+ type: 'integrate',
793
+ parallelizable: false,
794
+ relevantFiles: topFiles.slice(0, 5),
795
+ },
796
+ ];
797
+ const dependencyGraph = normalizer.buildDependencyGraph(subtasks);
798
+ const conflicts = normalizer.detectConflicts(subtasks);
799
+ return {
800
+ originalTask: task,
801
+ subtasks,
802
+ dependencyGraph,
803
+ conflicts,
804
+ strategy: 'adaptive',
805
+ totalComplexity: subtasks.reduce((sum, s) => sum + s.complexity, 0),
806
+ totalEstimatedTokens: subtasks.length * 4000,
807
+ metadata: {
808
+ decomposedAt: new Date(),
809
+ codebaseAware: !!repoMap,
810
+ llmAssisted: false,
811
+ },
812
+ };
813
+ }
814
+ /**
815
+ * Last-resort decomposition: radically simplified prompt that even weak models can handle.
816
+ * Uses shorter context, no examples, minimal schema, and lower maxTokens to avoid truncation.
817
+ */
818
+ async lastResortDecompose(task) {
819
+ // Include codebase grounding if repo map is available
820
+ let codebaseHint = '';
821
+ const repoMap = this.config.codebaseContext?.getRepoMap();
822
+ if (repoMap) {
823
+ const topFiles = Array.from(repoMap.chunks.values())
824
+ .sort((a, b) => b.importance - a.importance)
825
+ .slice(0, 10)
826
+ .map(c => c.filePath);
827
+ codebaseHint = `\nKey project files: ${topFiles.join(', ')}\nReference actual files in subtask descriptions.`;
828
+ }
829
+ const simplifiedPrompt = `Break this task into 2-6 subtasks. Return ONLY raw JSON, no markdown.
830
+
831
+ {"subtasks":[{"description":"...","type":"implement","complexity":3,"dependencies":[],"parallelizable":true,"relevantFiles":["src/..."]}],"strategy":"adaptive","reasoning":"..."}
832
+
833
+ Rules:
834
+ - dependencies: integer indices (e.g. [0] means depends on first subtask)
835
+ - type: one of research/implement/test/design/refactor/integrate/merge
836
+ - At least 2 subtasks${codebaseHint}`;
837
+ const response = await this.provider.chat([
838
+ { role: 'system', content: simplifiedPrompt },
839
+ { role: 'user', content: task },
840
+ ], {
841
+ model: this.config.orchestratorModel,
842
+ maxTokens: 4096, // Short — avoids truncation
843
+ temperature: 0.1, // Very deterministic
844
+ });
845
+ this.trackOrchestratorUsage(response, 'decompose-last-resort');
846
+ const parsed = parseDecompositionResponse(response.content);
847
+ if (parsed.subtasks.length < 2)
848
+ return null;
849
+ // Build a proper SmartDecompositionResult from the parsed LLM output
850
+ const decomposer = createSmartDecomposer({ detectConflicts: true });
851
+ const subtasks = parsed.subtasks.map((s, index) => ({
852
+ id: `task-lr-${index}`,
853
+ description: s.description,
854
+ status: (s.dependencies.length > 0 ? 'blocked' : 'ready'),
855
+ dependencies: s.dependencies.map((d) => `task-lr-${d}`),
856
+ complexity: s.complexity,
857
+ type: s.type,
858
+ parallelizable: s.parallelizable,
859
+ relevantFiles: s.relevantFiles,
860
+ suggestedRole: s.suggestedRole,
861
+ }));
862
+ const dependencyGraph = decomposer.buildDependencyGraph(subtasks);
863
+ const conflicts = decomposer.detectConflicts(subtasks);
864
+ return {
865
+ originalTask: task,
866
+ subtasks,
867
+ dependencyGraph,
868
+ conflicts,
869
+ strategy: parsed.strategy,
870
+ totalComplexity: subtasks.reduce((sum, t) => sum + t.complexity, 0),
871
+ totalEstimatedTokens: subtasks.length * 5000,
872
+ metadata: {
873
+ decomposedAt: new Date(),
874
+ codebaseAware: false,
875
+ llmAssisted: true, // This IS LLM-assisted, just simplified
876
+ },
877
+ };
878
+ }
525
879
  // ─── V2: Planning Phase ───────────────────────────────────────────────
526
880
  /**
527
881
  * Create acceptance criteria and integration test plan.
@@ -828,6 +1182,13 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
828
1182
  this.totalCost = checkpoint.stats.totalCost;
829
1183
  this.qualityRejections = checkpoint.stats.qualityRejections;
830
1184
  this.retries = checkpoint.stats.retries;
1185
+ // Restore shared context & economics state from checkpoint
1186
+ if (checkpoint.sharedContext) {
1187
+ this.sharedContextState.restoreFrom(checkpoint.sharedContext);
1188
+ }
1189
+ if (checkpoint.sharedEconomics) {
1190
+ this.sharedEconomicsState.restoreFrom(checkpoint.sharedEconomics);
1191
+ }
831
1192
  // Restore task queue
832
1193
  this.taskQueue.restoreFromCheckpoint({
833
1194
  taskStates: checkpoint.taskStates,
@@ -835,14 +1196,17 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
835
1196
  currentWave: checkpoint.currentWave,
836
1197
  });
837
1198
  // Reset orphaned dispatched tasks — their workers died with the previous process
838
- let resetCount = 0;
839
- for (const task of this.taskQueue.getAllTasks()) {
840
- if (task.status === 'dispatched') {
841
- task.status = 'ready';
842
- // Preserve at least 1 retry attempt
843
- task.attempts = Math.min(task.attempts, Math.max(0, this.config.workerRetries - 1));
844
- resetCount++;
845
- }
1199
+ const resetIds = this.taskQueue.reconcileStaleDispatched({
1200
+ staleAfterMs: 0,
1201
+ activeTaskIds: new Set(),
1202
+ });
1203
+ const resetCount = resetIds.length;
1204
+ for (const taskId of resetIds) {
1205
+ const task = this.taskQueue.getTask(taskId);
1206
+ if (!task)
1207
+ continue;
1208
+ // Preserve at least 1 retry attempt
1209
+ task.attempts = Math.min(task.attempts, Math.max(0, this.config.workerRetries - 1));
846
1210
  }
847
1211
  if (resetCount > 0) {
848
1212
  this.logDecision('resume', `Reset ${resetCount} orphaned dispatched tasks to ready`, 'Workers died with previous process');
@@ -904,9 +1268,15 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
904
1268
  this.checkpoint('final');
905
1269
  const hasArtifacts = (this.artifactInventory?.totalFiles ?? 0) > 0;
906
1270
  this.emit({ type: 'swarm.complete', stats: executionStats, errors: this.errors, artifactInventory: this.artifactInventory });
1271
+ // Success requires completing at least 70% of tasks (not just > 0)
1272
+ const completionRatio = executionStats.totalTasks > 0
1273
+ ? executionStats.completedTasks / executionStats.totalTasks
1274
+ : 0;
1275
+ const isSuccess = completionRatio >= 0.7;
1276
+ const isPartialSuccess = !isSuccess && executionStats.completedTasks > 0;
907
1277
  return {
908
- success: executionStats.completedTasks > 0,
909
- partialSuccess: !executionStats.completedTasks && hasArtifacts,
1278
+ success: isSuccess,
1279
+ partialSuccess: isPartialSuccess || (!executionStats.completedTasks && hasArtifacts),
910
1280
  partialFailure: executionStats.failedTasks > 0,
911
1281
  synthesisResult: synthesisResult ?? undefined,
912
1282
  artifactInventory: this.artifactInventory,
@@ -923,7 +1293,16 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
923
1293
  async executeWaves() {
924
1294
  let waveIndex = this.taskQueue.getCurrentWave();
925
1295
  const totalWaves = this.taskQueue.getTotalWaves();
1296
+ const dispatchLeaseStaleMs = this.config.dispatchLeaseStaleMs ?? 5 * 60 * 1000;
926
1297
  while (waveIndex < totalWaves && !this.cancelled) {
1298
+ const activeTaskIds = new Set(this.workerPool.getActiveWorkerStatus().map(w => w.taskId));
1299
+ const recovered = this.taskQueue.reconcileStaleDispatched({
1300
+ staleAfterMs: dispatchLeaseStaleMs,
1301
+ activeTaskIds,
1302
+ });
1303
+ if (recovered.length > 0) {
1304
+ this.logDecision('lease-recovery', `Recovered ${recovered.length} stale dispatched task(s)`, recovered.join(', '));
1305
+ }
927
1306
  const readyTasks = this.taskQueue.getReadyTasks();
928
1307
  const queueStats = this.taskQueue.getStats();
929
1308
  // F18: Skip empty waves — if no tasks are ready and none are running,
@@ -1282,6 +1661,10 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
1282
1661
  this.modelUsage.set(model, usage);
1283
1662
  this.totalTokens += taskResult.tokensUsed;
1284
1663
  this.totalCost += taskResult.costUsed;
1664
+ // Log per-worker budget utilization for orchestrator visibility
1665
+ if (taskResult.budgetUtilization) {
1666
+ this.logDecision('budget-utilization', `${taskId}: token ${taskResult.budgetUtilization.tokenPercent}%, iter ${taskResult.budgetUtilization.iterationPercent}%`, `model=${model}, tokens=${taskResult.tokensUsed}, duration=${durationMs}ms`);
1667
+ }
1285
1668
  // V10: Emit per-attempt event for full decision traceability
1286
1669
  this.emit({
1287
1670
  type: 'swarm.task.attempt',
@@ -1297,18 +1680,18 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
1297
1680
  });
1298
1681
  if (!spawnResult.success) {
1299
1682
  // V2: Record model health
1300
- const errorMsg = spawnResult.output.toLowerCase();
1301
- const is429 = errorMsg.includes('429') || errorMsg.includes('rate');
1302
- const is402 = errorMsg.includes('402') || errorMsg.includes('spend limit');
1303
- const isTimeout = spawnResult.metrics.toolCalls === -1;
1304
- // F25: Use 'timeout' errorType for timeouts (was 'error')
1305
- const errorType = is429 ? '429' : is402 ? '402' : isTimeout ? 'timeout' : 'error';
1683
+ const failure = classifySwarmFailure(spawnResult.output, spawnResult.metrics.toolCalls);
1684
+ const { failureClass, retryable, errorType, failureMode, reason } = failure;
1685
+ const isTimeout = failureMode === 'timeout';
1686
+ const isRateLimited = failureClass === 'rate_limited';
1687
+ const isSpendLimit = failureClass === 'provider_spend_limit';
1688
+ const isNonRetryable = !retryable;
1306
1689
  this.healthTracker.recordFailure(model, errorType);
1307
1690
  this.emit({ type: 'swarm.model.health', record: { model, ...this.getModelHealthSummary(model) } });
1308
1691
  // P6: Tag failure mode for cascade threshold awareness
1309
- task.failureMode = (is429 || is402) ? 'rate-limit' : (spawnResult.metrics.toolCalls === -1 ? 'timeout' : 'error');
1310
- // Feed circuit breaker
1311
- if (is429 || is402) {
1692
+ task.failureMode = failureMode;
1693
+ // Feed circuit breaker only for retryable rate limiting
1694
+ if (isRateLimited) {
1312
1695
  this.recordRateLimit();
1313
1696
  }
1314
1697
  // F25a: Consecutive timeout tracking — early-fail after N consecutive timeouts
@@ -1356,6 +1739,9 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
1356
1739
  maxAttempts: maxDispatches,
1357
1740
  willRetry: false,
1358
1741
  failureMode: 'timeout',
1742
+ failureClass: 'timeout',
1743
+ retrySuppressed: true,
1744
+ retryReason: 'Consecutive timeout limit reached with no alternative model',
1359
1745
  });
1360
1746
  this.logDecision('timeout-early-fail', `${taskId}: ${count} consecutive timeouts, no alt model — resilience recovery also failed`, '');
1361
1747
  this.taskTimeoutCounts.delete(taskId);
@@ -1367,8 +1753,8 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
1367
1753
  // Non-timeout failure — reset the counter
1368
1754
  this.taskTimeoutCounts.delete(taskId);
1369
1755
  }
1370
- // V2: Model failover on rate limits
1371
- if ((is429 || is402) && this.config.enableModelFailover) {
1756
+ // V2: Model failover on retryable rate limits
1757
+ if (isRateLimited && this.config.enableModelFailover) {
1372
1758
  const capability = getTaskTypeConfig(task.type, this.config).capability ?? 'code';
1373
1759
  const alternative = selectAlternativeModel(this.config.workers, model, capability, this.healthTracker);
1374
1760
  if (alternative) {
@@ -1384,7 +1770,7 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
1384
1770
  }
1385
1771
  }
1386
1772
  // V5/V7: Store error context so retry gets different prompt
1387
- if (!(is429 || is402)) {
1773
+ if (!(isRateLimited || isSpendLimit)) {
1388
1774
  // V7: Timeout-specific feedback — the worker WAS working, just ran out of time
1389
1775
  const timeoutSeconds = isTimeout ? Math.round(durationMs / 1000) : 0;
1390
1776
  task.retryContext = {
@@ -1397,27 +1783,37 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
1397
1783
  previousFiles: taskResult.filesModified,
1398
1784
  swarmProgress: this.getSwarmProgressSummary(),
1399
1785
  };
1786
+ // Phase 3.1: Report failure to shared context engine for cross-worker learning
1787
+ this.sharedContextEngine.reportFailure(taskId, {
1788
+ action: task.description.slice(0, 200),
1789
+ error: spawnResult.output.slice(0, 500),
1790
+ });
1400
1791
  }
1401
1792
  // V7: Reset hollow streak on non-hollow failure (error is not a hollow completion)
1402
1793
  this.hollowStreak = 0;
1403
1794
  // Worker failed — use higher retry limit for rate limit errors.
1404
1795
  // V7: Fixup tasks get capped retries, foundation tasks get +1.
1405
1796
  const baseRetries = this.getEffectiveRetries(task);
1406
- const retryLimit = (is429 || is402)
1407
- ? Math.min(this.config.rateLimitRetries ?? 3, baseRetries + 1)
1408
- : baseRetries;
1797
+ const retryLimit = isNonRetryable
1798
+ ? 0
1799
+ : isRateLimited
1800
+ ? Math.min(this.config.rateLimitRetries ?? 3, baseRetries + 1)
1801
+ : baseRetries;
1409
1802
  const canRetry = this.taskQueue.markFailedWithoutCascade(taskId, retryLimit);
1803
+ if (isNonRetryable) {
1804
+ this.logDecision('retry-suppressed', `${taskId}: ${failureClass}`, reason);
1805
+ }
1410
1806
  if (canRetry) {
1411
1807
  this.retries++;
1412
1808
  // Non-blocking cooldown: set retryAfter timestamp instead of blocking
1413
- if (is429 || is402) {
1809
+ if (isRateLimited) {
1414
1810
  const baseDelay = this.config.retryBaseDelayMs ?? 5000;
1415
1811
  const cooldownMs = Math.min(baseDelay * Math.pow(2, task.attempts - 1), 30000);
1416
1812
  this.taskQueue.setRetryAfter(taskId, cooldownMs);
1417
1813
  this.logDecision('rate-limit-cooldown', `${taskId}: ${errorType} cooldown ${cooldownMs}ms, model ${model}`, '');
1418
1814
  }
1419
1815
  }
1420
- else if (!(is429 || is402)) {
1816
+ else if (!isRateLimited) {
1421
1817
  // Resilience recovery for non-rate-limit errors (micro-decompose + degraded acceptance)
1422
1818
  if (await this.tryResilienceRecovery(task, taskId, taskResult, spawnResult)) {
1423
1819
  return;
@@ -1439,6 +1835,9 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
1439
1835
  toolCalls: spawnResult.metrics.toolCalls,
1440
1836
  failoverModel: task.assignedModel !== model ? task.assignedModel : undefined,
1441
1837
  failureMode: task.failureMode,
1838
+ failureClass,
1839
+ retrySuppressed: isNonRetryable,
1840
+ retryReason: reason,
1442
1841
  });
1443
1842
  return;
1444
1843
  }
@@ -1470,6 +1869,11 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
1470
1869
  previousFiles: taskResult.filesModified,
1471
1870
  swarmProgress: this.getSwarmProgressSummary(),
1472
1871
  };
1872
+ // Phase 3.1: Report hollow completion to shared context engine
1873
+ this.sharedContextEngine.reportFailure(taskId, {
1874
+ action: task.description.slice(0, 200),
1875
+ error: 'Hollow completion: worker produced no meaningful output',
1876
+ });
1473
1877
  // Model failover for hollow completions — same pattern as quality failover
1474
1878
  if (this.config.enableModelFailover) {
1475
1879
  const capability = getTaskTypeConfig(task.type, this.config).capability ?? 'code';
@@ -1704,6 +2108,11 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
1704
2108
  previousFiles: taskResult.filesModified,
1705
2109
  swarmProgress: this.getSwarmProgressSummary(),
1706
2110
  };
2111
+ // Phase 3.1: Report quality rejection to shared context engine
2112
+ this.sharedContextEngine.reportFailure(taskId, {
2113
+ action: task.description.slice(0, 200),
2114
+ error: `Quality gate rejection (score ${quality.score}): ${quality.feedback.slice(0, 300)}`,
2115
+ });
1707
2116
  // V5: Model failover on quality rejection — but NOT on artifact auto-fails
1708
2117
  // P1: Widened from score<=1 to score<threshold so failover triggers on any rejection
1709
2118
  if (quality.score < qualityThreshold && this.config.enableModelFailover && !quality.artifactAutoFail) {
@@ -1853,6 +2262,65 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
1853
2262
  }
1854
2263
  }
1855
2264
  }
2265
+ // Final completion guard: block "narrative success" for action tasks.
2266
+ const completionGuard = this.config.completionGuard ?? {};
2267
+ const rejectFutureIntentOutputs = completionGuard.rejectFutureIntentOutputs ?? true;
2268
+ const requireConcreteArtifactsForActionTasks = completionGuard.requireConcreteArtifactsForActionTasks ?? true;
2269
+ const typeConfig = getTaskTypeConfig(task.type, this.config);
2270
+ const artifactReport = checkArtifactsEnhanced(task, taskResult);
2271
+ const filesOnDisk = artifactReport.files.filter(f => f.exists && f.sizeBytes > 0).length;
2272
+ const hasConcreteArtifacts = filesOnDisk > 0 || (taskResult.filesModified?.length ?? 0) > 0;
2273
+ const isActionTask = !!typeConfig.requiresToolCalls;
2274
+ if (rejectFutureIntentOutputs && hasFutureIntentLanguage(taskResult.output ?? '')) {
2275
+ taskResult.qualityScore = 1;
2276
+ taskResult.qualityFeedback = 'Completion rejected: output indicates pending, unexecuted work';
2277
+ const canRetry = this.taskQueue.markFailedWithoutCascade(taskId, effectiveRetries);
2278
+ if (canRetry) {
2279
+ this.retries++;
2280
+ }
2281
+ else {
2282
+ if (await this.tryResilienceRecovery(task, taskId, taskResult, spawnResult)) {
2283
+ return;
2284
+ }
2285
+ this.taskQueue.triggerCascadeSkip(taskId);
2286
+ }
2287
+ this.emit({
2288
+ type: 'swarm.quality.rejected',
2289
+ taskId,
2290
+ score: 1,
2291
+ feedback: taskResult.qualityFeedback,
2292
+ artifactCount: filesOnDisk,
2293
+ outputLength: taskResult.output.length,
2294
+ preFlightReject: true,
2295
+ filesOnDisk,
2296
+ });
2297
+ return;
2298
+ }
2299
+ if (requireConcreteArtifactsForActionTasks && isActionTask && !hasConcreteArtifacts) {
2300
+ taskResult.qualityScore = 1;
2301
+ taskResult.qualityFeedback = 'Completion rejected: action task produced no concrete artifacts';
2302
+ const canRetry = this.taskQueue.markFailedWithoutCascade(taskId, effectiveRetries);
2303
+ if (canRetry) {
2304
+ this.retries++;
2305
+ }
2306
+ else {
2307
+ if (await this.tryResilienceRecovery(task, taskId, taskResult, spawnResult)) {
2308
+ return;
2309
+ }
2310
+ this.taskQueue.triggerCascadeSkip(taskId);
2311
+ }
2312
+ this.emit({
2313
+ type: 'swarm.quality.rejected',
2314
+ taskId,
2315
+ score: 1,
2316
+ feedback: taskResult.qualityFeedback,
2317
+ artifactCount: filesOnDisk,
2318
+ outputLength: taskResult.output.length,
2319
+ preFlightReject: true,
2320
+ filesOnDisk,
2321
+ });
2322
+ return;
2323
+ }
1856
2324
  // Task passed — mark completed
1857
2325
  this.taskQueue.markCompleted(taskId, taskResult);
1858
2326
  this.hollowStreak = 0;
@@ -2101,6 +2569,8 @@ Respond with JSON: { "fixups": [{ "description": "what to fix", "type": "impleme
2101
2569
  decisions: this.orchestratorDecisions,
2102
2570
  errors: this.errors,
2103
2571
  originalPrompt: this.originalPrompt,
2572
+ sharedContext: this.sharedContextState.toJSON(),
2573
+ sharedEconomics: this.sharedEconomicsState.toJSON(),
2104
2574
  });
2105
2575
  this.emit({
2106
2576
  type: 'swarm.state.checkpoint',
@@ -2634,7 +3104,12 @@ Return ONLY the JSON object, no other text.`;
2634
3104
  const toolCalls = spawnResult.metrics.toolCalls ?? 0;
2635
3105
  const hadToolCalls = toolCalls > 0 || toolCalls === -1
2636
3106
  || (taskResult.filesModified && taskResult.filesModified.length > 0);
2637
- if (hasArtifacts || hadToolCalls) {
3107
+ const isNarrativeOnly = hasFutureIntentLanguage(taskResult.output ?? '');
3108
+ const typeConfig = getTaskTypeConfig(task.type, this.config);
3109
+ const actionTaskNeedsArtifacts = (this.config.completionGuard?.requireConcreteArtifactsForActionTasks ?? true)
3110
+ && !!typeConfig.requiresToolCalls;
3111
+ const allowDegradedWithoutArtifacts = !actionTaskNeedsArtifacts && hadToolCalls && !isNarrativeOnly;
3112
+ if (hasArtifacts || allowDegradedWithoutArtifacts) {
2638
3113
  // Accept with degraded flag — prevents cascade-skip of dependents
2639
3114
  taskResult.success = true;
2640
3115
  taskResult.degraded = true;