@nathapp/nax 0.41.0 → 0.42.1
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 +132 -11
- package/dist/nax.js +556 -207
- package/package.json +7 -6
- package/src/agents/acp/adapter.ts +63 -30
- package/src/agents/claude.ts +12 -2
- package/src/agents/types.ts +6 -0
- package/src/analyze/scanner.ts +16 -20
- package/src/cli/plan.ts +223 -144
- package/src/commands/precheck.ts +1 -1
- package/src/interaction/plugins/webhook.ts +10 -1
- package/src/pipeline/stages/acceptance.ts +21 -6
- package/src/prd/schema.ts +253 -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,9 @@ 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)")
|
|
280
|
+
.option("--one-shot", "Skip interactive planning Q&A, use single LLM call (ACP only)", false)
|
|
234
281
|
.option("--headless", "Force headless mode (disable TUI, use pipe mode)", false)
|
|
235
282
|
.option("--verbose", "Enable verbose logging (debug level)", false)
|
|
236
283
|
.option("--quiet", "Quiet mode (warnings and errors only)", false)
|
|
@@ -248,6 +295,18 @@ program
|
|
|
248
295
|
process.exit(1);
|
|
249
296
|
}
|
|
250
297
|
|
|
298
|
+
// Validate --plan and --from flags (AC-8: --plan without --from is error)
|
|
299
|
+
if (options.plan && !options.from) {
|
|
300
|
+
console.error(chalk.red("Error: --plan requires --from <spec-path>"));
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Validate --from path exists (AC-7: --from without existing file throws error)
|
|
305
|
+
if (options.from && !existsSync(options.from)) {
|
|
306
|
+
console.error(chalk.red(`Error: File not found: ${options.from} (required with --plan)`));
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
|
|
251
310
|
// Determine log level from flags or env var (env var takes precedence)
|
|
252
311
|
let logLevel: LogLevel = "info"; // default
|
|
253
312
|
const envLevel = process.env.NAX_LOG_LEVEL?.toLowerCase();
|
|
@@ -282,6 +341,51 @@ program
|
|
|
282
341
|
const featureDir = join(naxDir, "features", options.feature);
|
|
283
342
|
const prdPath = join(featureDir, "prd.json");
|
|
284
343
|
|
|
344
|
+
// Run plan phase if --plan flag is set (AC-4: runs plan then execute)
|
|
345
|
+
if (options.plan && options.from) {
|
|
346
|
+
try {
|
|
347
|
+
console.log(chalk.dim(" [Planning phase: generating PRD from spec]"));
|
|
348
|
+
const generatedPrdPath = await planCommand(workdir, config, {
|
|
349
|
+
from: options.from,
|
|
350
|
+
feature: options.feature,
|
|
351
|
+
auto: options.oneShot ?? false, // interactive by default; --one-shot skips Q&A
|
|
352
|
+
branch: undefined,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Load the generated PRD to display confirmation gate
|
|
356
|
+
const generatedPrd = await loadPRD(generatedPrdPath);
|
|
357
|
+
|
|
358
|
+
// Display story breakdown (AC-5: confirmation gate displays story breakdown)
|
|
359
|
+
console.log(chalk.bold("\n── Planning Summary ──────────────────────────────"));
|
|
360
|
+
console.log(chalk.dim(`Feature: ${generatedPrd.feature}`));
|
|
361
|
+
console.log(chalk.dim(`Stories: ${generatedPrd.userStories.length}`));
|
|
362
|
+
console.log();
|
|
363
|
+
|
|
364
|
+
for (const story of generatedPrd.userStories) {
|
|
365
|
+
const complexity = story.routing?.complexity || "unknown";
|
|
366
|
+
console.log(chalk.dim(` ${story.id}: ${story.title} [${complexity}]`));
|
|
367
|
+
}
|
|
368
|
+
console.log();
|
|
369
|
+
|
|
370
|
+
// Show confirmation gate unless --headless (AC-5, AC-6)
|
|
371
|
+
if (!options.headless) {
|
|
372
|
+
// Prompt for user confirmation
|
|
373
|
+
const confirmationResult = await promptForConfirmation("Proceed with execution?");
|
|
374
|
+
if (!confirmationResult) {
|
|
375
|
+
console.log(chalk.yellow("Execution cancelled."));
|
|
376
|
+
process.exit(0);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Continue with normal run using the generated prd.json
|
|
381
|
+
// (prdPath already points to the generated file)
|
|
382
|
+
} catch (err) {
|
|
383
|
+
console.error(chalk.red(`Error during planning: ${(err as Error).message}`));
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Check if prd.json exists (skip if --plan already generated it)
|
|
285
389
|
if (!existsSync(prdPath)) {
|
|
286
390
|
console.error(chalk.red(`Feature "${options.feature}" not found or missing prd.json`));
|
|
287
391
|
process.exit(1);
|
|
@@ -463,7 +567,7 @@ features
|
|
|
463
567
|
console.log(chalk.dim(" ├── plan.md"));
|
|
464
568
|
console.log(chalk.dim(" ├── tasks.md"));
|
|
465
569
|
console.log(chalk.dim(" └── progress.txt"));
|
|
466
|
-
console.log(chalk.dim(`\nNext: Edit spec.md and tasks.md, then: nax
|
|
570
|
+
console.log(chalk.dim(`\nNext: Edit spec.md and tasks.md, then: nax plan -f ${name} --from spec.md --auto`));
|
|
467
571
|
});
|
|
468
572
|
|
|
469
573
|
features
|
|
@@ -518,11 +622,22 @@ features
|
|
|
518
622
|
|
|
519
623
|
// ── plan ─────────────────────────────────────────────
|
|
520
624
|
program
|
|
521
|
-
.command("plan
|
|
522
|
-
.description("
|
|
523
|
-
.
|
|
625
|
+
.command("plan [description]")
|
|
626
|
+
.description("Generate prd.json from a spec file via LLM one-shot call (replaces deprecated 'nax analyze')")
|
|
627
|
+
.requiredOption("--from <spec-path>", "Path to spec file (required)")
|
|
628
|
+
.requiredOption("-f, --feature <name>", "Feature name (required)")
|
|
629
|
+
.option("--auto", "Run in one-shot LLM mode (alias: --one-shot)", false)
|
|
630
|
+
.option("--one-shot", "Run in one-shot LLM mode (alias: --auto)", false)
|
|
631
|
+
.option("-b, --branch <branch>", "Override default branch name")
|
|
524
632
|
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
525
|
-
.action(async (description
|
|
633
|
+
.action(async (description, options) => {
|
|
634
|
+
// AC-3: Detect and reject old positional argument form
|
|
635
|
+
if (description) {
|
|
636
|
+
console.error(
|
|
637
|
+
chalk.red("Error: Positional args removed in plan v2.\n\nUse: nax plan -f <feature> --from <spec>"),
|
|
638
|
+
);
|
|
639
|
+
process.exit(1);
|
|
640
|
+
}
|
|
526
641
|
// Validate directory path
|
|
527
642
|
let workdir: string;
|
|
528
643
|
try {
|
|
@@ -542,14 +657,16 @@ program
|
|
|
542
657
|
const config = await loadConfig(workdir);
|
|
543
658
|
|
|
544
659
|
try {
|
|
545
|
-
const
|
|
546
|
-
interactive: !options.from,
|
|
660
|
+
const prdPath = await planCommand(workdir, config, {
|
|
547
661
|
from: options.from,
|
|
662
|
+
feature: options.feature,
|
|
663
|
+
auto: options.auto || options.oneShot, // --auto and --one-shot are aliases
|
|
664
|
+
branch: options.branch,
|
|
548
665
|
});
|
|
549
666
|
|
|
550
|
-
console.log(chalk.green("\n
|
|
551
|
-
console.log(chalk.dim(`
|
|
552
|
-
console.log(chalk.dim(
|
|
667
|
+
console.log(chalk.green("\n[OK] PRD generated"));
|
|
668
|
+
console.log(chalk.dim(` PRD: ${prdPath}`));
|
|
669
|
+
console.log(chalk.dim(`\nNext: nax run -f ${options.feature}`));
|
|
553
670
|
} catch (err) {
|
|
554
671
|
console.error(chalk.red(`Error: ${(err as Error).message}`));
|
|
555
672
|
process.exit(1);
|
|
@@ -559,13 +676,17 @@ program
|
|
|
559
676
|
// ── analyze ──────────────────────────────────────────
|
|
560
677
|
program
|
|
561
678
|
.command("analyze")
|
|
562
|
-
.description("Parse spec.md into prd.json via agent decompose")
|
|
679
|
+
.description("(deprecated) Parse spec.md into prd.json via agent decompose — use 'nax plan' instead")
|
|
563
680
|
.requiredOption("-f, --feature <name>", "Feature name")
|
|
564
681
|
.option("-b, --branch <name>", "Branch name", "feat/<feature>")
|
|
565
682
|
.option("--from <path>", "Explicit spec path (overrides default spec.md)")
|
|
566
683
|
.option("--reclassify", "Re-classify existing prd.json without decompose", false)
|
|
567
684
|
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
568
685
|
.action(async (options) => {
|
|
686
|
+
// AC-1: Print deprecation warning to stderr
|
|
687
|
+
const deprecationMsg = "⚠️ 'nax analyze' is deprecated. Use 'nax plan -f <feature> --from <spec> --auto' instead.";
|
|
688
|
+
process.stderr.write(`${chalk.yellow(deprecationMsg)}\n`);
|
|
689
|
+
|
|
569
690
|
// Validate directory path
|
|
570
691
|
let workdir: string;
|
|
571
692
|
try {
|