@nathapp/nax 0.41.0 → 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.
- package/README.md +1 -0
- package/bin/nax.ts +130 -11
- package/dist/nax.js +478 -186
- package/package.json +7 -6
- package/src/agents/acp/adapter.ts +3 -5
- package/src/agents/claude.ts +12 -2
- package/src/analyze/scanner.ts +16 -20
- package/src/cli/plan.ts +211 -145
- package/src/commands/precheck.ts +1 -1
- package/src/interaction/plugins/webhook.ts +10 -1
- package/src/prd/schema.ts +249 -0
- package/src/tdd/session-runner.ts +11 -2
- package/src/utils/git.ts +30 -0
- package/src/verification/runners.ts +10 -1
package/README.md
CHANGED
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
|
|
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
|
|
522
|
-
.description("
|
|
523
|
-
.
|
|
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
|
|
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
|
|
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
|
|
551
|
-
console.log(chalk.dim(`
|
|
552
|
-
console.log(chalk.dim(
|
|
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 {
|