@nathapp/nax 0.50.3 → 0.51.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/CHANGELOG.md +30 -0
- package/dist/nax.js +393 -197
- package/package.json +1 -3
- package/bin/nax.ts +0 -1195
- package/src/acceptance/fix-generator.ts +0 -322
- package/src/acceptance/generator.ts +0 -415
- package/src/acceptance/index.ts +0 -42
- package/src/acceptance/refinement.ts +0 -224
- package/src/acceptance/templates/cli.ts +0 -47
- package/src/acceptance/templates/component.ts +0 -78
- package/src/acceptance/templates/e2e.ts +0 -43
- package/src/acceptance/templates/index.ts +0 -21
- package/src/acceptance/templates/snapshot.ts +0 -50
- package/src/acceptance/templates/unit.ts +0 -48
- package/src/acceptance/types.ts +0 -138
- package/src/agents/acp/adapter.ts +0 -888
- package/src/agents/acp/cost.ts +0 -9
- package/src/agents/acp/index.ts +0 -7
- package/src/agents/acp/interaction-bridge.ts +0 -126
- package/src/agents/acp/parser.ts +0 -119
- package/src/agents/acp/spawn-client.ts +0 -373
- package/src/agents/acp/types.ts +0 -22
- package/src/agents/aider/adapter.ts +0 -135
- package/src/agents/claude/adapter.ts +0 -258
- package/src/agents/claude/complete.ts +0 -80
- package/src/agents/claude/cost.ts +0 -16
- package/src/agents/claude/execution.ts +0 -215
- package/src/agents/claude/index.ts +0 -3
- package/src/agents/claude/interactive.ts +0 -77
- package/src/agents/claude/plan.ts +0 -179
- package/src/agents/codex/adapter.ts +0 -153
- package/src/agents/cost/calculate.ts +0 -154
- package/src/agents/cost/index.ts +0 -10
- package/src/agents/cost/parse.ts +0 -97
- package/src/agents/cost/pricing.ts +0 -59
- package/src/agents/cost/types.ts +0 -45
- package/src/agents/gemini/adapter.ts +0 -177
- package/src/agents/index.ts +0 -18
- package/src/agents/opencode/adapter.ts +0 -106
- package/src/agents/registry.ts +0 -136
- package/src/agents/shared/decompose.ts +0 -154
- package/src/agents/shared/model-resolution.ts +0 -43
- package/src/agents/shared/types-extended.ts +0 -164
- package/src/agents/shared/validation.ts +0 -69
- package/src/agents/shared/version-detection.ts +0 -109
- package/src/agents/types.ts +0 -205
- package/src/analyze/classifier.ts +0 -282
- package/src/analyze/index.ts +0 -16
- package/src/analyze/scanner.ts +0 -171
- package/src/analyze/types.ts +0 -51
- package/src/cli/accept.ts +0 -108
- package/src/cli/agents.ts +0 -87
- package/src/cli/analyze-parser.ts +0 -291
- package/src/cli/analyze.ts +0 -352
- package/src/cli/config-descriptions.ts +0 -219
- package/src/cli/config-diff.ts +0 -103
- package/src/cli/config-display.ts +0 -285
- package/src/cli/config-get.ts +0 -55
- package/src/cli/config.ts +0 -14
- package/src/cli/constitution.ts +0 -17
- package/src/cli/diagnose-analysis.ts +0 -159
- package/src/cli/diagnose-formatter.ts +0 -87
- package/src/cli/diagnose.ts +0 -203
- package/src/cli/generate.ts +0 -250
- package/src/cli/index.ts +0 -42
- package/src/cli/init-context.ts +0 -405
- package/src/cli/init-detect.ts +0 -303
- package/src/cli/init.ts +0 -296
- package/src/cli/interact.ts +0 -295
- package/src/cli/plan.ts +0 -509
- package/src/cli/plugins.ts +0 -122
- package/src/cli/prompts-export.ts +0 -58
- package/src/cli/prompts-init.ts +0 -200
- package/src/cli/prompts-main.ts +0 -183
- package/src/cli/prompts-shared.ts +0 -70
- package/src/cli/prompts-tdd.ts +0 -88
- package/src/cli/prompts.ts +0 -17
- package/src/cli/runs.ts +0 -174
- package/src/cli/status-cost.ts +0 -151
- package/src/cli/status-features.ts +0 -405
- package/src/cli/status.ts +0 -13
- package/src/commands/common.ts +0 -171
- package/src/commands/diagnose.ts +0 -17
- package/src/commands/index.ts +0 -9
- package/src/commands/logs-formatter.ts +0 -201
- package/src/commands/logs-reader.ts +0 -171
- package/src/commands/logs.ts +0 -103
- package/src/commands/precheck.ts +0 -86
- package/src/commands/runs.ts +0 -220
- package/src/commands/unlock.ts +0 -96
- package/src/config/defaults.ts +0 -218
- package/src/config/index.ts +0 -22
- package/src/config/loader.ts +0 -143
- package/src/config/merge.ts +0 -106
- package/src/config/merger.ts +0 -147
- package/src/config/path-security.ts +0 -121
- package/src/config/paths.ts +0 -27
- package/src/config/permissions.ts +0 -63
- package/src/config/runtime-types.ts +0 -522
- package/src/config/schema-types.ts +0 -53
- package/src/config/schema.ts +0 -60
- package/src/config/schemas.ts +0 -426
- package/src/config/test-strategy.ts +0 -71
- package/src/config/types.ts +0 -57
- package/src/config/validate.ts +0 -103
- package/src/constitution/generator.ts +0 -158
- package/src/constitution/generators/aider.ts +0 -41
- package/src/constitution/generators/claude.ts +0 -35
- package/src/constitution/generators/cursor.ts +0 -36
- package/src/constitution/generators/opencode.ts +0 -38
- package/src/constitution/generators/types.ts +0 -33
- package/src/constitution/generators/windsurf.ts +0 -36
- package/src/constitution/index.ts +0 -11
- package/src/constitution/loader.ts +0 -121
- package/src/constitution/types.ts +0 -31
- package/src/context/auto-detect.ts +0 -228
- package/src/context/builder.ts +0 -299
- package/src/context/elements.ts +0 -122
- package/src/context/formatter.ts +0 -107
- package/src/context/generator.ts +0 -343
- package/src/context/generators/aider.ts +0 -34
- package/src/context/generators/claude.ts +0 -28
- package/src/context/generators/codex.ts +0 -28
- package/src/context/generators/cursor.ts +0 -28
- package/src/context/generators/gemini.ts +0 -28
- package/src/context/generators/opencode.ts +0 -30
- package/src/context/generators/windsurf.ts +0 -28
- package/src/context/greenfield.ts +0 -114
- package/src/context/index.ts +0 -34
- package/src/context/injector.ts +0 -279
- package/src/context/parent-context.ts +0 -39
- package/src/context/test-scanner.ts +0 -370
- package/src/context/types.ts +0 -98
- package/src/decompose/apply.ts +0 -50
- package/src/decompose/builder.ts +0 -181
- package/src/decompose/index.ts +0 -8
- package/src/decompose/sections/codebase.ts +0 -26
- package/src/decompose/sections/constraints.ts +0 -32
- package/src/decompose/sections/index.ts +0 -4
- package/src/decompose/sections/sibling-stories.ts +0 -25
- package/src/decompose/sections/target-story.ts +0 -31
- package/src/decompose/types.ts +0 -55
- package/src/decompose/validators/complexity.ts +0 -45
- package/src/decompose/validators/coverage.ts +0 -134
- package/src/decompose/validators/dependency.ts +0 -91
- package/src/decompose/validators/index.ts +0 -35
- package/src/decompose/validators/overlap.ts +0 -128
- package/src/errors.ts +0 -67
- package/src/execution/batching.ts +0 -157
- package/src/execution/crash-heartbeat.ts +0 -77
- package/src/execution/crash-recovery.ts +0 -79
- package/src/execution/crash-signals.ts +0 -165
- package/src/execution/crash-writer.ts +0 -154
- package/src/execution/deferred-review.ts +0 -105
- package/src/execution/dry-run.ts +0 -81
- package/src/execution/escalation/escalation.ts +0 -46
- package/src/execution/escalation/index.ts +0 -13
- package/src/execution/escalation/tier-escalation.ts +0 -346
- package/src/execution/escalation/tier-outcome.ts +0 -143
- package/src/execution/executor-types.ts +0 -73
- package/src/execution/helpers.ts +0 -38
- package/src/execution/index.ts +0 -27
- package/src/execution/iteration-runner.ts +0 -160
- package/src/execution/lifecycle/acceptance-loop.ts +0 -309
- package/src/execution/lifecycle/headless-formatter.ts +0 -83
- package/src/execution/lifecycle/index.ts +0 -11
- package/src/execution/lifecycle/parallel-lifecycle.ts +0 -101
- package/src/execution/lifecycle/precheck-runner.ts +0 -140
- package/src/execution/lifecycle/run-cleanup.ts +0 -81
- package/src/execution/lifecycle/run-completion.ts +0 -247
- package/src/execution/lifecycle/run-initialization.ts +0 -187
- package/src/execution/lifecycle/run-regression.ts +0 -305
- package/src/execution/lifecycle/run-setup.ts +0 -240
- package/src/execution/lifecycle/story-size-prompts.ts +0 -123
- package/src/execution/lock.ts +0 -129
- package/src/execution/parallel-coordinator.ts +0 -281
- package/src/execution/parallel-executor-rectification-pass.ts +0 -117
- package/src/execution/parallel-executor-rectify.ts +0 -136
- package/src/execution/parallel-executor.ts +0 -330
- package/src/execution/parallel-worker.ts +0 -149
- package/src/execution/parallel.ts +0 -13
- package/src/execution/pid-registry.ts +0 -275
- package/src/execution/pipeline-result-handler.ts +0 -221
- package/src/execution/progress.ts +0 -27
- package/src/execution/queue-handler.ts +0 -109
- package/src/execution/runner-completion.ts +0 -171
- package/src/execution/runner-execution.ts +0 -243
- package/src/execution/runner-setup.ts +0 -86
- package/src/execution/runner.ts +0 -265
- package/src/execution/sequential-executor.ts +0 -219
- package/src/execution/status-file.ts +0 -264
- package/src/execution/status-writer.ts +0 -181
- package/src/execution/story-context.ts +0 -266
- package/src/execution/story-selector.ts +0 -76
- package/src/execution/test-output-parser.ts +0 -14
- package/src/execution/timeout-handler.ts +0 -100
- package/src/hooks/index.ts +0 -2
- package/src/hooks/runner.ts +0 -280
- package/src/hooks/types.ts +0 -79
- package/src/interaction/chain.ts +0 -170
- package/src/interaction/index.ts +0 -61
- package/src/interaction/init.ts +0 -84
- package/src/interaction/plugins/auto.ts +0 -243
- package/src/interaction/plugins/cli.ts +0 -300
- package/src/interaction/plugins/telegram.ts +0 -384
- package/src/interaction/plugins/webhook.ts +0 -286
- package/src/interaction/state.ts +0 -171
- package/src/interaction/triggers.ts +0 -250
- package/src/interaction/types.ts +0 -170
- package/src/logger/formatters.ts +0 -84
- package/src/logger/index.ts +0 -16
- package/src/logger/logger.ts +0 -296
- package/src/logger/types.ts +0 -48
- package/src/logging/formatter.ts +0 -355
- package/src/logging/index.ts +0 -22
- package/src/logging/types.ts +0 -93
- package/src/metrics/aggregator.ts +0 -191
- package/src/metrics/index.ts +0 -14
- package/src/metrics/tracker.ts +0 -200
- package/src/metrics/types.ts +0 -115
- package/src/optimizer/index.ts +0 -63
- package/src/optimizer/noop.optimizer.ts +0 -24
- package/src/optimizer/rule-based.optimizer.ts +0 -248
- package/src/optimizer/types.ts +0 -53
- package/src/pipeline/event-bus.ts +0 -297
- package/src/pipeline/events.ts +0 -130
- package/src/pipeline/index.ts +0 -19
- package/src/pipeline/runner.ts +0 -149
- package/src/pipeline/stages/acceptance-setup.ts +0 -144
- package/src/pipeline/stages/acceptance.ts +0 -215
- package/src/pipeline/stages/autofix.ts +0 -262
- package/src/pipeline/stages/completion.ts +0 -110
- package/src/pipeline/stages/constitution.ts +0 -63
- package/src/pipeline/stages/context.ts +0 -122
- package/src/pipeline/stages/execution.ts +0 -359
- package/src/pipeline/stages/index.ts +0 -86
- package/src/pipeline/stages/optimizer.ts +0 -74
- package/src/pipeline/stages/prompt.ts +0 -79
- package/src/pipeline/stages/queue-check.ts +0 -103
- package/src/pipeline/stages/rectify.ts +0 -101
- package/src/pipeline/stages/regression.ts +0 -99
- package/src/pipeline/stages/review.ts +0 -94
- package/src/pipeline/stages/routing.ts +0 -276
- package/src/pipeline/stages/verify.ts +0 -286
- package/src/pipeline/subscribers/events-writer.ts +0 -135
- package/src/pipeline/subscribers/hooks.ts +0 -179
- package/src/pipeline/subscribers/interaction.ts +0 -103
- package/src/pipeline/subscribers/registry.ts +0 -73
- package/src/pipeline/subscribers/reporters.ts +0 -174
- package/src/pipeline/types.ts +0 -220
- package/src/plugins/extensions.ts +0 -225
- package/src/plugins/index.ts +0 -33
- package/src/plugins/loader.ts +0 -352
- package/src/plugins/plugin-logger.ts +0 -41
- package/src/plugins/registry.ts +0 -168
- package/src/plugins/types.ts +0 -206
- package/src/plugins/validator.ts +0 -352
- package/src/prd/index.ts +0 -220
- package/src/prd/schema.ts +0 -268
- package/src/prd/types.ts +0 -273
- package/src/prd/validate.ts +0 -41
- package/src/precheck/checks-agents.ts +0 -63
- package/src/precheck/checks-blockers.ts +0 -23
- package/src/precheck/checks-cli.ts +0 -68
- package/src/precheck/checks-config.ts +0 -102
- package/src/precheck/checks-git.ts +0 -117
- package/src/precheck/checks-system.ts +0 -101
- package/src/precheck/checks-warnings.ts +0 -221
- package/src/precheck/checks.ts +0 -36
- package/src/precheck/index.ts +0 -374
- package/src/precheck/story-size-gate.ts +0 -144
- package/src/precheck/types.ts +0 -31
- package/src/prompts/builder.ts +0 -166
- package/src/prompts/index.ts +0 -2
- package/src/prompts/loader.ts +0 -43
- package/src/prompts/sections/conventions.ts +0 -19
- package/src/prompts/sections/hermetic.ts +0 -41
- package/src/prompts/sections/index.ts +0 -12
- package/src/prompts/sections/isolation.ts +0 -70
- package/src/prompts/sections/role-task.ts +0 -182
- package/src/prompts/sections/story.ts +0 -55
- package/src/prompts/sections/verdict.ts +0 -70
- package/src/prompts/types.ts +0 -21
- package/src/queue/index.ts +0 -2
- package/src/queue/manager.ts +0 -254
- package/src/queue/types.ts +0 -54
- package/src/review/index.ts +0 -8
- package/src/review/orchestrator.ts +0 -154
- package/src/review/runner.ts +0 -303
- package/src/review/types.ts +0 -70
- package/src/routing/batch-route.ts +0 -35
- package/src/routing/builder.ts +0 -81
- package/src/routing/chain.ts +0 -75
- package/src/routing/content-hash.ts +0 -25
- package/src/routing/index.ts +0 -20
- package/src/routing/loader.ts +0 -62
- package/src/routing/router.ts +0 -305
- package/src/routing/strategies/adaptive.ts +0 -215
- package/src/routing/strategies/index.ts +0 -8
- package/src/routing/strategies/keyword.ts +0 -180
- package/src/routing/strategies/llm-prompts.ts +0 -224
- package/src/routing/strategies/llm.ts +0 -320
- package/src/routing/strategies/manual.ts +0 -50
- package/src/routing/strategy.ts +0 -102
- package/src/tdd/cleanup.ts +0 -120
- package/src/tdd/index.ts +0 -22
- package/src/tdd/isolation.ts +0 -117
- package/src/tdd/orchestrator.ts +0 -406
- package/src/tdd/prompts.ts +0 -40
- package/src/tdd/rectification-gate.ts +0 -274
- package/src/tdd/session-runner.ts +0 -263
- package/src/tdd/types.ts +0 -84
- package/src/tdd/verdict-reader.ts +0 -266
- package/src/tdd/verdict.ts +0 -152
- package/src/tui/App.tsx +0 -265
- package/src/tui/components/AgentPanel.tsx +0 -75
- package/src/tui/components/CostOverlay.tsx +0 -118
- package/src/tui/components/HelpOverlay.tsx +0 -107
- package/src/tui/components/StatusBar.tsx +0 -63
- package/src/tui/components/StoriesPanel.tsx +0 -177
- package/src/tui/hooks/useKeyboard.ts +0 -142
- package/src/tui/hooks/useLayout.ts +0 -137
- package/src/tui/hooks/usePipelineEvents.ts +0 -183
- package/src/tui/hooks/usePty.ts +0 -189
- package/src/tui/index.tsx +0 -38
- package/src/tui/types.ts +0 -76
- package/src/utils/errors.ts +0 -12
- package/src/utils/git.ts +0 -245
- package/src/utils/json-file.ts +0 -72
- package/src/utils/log-test-output.ts +0 -25
- package/src/utils/path-security.ts +0 -73
- package/src/utils/queue-writer.ts +0 -54
- package/src/verification/crash-detector.ts +0 -34
- package/src/verification/executor.ts +0 -250
- package/src/verification/index.ts +0 -12
- package/src/verification/orchestrator-types.ts +0 -154
- package/src/verification/orchestrator.ts +0 -76
- package/src/verification/parser.ts +0 -220
- package/src/verification/rectification-loop.ts +0 -172
- package/src/verification/rectification.ts +0 -108
- package/src/verification/runners.ts +0 -129
- package/src/verification/smart-runner.ts +0 -307
- package/src/verification/strategies/acceptance.ts +0 -136
- package/src/verification/strategies/regression.ts +0 -90
- package/src/verification/strategies/scoped.ts +0 -154
- package/src/verification/types.ts +0 -117
- package/src/version.ts +0 -40
- package/src/worktree/dispatcher.ts +0 -6
- package/src/worktree/index.ts +0 -2
- package/src/worktree/manager.ts +0 -193
- package/src/worktree/merge.ts +0 -302
- package/src/worktree/types.ts +0 -4
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test Execution Core
|
|
3
|
-
*
|
|
4
|
-
* Unified test execution logic with timeout handling and process cleanup.
|
|
5
|
-
* Extracted from execution/verification.ts to eliminate duplication.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { Subprocess } from "bun";
|
|
9
|
-
import { errorMessage } from "../utils/errors";
|
|
10
|
-
import type { TestExecutionResult } from "./types";
|
|
11
|
-
|
|
12
|
-
/** Injectable deps for testability — mock _executorDeps.spawn instead of global Bun.spawn */
|
|
13
|
-
export const _executorDeps = { spawn: Bun.spawn as typeof Bun.spawn };
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Drain stdout+stderr from a killed Bun subprocess with a hard deadline.
|
|
17
|
-
*
|
|
18
|
-
* Bun doesn't close piped streams when a child process is killed (unlike Node).
|
|
19
|
-
* `await new Response(proc.stdout).text()` hangs forever. This races the read
|
|
20
|
-
* against a timeout so we get whatever output was buffered without blocking.
|
|
21
|
-
*/
|
|
22
|
-
async function drainWithDeadline(proc: Subprocess, deadlineMs: number): Promise<string> {
|
|
23
|
-
const EMPTY = Symbol("timeout");
|
|
24
|
-
const race = <T>(p: Promise<T>) => {
|
|
25
|
-
// BUG-039: Store timer handle so it can be cleared after race resolves (prevent leak)
|
|
26
|
-
let timerId: ReturnType<typeof setTimeout>;
|
|
27
|
-
const timeoutPromise = new Promise<typeof EMPTY>((r) => {
|
|
28
|
-
timerId = setTimeout(() => r(EMPTY), deadlineMs);
|
|
29
|
-
});
|
|
30
|
-
return Promise.race([p, timeoutPromise]).finally(() => clearTimeout(timerId));
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
let out = "";
|
|
34
|
-
try {
|
|
35
|
-
const stdout = race(new Response(proc.stdout as ReadableStream).text());
|
|
36
|
-
const stderr = race(new Response(proc.stderr as ReadableStream).text());
|
|
37
|
-
const [o, e] = await Promise.all([stdout, stderr]);
|
|
38
|
-
if (o !== EMPTY) out += o;
|
|
39
|
-
if (e !== EMPTY) out += (out ? "\n" : "") + e;
|
|
40
|
-
} catch (error) {
|
|
41
|
-
// Expected: streams destroyed after kill (e.g. TypeError from closed ReadableStream)
|
|
42
|
-
const isExpectedStreamError =
|
|
43
|
-
error instanceof TypeError ||
|
|
44
|
-
(error instanceof Error && /abort|cancel|close|destroy|locked/i.test(error.message));
|
|
45
|
-
if (!isExpectedStreamError) {
|
|
46
|
-
const { getSafeLogger } = await import("../logger");
|
|
47
|
-
getSafeLogger()?.debug("executor", "Unexpected error draining process output", {
|
|
48
|
-
error: errorMessage(error),
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return out;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Normalize environment variables for verification subprocess.
|
|
57
|
-
*
|
|
58
|
-
* Force standard output mode during orchestrator-controlled test runs by
|
|
59
|
-
* unsetting AI-optimized env vars (CLAUDECODE, REPL_ID, AGENT).
|
|
60
|
-
*/
|
|
61
|
-
const DEFAULT_STRIP_ENV_VARS = ["CLAUDECODE", "REPL_ID", "AGENT"];
|
|
62
|
-
|
|
63
|
-
export function normalizeEnvironment(
|
|
64
|
-
env: Record<string, string | undefined>,
|
|
65
|
-
stripVars?: string[],
|
|
66
|
-
): Record<string, string | undefined> {
|
|
67
|
-
const normalized = { ...env };
|
|
68
|
-
const varsToStrip = stripVars ?? DEFAULT_STRIP_ENV_VARS;
|
|
69
|
-
|
|
70
|
-
for (const varName of varsToStrip) {
|
|
71
|
-
delete normalized[varName];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return normalized;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Execute test command with hard timeout and process cleanup.
|
|
79
|
-
*
|
|
80
|
-
* Prevents zombie processes by sending SIGTERM, waiting for grace period,
|
|
81
|
-
* then SIGKILL to entire process group.
|
|
82
|
-
*/
|
|
83
|
-
export async function executeWithTimeout(
|
|
84
|
-
command: string,
|
|
85
|
-
timeoutSeconds: number,
|
|
86
|
-
env?: Record<string, string | undefined>,
|
|
87
|
-
options?: {
|
|
88
|
-
shell?: string;
|
|
89
|
-
gracePeriodMs?: number;
|
|
90
|
-
drainTimeoutMs?: number;
|
|
91
|
-
cwd?: string;
|
|
92
|
-
},
|
|
93
|
-
): Promise<TestExecutionResult> {
|
|
94
|
-
const shell = options?.shell ?? "/bin/sh";
|
|
95
|
-
const gracePeriodMs = options?.gracePeriodMs ?? 5000;
|
|
96
|
-
const drainTimeoutMs = options?.drainTimeoutMs ?? 2000;
|
|
97
|
-
|
|
98
|
-
const proc = _executorDeps.spawn([shell, "-c", command], {
|
|
99
|
-
stdout: "pipe",
|
|
100
|
-
stderr: "pipe",
|
|
101
|
-
env: env || normalizeEnvironment(process.env as Record<string, string | undefined>),
|
|
102
|
-
cwd: options?.cwd,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const timeoutMs = timeoutSeconds * 1000;
|
|
106
|
-
|
|
107
|
-
let timedOut = false;
|
|
108
|
-
const timer = { id: undefined as ReturnType<typeof setTimeout> | undefined };
|
|
109
|
-
|
|
110
|
-
const timeoutPromise = new Promise<void>((resolve) => {
|
|
111
|
-
timer.id = setTimeout(() => {
|
|
112
|
-
timedOut = true;
|
|
113
|
-
resolve();
|
|
114
|
-
}, timeoutMs);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const processPromise = proc.exited;
|
|
118
|
-
|
|
119
|
-
const raceResult = await Promise.race([processPromise, timeoutPromise]);
|
|
120
|
-
clearTimeout(timer.id);
|
|
121
|
-
|
|
122
|
-
if (timedOut) {
|
|
123
|
-
const pid = proc.pid;
|
|
124
|
-
|
|
125
|
-
// Send SIGTERM to process group (negative PID) to kill children too
|
|
126
|
-
try {
|
|
127
|
-
process.kill(-pid, "SIGTERM");
|
|
128
|
-
} catch (error) {
|
|
129
|
-
// Fallback: kill direct process if process group kill fails
|
|
130
|
-
try {
|
|
131
|
-
proc.kill("SIGTERM");
|
|
132
|
-
} catch (fallbackError) {
|
|
133
|
-
// Process may have already exited
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Wait for graceful shutdown
|
|
138
|
-
await Bun.sleep(gracePeriodMs);
|
|
139
|
-
|
|
140
|
-
// Force SIGKILL entire process group if still running
|
|
141
|
-
try {
|
|
142
|
-
process.kill(-pid, "SIGKILL");
|
|
143
|
-
} catch (error) {
|
|
144
|
-
try {
|
|
145
|
-
proc.kill("SIGKILL");
|
|
146
|
-
} catch (fallbackError) {
|
|
147
|
-
// Process may have already exited
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Bun bug workaround: piped streams don't close after kill
|
|
152
|
-
const partialOutput = await drainWithDeadline(proc, drainTimeoutMs);
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
success: false,
|
|
156
|
-
timeout: true,
|
|
157
|
-
killed: true,
|
|
158
|
-
childProcessesKilled: true,
|
|
159
|
-
output: partialOutput || undefined,
|
|
160
|
-
error: `EXECUTION_TIMEOUT: Verification process exceeded ${timeoutSeconds}s. Process group (PID ${pid}) killed.`,
|
|
161
|
-
countsTowardEscalation: false, // Timeout is environmental, not code failure
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const exitCode = raceResult as number;
|
|
166
|
-
const stdout = await new Response(proc.stdout).text();
|
|
167
|
-
const stderr = await new Response(proc.stderr).text();
|
|
168
|
-
const output = `${stdout}\n${stderr}`;
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
success: exitCode === 0,
|
|
172
|
-
timeout: false,
|
|
173
|
-
exitCode,
|
|
174
|
-
output,
|
|
175
|
-
countsTowardEscalation: true,
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Check if a test command already includes --detectOpenHandles.
|
|
181
|
-
*/
|
|
182
|
-
function detectOpenHandlesFlag(command: string): boolean {
|
|
183
|
-
return command.includes("--detectOpenHandles");
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Append --detectOpenHandles to a test command for diagnostic retry.
|
|
188
|
-
*/
|
|
189
|
-
export function appendOpenHandlesFlag(command: string): string {
|
|
190
|
-
if (detectOpenHandlesFlag(command)) return command;
|
|
191
|
-
return appendFlag(command, "--detectOpenHandles");
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Check if a test command already includes --forceExit.
|
|
196
|
-
*/
|
|
197
|
-
function forceExitFlag(command: string): boolean {
|
|
198
|
-
return command.includes("--forceExit");
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Append --forceExit to a test command to force process exit after tests.
|
|
203
|
-
*/
|
|
204
|
-
export function appendForceExitFlag(command: string): string {
|
|
205
|
-
if (forceExitFlag(command)) return command;
|
|
206
|
-
return appendFlag(command, "--forceExit");
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Append a flag to a command, inserting before any pipe/redirect.
|
|
211
|
-
*/
|
|
212
|
-
function appendFlag(command: string, flag: string): string {
|
|
213
|
-
const pipeIndex = command.search(/[|>]/);
|
|
214
|
-
if (pipeIndex > 0) {
|
|
215
|
-
return `${command.slice(0, pipeIndex).trimEnd()} ${flag} ${command.slice(pipeIndex)}`;
|
|
216
|
-
}
|
|
217
|
-
return `${command} ${flag}`;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Build the final test command based on quality config and retry state.
|
|
222
|
-
*/
|
|
223
|
-
export function buildTestCommand(
|
|
224
|
-
baseCommand: string,
|
|
225
|
-
options: {
|
|
226
|
-
forceExit?: boolean;
|
|
227
|
-
detectOpenHandles?: boolean;
|
|
228
|
-
detectOpenHandlesRetries?: number;
|
|
229
|
-
timeoutRetryCount?: number;
|
|
230
|
-
},
|
|
231
|
-
): string {
|
|
232
|
-
let command = baseCommand;
|
|
233
|
-
|
|
234
|
-
const { forceExit = false, detectOpenHandles = true, detectOpenHandlesRetries = 1, timeoutRetryCount = 0 } = options;
|
|
235
|
-
|
|
236
|
-
// If we've exhausted detectOpenHandles retries, force exit as last resort
|
|
237
|
-
const exhaustedDiagnosticRetries = timeoutRetryCount > detectOpenHandlesRetries;
|
|
238
|
-
|
|
239
|
-
// Apply --forceExit if configured or if diagnostic retries exhausted
|
|
240
|
-
if (forceExit || exhaustedDiagnosticRetries) {
|
|
241
|
-
command = appendForceExitFlag(command);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Apply --detectOpenHandles on timeout retries (within cap)
|
|
245
|
-
if (detectOpenHandles && timeoutRetryCount > 0 && timeoutRetryCount <= detectOpenHandlesRetries) {
|
|
246
|
-
command = appendOpenHandlesFlag(command);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return command;
|
|
250
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unified Verification Layer
|
|
3
|
-
*
|
|
4
|
-
* Central module for test execution, parsing, and verification gates.
|
|
5
|
-
* Eliminates duplication across execution/, tdd/, and pipeline/stages/.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export * from "./types";
|
|
9
|
-
export * from "./executor";
|
|
10
|
-
export * from "./parser";
|
|
11
|
-
export * from "./runners";
|
|
12
|
-
export * from "./rectification";
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
// RE-ARCH: keep
|
|
2
|
-
/**
|
|
3
|
-
* Unified Verification Orchestrator Types (ADR-005, Phase 1)
|
|
4
|
-
*
|
|
5
|
-
* Defines the shared types used by the VerificationOrchestrator and all
|
|
6
|
-
* verification strategies. Coexists with existing types.ts — these types
|
|
7
|
-
* are additive and do not replace existing interfaces until Phase 3.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { NaxConfig } from "../config";
|
|
11
|
-
import type { SmartTestRunnerConfig } from "../config/types";
|
|
12
|
-
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Strategy enum
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
export type VerifyStrategy = "scoped" | "regression" | "deferred-regression" | "acceptance";
|
|
18
|
-
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
// Input context
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
|
|
23
|
-
export interface VerifyContext {
|
|
24
|
-
workdir: string;
|
|
25
|
-
testCommand: string;
|
|
26
|
-
/** Scoped test command template with {{files}} placeholder — overrides buildSmartTestCommand heuristic */
|
|
27
|
-
testScopedTemplate?: string;
|
|
28
|
-
timeoutSeconds: number;
|
|
29
|
-
storyId: string;
|
|
30
|
-
storyGitRef?: string;
|
|
31
|
-
smartRunnerConfig?: SmartTestRunnerConfig;
|
|
32
|
-
regressionMode?: string;
|
|
33
|
-
acceptOnTimeout?: boolean;
|
|
34
|
-
acceptanceTestPath?: string;
|
|
35
|
-
config?: NaxConfig;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
// Structured failure
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
|
|
42
|
-
export interface StructuredTestFailure {
|
|
43
|
-
file: string;
|
|
44
|
-
testName: string;
|
|
45
|
-
error: string;
|
|
46
|
-
stackTrace: string[];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
// Result
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
|
|
53
|
-
export type VerifyStatus =
|
|
54
|
-
| "PASS"
|
|
55
|
-
| "TEST_FAILURE"
|
|
56
|
-
| "TIMEOUT"
|
|
57
|
-
| "BUILD_ERROR"
|
|
58
|
-
| "SKIPPED"
|
|
59
|
-
| "ASSET_CHECK_FAILED"
|
|
60
|
-
| "RUNTIME_CRASH";
|
|
61
|
-
|
|
62
|
-
export interface VerifyResult {
|
|
63
|
-
success: boolean;
|
|
64
|
-
status: VerifyStatus;
|
|
65
|
-
storyId: string;
|
|
66
|
-
strategy: VerifyStrategy;
|
|
67
|
-
passCount: number;
|
|
68
|
-
failCount: number;
|
|
69
|
-
totalCount: number;
|
|
70
|
-
failures: StructuredTestFailure[];
|
|
71
|
-
rawOutput?: string;
|
|
72
|
-
durationMs: number;
|
|
73
|
-
countsTowardEscalation: boolean;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// ---------------------------------------------------------------------------
|
|
77
|
-
// Strategy interface
|
|
78
|
-
// ---------------------------------------------------------------------------
|
|
79
|
-
|
|
80
|
-
export interface IVerificationStrategy {
|
|
81
|
-
readonly name: VerifyStrategy;
|
|
82
|
-
execute(ctx: VerifyContext): Promise<VerifyResult>;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ---------------------------------------------------------------------------
|
|
86
|
-
// Helpers
|
|
87
|
-
// ---------------------------------------------------------------------------
|
|
88
|
-
|
|
89
|
-
export function makeSkippedResult(storyId: string, strategy: VerifyStrategy): VerifyResult {
|
|
90
|
-
return {
|
|
91
|
-
success: true,
|
|
92
|
-
status: "SKIPPED",
|
|
93
|
-
storyId,
|
|
94
|
-
strategy,
|
|
95
|
-
passCount: 0,
|
|
96
|
-
failCount: 0,
|
|
97
|
-
totalCount: 0,
|
|
98
|
-
failures: [],
|
|
99
|
-
durationMs: 0,
|
|
100
|
-
countsTowardEscalation: false,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function makeFailResult(
|
|
105
|
-
storyId: string,
|
|
106
|
-
strategy: VerifyStrategy,
|
|
107
|
-
status: VerifyStatus,
|
|
108
|
-
opts: {
|
|
109
|
-
rawOutput?: string;
|
|
110
|
-
failures?: StructuredTestFailure[];
|
|
111
|
-
passCount?: number;
|
|
112
|
-
failCount?: number;
|
|
113
|
-
durationMs?: number;
|
|
114
|
-
countsTowardEscalation?: boolean;
|
|
115
|
-
} = {},
|
|
116
|
-
): VerifyResult {
|
|
117
|
-
return {
|
|
118
|
-
success: false,
|
|
119
|
-
status,
|
|
120
|
-
storyId,
|
|
121
|
-
strategy,
|
|
122
|
-
passCount: opts.passCount ?? 0,
|
|
123
|
-
failCount: opts.failCount ?? 0,
|
|
124
|
-
totalCount: (opts.passCount ?? 0) + (opts.failCount ?? 0),
|
|
125
|
-
failures: opts.failures ?? [],
|
|
126
|
-
rawOutput: opts.rawOutput,
|
|
127
|
-
durationMs: opts.durationMs ?? 0,
|
|
128
|
-
countsTowardEscalation: opts.countsTowardEscalation ?? true,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function makePassResult(
|
|
133
|
-
storyId: string,
|
|
134
|
-
strategy: VerifyStrategy,
|
|
135
|
-
opts: {
|
|
136
|
-
rawOutput?: string;
|
|
137
|
-
passCount?: number;
|
|
138
|
-
durationMs?: number;
|
|
139
|
-
} = {},
|
|
140
|
-
): VerifyResult {
|
|
141
|
-
return {
|
|
142
|
-
success: true,
|
|
143
|
-
status: "PASS",
|
|
144
|
-
storyId,
|
|
145
|
-
strategy,
|
|
146
|
-
passCount: opts.passCount ?? 0,
|
|
147
|
-
failCount: 0,
|
|
148
|
-
totalCount: opts.passCount ?? 0,
|
|
149
|
-
failures: [],
|
|
150
|
-
rawOutput: opts.rawOutput,
|
|
151
|
-
durationMs: opts.durationMs ?? 0,
|
|
152
|
-
countsTowardEscalation: false,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// RE-ARCH: keep
|
|
2
|
-
/**
|
|
3
|
-
* Verification Orchestrator (ADR-005, Phase 1)
|
|
4
|
-
*
|
|
5
|
-
* Single entry point for all test verification. Selects and delegates to the
|
|
6
|
-
* appropriate strategy based on context. Additive — existing code paths are
|
|
7
|
-
* unchanged until Phase 3.
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* const result = await verificationOrchestrator.verify(ctx, "scoped");
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { IVerificationStrategy, VerifyContext, VerifyResult, VerifyStrategy } from "./orchestrator-types";
|
|
14
|
-
import { makeSkippedResult } from "./orchestrator-types";
|
|
15
|
-
import { AcceptanceStrategy } from "./strategies/acceptance";
|
|
16
|
-
import { DeferredRegressionStrategy, RegressionStrategy } from "./strategies/regression";
|
|
17
|
-
import { ScopedStrategy } from "./strategies/scoped";
|
|
18
|
-
|
|
19
|
-
export class VerificationOrchestrator {
|
|
20
|
-
private readonly strategies: Map<VerifyStrategy, IVerificationStrategy>;
|
|
21
|
-
|
|
22
|
-
constructor(overrides?: Partial<Record<VerifyStrategy, IVerificationStrategy>>) {
|
|
23
|
-
this.strategies = new Map([
|
|
24
|
-
["scoped", overrides?.scoped ?? new ScopedStrategy()],
|
|
25
|
-
["regression", overrides?.regression ?? new RegressionStrategy()],
|
|
26
|
-
["deferred-regression", overrides?.["deferred-regression"] ?? new DeferredRegressionStrategy()],
|
|
27
|
-
["acceptance", overrides?.acceptance ?? new AcceptanceStrategy()],
|
|
28
|
-
]);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Run verification using the specified strategy.
|
|
33
|
-
*
|
|
34
|
-
* @param ctx - Context with workdir, testCommand, storyId, etc.
|
|
35
|
-
* @param strategy - Which strategy to use
|
|
36
|
-
* @returns Unified VerifyResult
|
|
37
|
-
*/
|
|
38
|
-
async verify(ctx: VerifyContext, strategy: VerifyStrategy): Promise<VerifyResult> {
|
|
39
|
-
const impl = this.strategies.get(strategy);
|
|
40
|
-
if (!impl) {
|
|
41
|
-
return makeSkippedResult(ctx.storyId, strategy);
|
|
42
|
-
}
|
|
43
|
-
return impl.execute(ctx);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Run scoped verification (smart-runner selects test files).
|
|
48
|
-
*/
|
|
49
|
-
async verifyScoped(ctx: VerifyContext): Promise<VerifyResult> {
|
|
50
|
-
return this.verify(ctx, "scoped");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Run full-suite regression gate.
|
|
55
|
-
*/
|
|
56
|
-
async verifyRegression(ctx: VerifyContext): Promise<VerifyResult> {
|
|
57
|
-
return this.verify(ctx, "regression");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Run deferred regression gate (same as regression — caller decides timing).
|
|
62
|
-
*/
|
|
63
|
-
async verifyDeferredRegression(ctx: VerifyContext): Promise<VerifyResult> {
|
|
64
|
-
return this.verify(ctx, "deferred-regression");
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Run acceptance tests from feature directory.
|
|
69
|
-
*/
|
|
70
|
-
async verifyAcceptance(ctx: VerifyContext): Promise<VerifyResult> {
|
|
71
|
-
return this.verify(ctx, "acceptance");
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Singleton instance for production use. */
|
|
76
|
-
export const verificationOrchestrator = new VerificationOrchestrator();
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test Output Parsing
|
|
3
|
-
*
|
|
4
|
-
* Unified test output parsing logic for Bun test framework.
|
|
5
|
-
* Extracted from execution/test-output-parser.ts and execution/verification.ts.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { TestFailure, TestOutputAnalysis, TestSummary } from "./types";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Parse Bun test output into structured failure objects.
|
|
12
|
-
*
|
|
13
|
-
* Example output format:
|
|
14
|
-
* ```
|
|
15
|
-
* bun test v1.0.0
|
|
16
|
-
*
|
|
17
|
-
* test/example.test.ts:
|
|
18
|
-
* ✓ passing test [0.5ms]
|
|
19
|
-
* ✗ failing test [1.2ms]
|
|
20
|
-
*
|
|
21
|
-
* (fail) describe block > nested block > test name [1.2ms]
|
|
22
|
-
* Error: Expected 1 to equal 2
|
|
23
|
-
* at /path/to/file.ts:10:15
|
|
24
|
-
* at Object.test (/path/to/file.ts:8:3)
|
|
25
|
-
* ```
|
|
26
|
-
*/
|
|
27
|
-
export function parseBunTestOutput(output: string): TestSummary {
|
|
28
|
-
const lines = output.split("\n");
|
|
29
|
-
const failures: TestFailure[] = [];
|
|
30
|
-
let passed = 0;
|
|
31
|
-
let failed = 0;
|
|
32
|
-
let currentFile = "";
|
|
33
|
-
let i = 0;
|
|
34
|
-
|
|
35
|
-
while (i < lines.length) {
|
|
36
|
-
const line = lines[i];
|
|
37
|
-
|
|
38
|
-
// Extract file path from headers like "test/example.test.ts:"
|
|
39
|
-
if (line.trim().endsWith(".test.ts:") || line.trim().endsWith(".test.js:")) {
|
|
40
|
-
currentFile = line.trim().replace(/:$/, "");
|
|
41
|
-
i++;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Count passed tests (✓ or ✔)
|
|
46
|
-
if (line.includes("✓") || line.includes("✔")) {
|
|
47
|
-
passed++;
|
|
48
|
-
i++;
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Count failed tests (✗ or ✘)
|
|
53
|
-
if (line.includes("✗") || line.includes("✘")) {
|
|
54
|
-
failed++;
|
|
55
|
-
i++;
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Parse failure line: "(fail) TestName > nested > name [duration]"
|
|
60
|
-
const failMatch = line.match(/^\(fail\)\s+(.+?)\s+\[[\d.]+m?s\]/);
|
|
61
|
-
if (failMatch) {
|
|
62
|
-
const testName = failMatch[1].trim();
|
|
63
|
-
i++;
|
|
64
|
-
|
|
65
|
-
// Extract error message and stack trace
|
|
66
|
-
let error = "";
|
|
67
|
-
const stackTrace: string[] = [];
|
|
68
|
-
let stackLineCount = 0;
|
|
69
|
-
|
|
70
|
-
// Read lines until we hit a blank line or another test result
|
|
71
|
-
while (i < lines.length && stackLineCount < 5) {
|
|
72
|
-
const nextLine = lines[i];
|
|
73
|
-
|
|
74
|
-
// Stop at blank line or next test result
|
|
75
|
-
if (!nextLine.trim() || nextLine.includes("(fail)") || nextLine.includes("✓") || nextLine.includes("✗")) {
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// First non-blank line is typically the error message
|
|
80
|
-
if (!error && nextLine.trim()) {
|
|
81
|
-
error = nextLine.trim();
|
|
82
|
-
i++;
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Subsequent lines starting with "at" are stack trace
|
|
87
|
-
if (nextLine.trim().startsWith("at ")) {
|
|
88
|
-
stackTrace.push(nextLine.trim());
|
|
89
|
-
stackLineCount++;
|
|
90
|
-
}
|
|
91
|
-
i++;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
failures.push({
|
|
95
|
-
file: currentFile || "unknown",
|
|
96
|
-
testName,
|
|
97
|
-
error: error || "Unknown error",
|
|
98
|
-
stackTrace,
|
|
99
|
-
});
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
i++;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return { passed, failed, failures };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Format failure summary for agent feedback.
|
|
111
|
-
*
|
|
112
|
-
* Format:
|
|
113
|
-
* ```
|
|
114
|
-
* 1. file.test.ts > TestName > nested
|
|
115
|
-
* Error: message
|
|
116
|
-
* at file.ts:10:15
|
|
117
|
-
*
|
|
118
|
-
* 2. another.test.ts > OtherTest
|
|
119
|
-
* Error: another error
|
|
120
|
-
* at other.ts:20:10
|
|
121
|
-
* ```
|
|
122
|
-
*/
|
|
123
|
-
export function formatFailureSummary(failures: TestFailure[], maxChars = 2000): string {
|
|
124
|
-
if (failures.length === 0) {
|
|
125
|
-
return "No test failures";
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const lines: string[] = [];
|
|
129
|
-
let totalChars = 0;
|
|
130
|
-
|
|
131
|
-
for (let i = 0; i < failures.length; i++) {
|
|
132
|
-
const failure = failures[i];
|
|
133
|
-
const num = i + 1;
|
|
134
|
-
|
|
135
|
-
// Format: "1. file.test.ts > TestName"
|
|
136
|
-
const header = `${num}. ${failure.file} > ${failure.testName}`;
|
|
137
|
-
const errorLine = ` Error: ${failure.error}`;
|
|
138
|
-
|
|
139
|
-
// Add first stack trace line if available
|
|
140
|
-
const stackLine = failure.stackTrace.length > 0 ? ` ${failure.stackTrace[0]}` : "";
|
|
141
|
-
|
|
142
|
-
const blockLines = [header, errorLine];
|
|
143
|
-
if (stackLine) {
|
|
144
|
-
blockLines.push(stackLine);
|
|
145
|
-
}
|
|
146
|
-
blockLines.push(""); // blank line separator
|
|
147
|
-
|
|
148
|
-
const block = blockLines.join("\n");
|
|
149
|
-
const blockLength = block.length;
|
|
150
|
-
|
|
151
|
-
// Check if adding this block would exceed maxChars
|
|
152
|
-
if (totalChars + blockLength > maxChars && lines.length > 0) {
|
|
153
|
-
const remaining = failures.length - i;
|
|
154
|
-
lines.push(`\n... and ${remaining} more failure(s) (truncated)`);
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
lines.push(...blockLines);
|
|
159
|
-
totalChars += blockLength;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return lines.join("\n").trim();
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Parse test output to detect environmental failures.
|
|
167
|
-
*
|
|
168
|
-
* When exit code != 0 but all tests pass, classifies as ENVIRONMENTAL_FAILURE
|
|
169
|
-
* instead of TEST_FAILURE.
|
|
170
|
-
*/
|
|
171
|
-
export function parseTestOutput(output: string, exitCode: number): TestOutputAnalysis {
|
|
172
|
-
// Regex patterns for different test frameworks
|
|
173
|
-
const patterns = [
|
|
174
|
-
/(\d+)\s+pass(?:ed)?(?:,\s+|\s+)(\d+)\s+fail/i, // "5 pass, 0 fail" or "5 passed 0 fail"
|
|
175
|
-
/Tests:\s+(\d+)\s+passed,\s+(\d+)\s+failed/i, // Jest format
|
|
176
|
-
/(\d+)\s+pass/i, // Bun format (just pass count)
|
|
177
|
-
];
|
|
178
|
-
|
|
179
|
-
let passCount = 0;
|
|
180
|
-
let failCount = 0;
|
|
181
|
-
|
|
182
|
-
for (const pattern of patterns) {
|
|
183
|
-
// Match ALL occurrences — use the LAST one (final summary line)
|
|
184
|
-
const matches = Array.from(output.matchAll(new RegExp(pattern, "gi")));
|
|
185
|
-
if (matches.length > 0) {
|
|
186
|
-
const lastMatch = matches[matches.length - 1];
|
|
187
|
-
passCount = Number.parseInt(lastMatch[1], 10);
|
|
188
|
-
// Some formats only show pass count
|
|
189
|
-
failCount = lastMatch[2] ? Number.parseInt(lastMatch[2], 10) : 0;
|
|
190
|
-
break;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Check for explicit fail count if not found
|
|
195
|
-
if (failCount === 0) {
|
|
196
|
-
const failMatches = Array.from(output.matchAll(/(\d+)\s+fail/gi));
|
|
197
|
-
if (failMatches.length > 0) {
|
|
198
|
-
failCount = Number.parseInt(failMatches[failMatches.length - 1][1], 10);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const allTestsPassed = passCount > 0 && failCount === 0;
|
|
203
|
-
const isEnvironmentalFailure = allTestsPassed && exitCode !== 0;
|
|
204
|
-
|
|
205
|
-
const result: TestOutputAnalysis = {
|
|
206
|
-
allTestsPassed,
|
|
207
|
-
passCount,
|
|
208
|
-
failCount,
|
|
209
|
-
isEnvironmentalFailure,
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
if (isEnvironmentalFailure) {
|
|
213
|
-
result.error = `ENVIRONMENTAL_FAILURE: All ${passCount} tests passed but exit code was ${exitCode}. Check linter/typecheck/missing files.`;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return result;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Re-export types for consumers that import from this module
|
|
220
|
-
export type { TestFailure, TestSummary } from "./types";
|