@nathapp/nax 0.48.4 → 0.49.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/CHANGELOG.md +8 -0
- package/dist/nax.js +283 -184
- package/package.json +1 -1
- package/src/acceptance/generator.ts +7 -5
- package/src/agents/acp/adapter.ts +7 -2
- package/src/agents/acp/parser.ts +1 -1
- package/src/agents/acp/spawn-client.ts +2 -0
- package/src/agents/types.ts +6 -0
- package/src/cli/plan.ts +11 -1
- package/src/cli/prompts-main.ts +1 -0
- package/src/config/merge.ts +55 -9
- package/src/execution/iteration-runner.ts +15 -0
- package/src/execution/lifecycle/acceptance-loop.ts +2 -0
- package/src/execution/parallel-coordinator.ts +1 -0
- package/src/execution/parallel-executor-rectify.ts +1 -0
- package/src/execution/parallel-worker.ts +1 -0
- package/src/execution/sequential-executor.ts +1 -0
- package/src/pipeline/stages/acceptance.ts +6 -2
- package/src/pipeline/stages/autofix.ts +15 -7
- package/src/pipeline/stages/execution.ts +6 -0
- package/src/pipeline/stages/prompt.ts +5 -2
- package/src/pipeline/stages/rectify.ts +4 -2
- package/src/pipeline/stages/regression.ts +10 -6
- package/src/pipeline/stages/review.ts +11 -7
- package/src/pipeline/stages/routing.ts +20 -1
- package/src/pipeline/stages/verify.ts +23 -20
- package/src/pipeline/types.ts +7 -0
- package/src/utils/git.ts +10 -2
|
@@ -5,12 +5,11 @@
|
|
|
5
5
|
* This is a lightweight verification before the full review stage.
|
|
6
6
|
*
|
|
7
7
|
* @returns
|
|
8
|
-
* - `continue`: Tests passed
|
|
9
|
-
* - `escalate`:
|
|
8
|
+
* - `continue`: Tests passed, OR TEST_FAILURE (ctx.verifyResult.success===false → rectifyStage handles it)
|
|
9
|
+
* - `escalate`: TIMEOUT or RUNTIME_CRASH (structural — rectify can't fix these)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { basename, join } from "node:path";
|
|
13
|
-
import { loadConfigForWorkdir } from "../../config/loader";
|
|
14
13
|
import type { SmartTestRunnerConfig } from "../../config/types";
|
|
15
14
|
import { getLogger } from "../../logger";
|
|
16
15
|
import { logTestOutput } from "../../utils/log-test-output";
|
|
@@ -91,10 +90,9 @@ export const verifyStage: PipelineStage = {
|
|
|
91
90
|
async execute(ctx: PipelineContext): Promise<StageResult> {
|
|
92
91
|
const logger = getLogger();
|
|
93
92
|
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
: ctx.config;
|
|
93
|
+
// PKG-003: use centrally resolved effective config (set once per story in iteration-runner)
|
|
94
|
+
// Falls back to ctx.config for contexts that predate PKG-003 (e.g., acceptance-loop)
|
|
95
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
98
96
|
|
|
99
97
|
// Skip verification if tests are not required
|
|
100
98
|
if (!effectiveConfig.quality.requireTests) {
|
|
@@ -118,8 +116,8 @@ export const verifyStage: PipelineStage = {
|
|
|
118
116
|
// Determine effective test command (smart runner or full suite)
|
|
119
117
|
let effectiveCommand = testCommand;
|
|
120
118
|
let isFullSuite = true;
|
|
121
|
-
const smartRunnerConfig = coerceSmartTestRunner(
|
|
122
|
-
const regressionMode =
|
|
119
|
+
const smartRunnerConfig = coerceSmartTestRunner(effectiveConfig.execution.smartTestRunner);
|
|
120
|
+
const regressionMode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
|
|
123
121
|
|
|
124
122
|
// Resolve {{package}} in testScoped template for monorepo stories.
|
|
125
123
|
// Returns null if package.json is absent (non-JS project) — falls through to smart-runner.
|
|
@@ -207,8 +205,8 @@ export const verifyStage: PipelineStage = {
|
|
|
207
205
|
const result = await _verifyDeps.regression({
|
|
208
206
|
workdir: effectiveWorkdir,
|
|
209
207
|
command: effectiveCommand,
|
|
210
|
-
timeoutSeconds:
|
|
211
|
-
acceptOnTimeout:
|
|
208
|
+
timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
|
|
209
|
+
acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true,
|
|
212
210
|
});
|
|
213
211
|
|
|
214
212
|
// Store result on context for rectify stage
|
|
@@ -236,7 +234,7 @@ export const verifyStage: PipelineStage = {
|
|
|
236
234
|
if (!result.success) {
|
|
237
235
|
// BUG-019: Distinguish timeout from actual test failures
|
|
238
236
|
if (result.status === "TIMEOUT") {
|
|
239
|
-
const timeout =
|
|
237
|
+
const timeout = effectiveConfig.execution.verificationTimeoutSeconds;
|
|
240
238
|
logger.error(
|
|
241
239
|
"verify",
|
|
242
240
|
`Test suite exceeded timeout (${timeout}s). This is NOT a test failure — consider increasing execution.verificationTimeoutSeconds or scoping tests.`,
|
|
@@ -259,13 +257,19 @@ export const verifyStage: PipelineStage = {
|
|
|
259
257
|
logTestOutput(logger, "verify", result.output, { storyId: ctx.story.id });
|
|
260
258
|
}
|
|
261
259
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
260
|
+
// RUNTIME_CRASH and TIMEOUT are structural — escalate immediately (rectify can't fix them)
|
|
261
|
+
if (result.status === "TIMEOUT" || detectRuntimeCrash(result.output)) {
|
|
262
|
+
return {
|
|
263
|
+
action: "escalate",
|
|
264
|
+
reason:
|
|
265
|
+
result.status === "TIMEOUT"
|
|
266
|
+
? `Test suite TIMEOUT after ${effectiveConfig.execution.verificationTimeoutSeconds}s (not a code failure)`
|
|
267
|
+
: `Tests failed with runtime crash (exit code ${result.status ?? "non-zero"})`,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// TEST_FAILURE: ctx.verifyResult is set with success:false — rectifyStage handles it next
|
|
272
|
+
return { action: "continue" };
|
|
269
273
|
}
|
|
270
274
|
|
|
271
275
|
logger.info("verify", "Tests passed", { storyId: ctx.story.id });
|
|
@@ -278,6 +282,5 @@ export const verifyStage: PipelineStage = {
|
|
|
278
282
|
*/
|
|
279
283
|
export const _verifyDeps = {
|
|
280
284
|
regression,
|
|
281
|
-
loadConfigForWorkdir,
|
|
282
285
|
readPackageName,
|
|
283
286
|
};
|
package/src/pipeline/types.ts
CHANGED
|
@@ -58,6 +58,13 @@ export type AgentGetFn = (name: string) => import("../agents/types").AgentAdapte
|
|
|
58
58
|
export interface PipelineContext {
|
|
59
59
|
/** Ngent configuration */
|
|
60
60
|
config: NaxConfig;
|
|
61
|
+
/**
|
|
62
|
+
* Resolved config for this story's package.
|
|
63
|
+
* When story.workdir is set, this is root config merged with package config.
|
|
64
|
+
* When no workdir, this equals ctx.config (root).
|
|
65
|
+
* Set once per story in the iteration runner before pipeline execution.
|
|
66
|
+
*/
|
|
67
|
+
effectiveConfig: NaxConfig;
|
|
61
68
|
/** Full PRD document */
|
|
62
69
|
prd: PRD;
|
|
63
70
|
/** Current story (or batch leader) */
|
package/src/utils/git.ts
CHANGED
|
@@ -181,7 +181,11 @@ export async function autoCommitIfDirty(workdir: string, stage: string, role: st
|
|
|
181
181
|
return gitRoot;
|
|
182
182
|
}
|
|
183
183
|
})();
|
|
184
|
-
|
|
184
|
+
// Allow: workdir IS the git root, or workdir is a subdirectory (monorepo package)
|
|
185
|
+
// Reject: workdir has no git repo at all (realGitRoot would be empty/error)
|
|
186
|
+
const isAtRoot = realWorkdir === realGitRoot;
|
|
187
|
+
const isSubdir = realGitRoot && realWorkdir.startsWith(`${realGitRoot}/`);
|
|
188
|
+
if (!isAtRoot && !isSubdir) return;
|
|
185
189
|
|
|
186
190
|
const statusProc = _gitDeps.spawn(["git", "status", "--porcelain"], {
|
|
187
191
|
cwd: workdir,
|
|
@@ -199,7 +203,11 @@ export async function autoCommitIfDirty(workdir: string, stage: string, role: st
|
|
|
199
203
|
dirtyFiles: statusOutput.trim().split("\n").length,
|
|
200
204
|
});
|
|
201
205
|
|
|
202
|
-
|
|
206
|
+
// Use "git add ." when workdir is a monorepo package subdir — only stages files under
|
|
207
|
+
// that package, preventing accidental cross-package commits.
|
|
208
|
+
// Use "git add -A" at repo root to capture renames/deletions across the full tree.
|
|
209
|
+
const addArgs = isSubdir ? ["git", "add", "."] : ["git", "add", "-A"];
|
|
210
|
+
const addProc = _gitDeps.spawn(addArgs, { cwd: workdir, stdout: "pipe", stderr: "pipe" });
|
|
203
211
|
await addProc.exited;
|
|
204
212
|
|
|
205
213
|
const commitProc = _gitDeps.spawn(["git", "commit", "-m", `chore(${storyId}): auto-commit after ${role} session`], {
|