@nathapp/nax 0.18.6 → 0.20.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 (66) hide show
  1. package/docs/ROADMAP.md +2 -0
  2. package/nax/config.json +2 -2
  3. package/nax/features/nax-compliance/prd.json +52 -0
  4. package/nax/features/nax-compliance/progress.txt +1 -0
  5. package/nax/features/v0.19.0-hardening/plan.md +7 -0
  6. package/nax/features/v0.19.0-hardening/prd.json +84 -0
  7. package/nax/features/v0.19.0-hardening/progress.txt +7 -0
  8. package/nax/features/v0.19.0-hardening/spec.md +18 -0
  9. package/nax/features/v0.19.0-hardening/tasks.md +8 -0
  10. package/nax/features/verify-v2/prd.json +79 -0
  11. package/nax/features/verify-v2/progress.txt +3 -0
  12. package/nax/status.json +27 -0
  13. package/package.json +2 -2
  14. package/src/acceptance/fix-generator.ts +6 -2
  15. package/src/acceptance/generator.ts +3 -1
  16. package/src/acceptance/types.ts +3 -1
  17. package/src/agents/claude-plan.ts +6 -5
  18. package/src/cli/analyze.ts +1 -0
  19. package/src/cli/init.ts +7 -6
  20. package/src/config/defaults.ts +3 -1
  21. package/src/config/schemas.ts +2 -0
  22. package/src/config/types.ts +6 -0
  23. package/src/context/injector.ts +18 -18
  24. package/src/execution/crash-recovery.ts +7 -10
  25. package/src/execution/lifecycle/acceptance-loop.ts +1 -0
  26. package/src/execution/lifecycle/index.ts +1 -1
  27. package/src/execution/lifecycle/precheck-runner.ts +1 -1
  28. package/src/execution/lifecycle/run-completion.ts +29 -0
  29. package/src/execution/lifecycle/run-regression.ts +301 -0
  30. package/src/execution/lifecycle/run-setup.ts +14 -14
  31. package/src/execution/parallel.ts +1 -1
  32. package/src/execution/pipeline-result-handler.ts +0 -1
  33. package/src/execution/post-verify.ts +31 -194
  34. package/src/execution/runner.ts +2 -19
  35. package/src/execution/sequential-executor.ts +1 -1
  36. package/src/hooks/runner.ts +2 -2
  37. package/src/interaction/plugins/auto.ts +2 -2
  38. package/src/logger/logger.ts +3 -5
  39. package/src/pipeline/stages/verify.ts +26 -22
  40. package/src/plugins/loader.ts +36 -9
  41. package/src/routing/batch-route.ts +32 -0
  42. package/src/routing/index.ts +1 -0
  43. package/src/routing/loader.ts +7 -0
  44. package/src/utils/path-security.ts +56 -0
  45. package/src/verification/executor.ts +6 -13
  46. package/src/verification/smart-runner.ts +52 -0
  47. package/test/integration/plugins/config-resolution.test.ts +3 -3
  48. package/test/integration/plugins/loader.test.ts +3 -1
  49. package/test/integration/precheck-integration.test.ts +18 -11
  50. package/test/integration/rectification-flow.test.ts +3 -3
  51. package/test/integration/review-config-commands.test.ts +1 -1
  52. package/test/integration/security-loader.test.ts +83 -0
  53. package/test/integration/verify-stage.test.ts +9 -0
  54. package/test/unit/config/defaults.test.ts +69 -0
  55. package/test/unit/config/regression-gate-schema.test.ts +159 -0
  56. package/test/unit/execution/lifecycle/run-completion.test.ts +239 -0
  57. package/test/unit/execution/lifecycle/run-regression.test.ts +418 -0
  58. package/test/unit/execution/post-verify-regression.test.ts +31 -84
  59. package/test/unit/execution/post-verify.test.ts +28 -48
  60. package/test/unit/formatters.test.ts +2 -3
  61. package/test/unit/hooks/shell-security.test.ts +40 -0
  62. package/test/unit/pipeline/stages/verify.test.ts +266 -0
  63. package/test/unit/pipeline/verify-smart-runner.test.ts +1 -0
  64. package/test/unit/utils/path-security.test.ts +47 -0
  65. package/src/execution/lifecycle/run-lifecycle.ts +0 -312
  66. package/test/unit/run-lifecycle.test.ts +0 -140
package/docs/ROADMAP.md CHANGED
@@ -224,6 +224,8 @@
224
224
  - [x] **BUG-032:** Routing stage overrides escalated `modelTier` with complexity-derived tier. `src/pipeline/stages/routing.ts:43` always runs `complexityToModelTier(routing.complexity, config)` even when `story.routing.modelTier` was explicitly set by `handleTierEscalation()`. BUG-026 was escalated to `balanced` (logged in iteration header), but `Task classified` shows `modelTier=fast` because `complexityToModelTier("simple", config)` → `"fast"`. Related to BUG-013 (escalation routing not applied) which was marked fixed, but the fix in `applyCachedRouting()` in `pipeline-result-handler.ts:295-310` runs **after** the routing stage — too late. **Location:** `src/pipeline/stages/routing.ts:43`. **Fix:** When `story.routing.modelTier` is explicitly set (by escalation), skip `complexityToModelTier()` and use the cached tier directly. Only derive from complexity when `story.routing.modelTier` is absent.
225
225
  - [x] **BUG-033:** LLM routing has no retry on timeout — single attempt with hardcoded 15s default. All 5 LLM routing attempts in the v0.18.3 run timed out at 15s, forcing keyword fallback every time. `src/routing/strategies/llm.ts:63` reads `llmConfig?.timeoutMs ?? 15000` but there's no retry logic — one timeout = immediate fallback. **Location:** `src/routing/strategies/llm.ts:callLlm()`. **Fix:** Add `routing.llm.retries` config (default: 1) with backoff. Also surface `routing.llm.timeoutMs` in `nax config --explain` and consider raising default to 30s for batch routing which processes multiple stories.
226
226
 
227
+ - [ ] **BUG-037:** Test output summary (verify stage) captures precheck boilerplate instead of actual `bun test` failure. **Symptom:** Logs show successful prechecks (Head) instead of failed tests (Tail). **Fix:** Change `Test output preview` log to tail the last 20 lines of output instead of heading the first 10.
228
+ - [ ] **BUG-038:** `smart-runner` over-matching when global defaults change. **Symptom:** Changing `DEFAULT_CONFIG` matches broad integration tests that fail due to environment/precheck side effects, obscuring targeted results. **Fix:** Refine path mapping to prioritize direct unit tests and exclude known heavy integration tests from default smart-runner matches unless explicitly relevant.
227
229
  ### Features
228
230
  - [x] ~~`nax unlock` command~~
229
231
  - [x] ~~Constitution file support~~
package/nax/config.json CHANGED
@@ -59,7 +59,7 @@
59
59
  "maxIterations": 6,
60
60
  "iterationDelayMs": 2000,
61
61
  "costLimit": 8.0,
62
- "sessionTimeoutSeconds": 600,
62
+ "sessionTimeoutSeconds": 7200,
63
63
  "verificationTimeoutSeconds": 300,
64
64
  "maxStoriesPerFeature": 15,
65
65
  "rectification": {
@@ -147,4 +147,4 @@
147
147
  "scopeToStory": true
148
148
  }
149
149
  }
150
- }
150
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "project": "@nathapp/nax",
3
+ "feature": "nax-compliance",
4
+ "branchName": "feat/v0.19.0-sec",
5
+ "createdAt": "2026-03-05T02:37:00Z",
6
+ "updatedAt": "2026-03-05T02:38:46.450Z",
7
+ "userStories": [
8
+ {
9
+ "id": "US-001",
10
+ "title": "Node.js API Removal",
11
+ "description": "Replace all forbidden Node.js APIs (readFileSync, appendFileSync, existsSync, setTimeout) with Bun-native equivalents (Bun.file().text(), Bun.write, Bun.file().exists(), Bun.sleep) across the codebase as per the v0.19.0 roadmap. Refer to .claude/rules/04-forbidden-patterns.md.",
12
+ "acceptanceCriteria": [
13
+ "No occurrences of readFileSync or appendFileSync remain in src/",
14
+ "No occurrences of existsSync remain in src/ (unless performance critical)",
15
+ "No occurrences of setTimeout remain in src/",
16
+ "Codebase passes typecheck and lint after changes"
17
+ ],
18
+ "status": "failed",
19
+ "passes": false,
20
+ "attempts": 1,
21
+ "routing": {
22
+ "complexity": "simple",
23
+ "modelTier": "powerful",
24
+ "testStrategy": "test-after"
25
+ },
26
+ "priorErrors": [
27
+ "Attempt 1 failed with model tier: fast: Stage requested escalation to higher tier",
28
+ "Attempt 1 failed with model tier: balanced: Stage requested escalation to higher tier"
29
+ ],
30
+ "priorFailures": [
31
+ {
32
+ "attempt": 1,
33
+ "modelTier": "fast",
34
+ "stage": "escalation",
35
+ "summary": "Failed with tier fast, escalating to next tier",
36
+ "timestamp": "2026-03-05T02:37:41.586Z"
37
+ },
38
+ {
39
+ "attempt": 1,
40
+ "modelTier": "balanced",
41
+ "stage": "escalation",
42
+ "summary": "Failed with tier balanced, escalating to next tier",
43
+ "timestamp": "2026-03-05T02:38:14.713Z"
44
+ }
45
+ ],
46
+ "escalations": [],
47
+ "dependencies": [],
48
+ "tags": [],
49
+ "storyPoints": 1
50
+ }
51
+ ]
52
+ }
@@ -0,0 +1 @@
1
+ [2026-03-05T02:38:46.450Z] US-001 — FAILED — Node.js API Removal — Execution failed
@@ -0,0 +1,7 @@
1
+ # Plan: v0.19.0-hardening
2
+
3
+ ## Architecture
4
+
5
+ ## Phases
6
+
7
+ ## Dependencies
@@ -0,0 +1,84 @@
1
+ {
2
+ "project": "@nathapp/nax",
3
+ "feature": "v0.19.0-hardening",
4
+ "branchName": "feat/v0.19.0-sec",
5
+ "createdAt": "2026-03-05T02:58:00Z",
6
+ "updatedAt": "2026-03-05T03:13:29.408Z",
7
+ "userStories": [
8
+ {
9
+ "id": "US-001",
10
+ "title": "Security P0 Hardening",
11
+ "description": "Implement path validation for loaders (SEC-1, SEC-2), fix shell injection vectors (SEC-3, SEC-4), and respect dangerouslySkipPermissions config (SEC-5).",
12
+ "status": "passed",
13
+ "passes": true,
14
+ "attempts": 1,
15
+ "priorErrors": [],
16
+ "priorFailures": [],
17
+ "escalations": [],
18
+ "dependencies": [],
19
+ "tags": [],
20
+ "acceptanceCriteria": [],
21
+ "storyPoints": 1
22
+ },
23
+ {
24
+ "id": "US-002",
25
+ "title": "BUG-1 Parallel Race Condition",
26
+ "description": "Replace Promise.race loop with proper concurrency control in parallel executor.",
27
+ "status": "passed",
28
+ "passes": true,
29
+ "attempts": 1,
30
+ "priorErrors": [],
31
+ "priorFailures": [],
32
+ "escalations": [],
33
+ "dependencies": [],
34
+ "tags": [],
35
+ "acceptanceCriteria": [],
36
+ "storyPoints": 1
37
+ },
38
+ {
39
+ "id": "US-003",
40
+ "title": "Node.js API Removal",
41
+ "description": "Replace readFileSync, appendFileSync, existsSync, and setTimeout with Bun-native equivalents.",
42
+ "status": "passed",
43
+ "passes": true,
44
+ "attempts": 1,
45
+ "priorErrors": [],
46
+ "priorFailures": [],
47
+ "escalations": [],
48
+ "dependencies": [],
49
+ "tags": [],
50
+ "acceptanceCriteria": [],
51
+ "storyPoints": 1
52
+ },
53
+ {
54
+ "id": "US-004",
55
+ "title": "Type Fixes (v0.19.0)",
56
+ "description": "Fix the 18 type errors introduced during the Node.js API removal and security hardening in the src/ directory. Ensure all Bun-native APIs (Bun.file, Bun.write) are used correctly with valid types. Fix variable scopes and missing imports.",
57
+ "status": "failed",
58
+ "passes": false,
59
+ "attempts": 1,
60
+ "routing": {
61
+ "complexity": "medium",
62
+ "modelTier": "powerful",
63
+ "testStrategy": "test-after"
64
+ },
65
+ "priorErrors": [
66
+ "Attempt 1 failed with model tier: balanced: Stage requested escalation to higher tier"
67
+ ],
68
+ "priorFailures": [
69
+ {
70
+ "attempt": 1,
71
+ "modelTier": "balanced",
72
+ "stage": "escalation",
73
+ "summary": "Failed with tier balanced, escalating to next tier",
74
+ "timestamp": "2026-03-05T03:12:59.254Z"
75
+ }
76
+ ],
77
+ "escalations": [],
78
+ "dependencies": [],
79
+ "tags": [],
80
+ "acceptanceCriteria": [],
81
+ "storyPoints": 1
82
+ }
83
+ ]
84
+ }
@@ -0,0 +1,7 @@
1
+ # Progress: v0.19.0-hardening
2
+
3
+ Created: 2026-03-05T02:58:51.347Z
4
+
5
+ ---
6
+ [2026-03-05T03:01:55.028Z] US-003 — FAILED — Node.js API Removal — Execution failed
7
+ [2026-03-05T03:13:29.408Z] US-004 — FAILED — Type Fixes (v0.19.0) — Execution failed
@@ -0,0 +1,18 @@
1
+ # v0.19.0 Hardening & Compliance
2
+
3
+ Address Security, Reliability, and Technical Debt findings from the 2026-03-04 audit.
4
+
5
+ ## Goals
6
+ - SEC-1 to SEC-5: Complete security hardening (RCE, Shell, Permissions).
7
+ - BUG-1: Fix parallel concurrency race condition.
8
+ - BUG-3, BUG-5, MEM-2: Reliability fixes (metrics, mutation, memory leak).
9
+ - Technical Debt: Replace forbidden Node.js APIs (readFileSync, etc.) with Bun-native equivalents.
10
+ - Architecture: Split 400-line files and cleanup dead code.
11
+
12
+ ## Acceptance Criteria
13
+ - SEC-1/2: Dynamic imports restricted to allowed roots.
14
+ - SEC-3/4: Shell injection via backticks/dollar-signs blocked.
15
+ - SEC-5: --dangerously-skip-permissions respects config.
16
+ - BUG-1: Parallel execution respects maxConcurrency exactly.
17
+ - Node.js APIs: All readFileSync/appendFileSync/setTimeout replaced with Bun equivalents.
18
+ - Files: cli/config.ts split below 400 lines.
@@ -0,0 +1,8 @@
1
+ # Tasks: v0.19.0-hardening
2
+
3
+ ## US-001: [Title]
4
+
5
+ ### Description
6
+
7
+ ### Acceptance Criteria
8
+ - [ ] Criterion 1
@@ -0,0 +1,79 @@
1
+ {
2
+ "project": "nax",
3
+ "branchName": "feat/v0.20.0-verify-v2",
4
+ "feature": "verify-v2",
5
+ "userStories": [
6
+ {
7
+ "id": "US-001",
8
+ "title": "Remove test from review defaults",
9
+ "description": "Change review.checks default from ['typecheck', 'lint', 'test'] to ['typecheck', 'lint'] in src/config/defaults.ts. The test check in review duplicates the pipeline verify stage. Keep 'test' as a valid enum value in the schema for backwards compatibility but remove it from the default config. Update any tests that assert on the default review checks array.",
10
+ "complexity": "simple",
11
+ "status": "passed",
12
+ "attempts": 0,
13
+ "priorErrors": [
14
+ "Attempt 1 failed with model tier: balanced: Stage requested escalation to higher tier"
15
+ ],
16
+ "priorFailures": [
17
+ {
18
+ "attempt": 1,
19
+ "modelTier": "balanced",
20
+ "stage": "escalation",
21
+ "summary": "Failed with tier balanced, escalating to next tier",
22
+ "timestamp": "2026-03-05T11:14:42.773Z"
23
+ }
24
+ ],
25
+ "escalations": [],
26
+ "dependencies": [],
27
+ "tags": [],
28
+ "acceptanceCriteria": [],
29
+ "storyPoints": 1,
30
+ "routing": {
31
+ "complexity": "simple",
32
+ "modelTier": "powerful",
33
+ "testStrategy": "test-after",
34
+ "reasoning": "override: simple config default change, tests already exist"
35
+ },
36
+ "passes": true
37
+ },
38
+ {
39
+ "id": "US-002",
40
+ "title": "Remove post-verify scoped duplicate",
41
+ "description": "In src/execution/post-verify.ts, remove the scoped verification logic (getChangedTestFiles + runVerification for scoped tests) from runPostAgentVerification(). The pipeline verify stage already runs Smart Test Runner scoped tests. post-verify should ONLY run the regression gate (full suite) and handle failure revert with StructuredFailure. Remove getChangedTestFiles() and scopeTestCommand() helper functions. Remove the scoped rectification loop call. Update the function signature to no longer need storyGitRef. Update all tests for post-verify accordingly.",
42
+ "complexity": "medium",
43
+ "status": "passed",
44
+ "attempts": 0,
45
+ "priorErrors": [],
46
+ "priorFailures": [],
47
+ "escalations": [],
48
+ "dependencies": [],
49
+ "tags": [],
50
+ "acceptanceCriteria": [],
51
+ "storyPoints": 1,
52
+ "passes": true
53
+ },
54
+ {
55
+ "id": "US-003",
56
+ "title": "Deferred regression gate",
57
+ "description": "Create new src/execution/lifecycle/run-regression.ts that implements a deferred regression gate. Instead of running the full test suite after every story, run it once after all stories complete. Steps: (1) Add 'mode' field to RegressionGateConfigSchema with values 'deferred' | 'per-story' | 'disabled' (default: 'deferred'). (2) Add 'maxRectificationAttempts' field (default: 2). (3) In run-regression.ts: run full suite once, parse failures, use reverse Smart Test Runner mapping (test file -> source file -> responsible story via git log), attempt targeted rectification per responsible story, re-run full suite to confirm. (4) Call deferred regression from run-completion.ts before final metrics, only when mode is 'deferred'. (5) When mode is 'deferred', skip the per-story regression gate in post-verify.ts. (6) Add reverseMapTestToSource() to smart-runner.ts. (7) Handle edge cases: partial completion (only check passed stories), overlapping file changes (try last story first), unmapped tests (warn and mark all passed stories for re-verification).",
58
+ "complexity": "complex",
59
+ "status": "passed",
60
+ "attempts": 0,
61
+ "priorErrors": [],
62
+ "priorFailures": [],
63
+ "escalations": [],
64
+ "dependencies": [],
65
+ "tags": [],
66
+ "acceptanceCriteria": [],
67
+ "storyPoints": 1,
68
+ "routing": {
69
+ "complexity": "complex",
70
+ "modelTier": "balanced",
71
+ "testStrategy": "three-session-tdd-lite",
72
+ "reasoning": "override: complex new file + schema changes, needs powerful model"
73
+ },
74
+ "failureCategory": "session-failure",
75
+ "passes": true
76
+ }
77
+ ],
78
+ "updatedAt": "2026-03-05T11:58:46.858Z"
79
+ }
@@ -0,0 +1,3 @@
1
+ [2026-03-05T07:05:07.935Z] US-002 — PASSED — Remove post-verify scoped duplicate — Cost: $0.1170
2
+ [2026-03-05T08:08:59.656Z] US-003 — FAILED — Deferred regression gate — Execution failed
3
+ [2026-03-05T08:13:50.586Z] US-001 — FAILED — Remove test from review defaults — Execution failed
@@ -0,0 +1,27 @@
1
+ {
2
+ "version": 1,
3
+ "run": {
4
+ "id": "run-2026-03-05T02-37-04-540Z",
5
+ "feature": "nax-compliance",
6
+ "startedAt": "2026-03-05T02:37:04.540Z",
7
+ "status": "stalled",
8
+ "dryRun": false,
9
+ "pid": 814245
10
+ },
11
+ "progress": {
12
+ "total": 1,
13
+ "passed": 0,
14
+ "failed": 1,
15
+ "paused": 0,
16
+ "blocked": 0,
17
+ "pending": 0
18
+ },
19
+ "cost": {
20
+ "spent": 0,
21
+ "limit": 8
22
+ },
23
+ "current": null,
24
+ "iterations": 3,
25
+ "updatedAt": "2026-03-05T02:38:46.469Z",
26
+ "durationMs": 101929
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.18.6",
3
+ "version": "0.20.0",
4
4
  "description": "AI Coding Agent Orchestrator \u2014 loops until done",
5
5
  "type": "module",
6
6
  "bin": {
@@ -44,4 +44,4 @@
44
44
  "tdd",
45
45
  "coding"
46
46
  ]
47
- }
47
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { AgentAdapter } from "../agents/types";
9
- import type { ModelDef } from "../config/schema";
9
+ import type { ModelDef, NaxConfig } from "../config/schema";
10
10
  import { getLogger } from "../logger";
11
11
  import type { PRD, UserStory } from "../prd/types";
12
12
 
@@ -70,6 +70,8 @@ export interface GenerateFixStoriesOptions {
70
70
  workdir: string;
71
71
  /** Model definition for LLM call */
72
72
  modelDef: ModelDef;
73
+ /** Global config */
74
+ config: NaxConfig;
73
75
  }
74
76
 
75
77
  /**
@@ -224,7 +226,9 @@ export async function generateFixStories(
224
226
 
225
227
  try {
226
228
  // Call agent to generate fix description
227
- const cmd = [adapter.binary, "--model", modelDef.model, "--dangerously-skip-permissions", "-p", prompt];
229
+ const skipPerms = options.config.quality?.dangerouslySkipPermissions ?? true;
230
+ const permArgs = skipPerms ? ["--dangerously-skip-permissions"] : [];
231
+ const cmd = [adapter.binary, "--model", modelDef.model, ...permArgs, "-p", prompt];
228
232
 
229
233
  const proc = Bun.spawn(cmd, {
230
234
  cwd: workdir,
@@ -178,7 +178,9 @@ export async function generateAcceptanceTests(
178
178
 
179
179
  try {
180
180
  // Call agent to generate tests (using decompose as pattern)
181
- const cmd = [adapter.binary, "--model", options.modelDef.model, "--dangerously-skip-permissions", "-p", prompt];
181
+ const skipPerms = options.config.quality?.dangerouslySkipPermissions ?? true;
182
+ const permArgs = skipPerms ? ["--dangerously-skip-permissions"] : [];
183
+ const cmd = [adapter.binary, "--model", options.modelDef.model, ...permArgs, "-p", prompt];
182
184
 
183
185
  const proc = Bun.spawn(cmd, {
184
186
  cwd: options.workdir,
@@ -4,7 +4,7 @@
4
4
  * Types for generating acceptance tests from spec.md acceptance criteria.
5
5
  */
6
6
 
7
- import type { ModelDef, ModelTier } from "../config/schema";
7
+ import type { ModelDef, ModelTier, NaxConfig } from "../config/schema";
8
8
 
9
9
  /**
10
10
  * A single acceptance criterion extracted from spec.md.
@@ -55,6 +55,8 @@ export interface GenerateAcceptanceTestsOptions {
55
55
  modelTier: ModelTier;
56
56
  /** Resolved model definition */
57
57
  modelDef: ModelDef;
58
+ /** Global config for quality settings */
59
+ config: NaxConfig;
58
60
  }
59
61
 
60
62
  /**
@@ -1,3 +1,6 @@
1
+ import { mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
1
4
  /**
2
5
  * Claude Code Plan Logic
3
6
  *
@@ -96,9 +99,7 @@ export async function runPlan(
96
99
  }
97
100
 
98
101
  // Non-interactive: redirect stdout to temp file via Bun.file()
99
- const { join } = require("node:path");
100
- const { mkdtempSync, readFileSync, rmSync } = require("node:fs");
101
- const { tmpdir } = require("node:os");
102
+
102
103
  const tempDir = mkdtempSync(join(tmpdir(), "nax-plan-"));
103
104
  const outFile = join(tempDir, "stdout.txt");
104
105
  const errFile = join(tempDir, "stderr.txt");
@@ -120,8 +121,8 @@ export async function runPlan(
120
121
  // Unregister PID after exit
121
122
  await pidRegistry.unregister(proc.pid);
122
123
 
123
- const specContent = readFileSync(outFile, "utf-8");
124
- const conversationLog = readFileSync(errFile, "utf-8");
124
+ const specContent = await Bun.file(outFile).text();
125
+ const conversationLog = await Bun.file(errFile).text();
125
126
 
126
127
  if (exitCode !== 0) {
127
128
  throw new Error(`Plan mode failed with exit code ${exitCode}: ${conversationLog || "unknown error"}`);
@@ -193,6 +193,7 @@ async function generateAcceptanceTestsForFeature(
193
193
  codebaseContext,
194
194
  modelTier,
195
195
  modelDef,
196
+ config,
196
197
  });
197
198
 
198
199
  const acceptanceTestPath = join(featureDir, config.acceptance.testPath);
package/src/cli/init.ts CHANGED
@@ -4,7 +4,8 @@
4
4
  * Initializes nax configuration directories and files.
5
5
  */
6
6
 
7
- import { existsSync, mkdirSync, readFileSync } from "node:fs";
7
+ import { existsSync } from "node:fs";
8
+ import { mkdir } from "node:fs/promises";
8
9
  import { join } from "node:path";
9
10
  import { globalConfigDir, projectConfigDir } from "../config/paths";
10
11
  import { DEFAULT_CONFIG } from "../config/schema";
@@ -34,7 +35,7 @@ async function updateGitignore(projectRoot: string): Promise<void> {
34
35
 
35
36
  let existing = "";
36
37
  if (existsSync(gitignorePath)) {
37
- existing = readFileSync(gitignorePath, "utf-8");
38
+ existing = await Bun.file(gitignorePath).text();
38
39
  }
39
40
 
40
41
  const missingEntries = NAX_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
@@ -95,7 +96,7 @@ async function initGlobal(): Promise<void> {
95
96
 
96
97
  // Create ~/.nax if it doesn't exist
97
98
  if (!existsSync(globalDir)) {
98
- mkdirSync(globalDir, { recursive: true });
99
+ await mkdir(globalDir, { recursive: true });
99
100
  logger.info("init", "Created global config directory", { path: globalDir });
100
101
  }
101
102
 
@@ -120,7 +121,7 @@ async function initGlobal(): Promise<void> {
120
121
  // Create ~/.nax/hooks/ directory if it doesn't exist
121
122
  const hooksDir = join(globalDir, "hooks");
122
123
  if (!existsSync(hooksDir)) {
123
- mkdirSync(hooksDir, { recursive: true });
124
+ await mkdir(hooksDir, { recursive: true });
124
125
  logger.info("init", "Created global hooks directory", { path: hooksDir });
125
126
  } else {
126
127
  logger.info("init", "Global hooks directory already exists", { path: hooksDir });
@@ -138,7 +139,7 @@ async function initProject(projectRoot: string): Promise<void> {
138
139
 
139
140
  // Create nax/ directory if it doesn't exist
140
141
  if (!existsSync(projectDir)) {
141
- mkdirSync(projectDir, { recursive: true });
142
+ await mkdir(projectDir, { recursive: true });
142
143
  logger.info("init", "Created project config directory", { path: projectDir });
143
144
  }
144
145
 
@@ -163,7 +164,7 @@ async function initProject(projectRoot: string): Promise<void> {
163
164
  // Create nax/hooks/ directory if it doesn't exist
164
165
  const hooksDir = join(projectDir, "hooks");
165
166
  if (!existsSync(hooksDir)) {
166
- mkdirSync(hooksDir, { recursive: true });
167
+ await mkdir(hooksDir, { recursive: true });
167
168
  logger.info("init", "Created project hooks directory", { path: hooksDir });
168
169
  } else {
169
170
  logger.info("init", "Project hooks directory already exists", { path: hooksDir });
@@ -67,6 +67,7 @@ export const DEFAULT_CONFIG: NaxConfig = {
67
67
  enabled: true,
68
68
  timeoutSeconds: 120,
69
69
  acceptOnTimeout: true,
70
+ maxRectificationAttempts: 2,
70
71
  },
71
72
  contextProviderTokenBudget: 2000,
72
73
  smartTestRunner: true,
@@ -80,6 +81,7 @@ export const DEFAULT_CONFIG: NaxConfig = {
80
81
  detectOpenHandles: true,
81
82
  detectOpenHandlesRetries: 1,
82
83
  gracePeriodMs: 5000,
84
+ dangerouslySkipPermissions: true,
83
85
  drainTimeoutMs: 2000,
84
86
  shell: "/bin/sh",
85
87
  stripEnvVars: ["CLAUDECODE", "REPL_ID", "AGENT"],
@@ -112,7 +114,7 @@ export const DEFAULT_CONFIG: NaxConfig = {
112
114
  },
113
115
  review: {
114
116
  enabled: true,
115
- checks: ["typecheck", "lint", "test"],
117
+ checks: ["typecheck", "lint"],
116
118
  commands: {},
117
119
  },
118
120
  plan: {
@@ -63,6 +63,8 @@ const RegressionGateConfigSchema = z.object({
63
63
  enabled: z.boolean().default(true),
64
64
  timeoutSeconds: z.number().int().min(10).max(600).default(120),
65
65
  acceptOnTimeout: z.boolean().default(true),
66
+ mode: z.enum(["deferred", "per-story", "disabled"]).default("deferred"),
67
+ maxRectificationAttempts: z.number().int().min(1).default(2),
66
68
  });
67
69
 
68
70
  const SmartTestRunnerConfigSchema = z.object({
@@ -78,6 +78,10 @@ export interface RegressionGateConfig {
78
78
  timeoutSeconds: number;
79
79
  /** Accept timeout as pass instead of failing (BUG-026, default: true) */
80
80
  acceptOnTimeout?: boolean;
81
+ /** Mode of regression gate: 'deferred' (run once after all stories), 'per-story' (run after each story), 'disabled' (default: 'deferred') */
82
+ mode?: "deferred" | "per-story" | "disabled";
83
+ /** Max rectification attempts for deferred regression gate (default: 2) */
84
+ maxRectificationAttempts?: number;
81
85
  }
82
86
 
83
87
  /** Smart test runner configuration (STR-007) */
@@ -145,6 +149,8 @@ export interface QualityConfig {
145
149
  detectOpenHandlesRetries: number;
146
150
  /** Grace period in ms after SIGTERM before sending SIGKILL (default: 5000) */
147
151
  gracePeriodMs: number;
152
+ /** Use --dangerously-skip-permissions for agent sessions (default: false) */
153
+ dangerouslySkipPermissions: boolean;
148
154
  /** Deadline in ms to drain stdout/stderr after killing process (Bun stream workaround, default: 2000) */
149
155
  drainTimeoutMs: number;
150
156
  /** Shell to use for running verification commands (default: /bin/sh) */