@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
package/bin/nax.ts DELETED
@@ -1,1195 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * nax — AI Coding Agent Orchestrator
4
- *
5
- * CLI entry point for the nax orchestration system.
6
- *
7
- * Features:
8
- * - `init`: Initialize nax in a project directory
9
- * - `run`: Execute the orchestration loop for a feature
10
- * - `features create/list`: Manage feature definitions
11
- * - `analyze`: Parse spec.md + tasks.md into prd.json
12
- * - `agents`: Check available coding agent installations
13
- * - `status`: Show current feature progress
14
- *
15
- * Architecture:
16
- * - Complexity-based routing to model tiers (fast/balanced/powerful)
17
- * - Three-session TDD for security-critical and complex stories
18
- * - Story batching for simple stories to reduce overhead
19
- * - Lifecycle hooks for custom automation (on-start, on-complete, etc.)
20
- *
21
- * @example
22
- * ```bash
23
- * # Initialize in project
24
- * nax init
25
- *
26
- * # Create feature
27
- * nax features create auth-system
28
- *
29
- * # Analyze spec/tasks into PRD
30
- * nax analyze --feature auth-system
31
- *
32
- * # Run orchestration
33
- * nax run --feature auth-system
34
- *
35
- * # Check status
36
- * nax status --feature auth-system
37
- * ```
38
- */
39
-
40
- import { existsSync, mkdirSync } from "node:fs";
41
- import { homedir } from "node:os";
42
- import { join } from "node:path";
43
- import chalk from "chalk";
44
- import { Command } from "commander";
45
-
46
- import {
47
- acceptCommand,
48
- agentsListCommand,
49
- analyzeFeature,
50
- displayCostMetrics,
51
- displayFeatureStatus,
52
- displayLastRunMetrics,
53
- displayModelEfficiency,
54
- exportPromptCommand,
55
- planCommand,
56
- pluginsListCommand,
57
- promptsCommand,
58
- promptsInitCommand,
59
- runsListCommand,
60
- runsShowCommand,
61
- } from "../src/cli";
62
- import { configCommand } from "../src/cli/config";
63
- import { generateCommand } from "../src/cli/generate";
64
- import { diagnose } from "../src/commands/diagnose";
65
- import { logsCommand } from "../src/commands/logs";
66
- import { precheckCommand } from "../src/commands/precheck";
67
- import { runsCommand } from "../src/commands/runs";
68
- import { unlockCommand } from "../src/commands/unlock";
69
- import { DEFAULT_CONFIG, findProjectDir, loadConfig, validateDirectory } from "../src/config";
70
- import { run } from "../src/execution";
71
- import { loadHooksConfig } from "../src/hooks";
72
- import { type LogLevel, initLogger, resetLogger } from "../src/logger";
73
- import { countStories, loadPRD } from "../src/prd";
74
- import { PipelineEventEmitter, type StoryDisplayState, renderTui } from "../src/tui";
75
- import { NAX_VERSION } from "../src/version";
76
-
77
- const program = new Command();
78
-
79
- program.name("nax").description("AI Coding Agent Orchestrator — loops until done").version(NAX_VERSION);
80
-
81
- // ─────────────────────────────────────────────────────────────────────────────
82
- // Helpers
83
- // ─────────────────────────────────────────────────────────────────────────────
84
-
85
- /**
86
- * Prompt user for a yes/no confirmation via stdin.
87
- * In tests or non-TTY environments, defaults to true.
88
- *
89
- * @param question - Confirmation question to display
90
- * @returns true if user answers Y/y, false if N/n, true by default for non-TTY
91
- */
92
- async function promptForConfirmation(question: string): Promise<boolean> {
93
- // In non-TTY mode (tests, pipes), default to true
94
- if (!process.stdin.isTTY) {
95
- return true;
96
- }
97
-
98
- return new Promise((resolve) => {
99
- process.stdout.write(chalk.bold(`${question} [Y/n] `));
100
-
101
- process.stdin.setRawMode(true);
102
- process.stdin.resume();
103
- process.stdin.setEncoding("utf8");
104
-
105
- const handler = (char: string) => {
106
- process.stdin.setRawMode(false);
107
- process.stdin.pause();
108
- process.stdin.removeListener("data", handler);
109
-
110
- const answer = char.toLowerCase();
111
- process.stdout.write("\n");
112
-
113
- if (answer === "n") {
114
- resolve(false);
115
- } else {
116
- // Default to yes for Y, Enter, or any other input
117
- resolve(true);
118
- }
119
- };
120
-
121
- process.stdin.on("data", handler);
122
- });
123
- }
124
-
125
- // ── init ─────────────────────────────────────────────
126
- program
127
- .command("init")
128
- .description("Initialize nax in the current project")
129
- .option("-d, --dir <path>", "Project directory", process.cwd())
130
- .option("-f, --force", "Force overwrite existing files", false)
131
- .option("--package <dir>", "Scaffold per-package nax/context.md (e.g. packages/api)")
132
- .action(async (options) => {
133
- // Validate directory path
134
- let workdir: string;
135
- try {
136
- workdir = validateDirectory(options.dir);
137
- } catch (err) {
138
- console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
139
- process.exit(1);
140
- }
141
-
142
- // --package: scaffold per-package nax/context.md only
143
- if (options.package) {
144
- const { initPackage: initPkg } = await import("../src/cli/init-context");
145
- try {
146
- await initPkg(workdir, options.package, options.force);
147
- console.log(chalk.green("\n[OK] Package scaffold created."));
148
- console.log(chalk.dim(` Created: ${options.package}/nax/context.md`));
149
- console.log(chalk.dim(`\nNext: nax generate --package ${options.package}`));
150
- } catch (err) {
151
- console.error(chalk.red(`Error: ${(err as Error).message}`));
152
- process.exit(1);
153
- }
154
- return;
155
- }
156
-
157
- const naxDir = join(workdir, "nax");
158
-
159
- if (existsSync(naxDir) && !options.force) {
160
- console.log(chalk.yellow("nax already initialized. Use --force to overwrite."));
161
- return;
162
- }
163
-
164
- // Create directory structure
165
- mkdirSync(join(naxDir, "features"), { recursive: true });
166
- mkdirSync(join(naxDir, "hooks"), { recursive: true });
167
-
168
- // Write default config
169
- await Bun.write(join(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
170
-
171
- // Write default hooks.json
172
- await Bun.write(
173
- join(naxDir, "hooks.json"),
174
- JSON.stringify(
175
- {
176
- hooks: {
177
- "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
178
- "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
179
- "on-pause": { command: 'echo "nax paused: $NAX_REASON"', enabled: false },
180
- "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false },
181
- },
182
- },
183
- null,
184
- 2,
185
- ),
186
- );
187
-
188
- // Write .gitignore
189
- await Bun.write(join(naxDir, ".gitignore"), "# nax temp files\n*.tmp\n.paused.json\n.nax-verifier-verdict.json\n");
190
-
191
- // Write starter context.md
192
- await Bun.write(
193
- join(naxDir, "context.md"),
194
- `# Project Context
195
-
196
- This document defines coding standards, architectural decisions, and forbidden patterns for this project.
197
- Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
198
-
199
- > Project metadata (dependencies, commands) is auto-injected by \`nax generate\`.
200
-
201
- ## Coding Standards
202
-
203
- - Follow the project's existing code style and conventions
204
- - Write clear, self-documenting code with meaningful names
205
- - Keep functions small and focused (single responsibility)
206
- - Prefer immutability over mutation
207
- - Use consistent formatting throughout the codebase
208
-
209
- ## Testing Requirements
210
-
211
- - All new code must include tests
212
- - Tests should cover happy paths, edge cases, and error conditions
213
- - Aim for high test coverage (80%+ recommended)
214
- - Tests must pass before marking a story as complete
215
- - Before writing tests, read existing test files to understand what is already covered
216
- - Do not duplicate test coverage that prior stories already wrote
217
- - Focus on testing NEW behavior introduced by this story
218
-
219
- ## Architecture Rules
220
-
221
- - Follow the project's existing architecture patterns
222
- - Each module should have a clear, single purpose
223
- - Avoid tight coupling between modules
224
- - Use dependency injection where appropriate
225
- - Document architectural decisions in comments or docs
226
-
227
- ## Forbidden Patterns
228
-
229
- - No hardcoded secrets, API keys, or credentials
230
- - No console.log in production code (use proper logging)
231
- - No \`any\` types in TypeScript (use proper typing)
232
- - No commented-out code (use version control instead)
233
- - No large files (split into smaller, focused modules)
234
-
235
- ## Commit Standards
236
-
237
- - Write clear, descriptive commit messages
238
- - Follow conventional commits format (feat:, fix:, refactor:, etc.)
239
- - Commit early and often with atomic changes
240
- - Reference story IDs in commit messages
241
-
242
- ## Documentation
243
-
244
- - Add JSDoc comments for public APIs
245
- - Update README when adding new features
246
- - Document complex algorithms or business logic
247
- - Keep documentation up-to-date with code changes
248
-
249
- ---
250
-
251
- **Note:** Customize this file to match your project's specific needs.
252
- `,
253
- );
254
-
255
- // Initialize prompt templates (final step, don't auto-wire config)
256
- try {
257
- await promptsInitCommand({
258
- workdir,
259
- force: options.force,
260
- autoWireConfig: false,
261
- });
262
- } catch (err) {
263
- console.error(chalk.red(`Failed to initialize templates: ${(err as Error).message}`));
264
- process.exit(1);
265
- }
266
-
267
- console.log(chalk.green("✅ Initialized nax"));
268
- console.log(chalk.dim(` ${naxDir}/`));
269
- console.log(chalk.dim(" ├── config.json"));
270
- console.log(chalk.dim(" ├── context.md"));
271
- console.log(chalk.dim(" ├── hooks.json"));
272
- console.log(chalk.dim(" ├── features/"));
273
- console.log(chalk.dim(" ├── hooks/"));
274
- console.log(chalk.dim(" └── templates/"));
275
- console.log(chalk.dim(" ├── test-writer.md"));
276
- console.log(chalk.dim(" ├── implementer.md"));
277
- console.log(chalk.dim(" ├── verifier.md"));
278
- console.log(chalk.dim(" ├── single-session.md"));
279
- console.log(chalk.dim(" └── tdd-simple.md"));
280
- console.log(chalk.dim("\nNext: nax features create <name>"));
281
- });
282
-
283
- // ── run ──────────────────────────────────────────────
284
- program
285
- .command("run")
286
- .description("Run the orchestration loop for a feature")
287
- .requiredOption("-f, --feature <name>", "Feature name")
288
- .option("-a, --agent <name>", "Force a specific agent")
289
- .option("-m, --max-iterations <n>", "Max iterations", "20")
290
- .option("--dry-run", "Show plan without executing", false)
291
- .option("--no-context", "Disable context builder (skip file context in prompts)")
292
- .option("--no-batch", "Disable story batching (execute all stories individually)")
293
- .option("--parallel <n>", "Max parallel sessions (0=auto, omit=sequential)")
294
- .option("--plan", "Run plan phase first before execution", false)
295
- .option("--from <spec-path>", "Path to spec file (required when --plan is used)")
296
- .option("--one-shot", "Skip interactive planning Q&A, use single LLM call (ACP only)", false)
297
- .option("--force", "Force overwrite existing prd.json when using --plan", false)
298
- .option("--headless", "Force headless mode (disable TUI, use pipe mode)", false)
299
- .option("--verbose", "Enable verbose logging (debug level)", false)
300
- .option("--quiet", "Quiet mode (warnings and errors only)", false)
301
- .option("--silent", "Silent mode (errors only)", false)
302
- .option("--json", "JSON mode (raw JSONL output to stdout)", false)
303
- .option("-d, --dir <path>", "Working directory", process.cwd())
304
- .option("--skip-precheck", "Skip precheck validations (advanced users only)", false)
305
- .action(async (options) => {
306
- // Validate directory path
307
- let workdir: string;
308
- try {
309
- workdir = validateDirectory(options.dir);
310
- } catch (err) {
311
- console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
312
- process.exit(1);
313
- }
314
-
315
- // Validate --plan and --from flags (AC-8: --plan without --from is error)
316
- if (options.plan && !options.from) {
317
- console.error(chalk.red("Error: --plan requires --from <spec-path>"));
318
- process.exit(1);
319
- }
320
-
321
- // Validate --from path exists (AC-7: --from without existing file throws error)
322
- if (options.from && !existsSync(options.from)) {
323
- console.error(chalk.red(`Error: File not found: ${options.from} (required with --plan)`));
324
- process.exit(1);
325
- }
326
-
327
- // Determine log level from flags or env var (env var takes precedence)
328
- let logLevel: LogLevel = "info"; // default
329
- const envLevel = process.env.NAX_LOG_LEVEL?.toLowerCase();
330
- if (envLevel && ["error", "warn", "info", "debug"].includes(envLevel)) {
331
- logLevel = envLevel as LogLevel;
332
- } else if (options.verbose) {
333
- logLevel = "debug";
334
- } else if (options.quiet) {
335
- logLevel = "warn";
336
- } else if (options.silent) {
337
- logLevel = "error";
338
- }
339
-
340
- // Determine formatter mode from flags
341
- let formatterMode: "quiet" | "normal" | "verbose" | "json" = "normal"; // default
342
- if (options.json) {
343
- formatterMode = "json";
344
- } else if (options.verbose) {
345
- formatterMode = "verbose";
346
- } else if (options.quiet || options.silent) {
347
- formatterMode = "quiet";
348
- }
349
-
350
- const naxDir = findProjectDir(workdir);
351
- const config = await loadConfig(naxDir ?? undefined);
352
-
353
- if (!naxDir) {
354
- console.error(chalk.red("nax not initialized. Run: nax init"));
355
- process.exit(1);
356
- }
357
-
358
- const featureDir = join(naxDir, "features", options.feature);
359
- const prdPath = join(featureDir, "prd.json");
360
-
361
- // Run plan phase if --plan flag is set (AC-4: runs plan then execute)
362
- if (options.plan && options.from) {
363
- // Guard: block overwrite of existing prd.json unless --force
364
- if (existsSync(prdPath) && !options.force) {
365
- console.error(chalk.red(`Error: prd.json already exists for feature "${options.feature}".`));
366
- console.error(chalk.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
367
- process.exit(1);
368
- }
369
-
370
- // Run environment precheck before plan — catch blockers early (before expensive LLM calls)
371
- if (!options.skipPrecheck) {
372
- const { runEnvironmentPrecheck } = await import("../src/precheck");
373
- console.log(chalk.dim("\n [Pre-plan environment check]"));
374
- const envResult = await runEnvironmentPrecheck(config, workdir);
375
- if (!envResult.passed) {
376
- console.error(chalk.red("\n❌ Environment precheck failed — cannot proceed with planning."));
377
- for (const b of envResult.blockers) {
378
- console.error(chalk.red(` ${b.name}: ${b.message}`));
379
- }
380
- process.exit(1);
381
- }
382
- }
383
-
384
- try {
385
- // Initialize plan logger before calling planCommand — writes to features/<feature>/plan/<ts>.jsonl
386
- const planLogDir = join(featureDir, "plan");
387
- mkdirSync(planLogDir, { recursive: true });
388
- const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
389
- const planLogPath = join(planLogDir, `${planLogId}.jsonl`);
390
- initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
391
- console.log(chalk.dim(` [Plan log: ${planLogPath}]`));
392
-
393
- console.log(chalk.dim(" [Planning phase: generating PRD from spec]"));
394
- const generatedPrdPath = await planCommand(workdir, config, {
395
- from: options.from,
396
- feature: options.feature,
397
- auto: options.oneShot ?? false, // interactive by default; --one-shot skips Q&A
398
- branch: undefined,
399
- });
400
-
401
- // Load the generated PRD to display confirmation gate
402
- const generatedPrd = await loadPRD(generatedPrdPath);
403
-
404
- // Display story breakdown (AC-5: confirmation gate displays story breakdown)
405
- console.log(chalk.bold("\n── Planning Summary ──────────────────────────────"));
406
- console.log(chalk.dim(`Feature: ${generatedPrd.feature}`));
407
- console.log(chalk.dim(`Stories: ${generatedPrd.userStories.length}`));
408
- console.log();
409
-
410
- for (const story of generatedPrd.userStories) {
411
- const complexity = story.routing?.complexity || "unknown";
412
- console.log(chalk.dim(` ${story.id}: ${story.title} [${complexity}]`));
413
- }
414
- console.log();
415
-
416
- // Show confirmation gate unless --headless (AC-5, AC-6)
417
- if (!options.headless) {
418
- // Prompt for user confirmation
419
- const confirmationResult = await promptForConfirmation("Proceed with execution?");
420
- if (!confirmationResult) {
421
- console.log(chalk.yellow("Execution cancelled."));
422
- process.exit(0);
423
- }
424
- }
425
-
426
- // Continue with normal run using the generated prd.json
427
- // (prdPath already points to the generated file)
428
- } catch (err) {
429
- console.error(chalk.red(`Error during planning: ${(err as Error).message}`));
430
- process.exit(1);
431
- }
432
- }
433
-
434
- // Check if prd.json exists (skip if --plan already generated it)
435
- if (!existsSync(prdPath)) {
436
- console.error(chalk.red(`Feature "${options.feature}" not found or missing prd.json`));
437
- process.exit(1);
438
- }
439
-
440
- // Reset plan logger (if plan phase ran) so the run logger can be initialized fresh
441
- resetLogger();
442
-
443
- // Create run directory and JSONL log file path
444
- const runsDir = join(featureDir, "runs");
445
- mkdirSync(runsDir, { recursive: true });
446
-
447
- // Generate run ID from ISO timestamp
448
- const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
449
- const logFilePath = join(runsDir, `${runId}.jsonl`);
450
-
451
- // Determine TUI vs headless mode
452
- // TUI activates when:
453
- // 1. stdout is a TTY, AND
454
- // 2. --headless flag is NOT passed, AND
455
- // 3. NAX_HEADLESS env var is NOT set
456
- const isTTY = process.stdout.isTTY ?? false;
457
- const headlessFlag = options.headless ?? false;
458
- const headlessEnv = process.env.NAX_HEADLESS === "1";
459
- const useHeadless = !isTTY || headlessFlag || headlessEnv;
460
-
461
- // Initialize logger with selected level, file path, and formatter mode
462
- initLogger({
463
- level: logLevel,
464
- filePath: logFilePath,
465
- useChalk: true,
466
- formatterMode: useHeadless ? formatterMode : undefined,
467
- headless: useHeadless,
468
- });
469
-
470
- // Override config from CLI
471
- if (options.agent) {
472
- config.autoMode.defaultAgent = options.agent;
473
- }
474
- config.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
475
-
476
- const globalNaxDir = join(homedir(), ".nax");
477
- const hooks = await loadHooksConfig(naxDir, globalNaxDir);
478
-
479
- // Create event emitter for TUI integration
480
- const eventEmitter = new PipelineEventEmitter();
481
-
482
- // Render TUI if not in headless mode
483
- let tuiInstance: ReturnType<typeof renderTui> | undefined;
484
- if (!useHeadless) {
485
- // Load PRD to get initial story states
486
- const prd = await loadPRD(prdPath);
487
- const initialStories: StoryDisplayState[] = prd.userStories.map((story) => ({
488
- story,
489
- status: story.passes ? "passed" : "pending",
490
- routing: story.routing,
491
- cost: 0,
492
- }));
493
-
494
- tuiInstance = renderTui({
495
- feature: options.feature,
496
- stories: initialStories,
497
- totalCost: 0,
498
- elapsedMs: 0,
499
- events: eventEmitter,
500
- ptyOptions: null, // TODO: Pass actual PTY spawn options when runner supports it
501
- });
502
- } else {
503
- console.log(chalk.dim(" [Headless mode — pipe output]"));
504
- }
505
-
506
- // Compute status file path: <workdir>/nax/status.json
507
- const statusFilePath = join(workdir, "nax", "status.json");
508
-
509
- // Parse --parallel option
510
- let parallel: number | undefined;
511
- if (options.parallel !== undefined) {
512
- parallel = Number.parseInt(options.parallel, 10);
513
- if (Number.isNaN(parallel) || parallel < 0) {
514
- console.error(chalk.red("--parallel must be a non-negative integer"));
515
- process.exit(1);
516
- }
517
- }
518
-
519
- const result = await run({
520
- prdPath,
521
- workdir,
522
- config,
523
- hooks,
524
- feature: options.feature,
525
- featureDir,
526
- dryRun: options.dryRun,
527
- useBatch: options.batch ?? true,
528
- parallel,
529
- eventEmitter,
530
- statusFile: statusFilePath,
531
- logFilePath,
532
- formatterMode: useHeadless ? formatterMode : undefined,
533
- headless: useHeadless,
534
- skipPrecheck: options.skipPrecheck ?? false,
535
- });
536
-
537
- // Create/update latest.jsonl symlink
538
- const latestSymlink = join(runsDir, "latest.jsonl");
539
- try {
540
- // Remove existing symlink if present
541
- if (existsSync(latestSymlink)) {
542
- Bun.spawnSync(["rm", latestSymlink]);
543
- }
544
- // Create new symlink pointing to current run log
545
- Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
546
- cwd: runsDir,
547
- });
548
- } catch (error) {
549
- console.error(chalk.yellow(`Warning: Failed to create latest.jsonl symlink: ${error}`));
550
- }
551
-
552
- // Cleanup TUI if it was rendered
553
- if (tuiInstance) {
554
- tuiInstance.unmount();
555
- }
556
-
557
- // Summary (only in headless mode; TUI shows summary itself)
558
- if (useHeadless) {
559
- console.log(chalk.dim("\n── Summary ──────────────────────────────────"));
560
- console.log(chalk.dim(` Iterations: ${result.iterations}`));
561
- console.log(chalk.dim(` Completed: ${result.storiesCompleted}`));
562
- console.log(chalk.dim(` Cost: $${result.totalCost.toFixed(4)}`));
563
- console.log(chalk.dim(` Duration: ${(result.durationMs / 1000 / 60).toFixed(1)} min`));
564
- }
565
-
566
- process.exit(result.success ? 0 : 1);
567
- });
568
-
569
- // ── features ─────────────────────────────────────────
570
- const features = program.command("features").description("Manage features");
571
-
572
- features
573
- .command("create <name>")
574
- .description("Create a new feature")
575
- .option("-d, --dir <path>", "Project directory", process.cwd())
576
- .action(async (name, options) => {
577
- // Validate directory path
578
- let workdir: string;
579
- try {
580
- workdir = validateDirectory(options.dir);
581
- } catch (err) {
582
- console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
583
- process.exit(1);
584
- }
585
-
586
- const naxDir = findProjectDir(workdir);
587
- if (!naxDir) {
588
- console.error(chalk.red("nax not initialized. Run: nax init"));
589
- process.exit(1);
590
- }
591
-
592
- const featureDir = join(naxDir, "features", name);
593
- mkdirSync(featureDir, { recursive: true });
594
-
595
- // Create empty templates
596
- await Bun.write(
597
- join(featureDir, "spec.md"),
598
- `# Feature: ${name}\n\n## Overview\n\n## Requirements\n\n## Acceptance Criteria\n`,
599
- );
600
- await Bun.write(
601
- join(featureDir, "plan.md"),
602
- `# Plan: ${name}\n\n## Architecture\n\n## Phases\n\n## Dependencies\n`,
603
- );
604
- await Bun.write(
605
- join(featureDir, "tasks.md"),
606
- `# Tasks: ${name}\n\n## US-001: [Title]\n\n### Description\n\n### Acceptance Criteria\n- [ ] Criterion 1\n`,
607
- );
608
- await Bun.write(
609
- join(featureDir, "progress.txt"),
610
- `# Progress: ${name}\n\nCreated: ${new Date().toISOString()}\n\n---\n`,
611
- );
612
-
613
- console.log(chalk.green(`✅ Created feature: ${name}`));
614
- console.log(chalk.dim(` ${featureDir}/`));
615
- console.log(chalk.dim(" ├── spec.md"));
616
- console.log(chalk.dim(" ├── plan.md"));
617
- console.log(chalk.dim(" ├── tasks.md"));
618
- console.log(chalk.dim(" └── progress.txt"));
619
- console.log(chalk.dim(`\nNext: Edit spec.md and tasks.md, then: nax plan -f ${name} --from spec.md --auto`));
620
- });
621
-
622
- features
623
- .command("list")
624
- .description("List all features")
625
- .option("-d, --dir <path>", "Project directory", process.cwd())
626
- .action(async (options) => {
627
- // Validate directory path
628
- let workdir: string;
629
- try {
630
- workdir = validateDirectory(options.dir);
631
- } catch (err) {
632
- console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
633
- process.exit(1);
634
- }
635
-
636
- const naxDir = findProjectDir(workdir);
637
- if (!naxDir) {
638
- console.error(chalk.red("nax not initialized."));
639
- process.exit(1);
640
- }
641
-
642
- const featuresDir = join(naxDir, "features");
643
- if (!existsSync(featuresDir)) {
644
- console.log(chalk.dim("No features yet."));
645
- return;
646
- }
647
-
648
- const { readdirSync } = await import("node:fs");
649
- const entries = readdirSync(featuresDir, { withFileTypes: true })
650
- .filter((e) => e.isDirectory())
651
- .map((e) => e.name);
652
-
653
- if (entries.length === 0) {
654
- console.log(chalk.dim("No features yet."));
655
- return;
656
- }
657
-
658
- console.log(chalk.bold("\nFeatures:\n"));
659
- for (const name of entries) {
660
- const prdPath = join(featuresDir, name, "prd.json");
661
- if (existsSync(prdPath)) {
662
- const prd = await loadPRD(prdPath);
663
- const c = countStories(prd);
664
- console.log(` ${name} — ${c.passed}/${c.total} stories done`);
665
- } else {
666
- console.log(` ${name} (no prd.json yet)`);
667
- }
668
- }
669
- console.log();
670
- });
671
-
672
- // ── plan ─────────────────────────────────────────────
673
- program
674
- .command("plan [description]")
675
- .description("Generate prd.json from a spec file via LLM one-shot call (replaces deprecated 'nax analyze')")
676
- .requiredOption("--from <spec-path>", "Path to spec file (required)")
677
- .requiredOption("-f, --feature <name>", "Feature name (required)")
678
- .option("--auto", "Run in one-shot LLM mode (alias: --one-shot)", false)
679
- .option("--one-shot", "Run in one-shot LLM mode (alias: --auto)", false)
680
- .option("-b, --branch <branch>", "Override default branch name")
681
- .option("-d, --dir <path>", "Project directory", process.cwd())
682
- .action(async (description, options) => {
683
- // AC-3: Detect and reject old positional argument form
684
- if (description) {
685
- console.error(
686
- chalk.red("Error: Positional args removed in plan v2.\n\nUse: nax plan -f <feature> --from <spec>"),
687
- );
688
- process.exit(1);
689
- }
690
- // Validate directory path
691
- let workdir: string;
692
- try {
693
- workdir = validateDirectory(options.dir);
694
- } catch (err) {
695
- console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
696
- process.exit(1);
697
- }
698
-
699
- const naxDir = findProjectDir(workdir);
700
- if (!naxDir) {
701
- console.error(chalk.red("nax not initialized. Run: nax init"));
702
- process.exit(1);
703
- }
704
-
705
- // Load config
706
- const config = await loadConfig(workdir);
707
-
708
- // Initialize logger — writes to nax/features/<feature>/plan/<timestamp>.jsonl
709
- const featureLogDir = join(naxDir, "features", options.feature, "plan");
710
- mkdirSync(featureLogDir, { recursive: true });
711
- const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
712
- const planLogPath = join(featureLogDir, `${planLogId}.jsonl`);
713
- initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
714
- console.log(chalk.dim(` [Plan log: ${planLogPath}]`));
715
-
716
- try {
717
- const prdPath = await planCommand(workdir, config, {
718
- from: options.from,
719
- feature: options.feature,
720
- auto: options.auto || options.oneShot, // --auto and --one-shot are aliases
721
- branch: options.branch,
722
- });
723
-
724
- console.log(chalk.green("\n[OK] PRD generated"));
725
- console.log(chalk.dim(` PRD: ${prdPath}`));
726
- console.log(chalk.dim(` Log: ${planLogPath}`));
727
- console.log(chalk.dim(`\nNext: nax run -f ${options.feature}`));
728
- } catch (err) {
729
- console.error(chalk.red(`Error: ${(err as Error).message}`));
730
- process.exit(1);
731
- }
732
- });
733
-
734
- // ── analyze ──────────────────────────────────────────
735
- program
736
- .command("analyze")
737
- .description("(deprecated) Parse spec.md into prd.json via agent decompose — use 'nax plan' instead")
738
- .requiredOption("-f, --feature <name>", "Feature name")
739
- .option("-b, --branch <name>", "Branch name", "feat/<feature>")
740
- .option("--from <path>", "Explicit spec path (overrides default spec.md)")
741
- .option("--reclassify", "Re-classify existing prd.json without decompose", false)
742
- .option("-d, --dir <path>", "Project directory", process.cwd())
743
- .action(async (options) => {
744
- // AC-1: Print deprecation warning to stderr
745
- const deprecationMsg = "⚠️ 'nax analyze' is deprecated. Use 'nax plan -f <feature> --from <spec> --auto' instead.";
746
- process.stderr.write(`${chalk.yellow(deprecationMsg)}\n`);
747
-
748
- // Validate directory path
749
- let workdir: string;
750
- try {
751
- workdir = validateDirectory(options.dir);
752
- } catch (err) {
753
- console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
754
- process.exit(1);
755
- }
756
-
757
- const naxDir = findProjectDir(workdir);
758
- if (!naxDir) {
759
- console.error(chalk.red("nax not initialized. Run: nax init"));
760
- process.exit(1);
761
- }
762
-
763
- const featureDir = join(naxDir, "features", options.feature);
764
- if (!existsSync(featureDir)) {
765
- console.error(chalk.red(`Feature "${options.feature}" not found.`));
766
- process.exit(1);
767
- }
768
-
769
- const branchName = options.branch.replace("<feature>", options.feature);
770
-
771
- // Load config for validation
772
- const config = await loadConfig(workdir);
773
-
774
- try {
775
- const prd = await analyzeFeature({
776
- featureDir,
777
- featureName: options.feature,
778
- branchName,
779
- config,
780
- specPath: options.from,
781
- reclassify: options.reclassify,
782
- });
783
-
784
- const prdPath = join(featureDir, "prd.json");
785
- await Bun.write(prdPath, JSON.stringify(prd, null, 2));
786
-
787
- const c = countStories(prd);
788
- console.log(chalk.green(`\n✅ Generated prd.json for ${options.feature}`));
789
- console.log(chalk.dim(` Stories: ${c.total}`));
790
- console.log(chalk.dim(` Path: ${prdPath}`));
791
-
792
- for (const story of prd.userStories) {
793
- const routing = story.routing ? chalk.dim(` [${story.routing.complexity}]`) : "";
794
- console.log(chalk.dim(` ${story.id}: ${story.title}${routing}`));
795
- }
796
- console.log();
797
- } catch (err) {
798
- console.error(chalk.red(`Error: ${(err as Error).message}`));
799
- process.exit(1);
800
- }
801
- });
802
-
803
- // ── agents ───────────────────────────────────────────
804
- program
805
- .command("agents")
806
- .description("List available coding agents with status and capabilities")
807
- .option("-d, --dir <path>", "Project directory", process.cwd())
808
- .action(async (options) => {
809
- // Validate directory path
810
- let workdir: string;
811
- try {
812
- workdir = validateDirectory(options.dir);
813
- } catch (err) {
814
- console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
815
- process.exit(1);
816
- }
817
-
818
- try {
819
- const config = await loadConfig(workdir);
820
- await agentsListCommand(config, workdir);
821
- } catch (err) {
822
- console.error(chalk.red(`Error: ${(err as Error).message}`));
823
- process.exit(1);
824
- }
825
- });
826
-
827
- // ── config ───────────────────────────────────────────
828
- program
829
- .command("config")
830
- .description("Display effective merged configuration")
831
- .option("--explain", "Show detailed field descriptions", false)
832
- .option("--diff", "Show only fields where project overrides global", false)
833
- .action(async (options) => {
834
- try {
835
- const config = await loadConfig();
836
- await configCommand(config, { explain: options.explain, diff: options.diff });
837
- } catch (err) {
838
- console.error(chalk.red(`Error: ${(err as Error).message}`));
839
- process.exit(1);
840
- }
841
- });
842
-
843
- // ── status ───────────────────────────────────────────
844
- program
845
- .command("status")
846
- .description("Show current run status")
847
- .option("-f, --feature <name>", "Feature name")
848
- .option("-d, --dir <path>", "Project directory", process.cwd())
849
- .option("--cost", "Show cost metrics across all runs", false)
850
- .option("--last", "Show last run metrics (requires --cost)", false)
851
- .option("--model", "Show per-model efficiency (requires --cost)", false)
852
- .action(async (options) => {
853
- // Validate directory path
854
- let workdir: string;
855
- try {
856
- workdir = validateDirectory(options.dir);
857
- } catch (err) {
858
- console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
859
- process.exit(1);
860
- }
861
-
862
- const naxDir = findProjectDir(workdir);
863
- if (!naxDir) {
864
- console.error(chalk.red("nax not initialized."));
865
- process.exit(1);
866
- }
867
-
868
- // Handle cost metrics flags
869
- if (options.cost) {
870
- if (options.last) {
871
- await displayLastRunMetrics(workdir);
872
- } else if (options.model) {
873
- await displayModelEfficiency(workdir);
874
- } else {
875
- await displayCostMetrics(workdir);
876
- }
877
- return;
878
- }
879
-
880
- // Default status: show feature progress (new implementation with active run detection)
881
- await displayFeatureStatus({
882
- feature: options.feature,
883
- dir: options.dir,
884
- });
885
- });
886
-
887
- // ── logs ─────────────────────────────────────────────
888
- program
889
- .command("logs")
890
- .description("Display run logs with filtering and follow mode")
891
- .option("-d, --dir <path>", "Project directory", process.cwd())
892
- .option("-f, --follow", "Follow mode - stream new entries real-time", false)
893
- .option("-s, --story <id>", "Filter to specific story")
894
- .option("--level <level>", "Filter by log level (debug|info|warn|error)")
895
- .option("-l, --list", "List all runs in table format", false)
896
- .option("-r, --run <runId>", "Select run by run ID from central registry (global)")
897
- .option("-j, --json", "Output raw JSONL", false)
898
- .action(async (options) => {
899
- let workdir: string;
900
- try {
901
- workdir = validateDirectory(options.dir);
902
- } catch (err) {
903
- console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
904
- process.exit(1);
905
- }
906
-
907
- try {
908
- await logsCommand({
909
- dir: workdir,
910
- follow: options.follow,
911
- story: options.story,
912
- level: options.level,
913
- list: options.list,
914
- run: options.run,
915
- json: options.json,
916
- });
917
- } catch (err) {
918
- console.error(chalk.red(`Error: ${(err as Error).message}`));
919
- process.exit(1);
920
- }
921
- });
922
-
923
- // ── diagnose ─────────────────────────────────────────
924
- program
925
- .command("diagnose")
926
- .description("Diagnose run failures and generate recommendations")
927
- .option("-f, --feature <name>", "Feature name (defaults to current feature)")
928
- .option("-d, --dir <path>", "Working directory", process.cwd())
929
- .option("--json", "Output machine-readable JSON", false)
930
- .option("--verbose", "Verbose output with story breakdown", false)
931
- .action(async (options) => {
932
- try {
933
- await diagnose({
934
- feature: options.feature,
935
- workdir: options.dir,
936
- json: options.json,
937
- verbose: options.verbose,
938
- });
939
- } catch (err) {
940
- console.error(chalk.red(`Error: ${(err as Error).message}`));
941
- process.exit(1);
942
- }
943
- });
944
-
945
- // ── precheck ─────────────────────────────────────────
946
- program
947
- .command("precheck")
948
- .description("Validate feature readiness before execution")
949
- .option("-f, --feature <name>", "Feature name")
950
- .option("-d, --dir <path>", "Project directory", process.cwd())
951
- .option("--json", "Output machine-readable JSON", false)
952
- .action(async (options) => {
953
- try {
954
- await precheckCommand({
955
- feature: options.feature,
956
- dir: options.dir,
957
- json: options.json,
958
- });
959
- } catch (err) {
960
- console.error(chalk.red(`Error: ${(err as Error).message}`));
961
- process.exit(1);
962
- }
963
- });
964
-
965
- // ── unlock ───────────────────────────────────────────
966
- program
967
- .command("unlock")
968
- .description("Release stale lock from crashed nax process")
969
- .option("-d, --dir <path>", "Project directory", process.cwd())
970
- .option("--force", "Skip liveness check and remove unconditionally", false)
971
- .action(async (options) => {
972
- try {
973
- await unlockCommand({
974
- dir: options.dir,
975
- force: options.force,
976
- });
977
- } catch (err) {
978
- console.error(chalk.red(`Error: ${(err as Error).message}`));
979
- process.exit(1);
980
- }
981
- });
982
-
983
- // ── runs ─────────────────────────────────────────────
984
- const runs = program
985
- .command("runs")
986
- .description("Show all registered runs from the central registry (~/.nax/runs/)")
987
- .option("--project <name>", "Filter by project name")
988
- .option("--last <N>", "Limit to N most recent runs (default: 20)")
989
- .option("--status <status>", "Filter by status (running|completed|failed|crashed)")
990
- .action(async (options) => {
991
- try {
992
- await runsCommand({
993
- project: options.project,
994
- last: options.last !== undefined ? Number.parseInt(options.last, 10) : undefined,
995
- status: options.status,
996
- });
997
- } catch (err) {
998
- console.error(chalk.red(`Error: ${(err as Error).message}`));
999
- process.exit(1);
1000
- }
1001
- });
1002
-
1003
- runs
1004
- .command("list")
1005
- .description("List all runs for a feature")
1006
- .requiredOption("-f, --feature <name>", "Feature name")
1007
- .option("-d, --dir <path>", "Project directory", process.cwd())
1008
- .action(async (options) => {
1009
- let workdir: string;
1010
- try {
1011
- workdir = validateDirectory(options.dir);
1012
- } catch (err) {
1013
- console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
1014
- process.exit(1);
1015
- }
1016
-
1017
- await runsListCommand({ feature: options.feature, workdir });
1018
- });
1019
-
1020
- runs
1021
- .command("show <run-id>")
1022
- .description("Show detailed information for a specific run")
1023
- .requiredOption("-f, --feature <name>", "Feature name")
1024
- .option("-d, --dir <path>", "Project directory", process.cwd())
1025
- .action(async (runId, options) => {
1026
- let workdir: string;
1027
- try {
1028
- workdir = validateDirectory(options.dir);
1029
- } catch (err) {
1030
- console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
1031
- process.exit(1);
1032
- }
1033
-
1034
- await runsShowCommand({ runId, feature: options.feature, workdir });
1035
- });
1036
-
1037
- // ── accept ───────────────────────────────────────────
1038
- program
1039
- .command("accept")
1040
- .description("Override failed acceptance criteria")
1041
- .requiredOption("-f, --feature <name>", "Feature name")
1042
- .requiredOption("--override <ac-id>", "AC ID to override (e.g., AC-2)")
1043
- .requiredOption("-r, --reason <reason>", "Reason for accepting despite test failure")
1044
- .action(async (options) => {
1045
- try {
1046
- await acceptCommand({
1047
- feature: options.feature,
1048
- override: options.override,
1049
- reason: options.reason,
1050
- });
1051
- } catch (err) {
1052
- console.error(chalk.red(`Error: ${(err as Error).message}`));
1053
- process.exit(1);
1054
- }
1055
- });
1056
-
1057
- // ── prompts ──────────────────────────────────────────
1058
- program
1059
- .command("prompts")
1060
- .description("Assemble or initialize prompts")
1061
- .option("-f, --feature <name>", "Feature name (required unless using --init or --export)")
1062
- .option("--init", "Initialize default prompt templates", false)
1063
- .option("--export <role>", "Export default prompt for a role to stdout or --out file")
1064
- .option("--force", "Overwrite existing template files", false)
1065
- .option("--story <id>", "Filter to a single story ID (e.g., US-003)")
1066
- .option("--out <path>", "Output file path for --export, or directory for regular prompts (default: stdout)")
1067
- .option("-d, --dir <path>", "Project directory", process.cwd())
1068
- .action(async (options) => {
1069
- // Validate directory path
1070
- let workdir: string;
1071
- try {
1072
- workdir = validateDirectory(options.dir);
1073
- } catch (err) {
1074
- console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
1075
- process.exit(1);
1076
- }
1077
-
1078
- // Handle --init command
1079
- if (options.init) {
1080
- try {
1081
- await promptsInitCommand({
1082
- workdir,
1083
- force: options.force,
1084
- });
1085
- } catch (err) {
1086
- console.error(chalk.red(`Error: ${(err as Error).message}`));
1087
- process.exit(1);
1088
- }
1089
- return;
1090
- }
1091
-
1092
- // Handle --export command
1093
- if (options.export) {
1094
- try {
1095
- await exportPromptCommand({
1096
- role: options.export,
1097
- out: options.out,
1098
- });
1099
- } catch (err) {
1100
- console.error(chalk.red(`Error: ${(err as Error).message}`));
1101
- process.exit(1);
1102
- }
1103
- return;
1104
- }
1105
-
1106
- // Handle regular prompts command (requires --feature)
1107
- if (!options.feature) {
1108
- console.error(chalk.red("Error: --feature is required (unless using --init or --export)"));
1109
- process.exit(1);
1110
- }
1111
-
1112
- // Load config
1113
- const config = await loadConfig(workdir);
1114
-
1115
- try {
1116
- const processedStories = await promptsCommand({
1117
- feature: options.feature,
1118
- workdir,
1119
- config,
1120
- storyId: options.story,
1121
- outputDir: options.out,
1122
- });
1123
-
1124
- if (options.out) {
1125
- console.log(chalk.green(`\n✅ Prompts written to ${options.out}`));
1126
- console.log(chalk.dim(` Processed ${processedStories.length} stories`));
1127
- }
1128
- } catch (err) {
1129
- console.error(chalk.red(`Error: ${(err as Error).message}`));
1130
- process.exit(1);
1131
- }
1132
- });
1133
-
1134
- // ── generate ──────────────────────────────────────────
1135
- program
1136
- .command("generate")
1137
- .description("Generate agent config files (CLAUDE.md, AGENTS.md, etc.) from nax/context.md")
1138
- .option("-c, --context <path>", "Context file path (default: nax/context.md)")
1139
- .option("-o, --output <dir>", "Output directory (default: project root)")
1140
- .option("-a, --agent <name>", "Specific agent (claude|opencode|cursor|windsurf|aider)")
1141
- .option("--dry-run", "Preview without writing files", false)
1142
- .option("--no-auto-inject", "Disable auto-injection of project metadata")
1143
- .option("--package <dir>", "Generate CLAUDE.md for a specific package (e.g. packages/api)")
1144
- .option("--all-packages", "Generate CLAUDE.md for all discovered packages", false)
1145
- .action(async (options) => {
1146
- try {
1147
- await generateCommand({
1148
- context: options.context,
1149
- output: options.output,
1150
- agent: options.agent,
1151
- dryRun: options.dryRun,
1152
- noAutoInject: !options.autoInject,
1153
- package: options.package,
1154
- allPackages: options.allPackages,
1155
- });
1156
- } catch (err) {
1157
- console.error(chalk.red(`Error: ${(err as Error).message}`));
1158
- process.exit(1);
1159
- }
1160
- });
1161
-
1162
- // ── plugins ──────────────────────────────────────────
1163
- const plugins = program.command("plugins").description("Manage plugins");
1164
-
1165
- plugins
1166
- .command("list")
1167
- .description("List all installed plugins")
1168
- .option("-d, --dir <path>", "Project directory", process.cwd())
1169
- .action(async (options) => {
1170
- // Validate directory path
1171
- let workdir: string;
1172
- try {
1173
- workdir = validateDirectory(options.dir);
1174
- } catch (err) {
1175
- console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
1176
- process.exit(1);
1177
- }
1178
-
1179
- // Load config (or use default if outside project)
1180
- let config = DEFAULT_CONFIG;
1181
- try {
1182
- config = await loadConfig(workdir);
1183
- } catch {
1184
- // Outside project directory, use default config (global plugins only)
1185
- }
1186
-
1187
- try {
1188
- await pluginsListCommand(config, workdir);
1189
- } catch (err) {
1190
- console.error(chalk.red(`Error: ${(err as Error).message}`));
1191
- process.exit(1);
1192
- }
1193
- });
1194
-
1195
- program.parse();