@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.
- package/docs/ROADMAP.md +2 -0
- package/nax/config.json +2 -2
- package/nax/features/nax-compliance/prd.json +52 -0
- package/nax/features/nax-compliance/progress.txt +1 -0
- package/nax/features/v0.19.0-hardening/plan.md +7 -0
- package/nax/features/v0.19.0-hardening/prd.json +84 -0
- package/nax/features/v0.19.0-hardening/progress.txt +7 -0
- package/nax/features/v0.19.0-hardening/spec.md +18 -0
- package/nax/features/v0.19.0-hardening/tasks.md +8 -0
- package/nax/features/verify-v2/prd.json +79 -0
- package/nax/features/verify-v2/progress.txt +3 -0
- package/nax/status.json +27 -0
- package/package.json +2 -2
- package/src/acceptance/fix-generator.ts +6 -2
- package/src/acceptance/generator.ts +3 -1
- package/src/acceptance/types.ts +3 -1
- package/src/agents/claude-plan.ts +6 -5
- package/src/cli/analyze.ts +1 -0
- package/src/cli/init.ts +7 -6
- package/src/config/defaults.ts +3 -1
- package/src/config/schemas.ts +2 -0
- package/src/config/types.ts +6 -0
- package/src/context/injector.ts +18 -18
- package/src/execution/crash-recovery.ts +7 -10
- package/src/execution/lifecycle/acceptance-loop.ts +1 -0
- package/src/execution/lifecycle/index.ts +1 -1
- package/src/execution/lifecycle/precheck-runner.ts +1 -1
- package/src/execution/lifecycle/run-completion.ts +29 -0
- package/src/execution/lifecycle/run-regression.ts +301 -0
- package/src/execution/lifecycle/run-setup.ts +14 -14
- package/src/execution/parallel.ts +1 -1
- package/src/execution/pipeline-result-handler.ts +0 -1
- package/src/execution/post-verify.ts +31 -194
- package/src/execution/runner.ts +2 -19
- package/src/execution/sequential-executor.ts +1 -1
- package/src/hooks/runner.ts +2 -2
- package/src/interaction/plugins/auto.ts +2 -2
- package/src/logger/logger.ts +3 -5
- package/src/pipeline/stages/verify.ts +26 -22
- package/src/plugins/loader.ts +36 -9
- package/src/routing/batch-route.ts +32 -0
- package/src/routing/index.ts +1 -0
- package/src/routing/loader.ts +7 -0
- package/src/utils/path-security.ts +56 -0
- package/src/verification/executor.ts +6 -13
- package/src/verification/smart-runner.ts +52 -0
- package/test/integration/plugins/config-resolution.test.ts +3 -3
- package/test/integration/plugins/loader.test.ts +3 -1
- package/test/integration/precheck-integration.test.ts +18 -11
- package/test/integration/rectification-flow.test.ts +3 -3
- package/test/integration/review-config-commands.test.ts +1 -1
- package/test/integration/security-loader.test.ts +83 -0
- package/test/integration/verify-stage.test.ts +9 -0
- package/test/unit/config/defaults.test.ts +69 -0
- package/test/unit/config/regression-gate-schema.test.ts +159 -0
- package/test/unit/execution/lifecycle/run-completion.test.ts +239 -0
- package/test/unit/execution/lifecycle/run-regression.test.ts +418 -0
- package/test/unit/execution/post-verify-regression.test.ts +31 -84
- package/test/unit/execution/post-verify.test.ts +28 -48
- package/test/unit/formatters.test.ts +2 -3
- package/test/unit/hooks/shell-security.test.ts +40 -0
- package/test/unit/pipeline/stages/verify.test.ts +266 -0
- package/test/unit/pipeline/verify-smart-runner.test.ts +1 -0
- package/test/unit/utils/path-security.test.ts +47 -0
- package/src/execution/lifecycle/run-lifecycle.ts +0 -312
- package/test/unit/run-lifecycle.test.ts +0 -140
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* BUG-026: Regression gate timeout
|
|
2
|
+
* BUG-026: Regression gate timeout acceptance
|
|
3
3
|
*
|
|
4
|
-
* Tests that
|
|
4
|
+
* Tests that runPostAgentVerification:
|
|
5
5
|
* - Returns passed when regression gate TIMES OUT and acceptOnTimeout=true (default)
|
|
6
6
|
* - Returns failed when regression gate TIMES OUT and acceptOnTimeout=false
|
|
7
|
-
* - Returns failed when regression gate returns TEST_FAILURE
|
|
7
|
+
* - Returns failed when regression gate returns TEST_FAILURE
|
|
8
8
|
* - Defaults acceptOnTimeout to true when not set in config
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* With the removal of scoped verification, post-verify now ONLY runs the full-suite regression gate.
|
|
11
|
+
* These behavioral tests call the actual function with mocked dependencies.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
15
|
-
import {
|
|
15
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
import { tmpdir } from "node:os";
|
|
18
18
|
import type { NaxConfig } from "../../../src/config";
|
|
@@ -37,7 +37,7 @@ const mockRunVerification = mock(async (): Promise<VerResult> => {
|
|
|
37
37
|
return resp;
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
const mockRevertStoriesOnFailure = mock(async (
|
|
40
|
+
const mockRevertStoriesOnFailure = mock(async (opts: any) => opts.prd);
|
|
41
41
|
const mockRunRectificationLoop = mock(async () => false);
|
|
42
42
|
|
|
43
43
|
// ---------------------------------------------------------------------------
|
|
@@ -53,47 +53,10 @@ const _origPostVerifyDeps = { ..._postVerifyDeps };
|
|
|
53
53
|
// Fixtures
|
|
54
54
|
// ---------------------------------------------------------------------------
|
|
55
55
|
|
|
56
|
-
/**
|
|
57
|
-
function
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
throw new Error(`git ${args[0]} failed in ${cwd}`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Read stdout from a git command. */
|
|
65
|
-
function gitOutput(args: string[], cwd: string): string {
|
|
66
|
-
const proc = Bun.spawnSync(["git", ...args], { cwd, stdin: "ignore", stdout: "pipe", stderr: "ignore" });
|
|
67
|
-
return new TextDecoder().decode(proc.stdout).trim();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Create a temp git repo with two commits so that `git diff storyGitRef HEAD`
|
|
72
|
-
* returns at least one test file — needed for the regression gate to activate.
|
|
73
|
-
*/
|
|
74
|
-
function makeGitRepo(): { dir: string; storyGitRef: string } {
|
|
75
|
-
const dir = mkdtempSync(join(tmpdir(), "nax-bug026-"));
|
|
76
|
-
|
|
77
|
-
gitSync(["init"], dir);
|
|
78
|
-
gitSync(["config", "user.email", "test@example.com"], dir);
|
|
79
|
-
gitSync(["config", "user.name", "test"], dir);
|
|
80
|
-
|
|
81
|
-
// Initial commit → becomes storyGitRef
|
|
82
|
-
writeFileSync(join(dir, "src.ts"), "export const x = 1;");
|
|
83
|
-
gitSync(["add", "."], dir);
|
|
84
|
-
gitSync(["commit", "-m", "initial"], dir);
|
|
85
|
-
const storyGitRef = gitOutput(["rev-parse", "HEAD"], dir);
|
|
86
|
-
|
|
87
|
-
// Second commit: adds a test file (changed after storyGitRef)
|
|
88
|
-
mkdirSync(join(dir, "test"), { recursive: true });
|
|
89
|
-
writeFileSync(
|
|
90
|
-
join(dir, "test", "example.test.ts"),
|
|
91
|
-
'import { test, expect } from "bun:test";\ntest("x", () => expect(1).toBe(1));',
|
|
92
|
-
);
|
|
93
|
-
gitSync(["add", "."], dir);
|
|
94
|
-
gitSync(["commit", "-m", "add test"], dir);
|
|
95
|
-
|
|
96
|
-
return { dir, storyGitRef };
|
|
56
|
+
/** Create a temp directory for test fixtures. */
|
|
57
|
+
function makeTempDir(): string {
|
|
58
|
+
const dir = mkdtempSync(join(tmpdir(), "nax-post-verify-"));
|
|
59
|
+
return dir;
|
|
97
60
|
}
|
|
98
61
|
|
|
99
62
|
function makeConfig(
|
|
@@ -139,6 +102,7 @@ function makeConfig(
|
|
|
139
102
|
regressionGate: {
|
|
140
103
|
enabled: true,
|
|
141
104
|
timeoutSeconds: 120,
|
|
105
|
+
mode: "per-story",
|
|
142
106
|
...regressionGateOverrides,
|
|
143
107
|
},
|
|
144
108
|
contextProviderTokenBudget: 2000,
|
|
@@ -216,7 +180,6 @@ function makePRD(story: UserStory): PRD {
|
|
|
216
180
|
|
|
217
181
|
function makeOpts(
|
|
218
182
|
workdir: string,
|
|
219
|
-
storyGitRef: string,
|
|
220
183
|
config: NaxConfig,
|
|
221
184
|
story: UserStory,
|
|
222
185
|
prd: PRD,
|
|
@@ -230,7 +193,6 @@ function makeOpts(
|
|
|
230
193
|
storiesToExecute: [story],
|
|
231
194
|
allStoryMetrics: [] as StoryMetrics[],
|
|
232
195
|
timeoutRetryCountMap: new Map<string, number>(),
|
|
233
|
-
storyGitRef,
|
|
234
196
|
};
|
|
235
197
|
}
|
|
236
198
|
|
|
@@ -239,19 +201,14 @@ function makeOpts(
|
|
|
239
201
|
// ---------------------------------------------------------------------------
|
|
240
202
|
|
|
241
203
|
let tempDir: string;
|
|
242
|
-
let storyGitRef: string;
|
|
243
204
|
|
|
244
205
|
beforeEach(() => {
|
|
245
206
|
// Wire _postVerifyDeps to mocks
|
|
246
207
|
_postVerifyDeps.runVerification = mockRunVerification as typeof _postVerifyDeps.runVerification;
|
|
247
|
-
_postVerifyDeps.parseTestOutput = () => ({ passCount: 5, failCount: 0, isEnvironmentalFailure: false }) as any;
|
|
248
|
-
_postVerifyDeps.getEnvironmentalEscalationThreshold = () => 3;
|
|
249
208
|
_postVerifyDeps.revertStoriesOnFailure = mockRevertStoriesOnFailure as typeof _postVerifyDeps.revertStoriesOnFailure;
|
|
250
209
|
_postVerifyDeps.runRectificationLoop = mockRunRectificationLoop as typeof _postVerifyDeps.runRectificationLoop;
|
|
251
210
|
_postVerifyDeps.getExpectedFiles = () => [];
|
|
252
211
|
_postVerifyDeps.savePRD = mock(async () => {}) as typeof _postVerifyDeps.savePRD;
|
|
253
|
-
_postVerifyDeps.appendProgress = mock(async () => {}) as typeof _postVerifyDeps.appendProgress;
|
|
254
|
-
_postVerifyDeps.getTierConfig = () => undefined as any;
|
|
255
212
|
_postVerifyDeps.parseBunTestOutput = () => ({ failed: 0, passed: 5, failures: [] }) as any;
|
|
256
213
|
mockRunVerification.mockClear();
|
|
257
214
|
mockRevertStoriesOnFailure.mockClear();
|
|
@@ -259,9 +216,7 @@ beforeEach(() => {
|
|
|
259
216
|
_verificationResponses = [];
|
|
260
217
|
_verificationCallIndex = 0;
|
|
261
218
|
|
|
262
|
-
|
|
263
|
-
tempDir = repo.dir;
|
|
264
|
-
storyGitRef = repo.storyGitRef;
|
|
219
|
+
tempDir = makeTempDir();
|
|
265
220
|
});
|
|
266
221
|
|
|
267
222
|
afterEach(() => {
|
|
@@ -276,9 +231,8 @@ afterEach(() => {
|
|
|
276
231
|
|
|
277
232
|
describe("BUG-026: regression gate TIMEOUT acceptance", () => {
|
|
278
233
|
test("TIMEOUT + acceptOnTimeout=true → runPostAgentVerification returns passed", async () => {
|
|
279
|
-
//
|
|
234
|
+
// Now only one call: regression gate times out with acceptOnTimeout=true
|
|
280
235
|
_verificationResponses = [
|
|
281
|
-
{ success: true, status: "SUCCESS", countsTowardEscalation: true, output: "pass 5" },
|
|
282
236
|
{ success: false, status: "TIMEOUT", countsTowardEscalation: false },
|
|
283
237
|
];
|
|
284
238
|
|
|
@@ -286,14 +240,13 @@ describe("BUG-026: regression gate TIMEOUT acceptance", () => {
|
|
|
286
240
|
const story = makeStory();
|
|
287
241
|
const prd = makePRD(story);
|
|
288
242
|
|
|
289
|
-
const result = await runPostAgentVerification(makeOpts(tempDir,
|
|
243
|
+
const result = await runPostAgentVerification(makeOpts(tempDir, config, story, prd));
|
|
290
244
|
|
|
291
245
|
expect(result.passed).toBe(true);
|
|
292
246
|
});
|
|
293
247
|
|
|
294
248
|
test("TIMEOUT + acceptOnTimeout=true → revertStoriesOnFailure is NOT called", async () => {
|
|
295
249
|
_verificationResponses = [
|
|
296
|
-
{ success: true, status: "SUCCESS", countsTowardEscalation: true, output: "pass 5" },
|
|
297
250
|
{ success: false, status: "TIMEOUT", countsTowardEscalation: false },
|
|
298
251
|
];
|
|
299
252
|
|
|
@@ -301,14 +254,13 @@ describe("BUG-026: regression gate TIMEOUT acceptance", () => {
|
|
|
301
254
|
const story = makeStory();
|
|
302
255
|
const prd = makePRD(story);
|
|
303
256
|
|
|
304
|
-
await runPostAgentVerification(makeOpts(tempDir,
|
|
257
|
+
await runPostAgentVerification(makeOpts(tempDir, config, story, prd));
|
|
305
258
|
|
|
306
259
|
expect(mockRevertStoriesOnFailure).not.toHaveBeenCalled();
|
|
307
260
|
});
|
|
308
261
|
|
|
309
262
|
test("TIMEOUT + acceptOnTimeout=false → runPostAgentVerification returns failed", async () => {
|
|
310
263
|
_verificationResponses = [
|
|
311
|
-
{ success: true, status: "SUCCESS", countsTowardEscalation: true, output: "pass 5" },
|
|
312
264
|
{ success: false, status: "TIMEOUT", countsTowardEscalation: false },
|
|
313
265
|
];
|
|
314
266
|
|
|
@@ -316,14 +268,13 @@ describe("BUG-026: regression gate TIMEOUT acceptance", () => {
|
|
|
316
268
|
const story = makeStory();
|
|
317
269
|
const prd = makePRD(story);
|
|
318
270
|
|
|
319
|
-
const result = await runPostAgentVerification(makeOpts(tempDir,
|
|
271
|
+
const result = await runPostAgentVerification(makeOpts(tempDir, config, story, prd));
|
|
320
272
|
|
|
321
273
|
expect(result.passed).toBe(false);
|
|
322
274
|
});
|
|
323
275
|
|
|
324
276
|
test("TIMEOUT + acceptOnTimeout=false → revertStoriesOnFailure IS called", async () => {
|
|
325
277
|
_verificationResponses = [
|
|
326
|
-
{ success: true, status: "SUCCESS", countsTowardEscalation: true, output: "pass 5" },
|
|
327
278
|
{ success: false, status: "TIMEOUT", countsTowardEscalation: false },
|
|
328
279
|
];
|
|
329
280
|
|
|
@@ -331,14 +282,13 @@ describe("BUG-026: regression gate TIMEOUT acceptance", () => {
|
|
|
331
282
|
const story = makeStory();
|
|
332
283
|
const prd = makePRD(story);
|
|
333
284
|
|
|
334
|
-
await runPostAgentVerification(makeOpts(tempDir,
|
|
285
|
+
await runPostAgentVerification(makeOpts(tempDir, config, story, prd));
|
|
335
286
|
|
|
336
287
|
expect(mockRevertStoriesOnFailure).toHaveBeenCalledTimes(1);
|
|
337
288
|
});
|
|
338
289
|
|
|
339
290
|
test("TIMEOUT + acceptOnTimeout not set → defaults to true → returns passed", async () => {
|
|
340
291
|
_verificationResponses = [
|
|
341
|
-
{ success: true, status: "SUCCESS", countsTowardEscalation: true, output: "pass 5" },
|
|
342
292
|
{ success: false, status: "TIMEOUT", countsTowardEscalation: false },
|
|
343
293
|
];
|
|
344
294
|
|
|
@@ -347,14 +297,13 @@ describe("BUG-026: regression gate TIMEOUT acceptance", () => {
|
|
|
347
297
|
const story = makeStory();
|
|
348
298
|
const prd = makePRD(story);
|
|
349
299
|
|
|
350
|
-
const result = await runPostAgentVerification(makeOpts(tempDir,
|
|
300
|
+
const result = await runPostAgentVerification(makeOpts(tempDir, config, story, prd));
|
|
351
301
|
|
|
352
302
|
expect(result.passed).toBe(true);
|
|
353
303
|
});
|
|
354
304
|
|
|
355
305
|
test("TEST_FAILURE in regression gate → returns failed regardless of acceptOnTimeout", async () => {
|
|
356
306
|
_verificationResponses = [
|
|
357
|
-
{ success: true, status: "SUCCESS", countsTowardEscalation: true, output: "pass 5" },
|
|
358
307
|
{ success: false, status: "TEST_FAILURE", countsTowardEscalation: true, output: "FAIL 1" },
|
|
359
308
|
];
|
|
360
309
|
|
|
@@ -362,14 +311,13 @@ describe("BUG-026: regression gate TIMEOUT acceptance", () => {
|
|
|
362
311
|
const story = makeStory();
|
|
363
312
|
const prd = makePRD(story);
|
|
364
313
|
|
|
365
|
-
const result = await runPostAgentVerification(makeOpts(tempDir,
|
|
314
|
+
const result = await runPostAgentVerification(makeOpts(tempDir, config, story, prd));
|
|
366
315
|
|
|
367
316
|
expect(result.passed).toBe(false);
|
|
368
317
|
});
|
|
369
318
|
|
|
370
319
|
test("TEST_FAILURE in regression gate → revertStoriesOnFailure IS called", async () => {
|
|
371
320
|
_verificationResponses = [
|
|
372
|
-
{ success: true, status: "SUCCESS", countsTowardEscalation: true, output: "pass 5" },
|
|
373
321
|
{ success: false, status: "TEST_FAILURE", countsTowardEscalation: true, output: "FAIL 1" },
|
|
374
322
|
];
|
|
375
323
|
|
|
@@ -377,39 +325,38 @@ describe("BUG-026: regression gate TIMEOUT acceptance", () => {
|
|
|
377
325
|
const story = makeStory();
|
|
378
326
|
const prd = makePRD(story);
|
|
379
327
|
|
|
380
|
-
await runPostAgentVerification(makeOpts(tempDir,
|
|
328
|
+
await runPostAgentVerification(makeOpts(tempDir, config, story, prd));
|
|
381
329
|
|
|
382
330
|
expect(mockRevertStoriesOnFailure).toHaveBeenCalledTimes(1);
|
|
383
331
|
});
|
|
384
332
|
|
|
385
|
-
test("regression gate
|
|
333
|
+
test("full-suite regression gate passes → returns passed (one call to runVerification)", async () => {
|
|
386
334
|
_verificationResponses = [
|
|
387
335
|
{ success: true, status: "SUCCESS", countsTowardEscalation: true, output: "pass 5" },
|
|
388
|
-
{ success: false, status: "TIMEOUT", countsTowardEscalation: false },
|
|
389
336
|
];
|
|
390
337
|
|
|
391
338
|
const config = makeConfig({ acceptOnTimeout: true });
|
|
392
339
|
const story = makeStory();
|
|
393
340
|
const prd = makePRD(story);
|
|
394
341
|
|
|
395
|
-
await runPostAgentVerification(makeOpts(tempDir,
|
|
342
|
+
const result = await runPostAgentVerification(makeOpts(tempDir, config, story, prd));
|
|
396
343
|
|
|
397
|
-
//
|
|
398
|
-
expect(
|
|
344
|
+
// Post-verify now ONLY runs the full-suite regression gate (no scoped verification)
|
|
345
|
+
expect(result.passed).toBe(true);
|
|
346
|
+
expect(mockRunVerification).toHaveBeenCalledTimes(1);
|
|
399
347
|
});
|
|
400
348
|
|
|
401
|
-
test("regression gate disabled →
|
|
402
|
-
_verificationResponses = [
|
|
403
|
-
{ success: true, status: "SUCCESS", countsTowardEscalation: true, output: "pass 5" },
|
|
404
|
-
];
|
|
349
|
+
test("regression gate disabled → returns passed (skips regression gate)", async () => {
|
|
350
|
+
_verificationResponses = [];
|
|
405
351
|
|
|
406
352
|
const config = makeConfig({ enabled: false, timeoutSeconds: 120 });
|
|
407
353
|
const story = makeStory();
|
|
408
354
|
const prd = makePRD(story);
|
|
409
355
|
|
|
410
|
-
const result = await runPostAgentVerification(makeOpts(tempDir,
|
|
356
|
+
const result = await runPostAgentVerification(makeOpts(tempDir, config, story, prd));
|
|
411
357
|
|
|
412
358
|
expect(result.passed).toBe(true);
|
|
413
|
-
|
|
359
|
+
// No verification calls when regression gate is disabled
|
|
360
|
+
expect(mockRunVerification).toHaveBeenCalledTimes(0);
|
|
414
361
|
});
|
|
415
362
|
});
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unit tests for
|
|
2
|
+
* Unit tests for regression gate configuration and behavior
|
|
3
3
|
*
|
|
4
|
-
* Tests the logic for:
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
4
|
+
* Tests the configuration and type-level logic for:
|
|
5
|
+
* - Regression gate enabled/disabled state
|
|
6
|
+
* - Timeout configuration and acceptOnTimeout behavior (BUG-026)
|
|
7
|
+
* - Story state transitions on regression failure
|
|
8
|
+
* - Metrics removal on regression failure
|
|
9
|
+
*
|
|
10
|
+
* Behavioral tests are in post-verify-regression.test.ts
|
|
8
11
|
*/
|
|
9
12
|
|
|
10
13
|
import { describe, expect, test } from "bun:test";
|
|
@@ -41,44 +44,28 @@ describe("RegressionGateConfig", () => {
|
|
|
41
44
|
});
|
|
42
45
|
|
|
43
46
|
describe("Regression Gate Logic", () => {
|
|
44
|
-
test("
|
|
45
|
-
const changedTestFiles = ["test/foo.test.ts", "test/bar.test.ts"];
|
|
46
|
-
const regressionGateEnabled = true;
|
|
47
|
-
const scopedTestsWereRun = changedTestFiles.length > 0;
|
|
48
|
-
|
|
49
|
-
// Logic: regression gate should run
|
|
50
|
-
const shouldRunRegressionGate = regressionGateEnabled && scopedTestsWereRun;
|
|
51
|
-
expect(shouldRunRegressionGate).toBe(true);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("should skip regression gate when scoped tests ran full suite (changed files = 0)", () => {
|
|
55
|
-
const changedTestFiles: string[] = [];
|
|
47
|
+
test("regression gate should run when enabled (post-verify always runs full suite)", () => {
|
|
56
48
|
const regressionGateEnabled = true;
|
|
57
|
-
const scopedTestsWereRun = changedTestFiles.length > 0;
|
|
58
49
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
expect(shouldRunRegressionGate).toBe(false);
|
|
50
|
+
// Post-verify now ONLY runs full-suite regression gate (no scoped logic)
|
|
51
|
+
expect(regressionGateEnabled).toBe(true);
|
|
62
52
|
});
|
|
63
53
|
|
|
64
|
-
test("should skip
|
|
65
|
-
const changedTestFiles = ["test/foo.test.ts"];
|
|
54
|
+
test("regression gate should skip when disabled in config", () => {
|
|
66
55
|
const regressionGateEnabled = false;
|
|
67
|
-
const scopedTestsWereRun = changedTestFiles.length > 0;
|
|
68
56
|
|
|
69
|
-
// Logic: regression gate should NOT run
|
|
70
|
-
|
|
71
|
-
expect(shouldRunRegressionGate).toBe(false);
|
|
57
|
+
// Logic: regression gate should NOT run
|
|
58
|
+
expect(regressionGateEnabled).toBe(false);
|
|
72
59
|
});
|
|
73
60
|
|
|
74
|
-
test("
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
61
|
+
test("post-verify removes scoped verification (always runs full suite)", () => {
|
|
62
|
+
// With the removal of scoped verification, post-verify always:
|
|
63
|
+
// 1. Runs the full-suite regression gate (if enabled)
|
|
64
|
+
// 2. Reverts on failure
|
|
65
|
+
// 3. Optionally runs rectification on test failures
|
|
78
66
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
expect(shouldRunRegressionGate).toBe(false);
|
|
67
|
+
const hasNoScopedVerification = true;
|
|
68
|
+
expect(hasNoScopedVerification).toBe(true);
|
|
82
69
|
});
|
|
83
70
|
});
|
|
84
71
|
|
|
@@ -104,27 +91,20 @@ describe("Regression Failure Handling", () => {
|
|
|
104
91
|
|
|
105
92
|
describe("Rectification Prompt for Regression", () => {
|
|
106
93
|
test("should include REGRESSION prefix in rectification prompt", () => {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
Your changes caused test regressions. Fix these without breaking existing logic.`;
|
|
110
|
-
|
|
111
|
-
const regressionPrompt = `# REGRESSION: Cross-Story Test Failures
|
|
112
|
-
|
|
113
|
-
Your changes passed scoped tests but broke unrelated tests. Fix these regressions.
|
|
94
|
+
const regressionPrompt = `# REGRESSION: Full-Suite Test Failures
|
|
114
95
|
|
|
115
|
-
|
|
96
|
+
Your changes broke tests in the full suite. Fix these regressions.`;
|
|
116
97
|
|
|
117
98
|
expect(regressionPrompt).toContain("# REGRESSION:");
|
|
118
|
-
expect(regressionPrompt).toContain("
|
|
119
|
-
expect(regressionPrompt).toContain(basePrompt);
|
|
99
|
+
expect(regressionPrompt).toContain("Full-Suite Test Failures");
|
|
120
100
|
});
|
|
121
101
|
|
|
122
|
-
test("regression prompt should emphasize
|
|
102
|
+
test("regression prompt should emphasize full-suite nature", () => {
|
|
123
103
|
const regressionPrompt =
|
|
124
|
-
"# REGRESSION:
|
|
104
|
+
"# REGRESSION: Full-Suite Test Failures\n\nYour changes broke tests in the full suite.";
|
|
125
105
|
|
|
126
|
-
expect(regressionPrompt).toContain("
|
|
127
|
-
expect(regressionPrompt).toContain("
|
|
106
|
+
expect(regressionPrompt).toContain("Full-Suite");
|
|
107
|
+
expect(regressionPrompt).toContain("broke tests");
|
|
128
108
|
});
|
|
129
109
|
});
|
|
130
110
|
|
|
@@ -44,9 +44,8 @@ describe("formatConsole", () => {
|
|
|
44
44
|
|
|
45
45
|
const output = formatConsole(entry);
|
|
46
46
|
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
expect(bracketCount).toBe(2); // Only timestamp and stage
|
|
47
|
+
// Visibility test instead of raw bracket count (avoid ANSI issues)
|
|
48
|
+
expect(output).not.toContain("[user-auth-001]");
|
|
50
49
|
});
|
|
51
50
|
|
|
52
51
|
test("formats data as pretty JSON on new line", () => {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
// Access internal functions for testing
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import { hasShellOperators, validateHookCommand } from "../../../src/hooks/runner";
|
|
5
|
+
|
|
6
|
+
describe("Hook Shell Security (SEC-3)", () => {
|
|
7
|
+
test("hasShellOperators detects backticks", () => {
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
expect(hasShellOperators("echo `whoami`")).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("hasShellOperators detects pipes and redirects", () => {
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
expect(hasShellOperators("echo hi | grep h")).toBe(true);
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
expect(hasShellOperators("echo hi > file.txt")).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("validateHookCommand blocks backtick substitution", () => {
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
expect(() => validateHookCommand("echo `whoami`")).toThrow(/dangerous pattern/);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("validateHookCommand blocks $(...) substitution", () => {
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
expect(() => validateHookCommand("echo $(whoami)")).toThrow(/dangerous pattern/);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("validateHookCommand blocks eval", () => {
|
|
30
|
+
// @ts-ignore
|
|
31
|
+
expect(() => validateHookCommand("eval 'echo hi'")).toThrow(/dangerous pattern/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("allows safe commands", () => {
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
expect(() => validateHookCommand("echo 'Hello World'")).not.toThrow();
|
|
37
|
+
// @ts-ignore
|
|
38
|
+
expect(() => validateHookCommand("bun test")).not.toThrow();
|
|
39
|
+
});
|
|
40
|
+
});
|