agent-pipeline 0.1.0 → 0.1.2

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 (331) hide show
  1. package/CLAUDE.md +4 -3
  2. package/{license → LICENSE} +1 -1
  3. package/README.md +120 -55
  4. package/dist/cli/commands/agent/info.js +1 -1
  5. package/dist/cli/commands/agent/info.js.map +1 -1
  6. package/dist/cli/commands/agent/list.js +1 -1
  7. package/dist/cli/commands/agent/list.js.map +1 -1
  8. package/dist/cli/commands/agent/pull.d.ts +1 -0
  9. package/dist/cli/commands/agent/pull.d.ts.map +1 -1
  10. package/dist/cli/commands/agent/pull.js +67 -44
  11. package/dist/cli/commands/agent/pull.js.map +1 -1
  12. package/dist/cli/commands/cleanup.d.ts +4 -0
  13. package/dist/cli/commands/cleanup.d.ts.map +1 -1
  14. package/dist/cli/commands/cleanup.js +197 -37
  15. package/dist/cli/commands/cleanup.js.map +1 -1
  16. package/dist/cli/commands/history.d.ts.map +1 -1
  17. package/dist/cli/commands/history.js +3 -22
  18. package/dist/cli/commands/history.js.map +1 -1
  19. package/dist/cli/commands/hooks.d.ts +10 -0
  20. package/dist/cli/commands/hooks.d.ts.map +1 -0
  21. package/dist/cli/commands/hooks.js +88 -0
  22. package/dist/cli/commands/hooks.js.map +1 -0
  23. package/dist/cli/commands/init.d.ts +1 -6
  24. package/dist/cli/commands/init.d.ts.map +1 -1
  25. package/dist/cli/commands/init.js +169 -88
  26. package/dist/cli/commands/init.js.map +1 -1
  27. package/dist/cli/commands/loop-context.d.ts +2 -0
  28. package/dist/cli/commands/loop-context.d.ts.map +1 -0
  29. package/dist/cli/commands/loop-context.js +88 -0
  30. package/dist/cli/commands/loop-context.js.map +1 -0
  31. package/dist/cli/commands/pipeline/create.d.ts.map +1 -1
  32. package/dist/cli/commands/pipeline/create.js +156 -68
  33. package/dist/cli/commands/pipeline/create.js.map +1 -1
  34. package/dist/cli/commands/run.d.ts +1 -1
  35. package/dist/cli/commands/run.d.ts.map +1 -1
  36. package/dist/cli/commands/run.js +29 -5
  37. package/dist/cli/commands/run.js.map +1 -1
  38. package/dist/cli/commands/schema.d.ts +9 -0
  39. package/dist/cli/commands/schema.d.ts.map +1 -0
  40. package/dist/cli/commands/schema.js +541 -0
  41. package/dist/cli/commands/schema.js.map +1 -0
  42. package/dist/cli/commands/status.d.ts.map +1 -1
  43. package/dist/cli/commands/status.js +0 -6
  44. package/dist/cli/commands/status.js.map +1 -1
  45. package/dist/cli/help/index.d.ts +6 -0
  46. package/dist/cli/help/index.d.ts.map +1 -0
  47. package/dist/cli/help/index.js +538 -0
  48. package/dist/cli/help/index.js.map +1 -0
  49. package/dist/cli/help/types.d.ts +22 -0
  50. package/dist/cli/help/types.d.ts.map +1 -0
  51. package/dist/cli/help/types.js +2 -0
  52. package/dist/cli/help/types.js.map +1 -0
  53. package/dist/cli/hooks.d.ts +5 -1
  54. package/dist/cli/hooks.d.ts.map +1 -1
  55. package/dist/cli/hooks.js +106 -12
  56. package/dist/cli/hooks.js.map +1 -1
  57. package/dist/cli/templates/agents/code-review-agents/code-reviewer.md +46 -0
  58. package/dist/cli/templates/agents/code-review-agents/memory-doc-updater.md +189 -0
  59. package/dist/cli/templates/agents/code-review-agents/quality-checker.md +52 -0
  60. package/dist/cli/templates/agents/frontend-agents/brutalist_purist.md +563 -0
  61. package/dist/cli/templates/agents/frontend-agents/cyberpunk_hacker.md +824 -0
  62. package/dist/cli/templates/agents/frontend-agents/frontend-pipeline-ref.md +32 -0
  63. package/dist/cli/templates/agents/frontend-agents/indie_game_dev.md +715 -0
  64. package/dist/cli/templates/agents/frontend-agents/luxury_editorial.md +628 -0
  65. package/dist/cli/templates/agents/frontend-agents/product_owner.md +159 -0
  66. package/dist/cli/templates/agents/frontend-agents/retro_90s_webmaster.md +385 -0
  67. package/dist/cli/templates/agents/frontend-agents/showcase.md +362 -0
  68. package/dist/cli/templates/agents/frontend-agents/swiss_modernist.md +716 -0
  69. package/dist/cli/templates/agents/loop-agents/socratic-explorer.md +91 -0
  70. package/dist/cli/templates/instructions/handover.md +40 -0
  71. package/dist/cli/templates/instructions/loop.md +39 -0
  72. package/dist/cli/templates/pipelines/front-end-parallel-example.yml +81 -0
  73. package/dist/cli/templates/pipelines/loop-example.yml +25 -0
  74. package/dist/cli/templates/pipelines/post-commit-example.yml +49 -58
  75. package/dist/cli/templates/schema/pipeline-config.schema.json +400 -0
  76. package/dist/cli/templates/schema/pipeline-config.schema.yaml +302 -0
  77. package/dist/cli/utils/agent-importer.d.ts +1 -0
  78. package/dist/cli/utils/agent-importer.d.ts.map +1 -1
  79. package/dist/cli/utils/agent-importer.js +67 -0
  80. package/dist/cli/utils/agent-importer.js.map +1 -1
  81. package/dist/cli/utils/interactive-prompts.d.ts +4 -0
  82. package/dist/cli/utils/interactive-prompts.d.ts.map +1 -1
  83. package/dist/cli/utils/interactive-prompts.js +59 -13
  84. package/dist/cli/utils/interactive-prompts.js.map +1 -1
  85. package/dist/config/pipeline-loader.d.ts +4 -1
  86. package/dist/config/pipeline-loader.d.ts.map +1 -1
  87. package/dist/config/pipeline-loader.js +58 -0
  88. package/dist/config/pipeline-loader.js.map +1 -1
  89. package/dist/config/schema.d.ts +64 -35
  90. package/dist/config/schema.d.ts.map +1 -1
  91. package/dist/core/abort-controller.d.ts +16 -0
  92. package/dist/core/abort-controller.d.ts.map +1 -0
  93. package/dist/core/abort-controller.js +52 -0
  94. package/dist/core/abort-controller.js.map +1 -0
  95. package/dist/core/agent-query-runner.d.ts +0 -1
  96. package/dist/core/agent-query-runner.d.ts.map +1 -1
  97. package/dist/core/agent-query-runner.js +0 -22
  98. package/dist/core/agent-query-runner.js.map +1 -1
  99. package/dist/core/agent-runtimes/claude-code-headless-runtime.d.ts +5 -1
  100. package/dist/core/agent-runtimes/claude-code-headless-runtime.d.ts.map +1 -1
  101. package/dist/core/agent-runtimes/claude-code-headless-runtime.js +127 -12
  102. package/dist/core/agent-runtimes/claude-code-headless-runtime.js.map +1 -1
  103. package/dist/core/agent-runtimes/claude-sdk-runtime.d.ts +0 -2
  104. package/dist/core/agent-runtimes/claude-sdk-runtime.d.ts.map +1 -1
  105. package/dist/core/agent-runtimes/claude-sdk-runtime.js +1 -22
  106. package/dist/core/agent-runtimes/claude-sdk-runtime.js.map +1 -1
  107. package/dist/core/agent-runtimes/codex-headless-runtime.d.ts +27 -0
  108. package/dist/core/agent-runtimes/codex-headless-runtime.d.ts.map +1 -0
  109. package/dist/core/agent-runtimes/codex-headless-runtime.js +623 -0
  110. package/dist/core/agent-runtimes/codex-headless-runtime.js.map +1 -0
  111. package/dist/core/agent-runtimes/openai-compatible-runtime.d.ts +17 -0
  112. package/dist/core/agent-runtimes/openai-compatible-runtime.d.ts.map +1 -0
  113. package/dist/core/agent-runtimes/openai-compatible-runtime.js +231 -0
  114. package/dist/core/agent-runtimes/openai-compatible-runtime.js.map +1 -0
  115. package/dist/core/branch-manager.d.ts +3 -1
  116. package/dist/core/branch-manager.d.ts.map +1 -1
  117. package/dist/core/branch-manager.js +11 -1
  118. package/dist/core/branch-manager.js.map +1 -1
  119. package/dist/core/git-manager.d.ts +17 -1
  120. package/dist/core/git-manager.d.ts.map +1 -1
  121. package/dist/core/git-manager.js +147 -3
  122. package/dist/core/git-manager.js.map +1 -1
  123. package/dist/core/group-execution-orchestrator.d.ts +4 -13
  124. package/dist/core/group-execution-orchestrator.d.ts.map +1 -1
  125. package/dist/core/group-execution-orchestrator.js +53 -142
  126. package/dist/core/group-execution-orchestrator.js.map +1 -1
  127. package/dist/core/handover-manager.d.ts +24 -0
  128. package/dist/core/handover-manager.d.ts.map +1 -0
  129. package/dist/core/handover-manager.js +214 -0
  130. package/dist/core/handover-manager.js.map +1 -0
  131. package/dist/core/instruction-loader.d.ts +23 -0
  132. package/dist/core/instruction-loader.d.ts.map +1 -0
  133. package/dist/core/instruction-loader.js +123 -0
  134. package/dist/core/instruction-loader.js.map +1 -0
  135. package/dist/core/loop-state-manager.d.ts +12 -7
  136. package/dist/core/loop-state-manager.d.ts.map +1 -1
  137. package/dist/core/loop-state-manager.js +49 -3
  138. package/dist/core/loop-state-manager.js.map +1 -1
  139. package/dist/core/parallel-executor.d.ts +8 -3
  140. package/dist/core/parallel-executor.d.ts.map +1 -1
  141. package/dist/core/parallel-executor.js +90 -16
  142. package/dist/core/parallel-executor.js.map +1 -1
  143. package/dist/core/pipeline-finalizer.d.ts +12 -4
  144. package/dist/core/pipeline-finalizer.d.ts.map +1 -1
  145. package/dist/core/pipeline-finalizer.js +244 -33
  146. package/dist/core/pipeline-finalizer.js.map +1 -1
  147. package/dist/core/pipeline-initializer.d.ts +13 -5
  148. package/dist/core/pipeline-initializer.d.ts.map +1 -1
  149. package/dist/core/pipeline-initializer.js +53 -25
  150. package/dist/core/pipeline-initializer.js.map +1 -1
  151. package/dist/core/pipeline-runner.d.ts +15 -1
  152. package/dist/core/pipeline-runner.d.ts.map +1 -1
  153. package/dist/core/pipeline-runner.js +434 -64
  154. package/dist/core/pipeline-runner.js.map +1 -1
  155. package/dist/core/pr-creator.d.ts +0 -1
  156. package/dist/core/pr-creator.d.ts.map +1 -1
  157. package/dist/core/pr-creator.js +12 -4
  158. package/dist/core/pr-creator.js.map +1 -1
  159. package/dist/core/stage-executor.d.ts +19 -11
  160. package/dist/core/stage-executor.d.ts.map +1 -1
  161. package/dist/core/stage-executor.js +151 -178
  162. package/dist/core/stage-executor.js.map +1 -1
  163. package/dist/core/types/agent-runtime.d.ts +3 -2
  164. package/dist/core/types/agent-runtime.d.ts.map +1 -1
  165. package/dist/core/worktree-manager.d.ts +21 -0
  166. package/dist/core/worktree-manager.d.ts.map +1 -0
  167. package/dist/core/worktree-manager.js +133 -0
  168. package/dist/core/worktree-manager.js.map +1 -0
  169. package/dist/index.js +147 -112
  170. package/dist/index.js.map +1 -1
  171. package/dist/notifications/notification-manager.d.ts +1 -0
  172. package/dist/notifications/notification-manager.d.ts.map +1 -1
  173. package/dist/notifications/notification-manager.js +17 -3
  174. package/dist/notifications/notification-manager.js.map +1 -1
  175. package/dist/notifications/notifiers/local-notifier.d.ts.map +1 -1
  176. package/dist/notifications/notifiers/local-notifier.js +5 -2
  177. package/dist/notifications/notifiers/local-notifier.js.map +1 -1
  178. package/dist/notifications/notifiers/slack-notifier.d.ts.map +1 -1
  179. package/dist/notifications/notifiers/slack-notifier.js +39 -8
  180. package/dist/notifications/notifiers/slack-notifier.js.map +1 -1
  181. package/dist/notifications/types.d.ts +1 -1
  182. package/dist/notifications/types.d.ts.map +1 -1
  183. package/dist/ui/components/execution-group.d.ts.map +1 -1
  184. package/dist/ui/components/execution-group.js +1 -1
  185. package/dist/ui/components/execution-group.js.map +1 -1
  186. package/dist/ui/components/interactive-summary.d.ts +9 -0
  187. package/dist/ui/components/interactive-summary.d.ts.map +1 -0
  188. package/dist/ui/components/interactive-summary.js +166 -0
  189. package/dist/ui/components/interactive-summary.js.map +1 -0
  190. package/dist/ui/components/keyboard-hints.d.ts +12 -0
  191. package/dist/ui/components/keyboard-hints.d.ts.map +1 -0
  192. package/dist/ui/components/keyboard-hints.js +13 -0
  193. package/dist/ui/components/keyboard-hints.js.map +1 -0
  194. package/dist/ui/components/live-timer.d.ts +9 -0
  195. package/dist/ui/components/live-timer.d.ts.map +1 -0
  196. package/dist/ui/components/live-timer.js +25 -0
  197. package/dist/ui/components/live-timer.js.map +1 -0
  198. package/dist/ui/components/loop-iteration-history.d.ts +8 -0
  199. package/dist/ui/components/loop-iteration-history.d.ts.map +1 -0
  200. package/dist/ui/components/loop-iteration-history.js +106 -0
  201. package/dist/ui/components/loop-iteration-history.js.map +1 -0
  202. package/dist/ui/components/loop-session-summary.d.ts +9 -0
  203. package/dist/ui/components/loop-session-summary.d.ts.map +1 -0
  204. package/dist/ui/components/loop-session-summary.js +39 -0
  205. package/dist/ui/components/loop-session-summary.js.map +1 -0
  206. package/dist/ui/components/stage-row.d.ts +0 -1
  207. package/dist/ui/components/stage-row.d.ts.map +1 -1
  208. package/dist/ui/components/stage-row.js +5 -15
  209. package/dist/ui/components/stage-row.js.map +1 -1
  210. package/dist/ui/components/status-badge.d.ts.map +1 -1
  211. package/dist/ui/components/status-badge.js +1 -0
  212. package/dist/ui/components/status-badge.js.map +1 -1
  213. package/dist/ui/pipeline-ui.d.ts +1 -0
  214. package/dist/ui/pipeline-ui.d.ts.map +1 -1
  215. package/dist/ui/pipeline-ui.js +22 -18
  216. package/dist/ui/pipeline-ui.js.map +1 -1
  217. package/dist/utils/error-factory.d.ts.map +1 -1
  218. package/dist/utils/error-factory.js +10 -2
  219. package/dist/utils/error-factory.js.map +1 -1
  220. package/dist/utils/pipeline-formatter.d.ts +8 -2
  221. package/dist/utils/pipeline-formatter.d.ts.map +1 -1
  222. package/dist/utils/pipeline-formatter.js +30 -26
  223. package/dist/utils/pipeline-formatter.js.map +1 -1
  224. package/dist/utils/pipeline-logger.d.ts +19 -0
  225. package/dist/utils/pipeline-logger.d.ts.map +1 -0
  226. package/dist/utils/pipeline-logger.js +80 -0
  227. package/dist/utils/pipeline-logger.js.map +1 -0
  228. package/dist/utils/platform-opener.d.ts +4 -0
  229. package/dist/utils/platform-opener.d.ts.map +1 -0
  230. package/dist/utils/platform-opener.js +52 -0
  231. package/dist/utils/platform-opener.js.map +1 -0
  232. package/dist/validators/agent-validator.d.ts +10 -0
  233. package/dist/validators/agent-validator.d.ts.map +1 -0
  234. package/dist/validators/agent-validator.js +87 -0
  235. package/dist/validators/agent-validator.js.map +1 -0
  236. package/dist/validators/dag-validator.d.ts +9 -0
  237. package/dist/validators/dag-validator.d.ts.map +1 -0
  238. package/dist/validators/dag-validator.js +51 -0
  239. package/dist/validators/dag-validator.js.map +1 -0
  240. package/dist/validators/environment-validator.d.ts +9 -0
  241. package/dist/validators/environment-validator.d.ts.map +1 -0
  242. package/dist/validators/environment-validator.js +35 -0
  243. package/dist/validators/environment-validator.js.map +1 -0
  244. package/dist/validators/execution-validator.d.ts +8 -0
  245. package/dist/validators/execution-validator.d.ts.map +1 -0
  246. package/dist/validators/execution-validator.js +51 -0
  247. package/dist/validators/execution-validator.js.map +1 -0
  248. package/dist/validators/git-validator.d.ts +13 -0
  249. package/dist/validators/git-validator.d.ts.map +1 -0
  250. package/dist/validators/git-validator.js +135 -0
  251. package/dist/validators/git-validator.js.map +1 -0
  252. package/dist/validators/notification-validator.d.ts +8 -0
  253. package/dist/validators/notification-validator.d.ts.map +1 -0
  254. package/dist/validators/notification-validator.js +27 -0
  255. package/dist/validators/notification-validator.js.map +1 -0
  256. package/dist/validators/pipeline-validator.d.ts +3 -26
  257. package/dist/validators/pipeline-validator.d.ts.map +1 -1
  258. package/dist/validators/pipeline-validator.js +5 -515
  259. package/dist/validators/pipeline-validator.js.map +1 -1
  260. package/dist/validators/retry-validator.d.ts +9 -0
  261. package/dist/validators/retry-validator.d.ts.map +1 -0
  262. package/dist/validators/retry-validator.js +34 -0
  263. package/dist/validators/retry-validator.js.map +1 -0
  264. package/dist/validators/runtime-validator.d.ts +9 -0
  265. package/dist/validators/runtime-validator.d.ts.map +1 -0
  266. package/dist/validators/runtime-validator.js +82 -0
  267. package/dist/validators/runtime-validator.js.map +1 -0
  268. package/dist/validators/structure-validator.d.ts +8 -0
  269. package/dist/validators/structure-validator.d.ts.map +1 -0
  270. package/dist/validators/structure-validator.js +39 -0
  271. package/dist/validators/structure-validator.js.map +1 -0
  272. package/dist/validators/types.d.ts +19 -0
  273. package/dist/validators/types.d.ts.map +1 -0
  274. package/dist/validators/types.js +2 -0
  275. package/dist/validators/types.js.map +1 -0
  276. package/dist/validators/validation-orchestrator.d.ts +9 -0
  277. package/dist/validators/validation-orchestrator.d.ts.map +1 -0
  278. package/dist/validators/validation-orchestrator.js +43 -0
  279. package/dist/validators/validation-orchestrator.js.map +1 -0
  280. package/package.json +18 -12
  281. package/dist/cli/commands/install.d.ts +0 -2
  282. package/dist/cli/commands/install.d.ts.map +0 -1
  283. package/dist/cli/commands/install.js +0 -15
  284. package/dist/cli/commands/install.js.map +0 -1
  285. package/dist/cli/commands/uninstall.d.ts +0 -2
  286. package/dist/cli/commands/uninstall.d.ts.map +0 -1
  287. package/dist/cli/commands/uninstall.js +0 -6
  288. package/dist/cli/commands/uninstall.js.map +0 -1
  289. package/dist/cli/templates/agents/cleanup-reporter.md +0 -107
  290. package/dist/cli/templates/agents/code-reducer.md +0 -51
  291. package/dist/cli/templates/agents/code-reviewer.md +0 -34
  292. package/dist/cli/templates/agents/context-reducer.md +0 -94
  293. package/dist/cli/templates/agents/dependency-auditor.md +0 -127
  294. package/dist/cli/templates/agents/detective-empath.md +0 -26
  295. package/dist/cli/templates/agents/detective-linguist.md +0 -26
  296. package/dist/cli/templates/agents/detective-logician.md +0 -26
  297. package/dist/cli/templates/agents/detective-skeptic.md +0 -26
  298. package/dist/cli/templates/agents/detective-statistician.md +0 -26
  299. package/dist/cli/templates/agents/doc-updater.md +0 -30
  300. package/dist/cli/templates/agents/judge.md +0 -27
  301. package/dist/cli/templates/agents/memory-updater.md +0 -72
  302. package/dist/cli/templates/agents/quality-checker.md +0 -32
  303. package/dist/cli/templates/agents/security-auditor.md +0 -32
  304. package/dist/cli/templates/agents/storyteller.md +0 -26
  305. package/dist/cli/templates/agents/summary.md +0 -32
  306. package/dist/cli/templates/agents/synthesizer.md +0 -26
  307. package/dist/cli/templates/pipelines/large-pipeline-example.yml +0 -178
  308. package/dist/cli/templates/pipelines/post-merge-example.yml +0 -78
  309. package/dist/cli/templates/pipelines/pre-commit-example.yml +0 -41
  310. package/dist/cli/templates/pipelines/pre-push-example.yml +0 -51
  311. package/dist/cli/templates/pipelines/test-pipeline.yml +0 -90
  312. package/dist/config/project-config-loader.d.ts +0 -11
  313. package/dist/config/project-config-loader.d.ts.map +0 -1
  314. package/dist/config/project-config-loader.js +0 -100
  315. package/dist/config/project-config-loader.js.map +0 -1
  316. package/dist/core/condition-evaluator.d.ts +0 -16
  317. package/dist/core/condition-evaluator.d.ts.map +0 -1
  318. package/dist/core/condition-evaluator.js +0 -121
  319. package/dist/core/condition-evaluator.js.map +0 -1
  320. package/dist/core/context-reducer.d.ts +0 -15
  321. package/dist/core/context-reducer.d.ts.map +0 -1
  322. package/dist/core/context-reducer.js +0 -224
  323. package/dist/core/context-reducer.js.map +0 -1
  324. package/dist/core/output-storage-manager.d.ts +0 -14
  325. package/dist/core/output-storage-manager.d.ts.map +0 -1
  326. package/dist/core/output-storage-manager.js +0 -68
  327. package/dist/core/output-storage-manager.js.map +0 -1
  328. package/dist/core/output-tool-builder.d.ts +0 -6
  329. package/dist/core/output-tool-builder.d.ts.map +0 -1
  330. package/dist/core/output-tool-builder.js +0 -50
  331. package/dist/core/output-tool-builder.js.map +0 -1
@@ -6,11 +6,12 @@ import { DAGPlanner } from './dag-planner.js';
6
6
  import { PipelineInitializer } from './pipeline-initializer.js';
7
7
  import { GroupExecutionOrchestrator } from './group-execution-orchestrator.js';
8
8
  import { PipelineFinalizer } from './pipeline-finalizer.js';
9
+ import { InstructionLoader } from './instruction-loader.js';
9
10
  import { NotificationManager } from '../notifications/notification-manager.js';
10
- import { ProjectConfigLoader } from '../config/project-config-loader.js';
11
11
  import { PipelineLoader } from '../config/pipeline-loader.js';
12
12
  import { LoopStateManager } from './loop-state-manager.js';
13
13
  import { AgentRuntimeRegistry } from './agent-runtime-registry.js';
14
+ import { PipelineAbortError } from './abort-controller.js';
14
15
  import * as fs from 'fs/promises';
15
16
  import * as path from 'path';
16
17
  export class PipelineRunner {
@@ -27,8 +28,9 @@ export class PipelineRunner {
27
28
  repoPath;
28
29
  runtime;
29
30
  stateUpdateCallbacks = [];
30
- projectConfigLoader;
31
31
  loopStateManager;
32
+ loopExecutionDirs;
33
+ loopMainDirs;
32
34
  constructor(repoPath, dryRun = false) {
33
35
  this.repoPath = repoPath;
34
36
  this.dryRun = dryRun;
@@ -37,55 +39,71 @@ export class PipelineRunner {
37
39
  this.prCreator = new PRCreator();
38
40
  this.stateManager = new StateManager(repoPath);
39
41
  this.dagPlanner = new DAGPlanner();
40
- this.projectConfigLoader = new ProjectConfigLoader(repoPath);
41
42
  this.loopStateManager = new LoopStateManager(repoPath);
42
- this.runtime = AgentRuntimeRegistry.getRuntime('claude-sdk');
43
- this.initializer = new PipelineInitializer(this.gitManager, this.branchManager, this.repoPath, this.dryRun, this.runtime);
44
- this.groupOrchestrator = new GroupExecutionOrchestrator(this.gitManager, this.stateManager, this.repoPath, this.dryRun, this.runtime, this.shouldLog.bind(this), this.notifyStateChange.bind(this), this.notifyStageResults.bind(this));
43
+ this.runtime = AgentRuntimeRegistry.getRuntime('claude-code-headless');
44
+ this.initializer = new PipelineInitializer(this.gitManager, this.repoPath, this.dryRun, this.runtime);
45
+ this.groupOrchestrator = new GroupExecutionOrchestrator(this.stateManager, this.shouldLog.bind(this), this.notifyStateChange.bind(this), this.notifyStageResults.bind(this));
45
46
  this.finalizer = new PipelineFinalizer(this.gitManager, this.branchManager, this.prCreator, this.stateManager, this.repoPath, this.dryRun, this.shouldLog.bind(this));
46
47
  }
47
48
  shouldLog(interactive) {
48
49
  return !interactive;
49
50
  }
50
51
  async notifyStageResults(executions, state) {
52
+ const notificationContexts = [];
51
53
  for (const execution of executions) {
52
54
  if (execution.status === 'success') {
53
- await this.notify({
55
+ notificationContexts.push({
54
56
  event: 'stage.completed',
55
57
  pipelineState: state,
56
58
  stage: execution
57
59
  });
58
60
  }
59
61
  else if (execution.status === 'failed') {
60
- await this.notify({
62
+ notificationContexts.push({
61
63
  event: 'stage.failed',
62
64
  pipelineState: state,
63
65
  stage: execution
64
66
  });
65
67
  }
66
68
  }
69
+ if (notificationContexts.length > 0) {
70
+ await Promise.all(notificationContexts.map(context => this.notify(context)));
71
+ }
67
72
  }
68
73
  async runPipeline(config, options = {}) {
69
74
  const interactive = options.interactive || false;
75
+ const verbose = options.verbose || false;
70
76
  const notificationManager = config.notifications
71
77
  ? new NotificationManager(config.notifications)
72
78
  : undefined;
73
- const loopingConfig = await this.projectConfigLoader.loadLoopingConfig();
74
- let loopEnabled = options.loop || false;
75
- if (loopEnabled && !loopingConfig.enabled) {
76
- console.warn('⚠️ Loop mode requested but looping is disabled in config');
79
+ this.loopExecutionDirs = undefined;
80
+ this.loopMainDirs = undefined;
81
+ let loopSession;
82
+ let loopEnabled;
83
+ let loopingConfig;
84
+ if (options.loop === false) {
77
85
  loopEnabled = false;
86
+ loopingConfig = this.getDefaultLoopingConfig();
87
+ }
88
+ else {
89
+ loopEnabled = config.looping?.enabled ?? false;
90
+ if (loopEnabled) {
91
+ loopSession = await this.loopStateManager.startSession(options.maxLoopIterations ?? config.looping?.maxIterations ?? 100);
92
+ loopingConfig = config.looping;
93
+ }
94
+ else {
95
+ loopingConfig = this.getDefaultLoopingConfig();
96
+ }
78
97
  }
79
- const maxIterations = options.maxLoopIterations ?? loopingConfig.maxIterations ?? 100;
98
+ const maxIterations = options.maxLoopIterations ?? loopingConfig.maxIterations;
80
99
  let iterationCount = 0;
81
100
  let lastState;
82
101
  let currentConfig = config;
83
102
  let currentMetadata = options.loopMetadata;
84
103
  let loopTerminationReason = 'natural';
85
- let loopSession;
86
- if (loopEnabled) {
87
- loopSession = this.loopStateManager.startSession(maxIterations);
88
- }
104
+ let loopDirCreated = false;
105
+ let loopDirs = loopingConfig.directories;
106
+ const loopIterationHistory = [];
89
107
  while (true) {
90
108
  iterationCount++;
91
109
  if (iterationCount > maxIterations) {
@@ -99,20 +117,65 @@ export class PipelineRunner {
99
117
  : currentConfig.name;
100
118
  console.log(`🔁 Loop iteration ${iterationCount}: Running pipeline '${pipelineName}'...`);
101
119
  }
120
+ if (loopEnabled && loopSession) {
121
+ const pipelineName = this.getPipelineName(currentConfig, currentMetadata);
122
+ await this.loopStateManager.appendIteration(loopSession.sessionId, {
123
+ iterationNumber: iterationCount,
124
+ pipelineName,
125
+ status: 'in-progress'
126
+ });
127
+ }
102
128
  const loopContext = loopEnabled && loopingConfig.enabled
103
129
  ? {
104
130
  enabled: true,
105
- directories: loopingConfig.directories,
131
+ directories: loopDirs,
106
132
  currentIteration: iterationCount,
107
- maxIterations
133
+ maxIterations,
134
+ sessionId: loopSession?.sessionId
108
135
  }
109
136
  : undefined;
110
137
  lastState = await this._executeSinglePipeline(currentConfig, currentMetadata, {
111
138
  interactive,
139
+ verbose,
112
140
  notificationManager,
113
141
  loopContext,
114
- loopSessionId: loopSession?.sessionId
142
+ loopSessionId: loopSession?.sessionId,
143
+ abortController: options.abortController,
144
+ isFirstLoopIteration: loopEnabled && !loopDirCreated,
145
+ suppressCompletionNotification: loopEnabled
115
146
  });
147
+ if (loopEnabled && !loopDirCreated && loopContext) {
148
+ loopDirCreated = true;
149
+ loopDirs = loopContext.directories;
150
+ }
151
+ if (loopEnabled && lastState) {
152
+ let totalInput = 0;
153
+ let totalOutput = 0;
154
+ let totalCacheRead = 0;
155
+ for (const stage of lastState.stages) {
156
+ if (stage.tokenUsage) {
157
+ totalInput += stage.tokenUsage.actual_input || 0;
158
+ totalOutput += stage.tokenUsage.output || 0;
159
+ totalCacheRead += stage.tokenUsage.cache_read || 0;
160
+ }
161
+ }
162
+ const historyEntry = {
163
+ iterationNumber: iterationCount,
164
+ pipelineName: currentMetadata?.sourcePath
165
+ ? path.basename(currentMetadata.sourcePath, '.yml')
166
+ : currentConfig.name,
167
+ status: lastState.status === 'completed' ? 'completed'
168
+ : lastState.status === 'aborted' ? 'aborted' : 'failed',
169
+ duration: lastState.artifacts.totalDuration,
170
+ commitCount: lastState.stages.filter(s => s.commitSha).length,
171
+ stageCount: lastState.stages.length,
172
+ successfulStages: lastState.stages.filter(s => s.status === 'success').length,
173
+ failedStages: lastState.stages.filter(s => s.status === 'failed').length,
174
+ tokenUsage: totalInput > 0 ? { totalInput, totalOutput, totalCacheRead } : undefined
175
+ };
176
+ loopIterationHistory.push(historyEntry);
177
+ lastState.loopIterationHistory = [...loopIterationHistory];
178
+ }
116
179
  this.notifyStateChange(lastState);
117
180
  if (this.shouldLog(interactive) && loopEnabled && lastState.status === 'completed') {
118
181
  console.log(`✅ Completed iteration ${iterationCount}`);
@@ -120,8 +183,8 @@ export class PipelineRunner {
120
183
  if (currentMetadata?.sourceType === 'loop-pending') {
121
184
  try {
122
185
  const destDir = lastState.status === 'completed'
123
- ? loopingConfig.directories.finished
124
- : loopingConfig.directories.failed;
186
+ ? loopDirs.finished
187
+ : loopDirs.failed;
125
188
  const fileName = path.basename(currentMetadata.sourcePath);
126
189
  await this._moveFile(currentMetadata.sourcePath, destDir, fileName);
127
190
  if (this.shouldLog(interactive)) {
@@ -133,26 +196,38 @@ export class PipelineRunner {
133
196
  console.error(`⚠️ Failed to move pipeline file: ${error}`);
134
197
  }
135
198
  }
136
- if (lastState.status === 'failed') {
199
+ if (lastState.status === 'aborted') {
137
200
  if (loopEnabled && loopSession) {
138
201
  await this.recordIteration(loopSession.sessionId, lastState, currentMetadata, false);
139
202
  }
140
- loopTerminationReason = 'failure';
141
- const pipelineName = currentMetadata?.sourcePath
142
- ? path.basename(currentMetadata.sourcePath, '.yml')
143
- : currentConfig.name;
144
203
  if (this.shouldLog(interactive)) {
145
- console.log(`Loop: terminating after failure of ${pipelineName}`);
204
+ console.log('Loop: terminating due to abort');
146
205
  }
147
206
  break;
148
207
  }
208
+ if (lastState.status === 'failed') {
209
+ if (loopEnabled && loopSession) {
210
+ await this.recordIteration(loopSession.sessionId, lastState, currentMetadata, false);
211
+ }
212
+ const failureStrategy = currentConfig.execution?.failureStrategy ?? 'stop';
213
+ if (failureStrategy === 'stop') {
214
+ loopTerminationReason = 'failure';
215
+ const pipelineName = currentMetadata?.sourcePath
216
+ ? path.basename(currentMetadata.sourcePath, '.yml')
217
+ : currentConfig.name;
218
+ if (this.shouldLog(interactive)) {
219
+ console.log(`Loop: terminating after failure of ${pipelineName}`);
220
+ }
221
+ break;
222
+ }
223
+ }
149
224
  if (!loopEnabled) {
150
225
  if (loopSession) {
151
226
  await this.recordIteration(loopSession.sessionId, lastState, currentMetadata, false);
152
227
  }
153
228
  break;
154
229
  }
155
- const nextFile = await this._findNextPipelineFile(loopingConfig);
230
+ const nextFile = await this._findNextPipelineFile(loopDirs);
156
231
  const triggeredNext = nextFile !== undefined;
157
232
  if (loopSession) {
158
233
  await this.recordIteration(loopSession.sessionId, lastState, currentMetadata, triggeredNext);
@@ -166,7 +241,7 @@ export class PipelineRunner {
166
241
  const fileName = path.basename(nextFile);
167
242
  let runningPath;
168
243
  try {
169
- runningPath = await this._moveFile(nextFile, loopingConfig.directories.running, fileName);
244
+ runningPath = await this._moveFile(nextFile, loopDirs.running, fileName);
170
245
  }
171
246
  catch (error) {
172
247
  console.error(`❌ Failed to move ${fileName} to running directory: ${error}`);
@@ -181,7 +256,7 @@ export class PipelineRunner {
181
256
  catch (error) {
182
257
  console.error(`❌ Failed to load pipeline ${fileName}: ${error}`);
183
258
  try {
184
- await this._moveFile(runningPath, loopingConfig.directories.failed, fileName);
259
+ await this._moveFile(runningPath, loopDirs.failed, fileName);
185
260
  }
186
261
  catch (moveError) {
187
262
  console.error(`⚠️ Failed to move ${fileName} to failed directory: ${moveError}`);
@@ -194,18 +269,55 @@ export class PipelineRunner {
194
269
  }
195
270
  if (lastState.loopContext) {
196
271
  lastState.loopContext.terminationReason = loopTerminationReason;
272
+ this.notifyStateChange(lastState);
273
+ }
274
+ if (loopEnabled &&
275
+ lastState.artifacts.worktreePath &&
276
+ this.loopExecutionDirs &&
277
+ this.loopMainDirs) {
278
+ try {
279
+ await this.copyLoopDirectories(this.loopExecutionDirs, this.loopMainDirs);
280
+ if (this.shouldLog(interactive)) {
281
+ console.log('📋 Copied loop directories to main repo');
282
+ }
283
+ }
284
+ catch (error) {
285
+ console.warn(`⚠️ Could not copy loop directories: ${error instanceof Error ? error.message : String(error)}`);
286
+ }
197
287
  }
198
288
  if (loopEnabled && loopSession) {
199
- const sessionStatus = loopTerminationReason === 'natural' ? 'completed' :
200
- loopTerminationReason === 'limit-reached' ? 'limit-reached' :
201
- 'failed';
289
+ const sessionStatus = lastState.status === 'aborted' ? 'aborted' :
290
+ loopTerminationReason === 'natural' ? 'completed' :
291
+ loopTerminationReason === 'limit-reached' ? 'limit-reached' :
292
+ 'failed';
202
293
  await this.loopStateManager.completeSession(loopSession.sessionId, sessionStatus);
203
294
  }
295
+ if (loopEnabled) {
296
+ const event = lastState.status === 'aborted'
297
+ ? 'pipeline.aborted'
298
+ : loopTerminationReason === 'natural'
299
+ ? 'pipeline.completed'
300
+ : 'pipeline.failed';
301
+ await this.notify({
302
+ event,
303
+ pipelineState: lastState,
304
+ metadata: {
305
+ loopCompleted: loopTerminationReason === 'natural',
306
+ terminationReason: loopTerminationReason,
307
+ totalIterations: iterationCount
308
+ }
309
+ });
310
+ }
204
311
  return lastState;
205
312
  }
206
313
  notifyStateChange(state) {
314
+ const clonedState = {
315
+ ...state,
316
+ stages: [...state.stages],
317
+ artifacts: { ...state.artifacts }
318
+ };
207
319
  for (const callback of this.stateUpdateCallbacks) {
208
- callback(state);
320
+ callback(clonedState);
209
321
  }
210
322
  }
211
323
  async notify(context) {
@@ -227,9 +339,85 @@ export class PipelineRunner {
227
339
  onStateChange(callback) {
228
340
  this.stateUpdateCallbacks.push(callback);
229
341
  }
230
- async _findNextPipelineFile(loopingConfig) {
342
+ getDefaultLoopingConfig(sessionId) {
343
+ const baseDir = sessionId
344
+ ? `.agent-pipeline/loops/${sessionId}`
345
+ : '.agent-pipeline/loops/default';
346
+ return {
347
+ enabled: true,
348
+ maxIterations: 100,
349
+ directories: this.getSessionLoopDirs(this.repoPath, baseDir),
350
+ };
351
+ }
352
+ getSessionLoopDirs(basePath, sessionBaseDir) {
353
+ return {
354
+ pending: path.resolve(basePath, `${sessionBaseDir}/pending`),
355
+ running: path.resolve(basePath, `${sessionBaseDir}/running`),
356
+ finished: path.resolve(basePath, `${sessionBaseDir}/finished`),
357
+ failed: path.resolve(basePath, `${sessionBaseDir}/failed`),
358
+ };
359
+ }
360
+ resolveLoopDirectories(loopContext, executionRepoPath, worktreePath) {
361
+ const sessionId = loopContext.sessionId ?? 'default';
362
+ const sessionBaseDir = `.agent-pipeline/loops/${sessionId}`;
363
+ const sessionMainDirs = this.getSessionLoopDirs(this.repoPath, sessionBaseDir);
364
+ const sessionExecutionDirs = this.getSessionLoopDirs(executionRepoPath, sessionBaseDir);
365
+ const providedDirs = loopContext.directories;
366
+ const mainDirs = {
367
+ pending: providedDirs.pending || sessionMainDirs.pending,
368
+ running: providedDirs.running || sessionMainDirs.running,
369
+ finished: providedDirs.finished || sessionMainDirs.finished,
370
+ failed: providedDirs.failed || sessionMainDirs.failed,
371
+ };
372
+ if (!worktreePath) {
373
+ return { executionDirs: mainDirs, mainDirs, sessionExecutionDirs };
374
+ }
375
+ const executionDirs = {
376
+ pending: this.mapToExecutionDir(mainDirs.pending, executionRepoPath, sessionExecutionDirs.pending),
377
+ running: this.mapToExecutionDir(mainDirs.running, executionRepoPath, sessionExecutionDirs.running),
378
+ finished: this.mapToExecutionDir(mainDirs.finished, executionRepoPath, sessionExecutionDirs.finished),
379
+ failed: this.mapToExecutionDir(mainDirs.failed, executionRepoPath, sessionExecutionDirs.failed),
380
+ };
381
+ return { executionDirs, mainDirs, sessionExecutionDirs };
382
+ }
383
+ mapToExecutionDir(mainDir, executionRepoPath, fallbackDir) {
384
+ const relativePath = path.relative(this.repoPath, mainDir);
385
+ const isInsideRepo = relativePath && !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
386
+ if (!isInsideRepo) {
387
+ return fallbackDir;
388
+ }
389
+ return path.resolve(executionRepoPath, relativePath);
390
+ }
391
+ areSameLoopDirs(left, right) {
392
+ return left.pending === right.pending &&
393
+ left.running === right.running &&
394
+ left.finished === right.finished &&
395
+ left.failed === right.failed;
396
+ }
397
+ async ensureLoopDirectoriesExist(directories) {
398
+ const dirs = [
399
+ directories.pending,
400
+ directories.running,
401
+ directories.finished,
402
+ directories.failed,
403
+ ];
404
+ await Promise.all(dirs.map(dir => fs.mkdir(dir, { recursive: true })));
405
+ }
406
+ async copyLoopDirectories(executionDirs, mainDirs) {
407
+ const dirPairs = [
408
+ { source: executionDirs.pending, dest: mainDirs.pending },
409
+ { source: executionDirs.running, dest: mainDirs.running },
410
+ { source: executionDirs.finished, dest: mainDirs.finished },
411
+ { source: executionDirs.failed, dest: mainDirs.failed },
412
+ ];
413
+ for (const { source, dest } of dirPairs) {
414
+ await fs.mkdir(path.dirname(dest), { recursive: true });
415
+ await fs.cp(source, dest, { recursive: true, force: true });
416
+ }
417
+ }
418
+ async _findNextPipelineFile(loopDirs) {
231
419
  try {
232
- const pendingDir = loopingConfig.directories.pending;
420
+ const pendingDir = loopDirs.pending;
233
421
  const files = await fs.readdir(pendingDir);
234
422
  const yamlFiles = files.filter(f => f.endsWith('.yml') || f.endsWith('.yaml'));
235
423
  if (yamlFiles.length === 0) {
@@ -267,19 +455,94 @@ export class PipelineRunner {
267
455
  return destPath;
268
456
  }
269
457
  async _executeSinglePipeline(config, metadata, options) {
270
- const { interactive, loopContext, loopSessionId } = options;
271
- this.notificationManager = options.notificationManager;
272
- const initResult = await this.initializer.initialize(config, {
273
- interactive,
274
- notificationManager: this.notificationManager,
275
- loopContext,
276
- loopSessionId,
277
- metadata
278
- }, this.notify.bind(this), this.notifyStateChange.bind(this));
279
- let { state, parallelExecutor, pipelineBranch, originalBranch, startTime } = initResult;
458
+ const { interactive, verbose, loopContext, loopSessionId, abortController, isFirstLoopIteration, suppressCompletionNotification } = options;
459
+ this.notificationManager = options.notificationManager ||
460
+ (config.notifications ? new NotificationManager(config.notifications) : undefined);
461
+ let initResult;
462
+ try {
463
+ initResult = await this.initializer.initialize(config, {
464
+ interactive,
465
+ verbose,
466
+ notificationManager: this.notificationManager,
467
+ loopContext,
468
+ loopSessionId,
469
+ metadata,
470
+ abortController
471
+ }, this.notify.bind(this), this.notifyStateChange.bind(this));
472
+ }
473
+ catch (error) {
474
+ const errorMessage = error instanceof Error ? error.message : String(error);
475
+ const now = new Date().toISOString();
476
+ if (this.shouldLog(interactive)) {
477
+ console.error(`\n❌ Pipeline initialization failed: ${errorMessage}\n`);
478
+ }
479
+ const failedState = {
480
+ runId: 'init-failed',
481
+ pipelineConfig: config,
482
+ trigger: {
483
+ type: config.trigger,
484
+ commitSha: '',
485
+ timestamp: now
486
+ },
487
+ stages: [
488
+ {
489
+ stageName: 'pipeline-initialization',
490
+ status: 'failed',
491
+ startTime: now,
492
+ endTime: now,
493
+ duration: 0,
494
+ error: {
495
+ message: errorMessage,
496
+ timestamp: now
497
+ }
498
+ }
499
+ ],
500
+ status: 'failed',
501
+ artifacts: {
502
+ initialCommit: '',
503
+ changedFiles: [],
504
+ totalDuration: 0,
505
+ handoverDir: ''
506
+ }
507
+ };
508
+ await this.notify({
509
+ event: 'pipeline.failed',
510
+ pipelineState: failedState,
511
+ metadata: { error: errorMessage }
512
+ });
513
+ return failedState;
514
+ }
515
+ let { state, parallelExecutor, pipelineBranch, worktreePath, executionRepoPath, startTime, pipelineLogger } = initResult;
280
516
  this.notificationManager = initResult.notificationManager;
517
+ if (isFirstLoopIteration && loopContext?.sessionId) {
518
+ const { executionDirs, mainDirs, sessionExecutionDirs } = this.resolveLoopDirectories(loopContext, executionRepoPath, worktreePath);
519
+ const usesSessionDirs = this.areSameLoopDirs(executionDirs, sessionExecutionDirs);
520
+ if (usesSessionDirs) {
521
+ await this.loopStateManager.createSessionDirectories(loopContext.sessionId, executionRepoPath);
522
+ }
523
+ else {
524
+ await this.ensureLoopDirectoriesExist(executionDirs);
525
+ }
526
+ loopContext.directories = executionDirs;
527
+ this.loopExecutionDirs = executionDirs;
528
+ this.loopMainDirs = mainDirs;
529
+ if (this.shouldLog(interactive)) {
530
+ const baseDir = usesSessionDirs
531
+ ? this.loopStateManager.getSessionQueueDir(loopContext.sessionId, executionRepoPath)
532
+ : executionRepoPath;
533
+ console.log(`📁 Created loop directories under: ${baseDir}`);
534
+ }
535
+ }
281
536
  try {
282
- const executionGraph = this.dagPlanner.buildExecutionPlan(config);
537
+ let effectiveConfig = config;
538
+ let loopStageName;
539
+ if (loopContext?.enabled) {
540
+ const injected = this.injectLoopStageIntoConfig(config, state);
541
+ effectiveConfig = injected.modifiedConfig;
542
+ loopStageName = injected.loopStageName;
543
+ state.pipelineConfig = effectiveConfig;
544
+ }
545
+ const executionGraph = this.dagPlanner.buildExecutionPlan(effectiveConfig);
283
546
  if (this.shouldLog(interactive) && executionGraph.plan.groups.length > 0) {
284
547
  console.log(`📊 Execution plan: ${executionGraph.plan.groups.length} groups, ` +
285
548
  `max parallelism: ${executionGraph.plan.maxParallelism}`);
@@ -288,39 +551,146 @@ export class PipelineRunner {
288
551
  }
289
552
  console.log('');
290
553
  }
291
- for (const group of executionGraph.plan.groups) {
292
- const result = await this.groupOrchestrator.processGroup(group, state, config, executionGraph, parallelExecutor, interactive);
293
- state = result.state;
294
- if (result.shouldStopPipeline) {
295
- state.status = 'failed';
554
+ const totalGroups = executionGraph.plan.groups.length;
555
+ let abortedAtGroup;
556
+ for (let groupIndex = 0; groupIndex < totalGroups; groupIndex++) {
557
+ if (abortController?.aborted) {
558
+ state.status = 'aborted';
559
+ abortedAtGroup = groupIndex + 1;
296
560
  break;
297
561
  }
562
+ const group = executionGraph.plan.groups[groupIndex];
563
+ const isLoopGroup = loopStageName && group.stages.some(s => s.name === loopStageName);
564
+ if (isLoopGroup) {
565
+ if (state.status === 'failed' || state.status === 'aborted') {
566
+ break;
567
+ }
568
+ try {
569
+ const instructionLoader = new InstructionLoader(this.repoPath);
570
+ const loopInstructionsPath = config.looping?.instructions;
571
+ const loopTemplateContext = {
572
+ pendingDir: loopContext.directories.pending,
573
+ currentIteration: loopContext.currentIteration,
574
+ maxIterations: loopContext.maxIterations,
575
+ pipelineName: config.name
576
+ };
577
+ const loopSystemPrompt = await instructionLoader.loadLoopInstructions(loopInstructionsPath, loopTemplateContext);
578
+ if (this.shouldLog(interactive)) {
579
+ console.log('🔁 Running loop agent...');
580
+ }
581
+ const result = await this.groupOrchestrator.processGroup(group, state, effectiveConfig, parallelExecutor, interactive, initResult.handoverManager, verbose, { [loopStageName]: loopSystemPrompt });
582
+ state = result.state;
583
+ if (this.shouldLog(interactive)) {
584
+ const loopExecution = state.stages.find((stage) => stage.stageName === loopStageName);
585
+ if (loopExecution?.status === 'success') {
586
+ console.log(`✅ ${loopStageName} (${loopExecution.duration?.toFixed(0) ?? 0}s)`);
587
+ }
588
+ else if (loopExecution) {
589
+ console.log(`⚠️ ${loopStageName} failed (non-fatal): ${loopExecution.error?.message ?? 'unknown'}`);
590
+ }
591
+ }
592
+ }
593
+ catch (error) {
594
+ if (this.shouldLog(interactive)) {
595
+ console.warn(`⚠️ Loop agent error (non-fatal): ${error instanceof Error ? error.message : String(error)}`);
596
+ }
597
+ }
598
+ }
599
+ else {
600
+ const result = await this.groupOrchestrator.processGroup(group, state, effectiveConfig, parallelExecutor, interactive, initResult.handoverManager, verbose);
601
+ state = result.state;
602
+ if (abortController?.aborted) {
603
+ state.status = 'aborted';
604
+ abortedAtGroup = groupIndex + 1;
605
+ break;
606
+ }
607
+ if (result.shouldStopPipeline) {
608
+ state.status = 'failed';
609
+ break;
610
+ }
611
+ }
298
612
  }
299
- if (state.status === 'running') {
613
+ if (abortedAtGroup !== undefined && this.shouldLog(interactive)) {
614
+ console.log(`\n⚠️ Pipeline aborted at group ${abortedAtGroup}/${totalGroups}\n`);
615
+ }
616
+ if (state.status === 'running' && !abortController?.aborted) {
300
617
  state.status = 'completed';
301
618
  }
302
619
  }
303
620
  catch (error) {
304
- state.status = 'failed';
305
- if (this.shouldLog(interactive)) {
306
- console.error(`\n❌ Pipeline failed: ${error}\n`);
621
+ if (error instanceof PipelineAbortError || abortController?.aborted) {
622
+ state.status = 'aborted';
623
+ if (this.shouldLog(interactive)) {
624
+ console.log(`\n⚠️ Pipeline aborted\n`);
625
+ }
626
+ }
627
+ else {
628
+ state.status = 'failed';
629
+ if (this.shouldLog(interactive)) {
630
+ console.error(`\n❌ Pipeline failed: ${error}\n`);
631
+ }
307
632
  }
308
633
  }
309
- state = await this.finalizer.finalize(state, config, pipelineBranch, originalBranch, startTime, interactive, this.notify.bind(this), this.notifyStateChange.bind(this));
634
+ state = await this.finalizer.finalize(state, config, pipelineBranch, worktreePath, executionRepoPath, startTime, interactive, verbose, this.notify.bind(this), this.notifyStateChange.bind(this), { suppressCompletionNotification, pipelineLogger });
310
635
  return state;
311
636
  }
312
637
  async recordIteration(sessionId, state, metadata, triggeredNext) {
313
- const pipelineName = metadata?.sourcePath
314
- ? path.basename(metadata.sourcePath, '.yml')
315
- : state.pipelineConfig.name;
316
- await this.loopStateManager.appendIteration(sessionId, {
317
- iterationNumber: state.loopContext?.currentIteration ?? 1,
638
+ const pipelineName = this.getPipelineName(state.pipelineConfig, metadata);
639
+ const iterationNumber = state.loopContext?.currentIteration ?? 1;
640
+ const updated = await this.loopStateManager.updateIteration(sessionId, iterationNumber, {
318
641
  pipelineName,
319
642
  runId: state.runId,
320
- status: state.status === 'completed' ? 'completed' : 'failed',
643
+ status: state.status === 'completed' ? 'completed' : state.status === 'aborted' ? 'aborted' : 'failed',
321
644
  duration: state.artifacts.totalDuration,
322
645
  triggeredNext
323
646
  });
647
+ if (!updated) {
648
+ await this.loopStateManager.appendIteration(sessionId, {
649
+ iterationNumber,
650
+ pipelineName,
651
+ runId: state.runId,
652
+ status: state.status === 'completed' ? 'completed' : state.status === 'aborted' ? 'aborted' : 'failed',
653
+ duration: state.artifacts.totalDuration,
654
+ triggeredNext
655
+ });
656
+ }
657
+ }
658
+ injectLoopStageIntoConfig(config, state) {
659
+ const loopStageName = this.getUniqueLoopStageName(config, state);
660
+ const loopStage = {
661
+ name: loopStageName,
662
+ agent: '__inline__',
663
+ onFail: 'warn',
664
+ dependsOn: config.agents.map(a => a.name)
665
+ };
666
+ const modifiedConfig = {
667
+ ...config,
668
+ agents: [...config.agents, loopStage]
669
+ };
670
+ return { modifiedConfig, loopStageName };
671
+ }
672
+ getUniqueLoopStageName(config, state) {
673
+ const baseName = 'loop-agent';
674
+ const usedNames = new Set([
675
+ ...config.agents.map((agent) => agent.name),
676
+ ...state.stages.map((stage) => stage.stageName)
677
+ ]);
678
+ if (!usedNames.has(baseName)) {
679
+ return baseName;
680
+ }
681
+ const runIdSuffix = state.runId?.slice(0, 8) ?? 'run';
682
+ let counter = 1;
683
+ let candidate = `${baseName}-${runIdSuffix}`;
684
+ while (usedNames.has(candidate)) {
685
+ candidate = `${baseName}-${runIdSuffix}-${counter}`;
686
+ counter += 1;
687
+ }
688
+ return candidate;
689
+ }
690
+ getPipelineName(config, metadata) {
691
+ return metadata?.sourcePath
692
+ ? path.basename(metadata.sourcePath, '.yml')
693
+ : config.name;
324
694
  }
325
695
  }
326
696
  //# sourceMappingURL=pipeline-runner.js.map