@nathapp/nax 0.50.3 → 0.51.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 (353) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +177 -104
  3. package/dist/nax.js +417 -213
  4. package/package.json +1 -3
  5. package/bin/nax.ts +0 -1195
  6. package/src/acceptance/fix-generator.ts +0 -322
  7. package/src/acceptance/generator.ts +0 -415
  8. package/src/acceptance/index.ts +0 -42
  9. package/src/acceptance/refinement.ts +0 -224
  10. package/src/acceptance/templates/cli.ts +0 -47
  11. package/src/acceptance/templates/component.ts +0 -78
  12. package/src/acceptance/templates/e2e.ts +0 -43
  13. package/src/acceptance/templates/index.ts +0 -21
  14. package/src/acceptance/templates/snapshot.ts +0 -50
  15. package/src/acceptance/templates/unit.ts +0 -48
  16. package/src/acceptance/types.ts +0 -138
  17. package/src/agents/acp/adapter.ts +0 -888
  18. package/src/agents/acp/cost.ts +0 -9
  19. package/src/agents/acp/index.ts +0 -7
  20. package/src/agents/acp/interaction-bridge.ts +0 -126
  21. package/src/agents/acp/parser.ts +0 -119
  22. package/src/agents/acp/spawn-client.ts +0 -373
  23. package/src/agents/acp/types.ts +0 -22
  24. package/src/agents/aider/adapter.ts +0 -135
  25. package/src/agents/claude/adapter.ts +0 -258
  26. package/src/agents/claude/complete.ts +0 -80
  27. package/src/agents/claude/cost.ts +0 -16
  28. package/src/agents/claude/execution.ts +0 -215
  29. package/src/agents/claude/index.ts +0 -3
  30. package/src/agents/claude/interactive.ts +0 -77
  31. package/src/agents/claude/plan.ts +0 -179
  32. package/src/agents/codex/adapter.ts +0 -153
  33. package/src/agents/cost/calculate.ts +0 -154
  34. package/src/agents/cost/index.ts +0 -10
  35. package/src/agents/cost/parse.ts +0 -97
  36. package/src/agents/cost/pricing.ts +0 -59
  37. package/src/agents/cost/types.ts +0 -45
  38. package/src/agents/gemini/adapter.ts +0 -177
  39. package/src/agents/index.ts +0 -18
  40. package/src/agents/opencode/adapter.ts +0 -106
  41. package/src/agents/registry.ts +0 -136
  42. package/src/agents/shared/decompose.ts +0 -154
  43. package/src/agents/shared/model-resolution.ts +0 -43
  44. package/src/agents/shared/types-extended.ts +0 -164
  45. package/src/agents/shared/validation.ts +0 -69
  46. package/src/agents/shared/version-detection.ts +0 -109
  47. package/src/agents/types.ts +0 -205
  48. package/src/analyze/classifier.ts +0 -282
  49. package/src/analyze/index.ts +0 -16
  50. package/src/analyze/scanner.ts +0 -171
  51. package/src/analyze/types.ts +0 -51
  52. package/src/cli/accept.ts +0 -108
  53. package/src/cli/agents.ts +0 -87
  54. package/src/cli/analyze-parser.ts +0 -291
  55. package/src/cli/analyze.ts +0 -352
  56. package/src/cli/config-descriptions.ts +0 -219
  57. package/src/cli/config-diff.ts +0 -103
  58. package/src/cli/config-display.ts +0 -285
  59. package/src/cli/config-get.ts +0 -55
  60. package/src/cli/config.ts +0 -14
  61. package/src/cli/constitution.ts +0 -17
  62. package/src/cli/diagnose-analysis.ts +0 -159
  63. package/src/cli/diagnose-formatter.ts +0 -87
  64. package/src/cli/diagnose.ts +0 -203
  65. package/src/cli/generate.ts +0 -250
  66. package/src/cli/index.ts +0 -42
  67. package/src/cli/init-context.ts +0 -405
  68. package/src/cli/init-detect.ts +0 -303
  69. package/src/cli/init.ts +0 -296
  70. package/src/cli/interact.ts +0 -295
  71. package/src/cli/plan.ts +0 -509
  72. package/src/cli/plugins.ts +0 -122
  73. package/src/cli/prompts-export.ts +0 -58
  74. package/src/cli/prompts-init.ts +0 -200
  75. package/src/cli/prompts-main.ts +0 -183
  76. package/src/cli/prompts-shared.ts +0 -70
  77. package/src/cli/prompts-tdd.ts +0 -88
  78. package/src/cli/prompts.ts +0 -17
  79. package/src/cli/runs.ts +0 -174
  80. package/src/cli/status-cost.ts +0 -151
  81. package/src/cli/status-features.ts +0 -405
  82. package/src/cli/status.ts +0 -13
  83. package/src/commands/common.ts +0 -171
  84. package/src/commands/diagnose.ts +0 -17
  85. package/src/commands/index.ts +0 -9
  86. package/src/commands/logs-formatter.ts +0 -201
  87. package/src/commands/logs-reader.ts +0 -171
  88. package/src/commands/logs.ts +0 -103
  89. package/src/commands/precheck.ts +0 -86
  90. package/src/commands/runs.ts +0 -220
  91. package/src/commands/unlock.ts +0 -96
  92. package/src/config/defaults.ts +0 -218
  93. package/src/config/index.ts +0 -22
  94. package/src/config/loader.ts +0 -143
  95. package/src/config/merge.ts +0 -106
  96. package/src/config/merger.ts +0 -147
  97. package/src/config/path-security.ts +0 -121
  98. package/src/config/paths.ts +0 -27
  99. package/src/config/permissions.ts +0 -63
  100. package/src/config/runtime-types.ts +0 -522
  101. package/src/config/schema-types.ts +0 -53
  102. package/src/config/schema.ts +0 -60
  103. package/src/config/schemas.ts +0 -426
  104. package/src/config/test-strategy.ts +0 -71
  105. package/src/config/types.ts +0 -57
  106. package/src/config/validate.ts +0 -103
  107. package/src/constitution/generator.ts +0 -158
  108. package/src/constitution/generators/aider.ts +0 -41
  109. package/src/constitution/generators/claude.ts +0 -35
  110. package/src/constitution/generators/cursor.ts +0 -36
  111. package/src/constitution/generators/opencode.ts +0 -38
  112. package/src/constitution/generators/types.ts +0 -33
  113. package/src/constitution/generators/windsurf.ts +0 -36
  114. package/src/constitution/index.ts +0 -11
  115. package/src/constitution/loader.ts +0 -121
  116. package/src/constitution/types.ts +0 -31
  117. package/src/context/auto-detect.ts +0 -228
  118. package/src/context/builder.ts +0 -299
  119. package/src/context/elements.ts +0 -122
  120. package/src/context/formatter.ts +0 -107
  121. package/src/context/generator.ts +0 -343
  122. package/src/context/generators/aider.ts +0 -34
  123. package/src/context/generators/claude.ts +0 -28
  124. package/src/context/generators/codex.ts +0 -28
  125. package/src/context/generators/cursor.ts +0 -28
  126. package/src/context/generators/gemini.ts +0 -28
  127. package/src/context/generators/opencode.ts +0 -30
  128. package/src/context/generators/windsurf.ts +0 -28
  129. package/src/context/greenfield.ts +0 -114
  130. package/src/context/index.ts +0 -34
  131. package/src/context/injector.ts +0 -279
  132. package/src/context/parent-context.ts +0 -39
  133. package/src/context/test-scanner.ts +0 -370
  134. package/src/context/types.ts +0 -98
  135. package/src/decompose/apply.ts +0 -50
  136. package/src/decompose/builder.ts +0 -181
  137. package/src/decompose/index.ts +0 -8
  138. package/src/decompose/sections/codebase.ts +0 -26
  139. package/src/decompose/sections/constraints.ts +0 -32
  140. package/src/decompose/sections/index.ts +0 -4
  141. package/src/decompose/sections/sibling-stories.ts +0 -25
  142. package/src/decompose/sections/target-story.ts +0 -31
  143. package/src/decompose/types.ts +0 -55
  144. package/src/decompose/validators/complexity.ts +0 -45
  145. package/src/decompose/validators/coverage.ts +0 -134
  146. package/src/decompose/validators/dependency.ts +0 -91
  147. package/src/decompose/validators/index.ts +0 -35
  148. package/src/decompose/validators/overlap.ts +0 -128
  149. package/src/errors.ts +0 -67
  150. package/src/execution/batching.ts +0 -157
  151. package/src/execution/crash-heartbeat.ts +0 -77
  152. package/src/execution/crash-recovery.ts +0 -79
  153. package/src/execution/crash-signals.ts +0 -165
  154. package/src/execution/crash-writer.ts +0 -154
  155. package/src/execution/deferred-review.ts +0 -105
  156. package/src/execution/dry-run.ts +0 -81
  157. package/src/execution/escalation/escalation.ts +0 -46
  158. package/src/execution/escalation/index.ts +0 -13
  159. package/src/execution/escalation/tier-escalation.ts +0 -346
  160. package/src/execution/escalation/tier-outcome.ts +0 -143
  161. package/src/execution/executor-types.ts +0 -73
  162. package/src/execution/helpers.ts +0 -38
  163. package/src/execution/index.ts +0 -27
  164. package/src/execution/iteration-runner.ts +0 -160
  165. package/src/execution/lifecycle/acceptance-loop.ts +0 -309
  166. package/src/execution/lifecycle/headless-formatter.ts +0 -83
  167. package/src/execution/lifecycle/index.ts +0 -11
  168. package/src/execution/lifecycle/parallel-lifecycle.ts +0 -101
  169. package/src/execution/lifecycle/precheck-runner.ts +0 -140
  170. package/src/execution/lifecycle/run-cleanup.ts +0 -81
  171. package/src/execution/lifecycle/run-completion.ts +0 -247
  172. package/src/execution/lifecycle/run-initialization.ts +0 -187
  173. package/src/execution/lifecycle/run-regression.ts +0 -305
  174. package/src/execution/lifecycle/run-setup.ts +0 -240
  175. package/src/execution/lifecycle/story-size-prompts.ts +0 -123
  176. package/src/execution/lock.ts +0 -129
  177. package/src/execution/parallel-coordinator.ts +0 -281
  178. package/src/execution/parallel-executor-rectification-pass.ts +0 -117
  179. package/src/execution/parallel-executor-rectify.ts +0 -136
  180. package/src/execution/parallel-executor.ts +0 -330
  181. package/src/execution/parallel-worker.ts +0 -149
  182. package/src/execution/parallel.ts +0 -13
  183. package/src/execution/pid-registry.ts +0 -275
  184. package/src/execution/pipeline-result-handler.ts +0 -221
  185. package/src/execution/progress.ts +0 -27
  186. package/src/execution/queue-handler.ts +0 -109
  187. package/src/execution/runner-completion.ts +0 -171
  188. package/src/execution/runner-execution.ts +0 -243
  189. package/src/execution/runner-setup.ts +0 -86
  190. package/src/execution/runner.ts +0 -265
  191. package/src/execution/sequential-executor.ts +0 -219
  192. package/src/execution/status-file.ts +0 -264
  193. package/src/execution/status-writer.ts +0 -181
  194. package/src/execution/story-context.ts +0 -266
  195. package/src/execution/story-selector.ts +0 -76
  196. package/src/execution/test-output-parser.ts +0 -14
  197. package/src/execution/timeout-handler.ts +0 -100
  198. package/src/hooks/index.ts +0 -2
  199. package/src/hooks/runner.ts +0 -280
  200. package/src/hooks/types.ts +0 -79
  201. package/src/interaction/chain.ts +0 -170
  202. package/src/interaction/index.ts +0 -61
  203. package/src/interaction/init.ts +0 -84
  204. package/src/interaction/plugins/auto.ts +0 -243
  205. package/src/interaction/plugins/cli.ts +0 -300
  206. package/src/interaction/plugins/telegram.ts +0 -384
  207. package/src/interaction/plugins/webhook.ts +0 -286
  208. package/src/interaction/state.ts +0 -171
  209. package/src/interaction/triggers.ts +0 -250
  210. package/src/interaction/types.ts +0 -170
  211. package/src/logger/formatters.ts +0 -84
  212. package/src/logger/index.ts +0 -16
  213. package/src/logger/logger.ts +0 -296
  214. package/src/logger/types.ts +0 -48
  215. package/src/logging/formatter.ts +0 -355
  216. package/src/logging/index.ts +0 -22
  217. package/src/logging/types.ts +0 -93
  218. package/src/metrics/aggregator.ts +0 -191
  219. package/src/metrics/index.ts +0 -14
  220. package/src/metrics/tracker.ts +0 -200
  221. package/src/metrics/types.ts +0 -115
  222. package/src/optimizer/index.ts +0 -63
  223. package/src/optimizer/noop.optimizer.ts +0 -24
  224. package/src/optimizer/rule-based.optimizer.ts +0 -248
  225. package/src/optimizer/types.ts +0 -53
  226. package/src/pipeline/event-bus.ts +0 -297
  227. package/src/pipeline/events.ts +0 -130
  228. package/src/pipeline/index.ts +0 -19
  229. package/src/pipeline/runner.ts +0 -149
  230. package/src/pipeline/stages/acceptance-setup.ts +0 -144
  231. package/src/pipeline/stages/acceptance.ts +0 -215
  232. package/src/pipeline/stages/autofix.ts +0 -262
  233. package/src/pipeline/stages/completion.ts +0 -110
  234. package/src/pipeline/stages/constitution.ts +0 -63
  235. package/src/pipeline/stages/context.ts +0 -122
  236. package/src/pipeline/stages/execution.ts +0 -359
  237. package/src/pipeline/stages/index.ts +0 -86
  238. package/src/pipeline/stages/optimizer.ts +0 -74
  239. package/src/pipeline/stages/prompt.ts +0 -79
  240. package/src/pipeline/stages/queue-check.ts +0 -103
  241. package/src/pipeline/stages/rectify.ts +0 -101
  242. package/src/pipeline/stages/regression.ts +0 -99
  243. package/src/pipeline/stages/review.ts +0 -94
  244. package/src/pipeline/stages/routing.ts +0 -276
  245. package/src/pipeline/stages/verify.ts +0 -286
  246. package/src/pipeline/subscribers/events-writer.ts +0 -135
  247. package/src/pipeline/subscribers/hooks.ts +0 -179
  248. package/src/pipeline/subscribers/interaction.ts +0 -103
  249. package/src/pipeline/subscribers/registry.ts +0 -73
  250. package/src/pipeline/subscribers/reporters.ts +0 -174
  251. package/src/pipeline/types.ts +0 -220
  252. package/src/plugins/extensions.ts +0 -225
  253. package/src/plugins/index.ts +0 -33
  254. package/src/plugins/loader.ts +0 -352
  255. package/src/plugins/plugin-logger.ts +0 -41
  256. package/src/plugins/registry.ts +0 -168
  257. package/src/plugins/types.ts +0 -206
  258. package/src/plugins/validator.ts +0 -352
  259. package/src/prd/index.ts +0 -220
  260. package/src/prd/schema.ts +0 -268
  261. package/src/prd/types.ts +0 -273
  262. package/src/prd/validate.ts +0 -41
  263. package/src/precheck/checks-agents.ts +0 -63
  264. package/src/precheck/checks-blockers.ts +0 -23
  265. package/src/precheck/checks-cli.ts +0 -68
  266. package/src/precheck/checks-config.ts +0 -102
  267. package/src/precheck/checks-git.ts +0 -117
  268. package/src/precheck/checks-system.ts +0 -101
  269. package/src/precheck/checks-warnings.ts +0 -221
  270. package/src/precheck/checks.ts +0 -36
  271. package/src/precheck/index.ts +0 -374
  272. package/src/precheck/story-size-gate.ts +0 -144
  273. package/src/precheck/types.ts +0 -31
  274. package/src/prompts/builder.ts +0 -166
  275. package/src/prompts/index.ts +0 -2
  276. package/src/prompts/loader.ts +0 -43
  277. package/src/prompts/sections/conventions.ts +0 -19
  278. package/src/prompts/sections/hermetic.ts +0 -41
  279. package/src/prompts/sections/index.ts +0 -12
  280. package/src/prompts/sections/isolation.ts +0 -70
  281. package/src/prompts/sections/role-task.ts +0 -182
  282. package/src/prompts/sections/story.ts +0 -55
  283. package/src/prompts/sections/verdict.ts +0 -70
  284. package/src/prompts/types.ts +0 -21
  285. package/src/queue/index.ts +0 -2
  286. package/src/queue/manager.ts +0 -254
  287. package/src/queue/types.ts +0 -54
  288. package/src/review/index.ts +0 -8
  289. package/src/review/orchestrator.ts +0 -154
  290. package/src/review/runner.ts +0 -303
  291. package/src/review/types.ts +0 -70
  292. package/src/routing/batch-route.ts +0 -35
  293. package/src/routing/builder.ts +0 -81
  294. package/src/routing/chain.ts +0 -75
  295. package/src/routing/content-hash.ts +0 -25
  296. package/src/routing/index.ts +0 -20
  297. package/src/routing/loader.ts +0 -62
  298. package/src/routing/router.ts +0 -305
  299. package/src/routing/strategies/adaptive.ts +0 -215
  300. package/src/routing/strategies/index.ts +0 -8
  301. package/src/routing/strategies/keyword.ts +0 -180
  302. package/src/routing/strategies/llm-prompts.ts +0 -224
  303. package/src/routing/strategies/llm.ts +0 -320
  304. package/src/routing/strategies/manual.ts +0 -50
  305. package/src/routing/strategy.ts +0 -102
  306. package/src/tdd/cleanup.ts +0 -120
  307. package/src/tdd/index.ts +0 -22
  308. package/src/tdd/isolation.ts +0 -117
  309. package/src/tdd/orchestrator.ts +0 -406
  310. package/src/tdd/prompts.ts +0 -40
  311. package/src/tdd/rectification-gate.ts +0 -274
  312. package/src/tdd/session-runner.ts +0 -263
  313. package/src/tdd/types.ts +0 -84
  314. package/src/tdd/verdict-reader.ts +0 -266
  315. package/src/tdd/verdict.ts +0 -152
  316. package/src/tui/App.tsx +0 -265
  317. package/src/tui/components/AgentPanel.tsx +0 -75
  318. package/src/tui/components/CostOverlay.tsx +0 -118
  319. package/src/tui/components/HelpOverlay.tsx +0 -107
  320. package/src/tui/components/StatusBar.tsx +0 -63
  321. package/src/tui/components/StoriesPanel.tsx +0 -177
  322. package/src/tui/hooks/useKeyboard.ts +0 -142
  323. package/src/tui/hooks/useLayout.ts +0 -137
  324. package/src/tui/hooks/usePipelineEvents.ts +0 -183
  325. package/src/tui/hooks/usePty.ts +0 -189
  326. package/src/tui/index.tsx +0 -38
  327. package/src/tui/types.ts +0 -76
  328. package/src/utils/errors.ts +0 -12
  329. package/src/utils/git.ts +0 -245
  330. package/src/utils/json-file.ts +0 -72
  331. package/src/utils/log-test-output.ts +0 -25
  332. package/src/utils/path-security.ts +0 -73
  333. package/src/utils/queue-writer.ts +0 -54
  334. package/src/verification/crash-detector.ts +0 -34
  335. package/src/verification/executor.ts +0 -250
  336. package/src/verification/index.ts +0 -12
  337. package/src/verification/orchestrator-types.ts +0 -154
  338. package/src/verification/orchestrator.ts +0 -76
  339. package/src/verification/parser.ts +0 -220
  340. package/src/verification/rectification-loop.ts +0 -172
  341. package/src/verification/rectification.ts +0 -108
  342. package/src/verification/runners.ts +0 -129
  343. package/src/verification/smart-runner.ts +0 -307
  344. package/src/verification/strategies/acceptance.ts +0 -136
  345. package/src/verification/strategies/regression.ts +0 -90
  346. package/src/verification/strategies/scoped.ts +0 -154
  347. package/src/verification/types.ts +0 -117
  348. package/src/version.ts +0 -40
  349. package/src/worktree/dispatcher.ts +0 -6
  350. package/src/worktree/index.ts +0 -2
  351. package/src/worktree/manager.ts +0 -193
  352. package/src/worktree/merge.ts +0 -302
  353. package/src/worktree/types.ts +0 -4
@@ -1,157 +0,0 @@
1
- /**
2
- * Story batching logic
3
- *
4
- * Groups consecutive simple-complexity stories into batches for efficient execution.
5
- */
6
-
7
- import type { UserStory } from "../prd";
8
-
9
- /**
10
- * Default maximum number of stories per batch.
11
- *
12
- * Rationale:
13
- * - Batch size must balance efficiency vs. blast radius
14
- * - 4 stories is optimal for most simple tasks (e.g., add 4 similar util functions)
15
- * - Keeps prompts manageable (~1500 tokens per story = ~6000 tokens total context)
16
- * - If one story in batch fails, only 3 others retry at next tier (acceptable waste)
17
- * - Larger batches (8+) increase risk of cascading failures and context overload
18
- *
19
- * This default can be overridden via config or function parameter.
20
- */
21
- const DEFAULT_MAX_BATCH_SIZE = 4;
22
-
23
- /**
24
- * Story batch for grouped execution
25
- */
26
- export interface StoryBatch {
27
- /** Stories in this batch */
28
- stories: UserStory[];
29
- /** True if this is a batch of multiple stories, false if single story */
30
- isBatch: boolean;
31
- }
32
-
33
- /**
34
- * Group consecutive simple-complexity stories into batches (max 4 per batch).
35
- * Non-simple stories execute individually.
36
- *
37
- * @param stories - Array of user stories to batch
38
- * @param maxBatchSize - Maximum stories per batch (default: 4)
39
- * @returns Array of story batches
40
- *
41
- * @example
42
- * ```typescript
43
- * const stories = [simpleStory1, simpleStory2, complexStory, simpleStory3];
44
- * const batches = groupStoriesIntoBatches(stories);
45
- * // Returns: [
46
- * // { stories: [simpleStory1, simpleStory2], isBatch: true },
47
- * // { stories: [complexStory], isBatch: false },
48
- * // { stories: [simpleStory3], isBatch: false }
49
- * // ]
50
- * ```
51
- */
52
- export function groupStoriesIntoBatches(stories: UserStory[], maxBatchSize = DEFAULT_MAX_BATCH_SIZE): StoryBatch[] {
53
- const batches: StoryBatch[] = [];
54
- let currentBatch: UserStory[] = [];
55
-
56
- for (const story of stories) {
57
- const isSimple = story.routing?.complexity === "simple";
58
-
59
- if (isSimple && currentBatch.length < maxBatchSize) {
60
- // Add to current batch
61
- currentBatch.push(story);
62
- } else {
63
- // Flush current batch if it exists
64
- if (currentBatch.length > 0) {
65
- batches.push({
66
- stories: [...currentBatch],
67
- isBatch: currentBatch.length > 1,
68
- });
69
- currentBatch = [];
70
- }
71
-
72
- // Add non-simple story as individual batch
73
- if (!isSimple) {
74
- batches.push({
75
- stories: [story],
76
- isBatch: false,
77
- });
78
- } else {
79
- // Start new batch with this simple story
80
- currentBatch.push(story);
81
- }
82
- }
83
- }
84
-
85
- // Flush remaining batch
86
- if (currentBatch.length > 0) {
87
- batches.push({
88
- stories: [...currentBatch],
89
- isBatch: currentBatch.length > 1,
90
- });
91
- }
92
-
93
- return batches;
94
- }
95
-
96
- /**
97
- * Precompute the full batch plan from ready stories.
98
- * This eliminates O(n²) re-checking by computing all batches upfront.
99
- * Maintains original story order from PRD.
100
- *
101
- * @param stories - Array of ready user stories (already filtered for dependencies)
102
- * @param maxBatchSize - Maximum stories per batch (default: 4)
103
- * @returns Array of story batches ready for sequential execution
104
- *
105
- * @example
106
- * ```typescript
107
- * const readyStories = getAllReadyStories(prd);
108
- * const batchPlan = precomputeBatchPlan(readyStories);
109
- * // Iterate through batches sequentially
110
- * for (const batch of batchPlan) {
111
- * await executeBatch(batch);
112
- * }
113
- * ```
114
- */
115
- export function precomputeBatchPlan(stories: UserStory[], maxBatchSize = DEFAULT_MAX_BATCH_SIZE): StoryBatch[] {
116
- const batches: StoryBatch[] = [];
117
- let currentBatch: UserStory[] = [];
118
-
119
- for (const story of stories) {
120
- const isSimple = story.routing?.complexity === "simple" && story.routing?.testStrategy === "test-after";
121
-
122
- if (isSimple && currentBatch.length < maxBatchSize) {
123
- // Add to current batch
124
- currentBatch.push(story);
125
- } else {
126
- // Flush current batch if it exists
127
- if (currentBatch.length > 0) {
128
- batches.push({
129
- stories: [...currentBatch],
130
- isBatch: currentBatch.length > 1,
131
- });
132
- currentBatch = [];
133
- }
134
-
135
- // Add non-simple story as individual batch
136
- if (!isSimple) {
137
- batches.push({
138
- stories: [story],
139
- isBatch: false,
140
- });
141
- } else {
142
- // Start new batch with this simple story
143
- currentBatch.push(story);
144
- }
145
- }
146
- }
147
-
148
- // Flush remaining batch
149
- if (currentBatch.length > 0) {
150
- batches.push({
151
- stories: [...currentBatch],
152
- isBatch: currentBatch.length > 1,
153
- });
154
- }
155
-
156
- return batches;
157
- }
@@ -1,77 +0,0 @@
1
- /**
2
- * Heartbeat monitoring — periodic health checks during execution
3
- */
4
-
5
- import { appendFileSync } from "node:fs";
6
- import { getSafeLogger } from "../logger";
7
- import type { StatusWriter } from "./status-writer";
8
-
9
- let heartbeatTimer: Timer | null = null;
10
-
11
- /**
12
- * Start heartbeat timer (60s interval)
13
- */
14
- export function startHeartbeat(
15
- statusWriter: StatusWriter,
16
- getTotalCost: () => number,
17
- getIterations: () => number,
18
- jsonlFilePath?: string,
19
- ): void {
20
- const logger = getSafeLogger();
21
-
22
- stopHeartbeat();
23
-
24
- heartbeatTimer = setInterval(async () => {
25
- logger?.debug("crash-recovery", "Heartbeat");
26
-
27
- if (jsonlFilePath) {
28
- try {
29
- const heartbeatEntry = {
30
- timestamp: new Date().toISOString(),
31
- level: "debug",
32
- stage: "heartbeat",
33
- message: "Process alive",
34
- data: {
35
- pid: process.pid,
36
- memoryUsageMB: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
37
- },
38
- };
39
- const line = `${JSON.stringify(heartbeatEntry)}\n`;
40
- appendFileSync(jsonlFilePath, line);
41
- } catch (err) {
42
- logger?.warn("crash-recovery", "Failed to write heartbeat", { error: (err as Error).message });
43
- }
44
- }
45
-
46
- try {
47
- await statusWriter.update(getTotalCost(), getIterations(), {
48
- lastHeartbeat: new Date().toISOString(),
49
- });
50
- } catch (err) {
51
- logger?.warn("crash-recovery", "Failed to update status during heartbeat", {
52
- error: (err as Error).message,
53
- });
54
- }
55
- }, 60_000);
56
-
57
- logger?.debug("crash-recovery", "Heartbeat started (60s interval)");
58
- }
59
-
60
- /**
61
- * Stop heartbeat timer
62
- */
63
- export function stopHeartbeat(): void {
64
- if (heartbeatTimer) {
65
- clearInterval(heartbeatTimer);
66
- heartbeatTimer = null;
67
- getSafeLogger()?.debug("crash-recovery", "Heartbeat stopped");
68
- }
69
- }
70
-
71
- /**
72
- * Returns true if heartbeat timer is currently active.
73
- * @internal - test use only.
74
- */
75
- export function _isHeartbeatActive(): boolean {
76
- return heartbeatTimer !== null;
77
- }
@@ -1,79 +0,0 @@
1
- /**
2
- * Crash Recovery Orchestrator
3
- *
4
- * Implements US-007:
5
- * - SIGTERM/SIGINT/SIGHUP handlers
6
- * - Uncaught exception handlers
7
- * - Fatal log + status.json update to "crashed"
8
- * - Heartbeat every 60s during agent execution
9
- * - Exit summary entry on normal exit
10
- *
11
- * Re-exports crash detection and writer modules.
12
- */
13
-
14
- import { stopHeartbeat } from "./crash-heartbeat";
15
- import { installSignalHandlers } from "./crash-signals";
16
- import type { PidRegistry } from "./pid-registry";
17
- import type { StatusWriter } from "./status-writer";
18
-
19
- // Re-export for backward compatibility
20
- export {
21
- type RunCompleteContext,
22
- updateStatusToCrashed,
23
- writeFatalLog,
24
- writeRunComplete,
25
- writeExitSummary,
26
- } from "./crash-writer";
27
-
28
- export { type SignalHandlerContext, installSignalHandlers } from "./crash-signals";
29
-
30
- export { startHeartbeat, stopHeartbeat, _isHeartbeatActive } from "./crash-heartbeat";
31
-
32
- /**
33
- * Crash recovery context — dependencies injected at setup
34
- */
35
- export interface CrashRecoveryContext {
36
- statusWriter: StatusWriter;
37
- getTotalCost: () => number;
38
- getIterations: () => number;
39
- jsonlFilePath?: string;
40
- pidRegistry?: PidRegistry;
41
- runId?: string;
42
- feature?: string;
43
- featureDir?: string;
44
- getStartTime?: () => number;
45
- getTotalStories?: () => number;
46
- getStoriesCompleted?: () => number;
47
- emitError?: (reason: string) => void;
48
- /** Called during graceful shutdown before process.exit — use to close ACP sessions etc. */
49
- onShutdown?: () => Promise<void>;
50
- }
51
-
52
- let handlersInstalled = false;
53
-
54
- /**
55
- * Install crash handlers for recovery
56
- */
57
- export function installCrashHandlers(ctx: CrashRecoveryContext): () => void {
58
- if (handlersInstalled) {
59
- return () => {};
60
- }
61
-
62
- const cleanup = installSignalHandlers(ctx);
63
- handlersInstalled = true;
64
-
65
- return () => {
66
- cleanup();
67
- stopHeartbeat();
68
- handlersInstalled = false;
69
- };
70
- }
71
-
72
- /**
73
- * Reset handlers (for testing)
74
- * @internal
75
- */
76
- export function resetCrashHandlers(): void {
77
- handlersInstalled = false;
78
- stopHeartbeat();
79
- }
@@ -1,165 +0,0 @@
1
- /**
2
- * Crash detection — Signal and exception handlers
3
- */
4
-
5
- import { getSafeLogger } from "../logger";
6
- import { type RunCompleteContext, updateStatusToCrashed, writeFatalLog, writeRunComplete } from "./crash-writer";
7
- import type { PidRegistry } from "./pid-registry";
8
- import type { StatusWriter } from "./status-writer";
9
-
10
- /**
11
- * Handler context for signal/exception management
12
- */
13
- export interface SignalHandlerContext extends RunCompleteContext {
14
- statusWriter: StatusWriter;
15
- pidRegistry?: PidRegistry;
16
- featureDir?: string;
17
- emitError?: (reason: string) => void;
18
- /** Called during graceful shutdown (signal/exception) before process.exit — use to close ACP sessions etc. */
19
- onShutdown?: () => Promise<void>;
20
- }
21
-
22
- /**
23
- * Get numeric signal number for exit code
24
- */
25
- function getSignalNumber(signal: NodeJS.Signals): number {
26
- const signalMap: Record<string, number> = {
27
- SIGTERM: 15,
28
- SIGINT: 2,
29
- SIGHUP: 1,
30
- };
31
- return signalMap[signal] ?? 15;
32
- }
33
-
34
- /**
35
- * Create signal handler
36
- */
37
- function createSignalHandler(ctx: SignalHandlerContext): (signal: NodeJS.Signals) => Promise<void> {
38
- return async (signal: NodeJS.Signals) => {
39
- const hardDeadline = setTimeout(() => {
40
- process.exit(128 + getSignalNumber(signal));
41
- }, 10_000);
42
- if (hardDeadline.unref) hardDeadline.unref();
43
-
44
- const logger = getSafeLogger();
45
- logger?.error("crash-recovery", `Received ${signal}, shutting down...`, { signal });
46
-
47
- if (ctx.pidRegistry) {
48
- await ctx.pidRegistry.killAll();
49
- }
50
-
51
- // Close any open ACP sessions before exiting (prevents orphaned acpx processes)
52
- if (ctx.onShutdown) {
53
- await ctx.onShutdown().catch(() => {});
54
- }
55
-
56
- ctx.emitError?.(signal.toLowerCase());
57
-
58
- await writeFatalLog(ctx.jsonlFilePath, signal);
59
- await writeRunComplete(ctx, signal.toLowerCase());
60
- await updateStatusToCrashed(ctx.statusWriter, ctx.getTotalCost(), ctx.getIterations(), signal, ctx.featureDir);
61
-
62
- clearTimeout(hardDeadline);
63
- process.exit(128 + getSignalNumber(signal));
64
- };
65
- }
66
-
67
- /**
68
- * Create uncaught exception handler
69
- */
70
- function createUncaughtExceptionHandler(ctx: SignalHandlerContext): (error: Error) => Promise<void> {
71
- return async (error: Error) => {
72
- const logger = getSafeLogger();
73
- logger?.error("crash-recovery", "Uncaught exception", {
74
- error: error.message,
75
- stack: error.stack,
76
- });
77
-
78
- if (ctx.pidRegistry) {
79
- await ctx.pidRegistry.killAll();
80
- }
81
-
82
- if (ctx.onShutdown) {
83
- await ctx.onShutdown().catch(() => {});
84
- }
85
-
86
- ctx.emitError?.("uncaughtException");
87
- await writeFatalLog(ctx.jsonlFilePath, "uncaughtException", error);
88
- await updateStatusToCrashed(
89
- ctx.statusWriter,
90
- ctx.getTotalCost(),
91
- ctx.getIterations(),
92
- "uncaughtException",
93
- ctx.featureDir,
94
- );
95
-
96
- process.exit(1);
97
- };
98
- }
99
-
100
- /**
101
- * Create unhandled promise rejection handler
102
- */
103
- function createUnhandledRejectionHandler(ctx: SignalHandlerContext): (reason: unknown) => Promise<void> {
104
- return async (reason: unknown) => {
105
- const error = reason instanceof Error ? reason : new Error(String(reason));
106
- const logger = getSafeLogger();
107
- logger?.error("crash-recovery", "Unhandled promise rejection", {
108
- error: error.message,
109
- stack: error.stack,
110
- });
111
-
112
- if (ctx.pidRegistry) {
113
- await ctx.pidRegistry.killAll();
114
- }
115
-
116
- if (ctx.onShutdown) {
117
- await ctx.onShutdown().catch(() => {});
118
- }
119
-
120
- ctx.emitError?.("unhandledRejection");
121
- await writeFatalLog(ctx.jsonlFilePath, "unhandledRejection", error);
122
- await updateStatusToCrashed(
123
- ctx.statusWriter,
124
- ctx.getTotalCost(),
125
- ctx.getIterations(),
126
- "unhandledRejection",
127
- ctx.featureDir,
128
- );
129
-
130
- process.exit(1);
131
- };
132
- }
133
-
134
- /**
135
- * Install signal and exception handlers, return cleanup function
136
- */
137
- export function installSignalHandlers(ctx: SignalHandlerContext): () => void {
138
- const logger = getSafeLogger();
139
-
140
- const signalHandler = createSignalHandler(ctx);
141
- const uncaughtExceptionHandler = createUncaughtExceptionHandler(ctx);
142
- const unhandledRejectionHandler = createUnhandledRejectionHandler(ctx);
143
-
144
- const sigtermHandler = () => signalHandler("SIGTERM");
145
- const sigintHandler = () => signalHandler("SIGINT");
146
- const sighupHandler = () => signalHandler("SIGHUP");
147
-
148
- process.on("SIGTERM", sigtermHandler);
149
- process.on("SIGINT", sigintHandler);
150
- process.on("SIGHUP", sighupHandler);
151
- process.on("uncaughtException", uncaughtExceptionHandler);
152
- const rejectionWrapper = (reason: unknown) => unhandledRejectionHandler(reason);
153
- process.on("unhandledRejection", rejectionWrapper);
154
-
155
- logger?.debug("crash-recovery", "Signal handlers installed");
156
-
157
- return () => {
158
- process.removeListener("SIGTERM", sigtermHandler);
159
- process.removeListener("SIGINT", sigintHandler);
160
- process.removeListener("SIGHUP", sighupHandler);
161
- process.removeListener("uncaughtException", uncaughtExceptionHandler);
162
- process.removeListener("unhandledRejection", rejectionWrapper);
163
- logger?.debug("crash-recovery", "Signal handlers unregistered");
164
- };
165
- }
@@ -1,154 +0,0 @@
1
- /**
2
- * Crash data serialization — write logs and event summaries
3
- */
4
-
5
- import { appendFileSync } from "node:fs";
6
- import { getSafeLogger } from "../logger";
7
- import type { StatusWriter } from "./status-writer";
8
-
9
- /**
10
- * Write fatal log entry to JSONL file
11
- */
12
- export async function writeFatalLog(jsonlFilePath: string | undefined, signal: string, error?: Error): Promise<void> {
13
- if (!jsonlFilePath) return;
14
-
15
- try {
16
- const fatalEntry = {
17
- timestamp: new Date().toISOString(),
18
- level: "error",
19
- stage: "crash-recovery",
20
- message: error ? `Uncaught exception: ${error.message}` : `Process terminated by ${signal}`,
21
- data: {
22
- signal,
23
- ...(error && {
24
- stack: error.stack,
25
- name: error.name,
26
- }),
27
- },
28
- };
29
-
30
- const line = `${JSON.stringify(fatalEntry)}\n`;
31
- appendFileSync(jsonlFilePath, line);
32
- } catch (err) {
33
- console.error("[crash-recovery] Failed to write fatal log:", err);
34
- }
35
- }
36
-
37
- /**
38
- * Write run.complete event to JSONL file
39
- * Called on SIGTERM to emit final run summary before exit
40
- */
41
- export interface RunCompleteContext {
42
- jsonlFilePath?: string;
43
- runId?: string;
44
- feature?: string;
45
- getTotalCost: () => number;
46
- getIterations: () => number;
47
- getStartTime?: () => number;
48
- getTotalStories?: () => number;
49
- getStoriesCompleted?: () => number;
50
- }
51
-
52
- export async function writeRunComplete(ctx: RunCompleteContext, exitReason: string): Promise<void> {
53
- if (!ctx.jsonlFilePath || !ctx.runId || !ctx.feature) return;
54
-
55
- const logger = getSafeLogger();
56
-
57
- try {
58
- const totalCost = ctx.getTotalCost();
59
- const iterations = ctx.getIterations();
60
- const startTime = ctx.getStartTime?.() ?? Date.now();
61
- const durationMs = Date.now() - startTime;
62
- const totalStories = ctx.getTotalStories?.() ?? 0;
63
- const storiesCompleted = ctx.getStoriesCompleted?.() ?? 0;
64
-
65
- const runCompleteEntry = {
66
- timestamp: new Date().toISOString(),
67
- level: "info",
68
- stage: "run.complete",
69
- message: "Feature execution terminated",
70
- data: {
71
- runId: ctx.runId,
72
- feature: ctx.feature,
73
- success: false,
74
- exitReason,
75
- totalCost,
76
- iterations,
77
- totalStories,
78
- storiesCompleted,
79
- durationMs,
80
- },
81
- };
82
-
83
- const line = `${JSON.stringify(runCompleteEntry)}\n`;
84
- appendFileSync(ctx.jsonlFilePath, line);
85
- logger?.debug("crash-recovery", "run.complete event written", { exitReason });
86
- } catch (err) {
87
- console.error("[crash-recovery] Failed to write run.complete event:", err);
88
- }
89
- }
90
-
91
- /**
92
- * Update status.json to "crashed" state (both project-level and feature-level)
93
- */
94
- export async function updateStatusToCrashed(
95
- statusWriter: StatusWriter,
96
- totalCost: number,
97
- iterations: number,
98
- signal: string,
99
- featureDir?: string,
100
- ): Promise<void> {
101
- try {
102
- statusWriter.setRunStatus("crashed");
103
- await statusWriter.update(totalCost, iterations, {
104
- crashedAt: new Date().toISOString(),
105
- crashSignal: signal,
106
- });
107
-
108
- if (featureDir) {
109
- await statusWriter.writeFeatureStatus(featureDir, totalCost, iterations, {
110
- crashedAt: new Date().toISOString(),
111
- crashSignal: signal,
112
- });
113
- }
114
- } catch (err) {
115
- console.error("[crash-recovery] Failed to update status.json:", err);
116
- }
117
- }
118
-
119
- /**
120
- * Write exit summary entry to JSONL
121
- */
122
- export async function writeExitSummary(
123
- jsonlFilePath: string | undefined,
124
- totalCost: number,
125
- iterations: number,
126
- storiesCompleted: number,
127
- durationMs: number,
128
- ): Promise<void> {
129
- if (!jsonlFilePath) return;
130
-
131
- const logger = getSafeLogger();
132
-
133
- try {
134
- const summaryEntry = {
135
- timestamp: new Date().toISOString(),
136
- level: "info",
137
- stage: "exit-summary",
138
- message: "Run completed",
139
- data: {
140
- totalCost,
141
- iterations,
142
- storiesCompleted,
143
- durationMs,
144
- exitedCleanly: true,
145
- },
146
- };
147
-
148
- const line = `${JSON.stringify(summaryEntry)}\n`;
149
- appendFileSync(jsonlFilePath, line);
150
- logger?.debug("crash-recovery", "Exit summary written");
151
- } catch (err) {
152
- logger?.warn("crash-recovery", "Failed to write exit summary", { error: (err as Error).message });
153
- }
154
- }