@nathapp/nax 0.50.2 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/nax.js +579 -373
  3. package/package.json +1 -3
  4. package/bin/nax.ts +0 -1195
  5. package/src/acceptance/fix-generator.ts +0 -322
  6. package/src/acceptance/generator.ts +0 -423
  7. package/src/acceptance/index.ts +0 -42
  8. package/src/acceptance/refinement.ts +0 -224
  9. package/src/acceptance/templates/cli.ts +0 -47
  10. package/src/acceptance/templates/component.ts +0 -78
  11. package/src/acceptance/templates/e2e.ts +0 -43
  12. package/src/acceptance/templates/index.ts +0 -21
  13. package/src/acceptance/templates/snapshot.ts +0 -50
  14. package/src/acceptance/templates/unit.ts +0 -48
  15. package/src/acceptance/types.ts +0 -135
  16. package/src/agents/acp/adapter.ts +0 -888
  17. package/src/agents/acp/cost.ts +0 -9
  18. package/src/agents/acp/index.ts +0 -7
  19. package/src/agents/acp/interaction-bridge.ts +0 -126
  20. package/src/agents/acp/parser.ts +0 -119
  21. package/src/agents/acp/spawn-client.ts +0 -373
  22. package/src/agents/acp/types.ts +0 -22
  23. package/src/agents/aider/adapter.ts +0 -135
  24. package/src/agents/claude/adapter.ts +0 -258
  25. package/src/agents/claude/complete.ts +0 -80
  26. package/src/agents/claude/cost.ts +0 -16
  27. package/src/agents/claude/execution.ts +0 -215
  28. package/src/agents/claude/index.ts +0 -3
  29. package/src/agents/claude/interactive.ts +0 -77
  30. package/src/agents/claude/plan.ts +0 -179
  31. package/src/agents/codex/adapter.ts +0 -153
  32. package/src/agents/cost/calculate.ts +0 -154
  33. package/src/agents/cost/index.ts +0 -10
  34. package/src/agents/cost/parse.ts +0 -97
  35. package/src/agents/cost/pricing.ts +0 -59
  36. package/src/agents/cost/types.ts +0 -45
  37. package/src/agents/gemini/adapter.ts +0 -177
  38. package/src/agents/index.ts +0 -18
  39. package/src/agents/opencode/adapter.ts +0 -106
  40. package/src/agents/registry.ts +0 -136
  41. package/src/agents/shared/decompose.ts +0 -154
  42. package/src/agents/shared/model-resolution.ts +0 -43
  43. package/src/agents/shared/types-extended.ts +0 -164
  44. package/src/agents/shared/validation.ts +0 -69
  45. package/src/agents/shared/version-detection.ts +0 -109
  46. package/src/agents/types.ts +0 -205
  47. package/src/analyze/classifier.ts +0 -282
  48. package/src/analyze/index.ts +0 -16
  49. package/src/analyze/scanner.ts +0 -171
  50. package/src/analyze/types.ts +0 -51
  51. package/src/cli/accept.ts +0 -108
  52. package/src/cli/agents.ts +0 -87
  53. package/src/cli/analyze-parser.ts +0 -291
  54. package/src/cli/analyze.ts +0 -352
  55. package/src/cli/config-descriptions.ts +0 -218
  56. package/src/cli/config-diff.ts +0 -103
  57. package/src/cli/config-display.ts +0 -285
  58. package/src/cli/config-get.ts +0 -55
  59. package/src/cli/config.ts +0 -14
  60. package/src/cli/constitution.ts +0 -17
  61. package/src/cli/diagnose-analysis.ts +0 -159
  62. package/src/cli/diagnose-formatter.ts +0 -87
  63. package/src/cli/diagnose.ts +0 -203
  64. package/src/cli/generate.ts +0 -250
  65. package/src/cli/index.ts +0 -42
  66. package/src/cli/init-context.ts +0 -405
  67. package/src/cli/init-detect.ts +0 -303
  68. package/src/cli/init.ts +0 -296
  69. package/src/cli/interact.ts +0 -295
  70. package/src/cli/plan.ts +0 -509
  71. package/src/cli/plugins.ts +0 -122
  72. package/src/cli/prompts-export.ts +0 -58
  73. package/src/cli/prompts-init.ts +0 -200
  74. package/src/cli/prompts-main.ts +0 -183
  75. package/src/cli/prompts-shared.ts +0 -70
  76. package/src/cli/prompts-tdd.ts +0 -88
  77. package/src/cli/prompts.ts +0 -17
  78. package/src/cli/runs.ts +0 -174
  79. package/src/cli/status-cost.ts +0 -151
  80. package/src/cli/status-features.ts +0 -405
  81. package/src/cli/status.ts +0 -13
  82. package/src/commands/common.ts +0 -171
  83. package/src/commands/diagnose.ts +0 -17
  84. package/src/commands/index.ts +0 -9
  85. package/src/commands/logs-formatter.ts +0 -201
  86. package/src/commands/logs-reader.ts +0 -171
  87. package/src/commands/logs.ts +0 -103
  88. package/src/commands/precheck.ts +0 -86
  89. package/src/commands/runs.ts +0 -220
  90. package/src/commands/unlock.ts +0 -96
  91. package/src/config/defaults.ts +0 -217
  92. package/src/config/index.ts +0 -22
  93. package/src/config/loader.ts +0 -143
  94. package/src/config/merge.ts +0 -106
  95. package/src/config/merger.ts +0 -147
  96. package/src/config/path-security.ts +0 -121
  97. package/src/config/paths.ts +0 -27
  98. package/src/config/permissions.ts +0 -63
  99. package/src/config/runtime-types.ts +0 -520
  100. package/src/config/schema-types.ts +0 -53
  101. package/src/config/schema.ts +0 -60
  102. package/src/config/schemas.ts +0 -425
  103. package/src/config/test-strategy.ts +0 -71
  104. package/src/config/types.ts +0 -57
  105. package/src/config/validate.ts +0 -103
  106. package/src/constitution/generator.ts +0 -158
  107. package/src/constitution/generators/aider.ts +0 -41
  108. package/src/constitution/generators/claude.ts +0 -35
  109. package/src/constitution/generators/cursor.ts +0 -36
  110. package/src/constitution/generators/opencode.ts +0 -38
  111. package/src/constitution/generators/types.ts +0 -33
  112. package/src/constitution/generators/windsurf.ts +0 -36
  113. package/src/constitution/index.ts +0 -11
  114. package/src/constitution/loader.ts +0 -121
  115. package/src/constitution/types.ts +0 -31
  116. package/src/context/auto-detect.ts +0 -228
  117. package/src/context/builder.ts +0 -299
  118. package/src/context/elements.ts +0 -122
  119. package/src/context/formatter.ts +0 -107
  120. package/src/context/generator.ts +0 -343
  121. package/src/context/generators/aider.ts +0 -34
  122. package/src/context/generators/claude.ts +0 -28
  123. package/src/context/generators/codex.ts +0 -28
  124. package/src/context/generators/cursor.ts +0 -28
  125. package/src/context/generators/gemini.ts +0 -28
  126. package/src/context/generators/opencode.ts +0 -30
  127. package/src/context/generators/windsurf.ts +0 -28
  128. package/src/context/greenfield.ts +0 -114
  129. package/src/context/index.ts +0 -34
  130. package/src/context/injector.ts +0 -279
  131. package/src/context/parent-context.ts +0 -39
  132. package/src/context/test-scanner.ts +0 -370
  133. package/src/context/types.ts +0 -98
  134. package/src/decompose/apply.ts +0 -50
  135. package/src/decompose/builder.ts +0 -181
  136. package/src/decompose/index.ts +0 -8
  137. package/src/decompose/sections/codebase.ts +0 -26
  138. package/src/decompose/sections/constraints.ts +0 -32
  139. package/src/decompose/sections/index.ts +0 -4
  140. package/src/decompose/sections/sibling-stories.ts +0 -25
  141. package/src/decompose/sections/target-story.ts +0 -31
  142. package/src/decompose/types.ts +0 -55
  143. package/src/decompose/validators/complexity.ts +0 -45
  144. package/src/decompose/validators/coverage.ts +0 -134
  145. package/src/decompose/validators/dependency.ts +0 -91
  146. package/src/decompose/validators/index.ts +0 -35
  147. package/src/decompose/validators/overlap.ts +0 -128
  148. package/src/errors.ts +0 -67
  149. package/src/execution/batching.ts +0 -157
  150. package/src/execution/crash-heartbeat.ts +0 -77
  151. package/src/execution/crash-recovery.ts +0 -79
  152. package/src/execution/crash-signals.ts +0 -165
  153. package/src/execution/crash-writer.ts +0 -154
  154. package/src/execution/deferred-review.ts +0 -105
  155. package/src/execution/dry-run.ts +0 -81
  156. package/src/execution/escalation/escalation.ts +0 -46
  157. package/src/execution/escalation/index.ts +0 -13
  158. package/src/execution/escalation/tier-escalation.ts +0 -346
  159. package/src/execution/escalation/tier-outcome.ts +0 -143
  160. package/src/execution/executor-types.ts +0 -73
  161. package/src/execution/helpers.ts +0 -38
  162. package/src/execution/index.ts +0 -27
  163. package/src/execution/iteration-runner.ts +0 -160
  164. package/src/execution/lifecycle/acceptance-loop.ts +0 -280
  165. package/src/execution/lifecycle/headless-formatter.ts +0 -83
  166. package/src/execution/lifecycle/index.ts +0 -11
  167. package/src/execution/lifecycle/parallel-lifecycle.ts +0 -101
  168. package/src/execution/lifecycle/precheck-runner.ts +0 -140
  169. package/src/execution/lifecycle/run-cleanup.ts +0 -81
  170. package/src/execution/lifecycle/run-completion.ts +0 -247
  171. package/src/execution/lifecycle/run-initialization.ts +0 -187
  172. package/src/execution/lifecycle/run-regression.ts +0 -305
  173. package/src/execution/lifecycle/run-setup.ts +0 -240
  174. package/src/execution/lifecycle/story-size-prompts.ts +0 -123
  175. package/src/execution/lock.ts +0 -129
  176. package/src/execution/parallel-coordinator.ts +0 -281
  177. package/src/execution/parallel-executor-rectification-pass.ts +0 -117
  178. package/src/execution/parallel-executor-rectify.ts +0 -136
  179. package/src/execution/parallel-executor.ts +0 -330
  180. package/src/execution/parallel-worker.ts +0 -149
  181. package/src/execution/parallel.ts +0 -13
  182. package/src/execution/pid-registry.ts +0 -275
  183. package/src/execution/pipeline-result-handler.ts +0 -221
  184. package/src/execution/progress.ts +0 -27
  185. package/src/execution/queue-handler.ts +0 -109
  186. package/src/execution/runner-completion.ts +0 -171
  187. package/src/execution/runner-execution.ts +0 -243
  188. package/src/execution/runner-setup.ts +0 -86
  189. package/src/execution/runner.ts +0 -265
  190. package/src/execution/sequential-executor.ts +0 -219
  191. package/src/execution/status-file.ts +0 -264
  192. package/src/execution/status-writer.ts +0 -181
  193. package/src/execution/story-context.ts +0 -266
  194. package/src/execution/story-selector.ts +0 -76
  195. package/src/execution/test-output-parser.ts +0 -14
  196. package/src/execution/timeout-handler.ts +0 -100
  197. package/src/hooks/index.ts +0 -2
  198. package/src/hooks/runner.ts +0 -280
  199. package/src/hooks/types.ts +0 -79
  200. package/src/interaction/chain.ts +0 -170
  201. package/src/interaction/index.ts +0 -61
  202. package/src/interaction/init.ts +0 -84
  203. package/src/interaction/plugins/auto.ts +0 -243
  204. package/src/interaction/plugins/cli.ts +0 -300
  205. package/src/interaction/plugins/telegram.ts +0 -384
  206. package/src/interaction/plugins/webhook.ts +0 -286
  207. package/src/interaction/state.ts +0 -171
  208. package/src/interaction/triggers.ts +0 -250
  209. package/src/interaction/types.ts +0 -170
  210. package/src/logger/formatters.ts +0 -84
  211. package/src/logger/index.ts +0 -16
  212. package/src/logger/logger.ts +0 -296
  213. package/src/logger/types.ts +0 -48
  214. package/src/logging/formatter.ts +0 -355
  215. package/src/logging/index.ts +0 -22
  216. package/src/logging/types.ts +0 -93
  217. package/src/metrics/aggregator.ts +0 -191
  218. package/src/metrics/index.ts +0 -14
  219. package/src/metrics/tracker.ts +0 -200
  220. package/src/metrics/types.ts +0 -115
  221. package/src/optimizer/index.ts +0 -63
  222. package/src/optimizer/noop.optimizer.ts +0 -24
  223. package/src/optimizer/rule-based.optimizer.ts +0 -248
  224. package/src/optimizer/types.ts +0 -53
  225. package/src/pipeline/event-bus.ts +0 -297
  226. package/src/pipeline/events.ts +0 -130
  227. package/src/pipeline/index.ts +0 -19
  228. package/src/pipeline/runner.ts +0 -149
  229. package/src/pipeline/stages/acceptance-setup.ts +0 -140
  230. package/src/pipeline/stages/acceptance.ts +0 -215
  231. package/src/pipeline/stages/autofix.ts +0 -262
  232. package/src/pipeline/stages/completion.ts +0 -110
  233. package/src/pipeline/stages/constitution.ts +0 -63
  234. package/src/pipeline/stages/context.ts +0 -122
  235. package/src/pipeline/stages/execution.ts +0 -359
  236. package/src/pipeline/stages/index.ts +0 -86
  237. package/src/pipeline/stages/optimizer.ts +0 -74
  238. package/src/pipeline/stages/prompt.ts +0 -79
  239. package/src/pipeline/stages/queue-check.ts +0 -103
  240. package/src/pipeline/stages/rectify.ts +0 -101
  241. package/src/pipeline/stages/regression.ts +0 -99
  242. package/src/pipeline/stages/review.ts +0 -94
  243. package/src/pipeline/stages/routing.ts +0 -276
  244. package/src/pipeline/stages/verify.ts +0 -286
  245. package/src/pipeline/subscribers/events-writer.ts +0 -135
  246. package/src/pipeline/subscribers/hooks.ts +0 -179
  247. package/src/pipeline/subscribers/interaction.ts +0 -103
  248. package/src/pipeline/subscribers/registry.ts +0 -73
  249. package/src/pipeline/subscribers/reporters.ts +0 -174
  250. package/src/pipeline/types.ts +0 -220
  251. package/src/plugins/extensions.ts +0 -225
  252. package/src/plugins/index.ts +0 -33
  253. package/src/plugins/loader.ts +0 -352
  254. package/src/plugins/plugin-logger.ts +0 -41
  255. package/src/plugins/registry.ts +0 -168
  256. package/src/plugins/types.ts +0 -206
  257. package/src/plugins/validator.ts +0 -352
  258. package/src/prd/index.ts +0 -220
  259. package/src/prd/schema.ts +0 -268
  260. package/src/prd/types.ts +0 -273
  261. package/src/prd/validate.ts +0 -41
  262. package/src/precheck/checks-agents.ts +0 -63
  263. package/src/precheck/checks-blockers.ts +0 -23
  264. package/src/precheck/checks-cli.ts +0 -68
  265. package/src/precheck/checks-config.ts +0 -102
  266. package/src/precheck/checks-git.ts +0 -117
  267. package/src/precheck/checks-system.ts +0 -101
  268. package/src/precheck/checks-warnings.ts +0 -221
  269. package/src/precheck/checks.ts +0 -36
  270. package/src/precheck/index.ts +0 -374
  271. package/src/precheck/story-size-gate.ts +0 -144
  272. package/src/precheck/types.ts +0 -31
  273. package/src/prompts/builder.ts +0 -166
  274. package/src/prompts/index.ts +0 -2
  275. package/src/prompts/loader.ts +0 -43
  276. package/src/prompts/sections/conventions.ts +0 -19
  277. package/src/prompts/sections/hermetic.ts +0 -41
  278. package/src/prompts/sections/index.ts +0 -12
  279. package/src/prompts/sections/isolation.ts +0 -70
  280. package/src/prompts/sections/role-task.ts +0 -182
  281. package/src/prompts/sections/story.ts +0 -55
  282. package/src/prompts/sections/verdict.ts +0 -70
  283. package/src/prompts/types.ts +0 -21
  284. package/src/queue/index.ts +0 -2
  285. package/src/queue/manager.ts +0 -254
  286. package/src/queue/types.ts +0 -54
  287. package/src/review/index.ts +0 -8
  288. package/src/review/orchestrator.ts +0 -154
  289. package/src/review/runner.ts +0 -303
  290. package/src/review/types.ts +0 -70
  291. package/src/routing/batch-route.ts +0 -35
  292. package/src/routing/builder.ts +0 -81
  293. package/src/routing/chain.ts +0 -75
  294. package/src/routing/content-hash.ts +0 -25
  295. package/src/routing/index.ts +0 -20
  296. package/src/routing/loader.ts +0 -62
  297. package/src/routing/router.ts +0 -305
  298. package/src/routing/strategies/adaptive.ts +0 -215
  299. package/src/routing/strategies/index.ts +0 -8
  300. package/src/routing/strategies/keyword.ts +0 -180
  301. package/src/routing/strategies/llm-prompts.ts +0 -224
  302. package/src/routing/strategies/llm.ts +0 -320
  303. package/src/routing/strategies/manual.ts +0 -50
  304. package/src/routing/strategy.ts +0 -102
  305. package/src/tdd/cleanup.ts +0 -120
  306. package/src/tdd/index.ts +0 -22
  307. package/src/tdd/isolation.ts +0 -117
  308. package/src/tdd/orchestrator.ts +0 -406
  309. package/src/tdd/prompts.ts +0 -40
  310. package/src/tdd/rectification-gate.ts +0 -274
  311. package/src/tdd/session-runner.ts +0 -263
  312. package/src/tdd/types.ts +0 -84
  313. package/src/tdd/verdict-reader.ts +0 -266
  314. package/src/tdd/verdict.ts +0 -152
  315. package/src/tui/App.tsx +0 -265
  316. package/src/tui/components/AgentPanel.tsx +0 -75
  317. package/src/tui/components/CostOverlay.tsx +0 -118
  318. package/src/tui/components/HelpOverlay.tsx +0 -107
  319. package/src/tui/components/StatusBar.tsx +0 -63
  320. package/src/tui/components/StoriesPanel.tsx +0 -177
  321. package/src/tui/hooks/useKeyboard.ts +0 -142
  322. package/src/tui/hooks/useLayout.ts +0 -137
  323. package/src/tui/hooks/usePipelineEvents.ts +0 -183
  324. package/src/tui/hooks/usePty.ts +0 -189
  325. package/src/tui/index.tsx +0 -38
  326. package/src/tui/types.ts +0 -76
  327. package/src/utils/errors.ts +0 -12
  328. package/src/utils/git.ts +0 -245
  329. package/src/utils/json-file.ts +0 -72
  330. package/src/utils/log-test-output.ts +0 -25
  331. package/src/utils/path-security.ts +0 -73
  332. package/src/utils/queue-writer.ts +0 -54
  333. package/src/verification/crash-detector.ts +0 -34
  334. package/src/verification/executor.ts +0 -250
  335. package/src/verification/index.ts +0 -12
  336. package/src/verification/orchestrator-types.ts +0 -154
  337. package/src/verification/orchestrator.ts +0 -76
  338. package/src/verification/parser.ts +0 -220
  339. package/src/verification/rectification-loop.ts +0 -172
  340. package/src/verification/rectification.ts +0 -108
  341. package/src/verification/runners.ts +0 -129
  342. package/src/verification/smart-runner.ts +0 -307
  343. package/src/verification/strategies/acceptance.ts +0 -136
  344. package/src/verification/strategies/regression.ts +0 -90
  345. package/src/verification/strategies/scoped.ts +0 -154
  346. package/src/verification/types.ts +0 -117
  347. package/src/version.ts +0 -40
  348. package/src/worktree/dispatcher.ts +0 -6
  349. package/src/worktree/index.ts +0 -2
  350. package/src/worktree/manager.ts +0 -193
  351. package/src/worktree/merge.ts +0 -302
  352. package/src/worktree/types.ts +0 -4
@@ -1,275 +0,0 @@
1
- /**
2
- * PID Registry — Track and cleanup spawned agent processes
3
- *
4
- * Implements BUG-002:
5
- * - Track PIDs of spawned Claude Code processes
6
- * - Write .nax-pids file for persistence across crashes
7
- * - Support killAll() for crash signal handlers
8
- * - Support cleanupStale() for startup cleanup
9
- * - Use process groups (setsid) on Linux, direct kill on macOS
10
- */
11
-
12
- import { existsSync } from "node:fs";
13
- import { appendFile } from "node:fs/promises";
14
- import { getSafeLogger } from "../logger";
15
-
16
- /**
17
- * PID registry file name
18
- */
19
- const PID_REGISTRY_FILE = ".nax-pids";
20
-
21
- /**
22
- * PID registry entry
23
- */
24
- interface PidEntry {
25
- pid: number;
26
- spawnedAt: string;
27
- workdir: string;
28
- }
29
-
30
- /**
31
- * PID Registry — Track spawned agent processes and cleanup orphans
32
- *
33
- * Maintains a .nax-pids file in the workdir to track spawned processes.
34
- * On crash, signal handlers call killAll() to terminate all tracked PIDs.
35
- * On startup, runner calls cleanupStale() to kill any orphaned processes from previous runs.
36
- *
37
- * @example
38
- * ```ts
39
- * const registry = new PidRegistry("/path/to/project");
40
- * await registry.register(12345);
41
- * // ... later, on crash or shutdown
42
- * await registry.killAll();
43
- * ```
44
- */
45
- export class PidRegistry {
46
- private readonly workdir: string;
47
- private readonly pidsFilePath: string;
48
- private readonly pids: Set<number> = new Set();
49
- private readonly platform: NodeJS.Platform;
50
-
51
- /**
52
- * Create a new PID registry for the given workdir.
53
- *
54
- * @param workdir - Working directory where .nax-pids will be stored
55
- * @param platform - Optional platform override (for testing)
56
- */
57
- constructor(workdir: string, platform?: NodeJS.Platform) {
58
- this.workdir = workdir;
59
- this.pidsFilePath = `${workdir}/${PID_REGISTRY_FILE}`;
60
- this.platform = platform ?? process.platform;
61
- }
62
-
63
- /**
64
- * Register a spawned process PID.
65
- *
66
- * Adds the PID to the in-memory set and writes to .nax-pids file.
67
- *
68
- * @param pid - Process ID to register
69
- */
70
- async register(pid: number): Promise<void> {
71
- const logger = getSafeLogger();
72
- this.pids.add(pid);
73
-
74
- const entry: PidEntry = {
75
- pid,
76
- spawnedAt: new Date().toISOString(),
77
- workdir: this.workdir,
78
- };
79
-
80
- try {
81
- // Atomically append to .nax-pids file (one JSON entry per line)
82
- const line = `${JSON.stringify(entry)}\n`;
83
- await appendFile(this.pidsFilePath, line);
84
- logger?.debug("pid-registry", `Registered PID ${pid}`, { pid });
85
- } catch (err) {
86
- logger?.warn("pid-registry", `Failed to write PID ${pid} to registry`, {
87
- error: (err as Error).message,
88
- });
89
- }
90
- }
91
-
92
- /**
93
- * Unregister a process PID (e.g., after clean exit).
94
- *
95
- * Removes the PID from the in-memory set and rewrites .nax-pids file.
96
- *
97
- * @param pid - Process ID to unregister
98
- */
99
- async unregister(pid: number): Promise<void> {
100
- const logger = getSafeLogger();
101
- this.pids.delete(pid);
102
-
103
- try {
104
- // Rewrite .nax-pids file without the unregistered PID
105
- await this.writePidsFile();
106
- logger?.debug("pid-registry", `Unregistered PID ${pid}`, { pid });
107
- } catch (err) {
108
- logger?.warn("pid-registry", `Failed to unregister PID ${pid}`, {
109
- error: (err as Error).message,
110
- });
111
- }
112
- }
113
-
114
- /**
115
- * Kill all registered processes.
116
- *
117
- * Called by crash signal handlers to cleanup spawned agent processes.
118
- * Uses process groups (setsid) on Linux, direct kill on macOS.
119
- *
120
- * On Linux: kill -TERM -<pid> kills the entire process group
121
- * On macOS: kill -TERM <pid> kills the process directly
122
- */
123
- async killAll(): Promise<void> {
124
- const logger = getSafeLogger();
125
- const pids = Array.from(this.pids);
126
-
127
- if (pids.length === 0) {
128
- logger?.debug("pid-registry", "No PIDs to kill");
129
- return;
130
- }
131
-
132
- logger?.info("pid-registry", `Killing ${pids.length} registered processes`, { pids });
133
-
134
- const killPromises = pids.map((pid) => this.killPid(pid));
135
- await Promise.allSettled(killPromises);
136
-
137
- // Clear the registry file
138
- try {
139
- await Bun.write(this.pidsFilePath, "");
140
- this.pids.clear();
141
- logger?.info("pid-registry", "All registered PIDs killed and registry cleared");
142
- } catch (err) {
143
- logger?.warn("pid-registry", "Failed to clear registry file", {
144
- error: (err as Error).message,
145
- });
146
- }
147
- }
148
-
149
- /**
150
- * Cleanup stale PIDs from previous runs.
151
- *
152
- * Called at runner startup before lock acquisition.
153
- * Reads .nax-pids file and kills any still-running processes.
154
- */
155
- async cleanupStale(): Promise<void> {
156
- const logger = getSafeLogger();
157
-
158
- if (!existsSync(this.pidsFilePath)) {
159
- logger?.debug("pid-registry", "No stale PIDs file found");
160
- return;
161
- }
162
-
163
- try {
164
- const content = await Bun.file(this.pidsFilePath).text();
165
- const lines = content
166
- .split("\n")
167
- .filter((line) => line.trim())
168
- .map((line) => {
169
- try {
170
- return JSON.parse(line) as PidEntry;
171
- } catch {
172
- return null;
173
- }
174
- })
175
- .filter((entry): entry is PidEntry => entry !== null);
176
-
177
- if (lines.length === 0) {
178
- logger?.debug("pid-registry", "No stale PIDs to cleanup");
179
- await Bun.write(this.pidsFilePath, "");
180
- return;
181
- }
182
-
183
- const stalePids = lines.map((entry) => entry.pid);
184
- logger?.info("pid-registry", `Cleaning up ${stalePids.length} stale PIDs from previous run`, {
185
- pids: stalePids,
186
- });
187
-
188
- const killPromises = stalePids.map((pid) => this.killPid(pid));
189
- await Promise.allSettled(killPromises);
190
-
191
- // Clear the registry file after cleanup
192
- await Bun.write(this.pidsFilePath, "");
193
- logger?.info("pid-registry", "Stale PIDs cleanup completed");
194
- } catch (err) {
195
- logger?.warn("pid-registry", "Failed to cleanup stale PIDs", {
196
- error: (err as Error).message,
197
- });
198
- }
199
- }
200
-
201
- /**
202
- * Kill a single PID.
203
- *
204
- * Uses process groups on Linux (kill -TERM -<pid>), direct kill on macOS (kill -TERM <pid>).
205
- * Ignores ESRCH (process not found) errors.
206
- *
207
- * @param pid - Process ID to kill
208
- */
209
- private async killPid(pid: number): Promise<void> {
210
- const logger = getSafeLogger();
211
-
212
- try {
213
- // Check if process exists first
214
- const checkProc = Bun.spawn(["kill", "-0", String(pid)], {
215
- stdout: "pipe",
216
- stderr: "pipe",
217
- });
218
- const checkCode = await checkProc.exited;
219
-
220
- if (checkCode !== 0) {
221
- // Process doesn't exist, skip
222
- logger?.debug("pid-registry", `PID ${pid} not found (already exited)`, { pid });
223
- return;
224
- }
225
-
226
- // On Linux, use process groups (kill -TERM -<pid>)
227
- // On macOS, use direct kill (kill -TERM <pid>)
228
- const killArgs = this.platform === "linux" ? ["kill", "-TERM", `-${pid}`] : ["kill", "-TERM", String(pid)];
229
-
230
- const killProc = Bun.spawn(killArgs, {
231
- stdout: "pipe",
232
- stderr: "pipe",
233
- });
234
-
235
- const killCode = await killProc.exited;
236
-
237
- if (killCode === 0) {
238
- logger?.debug("pid-registry", `Killed PID ${pid}`, { pid });
239
- } else {
240
- const stderr = await new Response(killProc.stderr).text();
241
- logger?.warn("pid-registry", `Failed to kill PID ${pid}`, {
242
- pid,
243
- exitCode: killCode,
244
- stderr: stderr.trim(),
245
- });
246
- }
247
- } catch (err) {
248
- logger?.warn("pid-registry", `Error killing PID ${pid}`, {
249
- pid,
250
- error: (err as Error).message,
251
- });
252
- }
253
- }
254
-
255
- /**
256
- * Rewrite .nax-pids file with current in-memory PIDs.
257
- */
258
- private async writePidsFile(): Promise<void> {
259
- const entries = Array.from(this.pids).map((pid) => ({
260
- pid,
261
- spawnedAt: new Date().toISOString(),
262
- workdir: this.workdir,
263
- }));
264
-
265
- const content = entries.map((entry) => JSON.stringify(entry)).join("\n");
266
- await Bun.write(this.pidsFilePath, content ? `${content}\n` : "");
267
- }
268
-
269
- /**
270
- * Get all registered PIDs (for testing)
271
- */
272
- getPids(): number[] {
273
- return Array.from(this.pids);
274
- }
275
- }
@@ -1,221 +0,0 @@
1
- /**
2
- * Pipeline Result Handlers (ADR-005, Phase 4)
3
- *
4
- * Handles pipeline success, failure outcomes after story execution.
5
- * Dry-run handling: see execution/dry-run.ts
6
- * applyCachedRouting: removed (P4-001 — pipeline routing stage is sole source)
7
- */
8
-
9
- import type { NaxConfig } from "../config";
10
- import type { LoadedHooksConfig } from "../hooks";
11
- import type { InteractionChain } from "../interaction/chain";
12
- import { getSafeLogger } from "../logger";
13
- import type { StoryMetrics } from "../metrics";
14
- import { pipelineEventBus } from "../pipeline/event-bus";
15
- import type { PipelineRunResult } from "../pipeline/runner";
16
- import type { PluginRegistry } from "../plugins";
17
- import { countStories, markStoryFailed, markStoryPaused, savePRD } from "../prd";
18
- import type { PRD, UserStory } from "../prd/types";
19
- import type { routeTask } from "../routing";
20
- import { captureOutputFiles } from "../utils/git";
21
- import { handleTierEscalation } from "./escalation";
22
- import { appendProgress } from "./progress";
23
-
24
- /** Filter noise from output files (test files, lock files, nax runtime files) */
25
- function filterOutputFiles(files: string[]): string[] {
26
- const NOISE = [
27
- /\.test\.(ts|js|tsx|jsx)$/,
28
- /\.spec\.(ts|js|tsx|jsx)$/,
29
- /package-lock\.json$/,
30
- /bun\.lock(b?)$/,
31
- /\.gitignore$/,
32
- /^nax\//,
33
- ];
34
- return files.filter((f) => !NOISE.some((p) => p.test(f))).slice(0, 15);
35
- }
36
-
37
- export interface PipelineHandlerContext {
38
- config: NaxConfig;
39
- prd: PRD;
40
- prdPath: string;
41
- workdir: string;
42
- featureDir?: string;
43
- hooks: LoadedHooksConfig;
44
- feature: string;
45
- totalCost: number;
46
- startTime: number;
47
- runId: string;
48
- pluginRegistry: PluginRegistry;
49
- story: UserStory;
50
- storiesToExecute: UserStory[];
51
- routing: ReturnType<typeof routeTask>;
52
- isBatchExecution: boolean;
53
- allStoryMetrics: StoryMetrics[];
54
- storyGitRef: string | null | undefined;
55
- interactionChain?: InteractionChain | null;
56
- storyStartTime?: number;
57
- }
58
-
59
- export interface PipelineSuccessResult {
60
- storiesCompletedDelta: number;
61
- costDelta: number;
62
- prd: PRD;
63
- prdDirty: boolean;
64
- }
65
-
66
- export async function handlePipelineSuccess(
67
- ctx: PipelineHandlerContext,
68
- pipelineResult: PipelineRunResult,
69
- ): Promise<PipelineSuccessResult> {
70
- const logger = getSafeLogger();
71
- const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
72
- const prd = ctx.prd;
73
-
74
- if (pipelineResult.context.storyMetrics) {
75
- ctx.allStoryMetrics.push(...pipelineResult.context.storyMetrics);
76
- }
77
-
78
- const storiesCompletedDelta = ctx.storiesToExecute.length;
79
- for (const completedStory of ctx.storiesToExecute) {
80
- const now = Date.now();
81
- logger?.info("story.complete", "Story completed successfully", {
82
- storyId: completedStory.id,
83
- storyTitle: completedStory.title,
84
- totalCost: ctx.totalCost + costDelta,
85
- runElapsedMs: now - ctx.startTime,
86
- storyDurationMs: ctx.storyStartTime ? now - ctx.storyStartTime : undefined,
87
- });
88
-
89
- pipelineEventBus.emit({
90
- type: "story:completed",
91
- storyId: completedStory.id,
92
- story: completedStory,
93
- passed: true,
94
- runElapsedMs: Date.now() - ctx.startTime,
95
- cost: costDelta,
96
- modelTier: ctx.routing.modelTier,
97
- testStrategy: ctx.routing.testStrategy,
98
- });
99
- }
100
-
101
- // ENH-005: Capture output files for context chaining
102
- if (ctx.storyGitRef) {
103
- for (const completedStory of ctx.storiesToExecute) {
104
- try {
105
- const rawFiles = await captureOutputFiles(ctx.workdir, ctx.storyGitRef, completedStory.workdir);
106
- const filtered = filterOutputFiles(rawFiles);
107
- if (filtered.length > 0) {
108
- completedStory.outputFiles = filtered;
109
- }
110
- } catch {
111
- // Non-fatal — context chaining is best-effort
112
- }
113
- }
114
- }
115
-
116
- const updatedCounts = countStories(prd);
117
- logger?.info("progress", "Progress update", {
118
- totalStories: updatedCounts.total,
119
- passedStories: updatedCounts.passed,
120
- failedStories: updatedCounts.failed,
121
- pendingStories: updatedCounts.pending,
122
- totalCost: ctx.totalCost + costDelta,
123
- costLimit: ctx.config.execution.costLimit,
124
- elapsedMs: Date.now() - ctx.startTime,
125
- storyDurationMs: ctx.storyStartTime ? Date.now() - ctx.storyStartTime : undefined,
126
- });
127
-
128
- return { storiesCompletedDelta, costDelta, prd, prdDirty: true };
129
- }
130
-
131
- export interface PipelineFailureResult {
132
- prd: PRD;
133
- prdDirty: boolean;
134
- costDelta: number;
135
- }
136
-
137
- export async function handlePipelineFailure(
138
- ctx: PipelineHandlerContext,
139
- pipelineResult: PipelineRunResult,
140
- ): Promise<PipelineFailureResult> {
141
- const logger = getSafeLogger();
142
- let prd = ctx.prd;
143
- let prdDirty = false;
144
- // Always capture cost even for failed stories — agent ran and spent tokens
145
- const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
146
-
147
- switch (pipelineResult.finalAction) {
148
- case "pause":
149
- markStoryPaused(prd, ctx.story.id);
150
- await savePRD(prd, ctx.prdPath);
151
- prdDirty = true;
152
- logger?.warn("pipeline", "Story paused", { storyId: ctx.story.id, reason: pipelineResult.reason });
153
- pipelineEventBus.emit({
154
- type: "story:paused",
155
- storyId: ctx.story.id,
156
- reason: pipelineResult.reason || "Pipeline paused",
157
- cost: ctx.totalCost,
158
- });
159
- break;
160
-
161
- case "skip":
162
- logger?.warn("pipeline", "Story skipped", { storyId: ctx.story.id, reason: pipelineResult.reason });
163
- prdDirty = true;
164
- break;
165
-
166
- case "fail":
167
- markStoryFailed(prd, ctx.story.id, pipelineResult.context.tddFailureCategory, pipelineResult.stoppedAtStage);
168
- await savePRD(prd, ctx.prdPath);
169
- prdDirty = true;
170
- logger?.error("pipeline", "Story failed", { storyId: ctx.story.id, reason: pipelineResult.reason });
171
-
172
- if (ctx.featureDir) {
173
- await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} — ${pipelineResult.reason}`);
174
- }
175
-
176
- pipelineEventBus.emit({
177
- type: "story:failed",
178
- storyId: ctx.story.id,
179
- story: ctx.story,
180
- reason: pipelineResult.reason || "Pipeline failed",
181
- countsTowardEscalation: true,
182
- feature: ctx.feature,
183
- attempts: ctx.story.attempts,
184
- });
185
-
186
- if (ctx.story.attempts !== undefined && ctx.story.attempts >= ctx.config.execution.rectification.maxRetries) {
187
- await pipelineEventBus.emitAsync({
188
- type: "human-review:requested",
189
- storyId: ctx.story.id,
190
- reason: pipelineResult.reason || "Max retries exceeded",
191
- feature: ctx.feature,
192
- attempts: ctx.story.attempts,
193
- });
194
- }
195
- break;
196
-
197
- case "escalate": {
198
- const escalationResult = await handleTierEscalation({
199
- story: ctx.story,
200
- storiesToExecute: ctx.storiesToExecute,
201
- isBatchExecution: ctx.isBatchExecution,
202
- routing: ctx.routing,
203
- pipelineResult,
204
- config: ctx.config,
205
- prd,
206
- prdPath: ctx.prdPath,
207
- featureDir: ctx.featureDir,
208
- hooks: ctx.hooks,
209
- feature: ctx.feature,
210
- totalCost: ctx.totalCost,
211
- workdir: ctx.workdir,
212
- attemptCost: pipelineResult.context.agentResult?.estimatedCost || 0,
213
- });
214
- prd = escalationResult.prd;
215
- prdDirty = escalationResult.prdDirty;
216
- break;
217
- }
218
- }
219
-
220
- return { prd, prdDirty, costDelta };
221
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Progress Logging
3
- *
4
- * Append timestamped entries to progress.txt after story completion.
5
- */
6
-
7
- import { mkdirSync } from "node:fs";
8
- import { join } from "node:path";
9
- import type { StoryStatus } from "../prd";
10
-
11
- /** Append a progress entry to progress.txt */
12
- export async function appendProgress(
13
- featureDir: string,
14
- storyId: string,
15
- status: StoryStatus,
16
- message: string,
17
- ): Promise<void> {
18
- mkdirSync(featureDir, { recursive: true });
19
- const progressPath = join(featureDir, "progress.txt");
20
- const timestamp = new Date().toISOString();
21
- const entry = `[${timestamp}] ${storyId} — ${status.toUpperCase()} — ${message}\n`;
22
-
23
- // Append to file (creates if doesn't exist)
24
- const file = Bun.file(progressPath);
25
- const existing = (await file.exists()) ? await file.text() : "";
26
- await Bun.write(progressPath, existing + entry);
27
- }
@@ -1,109 +0,0 @@
1
- /**
2
- * Queue File Handler
3
- *
4
- * Provides atomic read/write operations for .queue.txt command files.
5
- * Uses rename-before-read pattern to prevent race conditions.
6
- */
7
-
8
- import path from "node:path";
9
- import { getLogger } from "../logger";
10
- import { parseQueueFile } from "../queue";
11
- import type { QueueCommand } from "../queue";
12
-
13
- /**
14
- * Safely get logger instance, returns null if not initialized
15
- */
16
- function getSafeLogger() {
17
- try {
18
- return getLogger();
19
- } catch {
20
- return null;
21
- }
22
- }
23
-
24
- /**
25
- * Read and parse queue file atomically.
26
- * Uses rename-before-read pattern to prevent race conditions:
27
- * 1. Rename .queue.txt → .queue.txt.processing (atomic operation)
28
- * 2. Read from .queue.txt.processing
29
- * 3. Delete .queue.txt.processing after processing
30
- *
31
- * This ensures commands written during processing aren't lost.
32
- *
33
- * @param workdir - Working directory containing .queue.txt
34
- * @returns Array of parsed queue commands, or empty array if no queue file
35
- *
36
- * @example
37
- * ```typescript
38
- * const commands = await readQueueFile("/path/to/project");
39
- * for (const cmd of commands) {
40
- * if (cmd.type === "PAUSE") {
41
- * // Handle pause
42
- * }
43
- * }
44
- * await clearQueueFile("/path/to/project");
45
- * ```
46
- */
47
- export async function readQueueFile(workdir: string): Promise<QueueCommand[]> {
48
- const queuePath = path.join(workdir, ".queue.txt");
49
- const processingPath = path.join(workdir, ".queue.txt.processing");
50
- const logger = getSafeLogger();
51
-
52
- try {
53
- // Check if queue file exists
54
- const file = Bun.file(queuePath);
55
- const exists = await file.exists();
56
- if (!exists) {
57
- return [];
58
- }
59
-
60
- // Atomically rename to .processing (prevents concurrent reads)
61
- try {
62
- await Bun.spawn(["mv", queuePath, processingPath], { stdout: "pipe" }).exited;
63
- } catch (error) {
64
- // File was already moved by another process, or doesn't exist anymore
65
- return [];
66
- }
67
-
68
- // Read from processing file
69
- const processingFile = Bun.file(processingPath);
70
- const content = await processingFile.text();
71
- const result = parseQueueFile(content);
72
-
73
- return result.commands;
74
- } catch (error) {
75
- logger?.warn("queue", "Failed to read queue file", {
76
- error: (error as Error).message,
77
- });
78
- return [];
79
- }
80
- }
81
-
82
- /**
83
- * Clear queue file after processing commands.
84
- * Deletes .queue.txt.processing file.
85
- *
86
- * @param workdir - Working directory containing .queue.txt.processing
87
- *
88
- * @example
89
- * ```typescript
90
- * const commands = await readQueueFile("/path/to/project");
91
- * // Process commands...
92
- * await clearQueueFile("/path/to/project");
93
- * ```
94
- */
95
- export async function clearQueueFile(workdir: string): Promise<void> {
96
- const processingPath = path.join(workdir, ".queue.txt.processing");
97
- const logger = getSafeLogger();
98
- try {
99
- const file = Bun.file(processingPath);
100
- const exists = await file.exists();
101
- if (exists) {
102
- await Bun.spawn(["rm", processingPath], { stdout: "pipe" }).exited;
103
- }
104
- } catch (error) {
105
- logger?.warn("queue", "Failed to clear queue file", {
106
- error: (error as Error).message,
107
- });
108
- }
109
- }