@nathapp/nax 0.36.1 → 0.37.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.
@@ -11,6 +11,7 @@
11
11
 
12
12
  import type { SmartTestRunnerConfig } from "../../config/types";
13
13
  import { getLogger } from "../../logger";
14
+ import { detectRuntimeCrash } from "../../verification/crash-detector";
14
15
  import type { VerifyStatus } from "../../verification/orchestrator-types";
15
16
  import { regression } from "../../verification/runners";
16
17
  import { _smartRunnerDeps } from "../../verification/smart-runner";
@@ -133,7 +134,13 @@ export const verifyStage: PipelineStage = {
133
134
  // Store result on context for rectify stage
134
135
  ctx.verifyResult = {
135
136
  success: result.success,
136
- status: (result.status === "TIMEOUT" ? "TIMEOUT" : result.success ? "PASS" : "TEST_FAILURE") as VerifyStatus,
137
+ status: (result.status === "TIMEOUT"
138
+ ? "TIMEOUT"
139
+ : result.success
140
+ ? "PASS"
141
+ : detectRuntimeCrash(result.output)
142
+ ? "RUNTIME_CRASH"
143
+ : "TEST_FAILURE") as VerifyStatus,
137
144
  storyId: ctx.story.id,
138
145
  strategy: "scoped",
139
146
  passCount: result.passCount ?? 0,
@@ -74,7 +74,7 @@ export function wireReporters(
74
74
  runId,
75
75
  storyId: ev.storyId,
76
76
  status: "completed",
77
- durationMs: ev.durationMs,
77
+ runElapsedMs: ev.runElapsedMs,
78
78
  cost: ev.cost ?? 0,
79
79
  tier: ev.modelTier ?? "balanced",
80
80
  testStrategy: ev.testStrategy ?? "test-after",
@@ -100,7 +100,7 @@ export function wireReporters(
100
100
  runId,
101
101
  storyId: ev.storyId,
102
102
  status: "failed",
103
- durationMs: Date.now() - startTime,
103
+ runElapsedMs: Date.now() - startTime,
104
104
  cost: 0,
105
105
  tier: "balanced",
106
106
  testStrategy: "test-after",
@@ -126,7 +126,7 @@ export function wireReporters(
126
126
  runId,
127
127
  storyId: ev.storyId,
128
128
  status: "paused",
129
- durationMs: Date.now() - startTime,
129
+ runElapsedMs: Date.now() - startTime,
130
130
  cost: 0,
131
131
  tier: "balanced",
132
132
  testStrategy: "test-after",
@@ -110,8 +110,12 @@ export interface PipelineContext {
110
110
  tddFailureCategory?: FailureCategory;
111
111
  /** Set to true when TDD full-suite gate already passed — verify stage skips to avoid redundant run (BUG-054) */
112
112
  fullSuiteGatePassed?: boolean;
113
+ /** Number of runtime crashes (RUNTIME_CRASH verify status) encountered for this story (BUG-070) */
114
+ storyRuntimeCrashes?: number;
113
115
  /** Structured review findings from plugin reviewers — passed to escalation for retry context */
114
116
  reviewFindings?: import("../plugins/types").ReviewFinding[];
117
+ /** Accumulated cost across all prior escalation attempts (BUG-067) */
118
+ accumulatedAttemptCost?: number;
115
119
  }
116
120
 
117
121
  /**
@@ -274,7 +274,7 @@ export interface StoryCompleteEvent {
274
274
  runId: string;
275
275
  storyId: string;
276
276
  status: "completed" | "failed" | "skipped" | "paused";
277
- durationMs: number;
277
+ runElapsedMs: number;
278
278
  cost: number;
279
279
  tier: string;
280
280
  testStrategy: string;
package/src/prd/types.ts CHANGED
@@ -49,6 +49,8 @@ export interface StructuredFailure {
49
49
  testFailures?: TestFailureContext[];
50
50
  /** Structured review findings from plugin reviewers (e.g., semgrep, eslint) */
51
51
  reviewFindings?: import("../plugins/types").ReviewFinding[];
52
+ /** Estimated cost of this attempt (BUG-067: accumulated across escalations) */
53
+ cost?: number;
52
54
  /** ISO timestamp when failure was recorded */
53
55
  timestamp: string;
54
56
  }
@@ -15,16 +15,28 @@ import type { PluginRegistry } from "../plugins";
15
15
  import { runReview } from "./runner";
16
16
  import type { ReviewConfig, ReviewResult } from "./types";
17
17
 
18
- async function getChangedFiles(workdir: string): Promise<string[]> {
18
+ async function getChangedFiles(workdir: string, baseRef?: string): Promise<string[]> {
19
19
  try {
20
- const [stagedProc, unstagedProc] = [
21
- spawn({ cmd: ["git", "diff", "--name-only", "--cached"], cwd: workdir, stdout: "pipe", stderr: "pipe" }),
22
- spawn({ cmd: ["git", "diff", "--name-only"], cwd: workdir, stdout: "pipe", stderr: "pipe" }),
20
+ const diffArgs = ["diff", "--name-only"];
21
+ const [stagedProc, unstagedProc, baseProc] = [
22
+ spawn({ cmd: ["git", ...diffArgs, "--cached"], cwd: workdir, stdout: "pipe", stderr: "pipe" }),
23
+ spawn({ cmd: ["git", ...diffArgs], cwd: workdir, stdout: "pipe", stderr: "pipe" }),
24
+ baseRef
25
+ ? spawn({ cmd: ["git", ...diffArgs, `${baseRef}...HEAD`], cwd: workdir, stdout: "pipe", stderr: "pipe" })
26
+ : null,
23
27
  ];
24
- await Promise.all([stagedProc.exited, unstagedProc.exited]);
25
- const staged = (await new Response(stagedProc.stdout).text()).trim().split("\n").filter(Boolean);
26
- const unstaged = (await new Response(unstagedProc.stdout).text()).trim().split("\n").filter(Boolean);
27
- return Array.from(new Set([...staged, ...unstaged]));
28
+
29
+ await Promise.all([stagedProc.exited, unstagedProc.exited, baseProc?.exited]);
30
+
31
+ const [staged, unstaged, based] = await Promise.all([
32
+ new Response(stagedProc.stdout).text().then((t) => t.trim().split("\n").filter(Boolean)),
33
+ new Response(unstagedProc.stdout).text().then((t) => t.trim().split("\n").filter(Boolean)),
34
+ baseProc
35
+ ? new Response(baseProc.stdout).text().then((t) => t.trim().split("\n").filter(Boolean))
36
+ : Promise.resolve([]),
37
+ ]);
38
+
39
+ return Array.from(new Set([...staged, ...unstaged, ...based]));
28
40
  } catch {
29
41
  return [];
30
42
  }
@@ -60,7 +72,10 @@ export class ReviewOrchestrator {
60
72
  if (plugins) {
61
73
  const reviewers = plugins.getReviewers();
62
74
  if (reviewers.length > 0) {
63
- const changedFiles = await getChangedFiles(workdir);
75
+ // Use the story's start ref if available to capture auto-committed changes
76
+ // biome-ignore lint/suspicious/noExplicitAny: baseRef injected into config for pipeline use
77
+ const baseRef = (executionConfig as any)?.storyGitRef;
78
+ const changedFiles = await getChangedFiles(workdir, baseRef);
64
79
  const pluginResults: ReviewResult["pluginReviewers"] = [];
65
80
 
66
81
  for (const reviewer of reviewers) {
package/src/tdd/types.ts CHANGED
@@ -12,7 +12,8 @@ export type FailureCategory =
12
12
  /** Verifier explicitly rejected the implementation */
13
13
  | "verifier-rejected"
14
14
  /** Greenfield project with no test files — TDD not applicable (BUG-010) */
15
- | "greenfield-no-tests";
15
+ | "greenfield-no-tests"
16
+ | "runtime-crash";
16
17
 
17
18
  /** Isolation verification result */
18
19
  export interface IsolationCheck {
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Runtime Crash Detector — BUG-070
3
+ *
4
+ * Detects Bun runtime crashes in test output so they can be classified as
5
+ * RUNTIME_CRASH rather than TEST_FAILURE, preventing spurious tier escalation.
6
+ *
7
+ * STUB — implementation is intentionally absent. Tests are RED until
8
+ * the real logic is written.
9
+ */
10
+
11
+ /**
12
+ * Known patterns emitted by the Bun runtime before any test results
13
+ * when a crash occurs (segfault, panic, etc.).
14
+ */
15
+ export const CRASH_PATTERNS = [
16
+ "panic(main thread)",
17
+ "Segmentation fault",
18
+ "Bun has crashed",
19
+ "oh no: Bun has crashed",
20
+ ] as const;
21
+
22
+ /**
23
+ * Detect whether the given test runner output contains a Bun runtime crash.
24
+ *
25
+ * Returns true if any known crash pattern is found in the output.
26
+ * These patterns are emitted by Bun itself before any test result lines.
27
+ *
28
+ * @param output - Raw stdout/stderr from the test runner
29
+ */
30
+ export function detectRuntimeCrash(output: string | undefined | null): boolean {
31
+ // STUB: not implemented yet — always returns false
32
+ if (!output) return false;
33
+ return CRASH_PATTERNS.some((pattern) => output.includes(pattern));
34
+ }
@@ -50,7 +50,14 @@ export interface StructuredTestFailure {
50
50
  // Result
51
51
  // ---------------------------------------------------------------------------
52
52
 
53
- export type VerifyStatus = "PASS" | "TEST_FAILURE" | "TIMEOUT" | "BUILD_ERROR" | "SKIPPED" | "ASSET_CHECK_FAILED";
53
+ export type VerifyStatus =
54
+ | "PASS"
55
+ | "TEST_FAILURE"
56
+ | "TIMEOUT"
57
+ | "BUILD_ERROR"
58
+ | "SKIPPED"
59
+ | "ASSET_CHECK_FAILED"
60
+ | "RUNTIME_CRASH";
54
61
 
55
62
  export interface VerifyResult {
56
63
  success: boolean;