@nathapp/nax 0.40.1 → 0.42.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 (46) hide show
  1. package/README.md +1 -0
  2. package/bin/nax.ts +130 -11
  3. package/dist/nax.js +1520 -424
  4. package/package.json +8 -7
  5. package/src/acceptance/fix-generator.ts +4 -35
  6. package/src/acceptance/generator.ts +4 -27
  7. package/src/agents/acp/adapter.ts +642 -0
  8. package/src/agents/acp/cost.ts +79 -0
  9. package/src/agents/acp/index.ts +9 -0
  10. package/src/agents/acp/interaction-bridge.ts +126 -0
  11. package/src/agents/acp/parser.ts +166 -0
  12. package/src/agents/acp/spawn-client.ts +309 -0
  13. package/src/agents/acp/types.ts +22 -0
  14. package/src/agents/claude-complete.ts +3 -3
  15. package/src/agents/claude.ts +12 -2
  16. package/src/agents/registry.ts +83 -0
  17. package/src/agents/types-extended.ts +23 -0
  18. package/src/agents/types.ts +17 -0
  19. package/src/analyze/scanner.ts +16 -20
  20. package/src/cli/analyze.ts +6 -2
  21. package/src/cli/plan.ts +218 -129
  22. package/src/commands/precheck.ts +1 -1
  23. package/src/config/defaults.ts +1 -0
  24. package/src/config/runtime-types.ts +10 -0
  25. package/src/config/schema.ts +1 -0
  26. package/src/config/schemas.ts +6 -0
  27. package/src/config/types.ts +1 -0
  28. package/src/execution/executor-types.ts +6 -0
  29. package/src/execution/iteration-runner.ts +2 -0
  30. package/src/execution/lifecycle/acceptance-loop.ts +5 -2
  31. package/src/execution/lifecycle/run-initialization.ts +16 -4
  32. package/src/execution/lifecycle/run-setup.ts +4 -0
  33. package/src/execution/runner-completion.ts +11 -1
  34. package/src/execution/runner-execution.ts +8 -0
  35. package/src/execution/runner-setup.ts +4 -0
  36. package/src/execution/runner.ts +10 -0
  37. package/src/interaction/plugins/webhook.ts +10 -1
  38. package/src/pipeline/stages/execution.ts +33 -1
  39. package/src/pipeline/stages/routing.ts +18 -7
  40. package/src/pipeline/types.ts +10 -0
  41. package/src/prd/schema.ts +249 -0
  42. package/src/tdd/orchestrator.ts +7 -0
  43. package/src/tdd/rectification-gate.ts +6 -0
  44. package/src/tdd/session-runner.ts +15 -2
  45. package/src/utils/git.ts +30 -0
  46. package/src/verification/runners.ts +10 -1
package/README.md CHANGED
@@ -671,6 +671,7 @@ User stories are defined in `nax/features/<name>/prd.json`:
671
671
 
672
672
  ---
673
673
 
674
+
674
675
  ## License
675
676
 
676
677
  MIT
package/bin/nax.ts CHANGED
@@ -78,6 +78,50 @@ const program = new Command();
78
78
 
79
79
  program.name("nax").description("AI Coding Agent Orchestrator — loops until done").version(NAX_VERSION);
80
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
+
81
125
  // ── init ─────────────────────────────────────────────
82
126
  program
83
127
  .command("init")
@@ -231,6 +275,8 @@ program
231
275
  .option("--no-context", "Disable context builder (skip file context in prompts)")
232
276
  .option("--no-batch", "Disable story batching (execute all stories individually)")
233
277
  .option("--parallel <n>", "Max parallel sessions (0=auto, omit=sequential)")
278
+ .option("--plan", "Run plan phase first before execution", false)
279
+ .option("--from <spec-path>", "Path to spec file (required when --plan is used)")
234
280
  .option("--headless", "Force headless mode (disable TUI, use pipe mode)", false)
235
281
  .option("--verbose", "Enable verbose logging (debug level)", false)
236
282
  .option("--quiet", "Quiet mode (warnings and errors only)", false)
@@ -248,6 +294,18 @@ program
248
294
  process.exit(1);
249
295
  }
250
296
 
297
+ // Validate --plan and --from flags (AC-8: --plan without --from is error)
298
+ if (options.plan && !options.from) {
299
+ console.error(chalk.red("Error: --plan requires --from <spec-path>"));
300
+ process.exit(1);
301
+ }
302
+
303
+ // Validate --from path exists (AC-7: --from without existing file throws error)
304
+ if (options.from && !existsSync(options.from)) {
305
+ console.error(chalk.red(`Error: File not found: ${options.from} (required with --plan)`));
306
+ process.exit(1);
307
+ }
308
+
251
309
  // Determine log level from flags or env var (env var takes precedence)
252
310
  let logLevel: LogLevel = "info"; // default
253
311
  const envLevel = process.env.NAX_LOG_LEVEL?.toLowerCase();
@@ -282,6 +340,51 @@ program
282
340
  const featureDir = join(naxDir, "features", options.feature);
283
341
  const prdPath = join(featureDir, "prd.json");
284
342
 
343
+ // Run plan phase if --plan flag is set (AC-4: runs plan then execute)
344
+ if (options.plan && options.from) {
345
+ try {
346
+ console.log(chalk.dim(" [Planning phase: generating PRD from spec]"));
347
+ const generatedPrdPath = await planCommand(workdir, config, {
348
+ from: options.from,
349
+ feature: options.feature,
350
+ auto: true, // AC-1: --auto mode for one-shot planning
351
+ branch: undefined,
352
+ });
353
+
354
+ // Load the generated PRD to display confirmation gate
355
+ const generatedPrd = await loadPRD(generatedPrdPath);
356
+
357
+ // Display story breakdown (AC-5: confirmation gate displays story breakdown)
358
+ console.log(chalk.bold("\n── Planning Summary ──────────────────────────────"));
359
+ console.log(chalk.dim(`Feature: ${generatedPrd.feature}`));
360
+ console.log(chalk.dim(`Stories: ${generatedPrd.userStories.length}`));
361
+ console.log();
362
+
363
+ for (const story of generatedPrd.userStories) {
364
+ const complexity = story.routing?.complexity || "unknown";
365
+ console.log(chalk.dim(` ${story.id}: ${story.title} [${complexity}]`));
366
+ }
367
+ console.log();
368
+
369
+ // Show confirmation gate unless --headless (AC-5, AC-6)
370
+ if (!options.headless) {
371
+ // Prompt for user confirmation
372
+ const confirmationResult = await promptForConfirmation("Proceed with execution?");
373
+ if (!confirmationResult) {
374
+ console.log(chalk.yellow("Execution cancelled."));
375
+ process.exit(0);
376
+ }
377
+ }
378
+
379
+ // Continue with normal run using the generated prd.json
380
+ // (prdPath already points to the generated file)
381
+ } catch (err) {
382
+ console.error(chalk.red(`Error during planning: ${(err as Error).message}`));
383
+ process.exit(1);
384
+ }
385
+ }
386
+
387
+ // Check if prd.json exists (skip if --plan already generated it)
285
388
  if (!existsSync(prdPath)) {
286
389
  console.error(chalk.red(`Feature "${options.feature}" not found or missing prd.json`));
287
390
  process.exit(1);
@@ -463,7 +566,7 @@ features
463
566
  console.log(chalk.dim(" ├── plan.md"));
464
567
  console.log(chalk.dim(" ├── tasks.md"));
465
568
  console.log(chalk.dim(" └── progress.txt"));
466
- console.log(chalk.dim(`\nNext: Edit spec.md and tasks.md, then: nax analyze --feature ${name}`));
569
+ console.log(chalk.dim(`\nNext: Edit spec.md and tasks.md, then: nax plan -f ${name} --from spec.md --auto`));
467
570
  });
468
571
 
469
572
  features
@@ -518,11 +621,21 @@ features
518
621
 
519
622
  // ── plan ─────────────────────────────────────────────
520
623
  program
521
- .command("plan <description>")
522
- .description("Interactive planning via agent plan mode")
523
- .option("--from <file>", "Non-interactive mode: read from input file")
624
+ .command("plan [description]")
625
+ .description("Generate prd.json from a spec file via LLM one-shot call (replaces deprecated 'nax analyze')")
626
+ .requiredOption("--from <spec-path>", "Path to spec file (required)")
627
+ .requiredOption("-f, --feature <name>", "Feature name (required)")
628
+ .option("--auto", "Run in auto (one-shot LLM) mode", false)
629
+ .option("-b, --branch <branch>", "Override default branch name")
524
630
  .option("-d, --dir <path>", "Project directory", process.cwd())
525
- .action(async (description: string, options) => {
631
+ .action(async (description, options) => {
632
+ // AC-3: Detect and reject old positional argument form
633
+ if (description) {
634
+ console.error(
635
+ chalk.red("Error: Positional args removed in plan v2.\n\nUse: nax plan -f <feature> --from <spec>"),
636
+ );
637
+ process.exit(1);
638
+ }
526
639
  // Validate directory path
527
640
  let workdir: string;
528
641
  try {
@@ -542,14 +655,16 @@ program
542
655
  const config = await loadConfig(workdir);
543
656
 
544
657
  try {
545
- const specPath = await planCommand(description, workdir, config, {
546
- interactive: !options.from,
658
+ const prdPath = await planCommand(workdir, config, {
547
659
  from: options.from,
660
+ feature: options.feature,
661
+ auto: options.auto,
662
+ branch: options.branch,
548
663
  });
549
664
 
550
- console.log(chalk.green("\n Planning complete"));
551
- console.log(chalk.dim(` Spec: ${specPath}`));
552
- console.log(chalk.dim("\nNext: nax analyze -f <feature-name>"));
665
+ console.log(chalk.green("\n[OK] PRD generated"));
666
+ console.log(chalk.dim(` PRD: ${prdPath}`));
667
+ console.log(chalk.dim(`\nNext: nax run -f ${options.feature}`));
553
668
  } catch (err) {
554
669
  console.error(chalk.red(`Error: ${(err as Error).message}`));
555
670
  process.exit(1);
@@ -559,13 +674,17 @@ program
559
674
  // ── analyze ──────────────────────────────────────────
560
675
  program
561
676
  .command("analyze")
562
- .description("Parse spec.md into prd.json via agent decompose")
677
+ .description("(deprecated) Parse spec.md into prd.json via agent decompose — use 'nax plan' instead")
563
678
  .requiredOption("-f, --feature <name>", "Feature name")
564
679
  .option("-b, --branch <name>", "Branch name", "feat/<feature>")
565
680
  .option("--from <path>", "Explicit spec path (overrides default spec.md)")
566
681
  .option("--reclassify", "Re-classify existing prd.json without decompose", false)
567
682
  .option("-d, --dir <path>", "Project directory", process.cwd())
568
683
  .action(async (options) => {
684
+ // AC-1: Print deprecation warning to stderr
685
+ const deprecationMsg = "⚠️ 'nax analyze' is deprecated. Use 'nax plan -f <feature> --from <spec> --auto' instead.";
686
+ process.stderr.write(`${chalk.yellow(deprecationMsg)}\n`);
687
+
569
688
  // Validate directory path
570
689
  let workdir: string;
571
690
  try {