@renseiai/agentfactory 0.8.7 → 0.8.9

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 (276) hide show
  1. package/dist/src/config/index.d.ts +1 -1
  2. package/dist/src/config/index.d.ts.map +1 -1
  3. package/dist/src/config/index.js +1 -1
  4. package/dist/src/config/repository-config.d.ts +37 -0
  5. package/dist/src/config/repository-config.d.ts.map +1 -1
  6. package/dist/src/config/repository-config.js +47 -0
  7. package/dist/src/config/repository-config.test.js +140 -1
  8. package/dist/src/governor/decision-engine.d.ts +3 -0
  9. package/dist/src/governor/decision-engine.d.ts.map +1 -1
  10. package/dist/src/governor/decision-engine.js +11 -0
  11. package/dist/src/governor/decision-engine.test.js +33 -0
  12. package/dist/src/governor/event-types.d.ts +18 -1
  13. package/dist/src/governor/event-types.d.ts.map +1 -1
  14. package/dist/src/governor/event-types.js +4 -0
  15. package/dist/src/governor/governor-types.d.ts +1 -1
  16. package/dist/src/governor/governor-types.d.ts.map +1 -1
  17. package/dist/src/governor/governor.d.ts +17 -1
  18. package/dist/src/governor/governor.d.ts.map +1 -1
  19. package/dist/src/governor/governor.js +112 -1
  20. package/dist/src/governor/governor.test.js +155 -0
  21. package/dist/src/index.d.ts +1 -0
  22. package/dist/src/index.d.ts.map +1 -1
  23. package/dist/src/index.js +1 -0
  24. package/dist/src/merge-queue/adapters/github-native.d.ts +22 -0
  25. package/dist/src/merge-queue/adapters/github-native.d.ts.map +1 -0
  26. package/dist/src/merge-queue/adapters/github-native.js +243 -0
  27. package/dist/src/merge-queue/adapters/github-native.test.d.ts +2 -0
  28. package/dist/src/merge-queue/adapters/github-native.test.d.ts.map +1 -0
  29. package/dist/src/merge-queue/adapters/github-native.test.js +384 -0
  30. package/dist/src/merge-queue/index.d.ts +18 -0
  31. package/dist/src/merge-queue/index.d.ts.map +1 -0
  32. package/dist/src/merge-queue/index.js +28 -0
  33. package/dist/src/merge-queue/merge-queue.integration.test.d.ts +2 -0
  34. package/dist/src/merge-queue/merge-queue.integration.test.d.ts.map +1 -0
  35. package/dist/src/merge-queue/merge-queue.integration.test.js +128 -0
  36. package/dist/src/merge-queue/types.d.ts +48 -0
  37. package/dist/src/merge-queue/types.d.ts.map +1 -0
  38. package/dist/src/merge-queue/types.js +8 -0
  39. package/dist/src/orchestrator/artifact-tracker.d.ts +93 -0
  40. package/dist/src/orchestrator/artifact-tracker.d.ts.map +1 -0
  41. package/dist/src/orchestrator/artifact-tracker.js +235 -0
  42. package/dist/src/orchestrator/artifact-tracker.test.d.ts +2 -0
  43. package/dist/src/orchestrator/artifact-tracker.test.d.ts.map +1 -0
  44. package/dist/src/orchestrator/artifact-tracker.test.js +189 -0
  45. package/dist/src/orchestrator/context-manager.d.ts +72 -0
  46. package/dist/src/orchestrator/context-manager.d.ts.map +1 -0
  47. package/dist/src/orchestrator/context-manager.js +120 -0
  48. package/dist/src/orchestrator/context-manager.test.d.ts +2 -0
  49. package/dist/src/orchestrator/context-manager.test.d.ts.map +1 -0
  50. package/dist/src/orchestrator/context-manager.test.js +137 -0
  51. package/dist/src/orchestrator/index.d.ts +8 -2
  52. package/dist/src/orchestrator/index.d.ts.map +1 -1
  53. package/dist/src/orchestrator/index.js +8 -1
  54. package/dist/src/orchestrator/issue-tracker-client.d.ts +4 -0
  55. package/dist/src/orchestrator/issue-tracker-client.d.ts.map +1 -1
  56. package/dist/src/orchestrator/orchestrator.d.ts +12 -0
  57. package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
  58. package/dist/src/orchestrator/orchestrator.js +282 -2
  59. package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -1
  60. package/dist/src/orchestrator/parse-work-result.js +6 -0
  61. package/dist/src/orchestrator/parse-work-result.test.js +19 -0
  62. package/dist/src/orchestrator/state-recovery.d.ts +21 -2
  63. package/dist/src/orchestrator/state-recovery.d.ts.map +1 -1
  64. package/dist/src/orchestrator/state-recovery.js +54 -2
  65. package/dist/src/orchestrator/state-recovery.test.js +106 -2
  66. package/dist/src/orchestrator/state-types.d.ts +62 -0
  67. package/dist/src/orchestrator/state-types.d.ts.map +1 -1
  68. package/dist/src/orchestrator/state-types.js +5 -1
  69. package/dist/src/orchestrator/summary-builder.d.ts +47 -0
  70. package/dist/src/orchestrator/summary-builder.d.ts.map +1 -0
  71. package/dist/src/orchestrator/summary-builder.js +240 -0
  72. package/dist/src/orchestrator/summary-builder.test.d.ts +2 -0
  73. package/dist/src/orchestrator/summary-builder.test.d.ts.map +1 -0
  74. package/dist/src/orchestrator/summary-builder.test.js +236 -0
  75. package/dist/src/orchestrator/types.d.ts +2 -0
  76. package/dist/src/orchestrator/types.d.ts.map +1 -1
  77. package/dist/src/orchestrator/work-types.d.ts +1 -1
  78. package/dist/src/orchestrator/work-types.d.ts.map +1 -1
  79. package/dist/src/providers/index.d.ts +64 -1
  80. package/dist/src/providers/index.d.ts.map +1 -1
  81. package/dist/src/providers/index.js +132 -1
  82. package/dist/src/providers/index.test.js +340 -2
  83. package/dist/src/routing/index.d.ts +7 -0
  84. package/dist/src/routing/index.d.ts.map +1 -0
  85. package/dist/src/routing/index.js +6 -0
  86. package/dist/src/routing/observation-recorder.d.ts +19 -0
  87. package/dist/src/routing/observation-recorder.d.ts.map +1 -0
  88. package/dist/src/routing/observation-recorder.js +73 -0
  89. package/dist/src/routing/observation-recorder.test.d.ts +2 -0
  90. package/dist/src/routing/observation-recorder.test.d.ts.map +1 -0
  91. package/dist/src/routing/observation-recorder.test.js +322 -0
  92. package/dist/src/routing/observation-store.d.ts +40 -0
  93. package/dist/src/routing/observation-store.d.ts.map +1 -0
  94. package/dist/src/routing/observation-store.js +1 -0
  95. package/dist/src/routing/observation-store.test.d.ts +2 -0
  96. package/dist/src/routing/observation-store.test.d.ts.map +1 -0
  97. package/dist/src/routing/observation-store.test.js +138 -0
  98. package/dist/src/routing/posterior-store.d.ts +12 -0
  99. package/dist/src/routing/posterior-store.d.ts.map +1 -0
  100. package/dist/src/routing/posterior-store.js +13 -0
  101. package/dist/src/routing/posterior-store.test.d.ts +2 -0
  102. package/dist/src/routing/posterior-store.test.d.ts.map +1 -0
  103. package/dist/src/routing/posterior-store.test.js +37 -0
  104. package/dist/src/routing/reward.d.ts +16 -0
  105. package/dist/src/routing/reward.d.ts.map +1 -0
  106. package/dist/src/routing/reward.js +29 -0
  107. package/dist/src/routing/reward.test.d.ts +2 -0
  108. package/dist/src/routing/reward.test.d.ts.map +1 -0
  109. package/dist/src/routing/reward.test.js +210 -0
  110. package/dist/src/routing/routing-engine.d.ts +20 -0
  111. package/dist/src/routing/routing-engine.d.ts.map +1 -0
  112. package/dist/src/routing/routing-engine.js +113 -0
  113. package/dist/src/routing/routing-engine.test.d.ts +2 -0
  114. package/dist/src/routing/routing-engine.test.d.ts.map +1 -0
  115. package/dist/src/routing/routing-engine.test.js +310 -0
  116. package/dist/src/routing/types.d.ts +157 -0
  117. package/dist/src/routing/types.d.ts.map +1 -0
  118. package/dist/src/routing/types.js +68 -0
  119. package/dist/src/routing/types.test.d.ts +2 -0
  120. package/dist/src/routing/types.test.d.ts.map +1 -0
  121. package/dist/src/routing/types.test.js +184 -0
  122. package/dist/src/templates/registry.test.js +2 -2
  123. package/dist/src/templates/types.d.ts +5 -0
  124. package/dist/src/templates/types.d.ts.map +1 -1
  125. package/dist/src/templates/types.js +3 -0
  126. package/dist/src/workflow/agent-cancellation.d.ts +37 -0
  127. package/dist/src/workflow/agent-cancellation.d.ts.map +1 -0
  128. package/dist/src/workflow/agent-cancellation.js +41 -0
  129. package/dist/src/workflow/agent-cancellation.test.d.ts +2 -0
  130. package/dist/src/workflow/agent-cancellation.test.d.ts.map +1 -0
  131. package/dist/src/workflow/agent-cancellation.test.js +86 -0
  132. package/dist/src/workflow/branching-router.d.ts +38 -0
  133. package/dist/src/workflow/branching-router.d.ts.map +1 -0
  134. package/dist/src/workflow/branching-router.js +52 -0
  135. package/dist/src/workflow/branching-router.test.d.ts +2 -0
  136. package/dist/src/workflow/branching-router.test.d.ts.map +1 -0
  137. package/dist/src/workflow/branching-router.test.js +209 -0
  138. package/dist/src/workflow/concurrency-semaphore.d.ts +21 -0
  139. package/dist/src/workflow/concurrency-semaphore.d.ts.map +1 -0
  140. package/dist/src/workflow/concurrency-semaphore.js +46 -0
  141. package/dist/src/workflow/concurrency-semaphore.test.d.ts +2 -0
  142. package/dist/src/workflow/concurrency-semaphore.test.d.ts.map +1 -0
  143. package/dist/src/workflow/concurrency-semaphore.test.js +183 -0
  144. package/dist/src/workflow/duration.d.ts +28 -0
  145. package/dist/src/workflow/duration.d.ts.map +1 -0
  146. package/dist/src/workflow/duration.js +57 -0
  147. package/dist/src/workflow/duration.test.d.ts +2 -0
  148. package/dist/src/workflow/duration.test.d.ts.map +1 -0
  149. package/dist/src/workflow/duration.test.js +74 -0
  150. package/dist/src/workflow/expression/ast.d.ts +53 -0
  151. package/dist/src/workflow/expression/ast.d.ts.map +1 -0
  152. package/dist/src/workflow/expression/ast.js +8 -0
  153. package/dist/src/workflow/expression/context.d.ts +40 -0
  154. package/dist/src/workflow/expression/context.d.ts.map +1 -0
  155. package/dist/src/workflow/expression/context.js +37 -0
  156. package/dist/src/workflow/expression/evaluator.d.ts +28 -0
  157. package/dist/src/workflow/expression/evaluator.d.ts.map +1 -0
  158. package/dist/src/workflow/expression/evaluator.js +165 -0
  159. package/dist/src/workflow/expression/evaluator.test.d.ts +2 -0
  160. package/dist/src/workflow/expression/evaluator.test.d.ts.map +1 -0
  161. package/dist/src/workflow/expression/evaluator.test.js +792 -0
  162. package/dist/src/workflow/expression/expression.test.d.ts +2 -0
  163. package/dist/src/workflow/expression/expression.test.d.ts.map +1 -0
  164. package/dist/src/workflow/expression/expression.test.js +516 -0
  165. package/dist/src/workflow/expression/helpers.d.ts +21 -0
  166. package/dist/src/workflow/expression/helpers.d.ts.map +1 -0
  167. package/dist/src/workflow/expression/helpers.js +56 -0
  168. package/dist/src/workflow/expression/index.d.ts +55 -0
  169. package/dist/src/workflow/expression/index.d.ts.map +1 -0
  170. package/dist/src/workflow/expression/index.js +71 -0
  171. package/dist/src/workflow/expression/lexer.d.ts +37 -0
  172. package/dist/src/workflow/expression/lexer.d.ts.map +1 -0
  173. package/dist/src/workflow/expression/lexer.js +166 -0
  174. package/dist/src/workflow/expression/parser.d.ts +23 -0
  175. package/dist/src/workflow/expression/parser.d.ts.map +1 -0
  176. package/dist/src/workflow/expression/parser.js +181 -0
  177. package/dist/src/workflow/gate-state.d.ts +115 -0
  178. package/dist/src/workflow/gate-state.d.ts.map +1 -0
  179. package/dist/src/workflow/gate-state.js +185 -0
  180. package/dist/src/workflow/gate-state.test.d.ts +2 -0
  181. package/dist/src/workflow/gate-state.test.d.ts.map +1 -0
  182. package/dist/src/workflow/gate-state.test.js +251 -0
  183. package/dist/src/workflow/gates/gate-evaluator.d.ts +119 -0
  184. package/dist/src/workflow/gates/gate-evaluator.d.ts.map +1 -0
  185. package/dist/src/workflow/gates/gate-evaluator.js +243 -0
  186. package/dist/src/workflow/gates/gate-evaluator.test.d.ts +2 -0
  187. package/dist/src/workflow/gates/gate-evaluator.test.d.ts.map +1 -0
  188. package/dist/src/workflow/gates/gate-evaluator.test.js +240 -0
  189. package/dist/src/workflow/gates/signal-gate.d.ts +114 -0
  190. package/dist/src/workflow/gates/signal-gate.d.ts.map +1 -0
  191. package/dist/src/workflow/gates/signal-gate.js +216 -0
  192. package/dist/src/workflow/gates/signal-gate.test.d.ts +2 -0
  193. package/dist/src/workflow/gates/signal-gate.test.d.ts.map +1 -0
  194. package/dist/src/workflow/gates/signal-gate.test.js +199 -0
  195. package/dist/src/workflow/gates/timeout-engine.d.ts +96 -0
  196. package/dist/src/workflow/gates/timeout-engine.d.ts.map +1 -0
  197. package/dist/src/workflow/gates/timeout-engine.js +162 -0
  198. package/dist/src/workflow/gates/timeout-engine.test.d.ts +2 -0
  199. package/dist/src/workflow/gates/timeout-engine.test.d.ts.map +1 -0
  200. package/dist/src/workflow/gates/timeout-engine.test.js +186 -0
  201. package/dist/src/workflow/gates/timer-gate.d.ts +125 -0
  202. package/dist/src/workflow/gates/timer-gate.d.ts.map +1 -0
  203. package/dist/src/workflow/gates/timer-gate.js +381 -0
  204. package/dist/src/workflow/gates/timer-gate.test.d.ts +2 -0
  205. package/dist/src/workflow/gates/timer-gate.test.d.ts.map +1 -0
  206. package/dist/src/workflow/gates/timer-gate.test.js +211 -0
  207. package/dist/src/workflow/gates/webhook-gate.d.ts +132 -0
  208. package/dist/src/workflow/gates/webhook-gate.d.ts.map +1 -0
  209. package/dist/src/workflow/gates/webhook-gate.js +216 -0
  210. package/dist/src/workflow/gates/webhook-gate.test.d.ts +2 -0
  211. package/dist/src/workflow/gates/webhook-gate.test.d.ts.map +1 -0
  212. package/dist/src/workflow/gates/webhook-gate.test.js +182 -0
  213. package/dist/src/workflow/index.d.ts +31 -3
  214. package/dist/src/workflow/index.d.ts.map +1 -1
  215. package/dist/src/workflow/index.js +20 -1
  216. package/dist/src/workflow/parallelism-executor.d.ts +25 -0
  217. package/dist/src/workflow/parallelism-executor.d.ts.map +1 -0
  218. package/dist/src/workflow/parallelism-executor.js +53 -0
  219. package/dist/src/workflow/parallelism-executor.test.d.ts +2 -0
  220. package/dist/src/workflow/parallelism-executor.test.d.ts.map +1 -0
  221. package/dist/src/workflow/parallelism-executor.test.js +191 -0
  222. package/dist/src/workflow/parallelism-types.d.ts +80 -0
  223. package/dist/src/workflow/parallelism-types.d.ts.map +1 -0
  224. package/dist/src/workflow/parallelism-types.js +8 -0
  225. package/dist/src/workflow/phase-context-injector.d.ts +29 -0
  226. package/dist/src/workflow/phase-context-injector.d.ts.map +1 -0
  227. package/dist/src/workflow/phase-context-injector.js +43 -0
  228. package/dist/src/workflow/phase-context-injector.test.d.ts +2 -0
  229. package/dist/src/workflow/phase-context-injector.test.d.ts.map +1 -0
  230. package/dist/src/workflow/phase-context-injector.test.js +123 -0
  231. package/dist/src/workflow/phase-output-collector.d.ts +39 -0
  232. package/dist/src/workflow/phase-output-collector.d.ts.map +1 -0
  233. package/dist/src/workflow/phase-output-collector.js +141 -0
  234. package/dist/src/workflow/phase-output-collector.test.d.ts +2 -0
  235. package/dist/src/workflow/phase-output-collector.test.d.ts.map +1 -0
  236. package/dist/src/workflow/phase-output-collector.test.js +179 -0
  237. package/dist/src/workflow/retry-resolver.d.ts +51 -0
  238. package/dist/src/workflow/retry-resolver.d.ts.map +1 -0
  239. package/dist/src/workflow/retry-resolver.js +70 -0
  240. package/dist/src/workflow/retry-resolver.test.d.ts +2 -0
  241. package/dist/src/workflow/retry-resolver.test.d.ts.map +1 -0
  242. package/dist/src/workflow/retry-resolver.test.js +149 -0
  243. package/dist/src/workflow/strategies/fan-in-strategy.d.ts +21 -0
  244. package/dist/src/workflow/strategies/fan-in-strategy.d.ts.map +1 -0
  245. package/dist/src/workflow/strategies/fan-in-strategy.js +92 -0
  246. package/dist/src/workflow/strategies/fan-in-strategy.test.d.ts +2 -0
  247. package/dist/src/workflow/strategies/fan-in-strategy.test.d.ts.map +1 -0
  248. package/dist/src/workflow/strategies/fan-in-strategy.test.js +182 -0
  249. package/dist/src/workflow/strategies/fan-out-strategy.d.ts +16 -0
  250. package/dist/src/workflow/strategies/fan-out-strategy.d.ts.map +1 -0
  251. package/dist/src/workflow/strategies/fan-out-strategy.js +47 -0
  252. package/dist/src/workflow/strategies/fan-out-strategy.test.d.ts +2 -0
  253. package/dist/src/workflow/strategies/fan-out-strategy.test.d.ts.map +1 -0
  254. package/dist/src/workflow/strategies/fan-out-strategy.test.js +97 -0
  255. package/dist/src/workflow/strategies/index.d.ts +4 -0
  256. package/dist/src/workflow/strategies/index.d.ts.map +1 -0
  257. package/dist/src/workflow/strategies/index.js +3 -0
  258. package/dist/src/workflow/strategies/race-strategy.d.ts +19 -0
  259. package/dist/src/workflow/strategies/race-strategy.d.ts.map +1 -0
  260. package/dist/src/workflow/strategies/race-strategy.js +92 -0
  261. package/dist/src/workflow/strategies/race-strategy.test.d.ts +2 -0
  262. package/dist/src/workflow/strategies/race-strategy.test.d.ts.map +1 -0
  263. package/dist/src/workflow/strategies/race-strategy.test.js +318 -0
  264. package/dist/src/workflow/transition-engine.d.ts +3 -1
  265. package/dist/src/workflow/transition-engine.d.ts.map +1 -1
  266. package/dist/src/workflow/transition-engine.js +26 -7
  267. package/dist/src/workflow/transition-engine.test.js +215 -11
  268. package/dist/src/workflow/workflow-registry.d.ts +46 -1
  269. package/dist/src/workflow/workflow-registry.d.ts.map +1 -1
  270. package/dist/src/workflow/workflow-registry.js +74 -0
  271. package/dist/src/workflow/workflow-registry.test.js +54 -0
  272. package/dist/src/workflow/workflow-types.d.ts +330 -12
  273. package/dist/src/workflow/workflow-types.d.ts.map +1 -1
  274. package/dist/src/workflow/workflow-types.js +100 -5
  275. package/dist/src/workflow/workflow-types.test.js +293 -2
  276. package/package.json +2 -2
@@ -13,6 +13,7 @@ import { initializeAgentDir, writeState, updateState, writeTodos, createInitialS
13
13
  import { createHeartbeatWriter, getHeartbeatIntervalFromEnv } from './heartbeat-writer.js';
14
14
  import { createProgressLogger } from './progress-logger.js';
15
15
  import { createSessionLogger } from './session-logger.js';
16
+ import { ContextManager } from './context-manager.js';
16
17
  import { isSessionLoggingEnabled, getLogAnalysisConfig } from './log-config.js';
17
18
  import { parseWorkResult } from './parse-work-result.js';
18
19
  import { createActivityEmitter } from './activity-emitter.js';
@@ -21,6 +22,7 @@ import { createLogger } from '../logger.js';
21
22
  import { TemplateRegistry, createToolPermissionAdapter } from '../templates/index.js';
22
23
  import { loadRepositoryConfig, getProjectConfig, getProvidersConfig } from '../config/index.js';
23
24
  import { ToolRegistry } from '../tools/index.js';
25
+ import { createMergeQueueAdapter } from '../merge-queue/index.js';
24
26
  // Default inactivity timeout: 5 minutes
25
27
  const DEFAULT_INACTIVITY_TIMEOUT_MS = 300000;
26
28
  // Default max session timeout: unlimited (undefined)
@@ -368,6 +370,53 @@ function checkForIncompleteWork(worktreePath) {
368
370
  };
369
371
  }
370
372
  }
373
+ function checkForPushedWorkWithoutPR(worktreePath) {
374
+ try {
375
+ const currentBranch = execSync('git branch --show-current', {
376
+ cwd: worktreePath,
377
+ encoding: 'utf-8',
378
+ timeout: 10000,
379
+ }).trim();
380
+ // If on main, no work to check
381
+ if (currentBranch === 'main' || currentBranch === 'master') {
382
+ return { hasPushedWork: false };
383
+ }
384
+ // Count commits ahead of main
385
+ const aheadOutput = execSync(`git rev-list --count main..HEAD`, {
386
+ cwd: worktreePath,
387
+ encoding: 'utf-8',
388
+ timeout: 10000,
389
+ }).trim();
390
+ const aheadCount = parseInt(aheadOutput, 10);
391
+ if (aheadCount === 0) {
392
+ return { hasPushedWork: false };
393
+ }
394
+ // Branch has commits ahead of main — check if they've been pushed
395
+ try {
396
+ const remoteRef = execSync(`git ls-remote --heads origin ${currentBranch}`, {
397
+ cwd: worktreePath,
398
+ encoding: 'utf-8',
399
+ timeout: 10000,
400
+ }).trim();
401
+ if (remoteRef.length > 0) {
402
+ // Branch exists on remote with commits ahead of main — likely missing a PR
403
+ return {
404
+ hasPushedWork: true,
405
+ branch: currentBranch,
406
+ details: `Branch \`${currentBranch}\` has ${aheadCount} commit(s) ahead of main and has been pushed to the remote, but no PR was detected.`,
407
+ };
408
+ }
409
+ }
410
+ catch {
411
+ // ls-remote failed — can't confirm remote state
412
+ }
413
+ return { hasPushedWork: false };
414
+ }
415
+ catch {
416
+ // Git commands failed — don't block on our check failing
417
+ return { hasPushedWork: false };
418
+ }
419
+ }
371
420
  /**
372
421
  * Generate a prompt for the agent based on work type
373
422
  *
@@ -671,6 +720,13 @@ IMPORTANT: If you encounter "exceeds maximum allowed tokens" error when reading
671
720
  - Avoid reading auto-generated files like payload-types.ts (use Grep instead)
672
721
  See the "Working with Large Files" section in the project documentation (CLAUDE.md / AGENTS.md) for details.${LINEAR_CLI_INSTRUCTION}`;
673
722
  break;
723
+ case 'merge':
724
+ basePrompt = `Handle merge queue operations for ${identifier}.
725
+ Check PR merge readiness (CI status, approvals).
726
+ Attempt rebase onto latest main.
727
+ Resolve conflicts using mergiraf-enhanced git merge if available.
728
+ Push updated branch and trigger merge via configured merge queue provider.${LINEAR_CLI_INSTRUCTION}`;
729
+ break;
674
730
  }
675
731
  // Inject workflow failure context for retries
676
732
  if (options?.failureContext) {
@@ -698,6 +754,7 @@ const WORK_TYPE_SUFFIX = {
698
754
  'refinement-coordination': 'REF-COORD',
699
755
  'qa-coordination': 'QA-COORD',
700
756
  'acceptance-coordination': 'AC-COORD',
757
+ merge: 'MRG',
701
758
  };
702
759
  /**
703
760
  * Generate a worktree identifier that includes the work type suffix
@@ -761,6 +818,7 @@ export class AgentOrchestrator {
761
818
  progressLoggers = new Map();
762
819
  // Session loggers per agent for verbose analysis logging
763
820
  sessionLoggers = new Map();
821
+ contextManagers = new Map();
764
822
  // Template registry for configurable workflow prompts
765
823
  templateRegistry;
766
824
  // Allowlisted project names from .agentfactory/config.yaml
@@ -781,6 +839,8 @@ export class AgentOrchestrator {
781
839
  validateCommand;
782
840
  // Tool plugin registry for in-process agent tools
783
841
  toolRegistry;
842
+ // Merge queue adapter for automated merge operations (initialized from config or repo config)
843
+ mergeQueueAdapter;
784
844
  // Git repository root for running git commands (resolved from worktreePath or cwd)
785
845
  gitRoot;
786
846
  constructor(config = {}, events = {}) {
@@ -892,12 +952,26 @@ export class AgentOrchestrator {
892
952
  }
893
953
  // Store providers config for per-spawn resolution
894
954
  this.configProviders = getProvidersConfig(repoConfig);
955
+ // Initialize merge queue adapter from repository config
956
+ if (repoConfig.mergeQueue?.enabled && !config.mergeQueueAdapter) {
957
+ try {
958
+ this.mergeQueueAdapter = createMergeQueueAdapter(repoConfig.mergeQueue.provider ?? 'github-native');
959
+ console.log(`[orchestrator] Merge queue adapter initialized: ${repoConfig.mergeQueue.provider ?? 'github-native'}`);
960
+ }
961
+ catch (error) {
962
+ console.warn(`[orchestrator] Failed to initialize merge queue adapter: ${error instanceof Error ? error.message : String(error)}`);
963
+ }
964
+ }
895
965
  }
896
966
  }
897
967
  }
898
968
  catch (err) {
899
969
  console.warn('[orchestrator] Failed to load .agentfactory/config.yaml:', err instanceof Error ? err.message : err);
900
970
  }
971
+ // Accept merge queue adapter passed directly via config (takes precedence over repo config)
972
+ if (config.mergeQueueAdapter) {
973
+ this.mergeQueueAdapter = config.mergeQueueAdapter;
974
+ }
901
975
  // Initialize tool plugin registry with injected plugins
902
976
  this.toolRegistry = new ToolRegistry();
903
977
  if (config.toolPlugins) {
@@ -988,6 +1062,11 @@ export class AgentOrchestrator {
988
1062
  for (const issue of allIssues) {
989
1063
  if (results.length >= maxIssues)
990
1064
  break;
1065
+ // Skip sub-issues — coordinators manage their lifecycle, not the backlog scanner
1066
+ if (issue.parentId) {
1067
+ console.log(`[orchestrator] Skipping sub-issue ${issue.identifier} — managed by parent coordinator`);
1068
+ continue;
1069
+ }
991
1070
  // Filter by allowedProjects from .agentfactory/config.yaml
992
1071
  if (this.allowedProjects && this.allowedProjects.length > 0) {
993
1072
  if (!issue.projectName || !this.allowedProjects.includes(issue.projectName)) {
@@ -1423,6 +1502,8 @@ export class AgentOrchestrator {
1423
1502
  }
1424
1503
  // Write helper scripts into .agent/ for agent use
1425
1504
  this.writeWorktreeHelpers(worktreePath);
1505
+ // Configure mergiraf merge driver if enabled
1506
+ this.configureMergiraf(worktreePath);
1426
1507
  return { worktreePath, worktreeIdentifier };
1427
1508
  }
1428
1509
  /**
@@ -1503,6 +1584,39 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
1503
1584
  console.warn(`Failed to write worktree helper scripts: ${error instanceof Error ? error.message : String(error)}`);
1504
1585
  }
1505
1586
  }
1587
+ /**
1588
+ * Configure mergiraf as the git merge driver in a worktree.
1589
+ * Falls back silently to default git merge if mergiraf is not installed.
1590
+ */
1591
+ configureMergiraf(worktreePath) {
1592
+ // Check if mergiraf is disabled via config
1593
+ if (this.repoConfig?.mergeDriver === 'default') {
1594
+ return;
1595
+ }
1596
+ try {
1597
+ // Check if mergiraf binary is available
1598
+ execSync('which mergiraf', { stdio: 'pipe', encoding: 'utf-8' });
1599
+ }
1600
+ catch {
1601
+ // mergiraf not installed — fall back to default merge silently
1602
+ console.log('mergiraf not found on PATH, using default git merge driver');
1603
+ return;
1604
+ }
1605
+ try {
1606
+ // Register mergiraf as the merge driver in this worktree
1607
+ // This sets up .git/config merge driver entries and .gitattributes
1608
+ execSync('mergiraf register', {
1609
+ stdio: 'pipe',
1610
+ encoding: 'utf-8',
1611
+ cwd: worktreePath,
1612
+ });
1613
+ console.log(`mergiraf registered as merge driver in ${worktreePath}`);
1614
+ }
1615
+ catch (error) {
1616
+ // Log warning but don't fail — merge driver is non-critical
1617
+ console.warn(`Failed to register mergiraf in worktree: ${error instanceof Error ? error.message : String(error)}`);
1618
+ }
1619
+ }
1506
1620
  /**
1507
1621
  * Link dependencies from the main repo into a worktree via symlinks.
1508
1622
  *
@@ -1793,6 +1907,9 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
1793
1907
  this.sessionLoggers.set(issueId, sessionLogger);
1794
1908
  log.debug('Session logging initialized', { logsDir: logConfig.logsDir });
1795
1909
  }
1910
+ // Initialize context manager for context window management
1911
+ const contextManager = ContextManager.load(worktreePath);
1912
+ this.contextManagers.set(issueId, contextManager);
1796
1913
  log.debug('State persistence initialized', { agentDir: resolve(worktreePath, '.agent') });
1797
1914
  }
1798
1915
  catch (stateError) {
@@ -1996,6 +2113,41 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
1996
2113
  if (emitter) {
1997
2114
  await emitter.flush();
1998
2115
  }
2116
+ // Post-exit PR detection: if the agent exited without a detected PR URL,
2117
+ // check GitHub directly in case the PR was created but the output wasn't captured
2118
+ if (agent.status === 'completed' && !agent.pullRequestUrl && agent.worktreePath) {
2119
+ const postExitWorkType = agent.workType ?? 'development';
2120
+ const isPostExitCodeProducing = postExitWorkType === 'development' || postExitWorkType === 'inflight';
2121
+ if (isPostExitCodeProducing) {
2122
+ try {
2123
+ const currentBranch = execSync('git branch --show-current', {
2124
+ cwd: agent.worktreePath,
2125
+ encoding: 'utf-8',
2126
+ timeout: 10000,
2127
+ }).trim();
2128
+ if (currentBranch && currentBranch !== 'main' && currentBranch !== 'master') {
2129
+ const prJson = execSync(`gh pr list --head "${currentBranch}" --json url --limit 1`, {
2130
+ cwd: agent.worktreePath,
2131
+ encoding: 'utf-8',
2132
+ timeout: 15000,
2133
+ }).trim();
2134
+ const prs = JSON.parse(prJson);
2135
+ if (prs.length > 0 && prs[0].url) {
2136
+ log?.info('Post-exit PR detection found existing PR', { prUrl: prs[0].url, branch: currentBranch });
2137
+ agent.pullRequestUrl = prs[0].url;
2138
+ if (sessionId) {
2139
+ await this.updateSessionPullRequest(sessionId, prs[0].url, agent);
2140
+ }
2141
+ }
2142
+ }
2143
+ }
2144
+ catch (error) {
2145
+ log?.debug('Post-exit PR detection failed (non-fatal)', {
2146
+ error: error instanceof Error ? error.message : String(error),
2147
+ });
2148
+ }
2149
+ }
2150
+ }
1999
2151
  // Update Linear status based on work type if auto-transition is enabled
2000
2152
  if (agent.status === 'completed' && this.config.autoTransition) {
2001
2153
  const workType = agent.workType ?? 'development';
@@ -2073,8 +2225,32 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2073
2225
  // Do NOT set targetStatus — leave issue in current state
2074
2226
  }
2075
2227
  else {
2076
- // No PR but worktree is clean either no changes needed or agent cleaned up
2077
- targetStatus = this.statusMappings.workTypeCompleteStatus[workType];
2228
+ // Worktree is clean (no uncommitted/unpushed changes) but check if branch
2229
+ // has commits ahead of main that should have resulted in a PR
2230
+ const hasPushedWork = checkForPushedWorkWithoutPR(agent.worktreePath);
2231
+ if (hasPushedWork.hasPushedWork) {
2232
+ // Agent pushed commits to remote but never created a PR — block promotion
2233
+ log?.error('Code-producing agent pushed commits but no PR was created — blocking promotion', {
2234
+ workType,
2235
+ details: hasPushedWork.details,
2236
+ });
2237
+ try {
2238
+ await this.client.createComment(issueId, `⚠️ **Agent completed and pushed code, but no PR was created.**\n\n` +
2239
+ `${hasPushedWork.details}\n\n` +
2240
+ `**Issue status was NOT promoted** because work cannot be reviewed without a PR.\n\n` +
2241
+ `The branch has been pushed to the remote. To recover:\n` +
2242
+ `\`\`\`bash\ngh pr create --head ${hasPushedWork.branch} --title "feat: <title>" --body "..."\n\`\`\`\n` +
2243
+ `Or re-trigger the agent to complete the PR creation step.`);
2244
+ }
2245
+ catch {
2246
+ // Best-effort comment
2247
+ }
2248
+ // Do NOT set targetStatus — leave issue in current state
2249
+ }
2250
+ else {
2251
+ // No PR and no pushed commits ahead of main — genuinely clean completion
2252
+ targetStatus = this.statusMappings.workTypeCompleteStatus[workType];
2253
+ }
2078
2254
  }
2079
2255
  }
2080
2256
  else {
@@ -2096,6 +2272,28 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2096
2272
  else if (!isResultSensitive) {
2097
2273
  log?.info('No auto-transition configured for work type', { workType });
2098
2274
  }
2275
+ // Merge queue: enqueue PR after successful merge work
2276
+ if (workType === 'merge' && this.mergeQueueAdapter && agent.pullRequestUrl) {
2277
+ try {
2278
+ const prMatch = agent.pullRequestUrl.match(/\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
2279
+ if (prMatch) {
2280
+ const [, owner, repo, prNum] = prMatch;
2281
+ const canEnqueue = await this.mergeQueueAdapter.canEnqueue(owner, repo, parseInt(prNum, 10));
2282
+ if (canEnqueue) {
2283
+ const status = await this.mergeQueueAdapter.enqueue(owner, repo, parseInt(prNum, 10));
2284
+ log?.info('PR enqueued in merge queue', { owner, repo, prNumber: prNum, state: status.state });
2285
+ }
2286
+ else {
2287
+ log?.info('PR not eligible for merge queue', { owner, repo, prNumber: prNum });
2288
+ }
2289
+ }
2290
+ }
2291
+ catch (error) {
2292
+ log?.warn('Failed to enqueue PR in merge queue', {
2293
+ error: error instanceof Error ? error.message : String(error),
2294
+ });
2295
+ }
2296
+ }
2099
2297
  // Unassign agent from issue for clean handoff visibility
2100
2298
  // This enables automated QA pickup via webhook
2101
2299
  // Skip unassignment for research work (user should decide when to move to backlog)
@@ -2295,6 +2493,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2295
2493
  }
2296
2494
  else if (event.subtype === 'compact_boundary') {
2297
2495
  log?.debug('Context compacted');
2496
+ // Trigger incremental summarization on compaction boundary
2497
+ this.contextManagers.get(issueId)?.handleCompaction();
2298
2498
  }
2299
2499
  else if (event.subtype === 'hook_response') {
2300
2500
  // Provider-specific hook handling — access raw event for details
@@ -2315,6 +2515,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2315
2515
  case 'tool_result':
2316
2516
  // Tool results — track activity and detect PR URLs
2317
2517
  this.updateLastActivity(issueId, 'tool_result');
2518
+ // Feed to context manager for artifact tracking
2519
+ this.contextManagers.get(issueId)?.processEvent(event);
2318
2520
  sessionLogger?.logToolResult(event.toolUseId ?? 'unknown', event.content, event.isError);
2319
2521
  // Detect GitHub PR URLs in tool output (from gh pr create)
2320
2522
  if (sessionId) {
@@ -2329,8 +2531,19 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2329
2531
  case 'assistant_text':
2330
2532
  // Assistant text output
2331
2533
  this.updateLastActivity(issueId, 'assistant');
2534
+ // Feed to context manager for session intent extraction
2535
+ this.contextManagers.get(issueId)?.processEvent(event);
2332
2536
  heartbeatWriter?.recordThinking();
2333
2537
  sessionLogger?.logAssistant(event.text);
2538
+ // Detect GitHub PR URLs in assistant text (backup for tool_result detection)
2539
+ if (sessionId && !agent.pullRequestUrl) {
2540
+ const prUrl = this.extractPullRequestUrl(event.text);
2541
+ if (prUrl) {
2542
+ log?.info('Pull request detected in assistant text', { prUrl });
2543
+ agent.pullRequestUrl = prUrl;
2544
+ await this.updateSessionPullRequest(sessionId, prUrl, agent);
2545
+ }
2546
+ }
2334
2547
  if (emitter) {
2335
2548
  await emitter.emitThought(event.text.substring(0, 200));
2336
2549
  }
@@ -2338,6 +2551,8 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2338
2551
  case 'tool_use':
2339
2552
  // Tool invocation
2340
2553
  this.updateLastActivity(issueId, 'assistant');
2554
+ // Feed to context manager for artifact tracking
2555
+ this.contextManagers.get(issueId)?.processEvent(event);
2341
2556
  log?.toolCall(event.toolName, event.input);
2342
2557
  heartbeatWriter?.recordToolCall(event.toolName);
2343
2558
  progressLogger?.logTool(event.toolName, event.input);
@@ -2383,6 +2598,15 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2383
2598
  // Store full result for completion comment posting later
2384
2599
  if (event.message) {
2385
2600
  agent.resultMessage = event.message;
2601
+ // Detect GitHub PR URLs in final result message (backup for tool_result detection)
2602
+ if (sessionId && !agent.pullRequestUrl) {
2603
+ const prUrl = this.extractPullRequestUrl(event.message);
2604
+ if (prUrl) {
2605
+ log?.info('Pull request detected in result message', { prUrl });
2606
+ agent.pullRequestUrl = prUrl;
2607
+ await this.updateSessionPullRequest(sessionId, prUrl, agent);
2608
+ }
2609
+ }
2386
2610
  }
2387
2611
  // Update state to completing/completed (only for worktree-based agents)
2388
2612
  if (agent.worktreePath) {
@@ -2432,6 +2656,22 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2432
2656
  }
2433
2657
  progressLogger?.logError('Agent error result', new Error(errorMessage));
2434
2658
  sessionLogger?.logError('Agent error result', new Error(errorMessage), { subtype: event.errorSubtype });
2659
+ // Merge queue: dequeue PR on merge agent failure
2660
+ if (agent.workType === 'merge' && this.mergeQueueAdapter && agent.pullRequestUrl) {
2661
+ try {
2662
+ const prMatch = agent.pullRequestUrl.match(/\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
2663
+ if (prMatch) {
2664
+ const [, owner, repo, prNum] = prMatch;
2665
+ await this.mergeQueueAdapter.dequeue(owner, repo, parseInt(prNum, 10));
2666
+ log?.info('PR dequeued from merge queue after failure', { owner, repo, prNumber: prNum });
2667
+ }
2668
+ }
2669
+ catch (dequeueError) {
2670
+ log?.warn('Failed to dequeue PR from merge queue', {
2671
+ error: dequeueError instanceof Error ? dequeueError.message : String(dequeueError),
2672
+ });
2673
+ }
2674
+ }
2435
2675
  // Report tool errors as Linear issues for tracking
2436
2676
  // Only report for 'error_during_execution' subtype (tool/execution errors)
2437
2677
  if (event.errorSubtype === 'error_during_execution' &&
@@ -2619,6 +2859,17 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2619
2859
  progressLogger.stop();
2620
2860
  this.progressLoggers.delete(issueId);
2621
2861
  }
2862
+ // Persist and cleanup context manager
2863
+ const contextManager = this.contextManagers.get(issueId);
2864
+ if (contextManager) {
2865
+ try {
2866
+ contextManager.persist();
2867
+ }
2868
+ catch {
2869
+ // Ignore persistence errors during cleanup
2870
+ }
2871
+ this.contextManagers.delete(issueId);
2872
+ }
2622
2873
  // Session logger is cleaned up separately (in finalizeSessionLogger)
2623
2874
  // to ensure the final status is captured before cleanup
2624
2875
  this.sessionLoggers.delete(issueId);
@@ -2722,6 +2973,25 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2722
2973
  throw new Error(`Issue ${identifier} is in terminal status '${currentStatus}' — skipping ${workType ?? 'auto'} work. ` +
2723
2974
  `The issue was likely accepted/canceled after being queued.`);
2724
2975
  }
2976
+ // Guard: skip sub-issues that should be managed by a coordinator, not spawned independently.
2977
+ // Only applies when no explicit work type is provided (i.e., orchestrator auto-pickup).
2978
+ // Coordinators spawning sub-agents always pass an explicit work type, so they bypass this check.
2979
+ if (!workType) {
2980
+ try {
2981
+ const isChild = await this.client.isChildIssue(issueId);
2982
+ if (isChild) {
2983
+ throw new Error(`Issue ${identifier} is a sub-issue managed by a parent coordinator — skipping independent pickup. ` +
2984
+ `Sub-issues should only be processed by their parent's coordination agent.`);
2985
+ }
2986
+ }
2987
+ catch (err) {
2988
+ // Re-throw our own guard error; swallow API errors so we don't block on transient failures
2989
+ if (err instanceof Error && err.message.includes('managed by a parent coordinator')) {
2990
+ throw err;
2991
+ }
2992
+ console.warn(`Failed to check child status for ${identifier}:`, err);
2993
+ }
2994
+ }
2725
2995
  // Defense in depth: re-validate git remote before spawning (guards against long-running instances)
2726
2996
  if (this.config.repository) {
2727
2997
  validateGitRemote(this.config.repository, this.gitRoot);
@@ -2830,6 +3100,13 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
2830
3100
  labels: labelNames,
2831
3101
  });
2832
3102
  }
3103
+ /**
3104
+ * Get the merge queue adapter, if configured.
3105
+ * Returns undefined if no merge queue is enabled.
3106
+ */
3107
+ getMergeQueueAdapter() {
3108
+ return this.mergeQueueAdapter;
3109
+ }
2833
3110
  /**
2834
3111
  * Get all active agents
2835
3112
  */
@@ -3214,6 +3491,9 @@ ORCHESTRATOR_INSTALL=1 exec pnpm add "$@"
3214
3491
  this.sessionLoggers.set(issueId, sessionLogger);
3215
3492
  log.debug('Session logging initialized', { logsDir: logConfig.logsDir });
3216
3493
  }
3494
+ // Initialize context manager for context window management
3495
+ const contextManager = ContextManager.load(worktreePath);
3496
+ this.contextManagers.set(issueId, contextManager);
3217
3497
  log.debug('State persistence initialized', { agentDir: resolve(worktreePath, '.agent') });
3218
3498
  }
3219
3499
  catch (stateError) {
@@ -1 +1 @@
1
- {"version":3,"file":"parse-work-result.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/parse-work-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAuGjD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,QAAQ,EAAE,aAAa,GACtB,eAAe,CAyCjB"}
1
+ {"version":3,"file":"parse-work-result.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/parse-work-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA6GjD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,QAAQ,EAAE,aAAa,GACtB,eAAe,CAyCjB"}
@@ -91,6 +91,12 @@ const COORDINATION_FAIL_PATTERNS = [
91
91
  // Explicit result labels
92
92
  /\bCoordination\s+(?:Result|Status|Verdict):\s*\*{0,2}Fail(?:ed)?\*{0,2}/i,
93
93
  /\bCoordination\s+Failed/i,
94
+ // Early-exit detection — agent reported progress instead of completing coordination
95
+ /\bI'll wait\b.*\bcomplete\b/i,
96
+ /\bagents?\s+(?:are|is)\s+(?:actively\s+)?(?:working|running|in\s+progress)\b/i,
97
+ /\bwork\s+is\s+in\s+progress\b/i,
98
+ /\bwait(?:ing)?\s+for\s+(?:them|sub-?\s*agents?|sub-?\s*issues?)\s+to\s+(?:complete|finish)\b/i,
99
+ /\bbefore\s+proceeding\s+to\s+(?:Layer|Wave|Phase)\s+\d/i,
94
100
  ];
95
101
  /**
96
102
  * Parse the agent's result message to determine whether the work passed or failed.
@@ -192,6 +192,25 @@ describe('parseWorkResult', () => {
192
192
  it('checks fail patterns before pass patterns', () => {
193
193
  expect(parseWorkResult('All 8/8 sub-issues completed.\n### Must Fix Before Merge\n1. Bad code', 'coordination')).toBe('failed');
194
194
  });
195
+ // Early-exit detection — agent reported progress instead of completing
196
+ it('detects "I\'ll wait for them to complete" as early-exit fail', () => {
197
+ expect(parseWorkResult("Both agents are progressing well. I'll wait for them to complete before proceeding.", 'coordination')).toBe('failed');
198
+ });
199
+ it('detects "agents are actively working" as early-exit fail', () => {
200
+ expect(parseWorkResult('SUP-1552 agent has read the existing patterns. Agents are actively working on their sub-issues.', 'coordination')).toBe('failed');
201
+ });
202
+ it('detects "waiting for sub-agents to finish" as early-exit fail', () => {
203
+ expect(parseWorkResult('Spawned 2 agents. Waiting for sub-agents to finish their work.', 'coordination')).toBe('failed');
204
+ });
205
+ it('detects "before proceeding to Layer 2" as early-exit fail', () => {
206
+ expect(parseWorkResult('Wave 1 agents spawned. Will check results before proceeding to Layer 2.', 'coordination')).toBe('failed');
207
+ });
208
+ it('detects "work is in progress" as early-exit fail', () => {
209
+ expect(parseWorkResult('Work is in progress on 3 sub-issues. Status will be updated.', 'coordination')).toBe('failed');
210
+ });
211
+ it('early-exit patterns also apply to inflight-coordination', () => {
212
+ expect(parseWorkResult("Agents are actively running. I'll wait for them to complete.", 'inflight-coordination')).toBe('failed');
213
+ });
195
214
  });
196
215
  // QA coordination with real agent output formats
197
216
  describe('QA coordination real-world patterns', () => {
@@ -4,7 +4,7 @@
4
4
  * Detects and recovers agent state from the .agent/ directory.
5
5
  * Enables crash recovery and duplicate agent prevention.
6
6
  */
7
- import type { WorktreeState, HeartbeatState, TodosState, RecoveryCheckResult } from './state-types.js';
7
+ import type { WorktreeState, HeartbeatState, TodosState, RecoveryCheckResult, StructuredSummary } from './state-types.js';
8
8
  import type { AgentWorkType } from './work-types.js';
9
9
  /**
10
10
  * Get the .agent directory path for a worktree
@@ -84,7 +84,13 @@ export declare function getTaskListId(issueIdentifier: string, workType: AgentWo
84
84
  /**
85
85
  * Build a recovery prompt for resuming crashed work
86
86
  */
87
- export declare function buildRecoveryPrompt(state: WorktreeState, todos?: TodosState): string;
87
+ export declare function buildRecoveryPrompt(state: WorktreeState, todos?: TodosState, contextSection?: string): string;
88
+ /**
89
+ * Build a context injection string for session resume (non-crash).
90
+ * Returns empty string if no context is available.
91
+ * Uses neutral framing to avoid "context anxiety" (from SUP-1186 research).
92
+ */
93
+ export declare function buildResumeContext(contextSection: string): string;
88
94
  /**
89
95
  * Parse environment variable for heartbeat timeout
90
96
  */
@@ -93,4 +99,17 @@ export declare function getHeartbeatTimeoutFromEnv(): number;
93
99
  * Parse environment variable for max recovery attempts
94
100
  */
95
101
  export declare function getMaxRecoveryAttemptsFromEnv(): number;
102
+ /**
103
+ * Get the path to the summary.json file
104
+ */
105
+ export declare function getSummaryPath(worktreePath: string): string;
106
+ /**
107
+ * Read the persisted structured summary from a worktree
108
+ */
109
+ export declare function readSummary(worktreePath: string): StructuredSummary | null;
110
+ /**
111
+ * Write the structured summary with atomic writes (write to temp, then rename).
112
+ * This ensures the summary survives process crashes.
113
+ */
114
+ export declare function writeSummary(worktreePath: string, summary: StructuredSummary): void;
96
115
  //# sourceMappingURL=state-recovery.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"state-recovery.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/state-recovery.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EACd,UAAU,EACV,mBAAmB,EAEpB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAQpD;;GAEG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEzD;AAeD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,cAAc,GAAG,IAAI,EAChC,SAAS,GAAE,MAAqC,GAC/C,OAAO,CAIT;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAE5E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAEzE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAEjE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE;IACP,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAA;CACxB,GACL,mBAAmB,CA2ErB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAK7D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAI3E;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,GAC9B,aAAa,GAAG,IAAI,CAWtB;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAIxE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,QAAQ,EAAE,aAAa,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB,GAAG,aAAa,CAmBhB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,aAAa,GACtB,MAAM,CAgBR;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,aAAa,EACpB,KAAK,CAAC,EAAE,UAAU,GACjB,MAAM,CA6CR;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CASnD;AAED;;GAEG;AACH,wBAAgB,6BAA6B,IAAI,MAAM,CAStD"}
1
+ {"version":3,"file":"state-recovery.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/state-recovery.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EACd,UAAU,EACV,mBAAmB,EAEnB,iBAAiB,EAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAQpD;;GAEG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEzD;AAeD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,cAAc,GAAG,IAAI,EAChC,SAAS,GAAE,MAAqC,GAC/C,OAAO,CAIT;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAE5E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAEzE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAEjE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE;IACP,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAA;CACxB,GACL,mBAAmB,CA2ErB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAK7D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAI3E;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,GAC9B,aAAa,GAAG,IAAI,CAWtB;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAIxE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,QAAQ,EAAE,aAAa,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB,GAAG,aAAa,CAmBhB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,aAAa,GACtB,MAAM,CAiBR;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,aAAa,EACpB,KAAK,CAAC,EAAE,UAAU,EAClB,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,CA6DR;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CASjE;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CASnD;AAED;;GAEG;AACH,wBAAgB,6BAA6B,IAAI,MAAM,CAStD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAE1E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAMnF"}
@@ -4,7 +4,7 @@
4
4
  * Detects and recovers agent state from the .agent/ directory.
5
5
  * Enables crash recovery and duplicate agent prevention.
6
6
  */
7
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
7
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'fs';
8
8
  import { resolve } from 'path';
9
9
  // Default heartbeat timeout: 30 seconds
10
10
  const DEFAULT_HEARTBEAT_TIMEOUT_MS = 30000;
@@ -230,14 +230,23 @@ export function getTaskListId(issueIdentifier, workType) {
230
230
  'refinement-coordination': 'REF-COORD',
231
231
  'qa-coordination': 'QA-COORD',
232
232
  'acceptance-coordination': 'AC-COORD',
233
+ merge: 'MRG',
233
234
  };
234
235
  return `${issueIdentifier}-${suffixMap[workType]}`;
235
236
  }
236
237
  /**
237
238
  * Build a recovery prompt for resuming crashed work
238
239
  */
239
- export function buildRecoveryPrompt(state, todos) {
240
+ export function buildRecoveryPrompt(state, todos, contextSection) {
240
241
  const lines = [];
242
+ // Inject structured summary context at the top (if available)
243
+ if (contextSection) {
244
+ lines.push(contextSection);
245
+ lines.push('');
246
+ lines.push('---');
247
+ lines.push('You are resuming work on this task. The context above summarizes your progress so far.');
248
+ lines.push('');
249
+ }
241
250
  lines.push(`Resume work on ${state.issueIdentifier}.`);
242
251
  lines.push('');
243
252
  lines.push('RECOVERY CONTEXT:');
@@ -271,10 +280,30 @@ export function buildRecoveryPrompt(state, todos) {
271
280
  lines.push('3. Review the codebase for any partial changes');
272
281
  lines.push('4. Continue from where the previous session left off');
273
282
  lines.push('5. If work appears complete, verify and create PR if needed');
283
+ // Repeat key context at the end to mitigate "lost in the middle" phenomenon
284
+ if (contextSection) {
285
+ lines.push('');
286
+ lines.push('KEY CONTEXT REMINDER:');
287
+ lines.push(contextSection);
288
+ }
274
289
  lines.push('');
275
290
  lines.push(`Original prompt: ${state.prompt}`);
276
291
  return lines.join('\n');
277
292
  }
293
+ /**
294
+ * Build a context injection string for session resume (non-crash).
295
+ * Returns empty string if no context is available.
296
+ * Uses neutral framing to avoid "context anxiety" (from SUP-1186 research).
297
+ */
298
+ export function buildResumeContext(contextSection) {
299
+ if (!contextSection)
300
+ return '';
301
+ const lines = [];
302
+ lines.push('Here is your session context:');
303
+ lines.push('');
304
+ lines.push(contextSection);
305
+ return lines.join('\n');
306
+ }
278
307
  /**
279
308
  * Parse environment variable for heartbeat timeout
280
309
  */
@@ -301,3 +330,26 @@ export function getMaxRecoveryAttemptsFromEnv() {
301
330
  }
302
331
  return DEFAULT_MAX_RECOVERY_ATTEMPTS;
303
332
  }
333
+ /**
334
+ * Get the path to the summary.json file
335
+ */
336
+ export function getSummaryPath(worktreePath) {
337
+ return resolve(getAgentDir(worktreePath), 'summary.json');
338
+ }
339
+ /**
340
+ * Read the persisted structured summary from a worktree
341
+ */
342
+ export function readSummary(worktreePath) {
343
+ return readJsonSafe(getSummaryPath(worktreePath));
344
+ }
345
+ /**
346
+ * Write the structured summary with atomic writes (write to temp, then rename).
347
+ * This ensures the summary survives process crashes.
348
+ */
349
+ export function writeSummary(worktreePath, summary) {
350
+ const summaryPath = getSummaryPath(worktreePath);
351
+ initializeAgentDir(worktreePath);
352
+ const tempPath = summaryPath + '.tmp';
353
+ writeFileSync(tempPath, JSON.stringify(summary, null, 2));
354
+ renameSync(tempPath, summaryPath);
355
+ }