gsd-pi 2.63.0-dev.d04bbc5 → 2.64.0-dev.9c14bd0
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/dist/resources/extensions/gsd/auto-dashboard.js +5 -5
- package/dist/resources/extensions/gsd/auto-post-unit.js +98 -1
- package/dist/resources/extensions/gsd/auto-verification.js +138 -1
- package/dist/resources/extensions/gsd/auto.js +5 -0
- package/dist/resources/extensions/gsd/post-execution-checks.js +407 -0
- package/dist/resources/extensions/gsd/pre-execution-checks.js +464 -0
- package/dist/resources/extensions/gsd/preferences-types.js +4 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +33 -0
- package/dist/resources/extensions/gsd/preferences.js +4 -0
- package/dist/resources/extensions/gsd/verification-evidence.js +18 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto-dashboard.ts +5 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +122 -0
- package/src/resources/extensions/gsd/auto-verification.ts +190 -2
- package/src/resources/extensions/gsd/auto.ts +4 -0
- package/src/resources/extensions/gsd/post-execution-checks.ts +539 -0
- package/src/resources/extensions/gsd/pre-execution-checks.ts +573 -0
- package/src/resources/extensions/gsd/preferences-types.ts +28 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +33 -0
- package/src/resources/extensions/gsd/preferences.ts +4 -0
- package/src/resources/extensions/gsd/tests/auto-start-time-persistence.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/enhanced-verification-integration.test.ts +526 -0
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +312 -0
- package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +813 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +999 -0
- package/src/resources/extensions/gsd/tests/pre-execution-fail-closed.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +457 -0
- package/src/resources/extensions/gsd/verification-evidence.ts +68 -0
- /package/dist/web/standalone/.next/static/{vIq9fmvRUaFOpguoX5j4W → SoxM61WC_ia7R2gk4VMpJ}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{vIq9fmvRUaFOpguoX5j4W → SoxM61WC_ia7R2gk4VMpJ}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* post-exec-retry-bypass.test.ts — Tests for post-execution blocking failure retry bypass.
|
|
3
|
+
*
|
|
4
|
+
* Verifies that when post-execution checks fail (postExecBlockingFailure is true),
|
|
5
|
+
* the retry system is bypassed and auto-mode pauses immediately. Post-execution
|
|
6
|
+
* failures are cross-task consistency issues — retrying the same task won't fix them.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, test, mock, beforeEach, afterEach } from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
import { runPostUnitVerification, type VerificationContext } from "../auto-verification.ts";
|
|
16
|
+
import { AutoSession } from "../auto/session.ts";
|
|
17
|
+
import { openDatabase, closeDatabase, insertMilestone, insertSlice, insertTask } from "../gsd-db.ts";
|
|
18
|
+
import { invalidateAllCaches } from "../cache.ts";
|
|
19
|
+
import { _clearGsdRootCache } from "../paths.ts";
|
|
20
|
+
|
|
21
|
+
// ─── Test Fixtures ───────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
let tempDir: string;
|
|
24
|
+
let dbPath: string;
|
|
25
|
+
let originalCwd: string;
|
|
26
|
+
|
|
27
|
+
function makeMockCtx() {
|
|
28
|
+
return {
|
|
29
|
+
ui: {
|
|
30
|
+
notify: mock.fn(),
|
|
31
|
+
setStatus: () => {},
|
|
32
|
+
setWidget: () => {},
|
|
33
|
+
setFooter: () => {},
|
|
34
|
+
},
|
|
35
|
+
model: { id: "test-model" },
|
|
36
|
+
} as any;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function makeMockPi() {
|
|
40
|
+
return {
|
|
41
|
+
sendMessage: mock.fn(),
|
|
42
|
+
setModel: mock.fn(async () => true),
|
|
43
|
+
} as any;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function makeMockSession(basePath: string, currentUnit?: { type: string; id: string }): AutoSession {
|
|
47
|
+
const s = new AutoSession();
|
|
48
|
+
s.basePath = basePath;
|
|
49
|
+
s.active = true;
|
|
50
|
+
// verificationRetryCount is readonly but initialized as an empty Map in AutoSession
|
|
51
|
+
s.pendingVerificationRetry = null;
|
|
52
|
+
if (currentUnit) {
|
|
53
|
+
s.currentUnit = {
|
|
54
|
+
type: currentUnit.type,
|
|
55
|
+
id: currentUnit.id,
|
|
56
|
+
startedAt: Date.now(),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return s;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function setupTestEnvironment(): void {
|
|
63
|
+
originalCwd = process.cwd();
|
|
64
|
+
tempDir = join(tmpdir(), `post-exec-retry-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
65
|
+
mkdirSync(tempDir, { recursive: true });
|
|
66
|
+
|
|
67
|
+
const gsdDir = join(tempDir, ".gsd");
|
|
68
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
69
|
+
|
|
70
|
+
const milestonesDir = join(gsdDir, "milestones", "M001", "slices", "S01", "tasks");
|
|
71
|
+
mkdirSync(milestonesDir, { recursive: true });
|
|
72
|
+
|
|
73
|
+
process.chdir(tempDir);
|
|
74
|
+
_clearGsdRootCache();
|
|
75
|
+
|
|
76
|
+
dbPath = join(gsdDir, "gsd.db");
|
|
77
|
+
openDatabase(dbPath);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function cleanupTestEnvironment(): void {
|
|
81
|
+
try {
|
|
82
|
+
process.chdir(originalCwd);
|
|
83
|
+
} catch {
|
|
84
|
+
// Ignore
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
closeDatabase();
|
|
88
|
+
} catch {
|
|
89
|
+
// Ignore
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
93
|
+
} catch {
|
|
94
|
+
// Ignore
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function writePreferences(prefs: Record<string, unknown>): void {
|
|
99
|
+
const yamlLines = Object.entries(prefs).map(([k, v]) => `${k}: ${JSON.stringify(v)}`);
|
|
100
|
+
const prefsContent = `---
|
|
101
|
+
${yamlLines.join("\n")}
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
# GSD Preferences
|
|
105
|
+
`;
|
|
106
|
+
writeFileSync(join(tempDir, ".gsd", "PREFERENCES.md"), prefsContent);
|
|
107
|
+
invalidateAllCaches();
|
|
108
|
+
_clearGsdRootCache();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Create a task in DB that will pass basic verification but allows us to test the flow.
|
|
113
|
+
*/
|
|
114
|
+
function createBasicTask(): void {
|
|
115
|
+
insertMilestone({ id: "M001" });
|
|
116
|
+
insertSlice({
|
|
117
|
+
id: "S01",
|
|
118
|
+
milestoneId: "M001",
|
|
119
|
+
title: "Test Slice",
|
|
120
|
+
risk: "low",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Create a simple task
|
|
124
|
+
insertTask({
|
|
125
|
+
id: "T01",
|
|
126
|
+
sliceId: "S01",
|
|
127
|
+
milestoneId: "M001",
|
|
128
|
+
title: "Basic task",
|
|
129
|
+
status: "pending",
|
|
130
|
+
planning: {
|
|
131
|
+
description: "A basic task for testing",
|
|
132
|
+
estimate: "1h",
|
|
133
|
+
files: [],
|
|
134
|
+
verify: "echo pass", // Simple verification that always passes
|
|
135
|
+
inputs: [],
|
|
136
|
+
expectedOutput: ["output.ts"],
|
|
137
|
+
observabilityImpact: "",
|
|
138
|
+
},
|
|
139
|
+
sequence: 0,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
describe("Post-execution blocking failure retry bypass", () => {
|
|
146
|
+
beforeEach(() => {
|
|
147
|
+
setupTestEnvironment();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
afterEach(() => {
|
|
151
|
+
cleanupTestEnvironment();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("skips verification when unit type is not execute-task", async () => {
|
|
155
|
+
createBasicTask();
|
|
156
|
+
writePreferences({
|
|
157
|
+
enhanced_verification: true,
|
|
158
|
+
enhanced_verification_post: true,
|
|
159
|
+
verification_auto_fix: true,
|
|
160
|
+
verification_max_retries: 3,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const ctx = makeMockCtx();
|
|
164
|
+
const pi = makeMockPi();
|
|
165
|
+
const pauseAutoMock = mock.fn(async () => {});
|
|
166
|
+
const s = makeMockSession(tempDir, { type: "plan-slice", id: "M001/S01" });
|
|
167
|
+
|
|
168
|
+
const vctx: VerificationContext = { s, ctx, pi };
|
|
169
|
+
const result = await runPostUnitVerification(vctx, pauseAutoMock);
|
|
170
|
+
|
|
171
|
+
// Non-execute-task units should return "continue" immediately
|
|
172
|
+
assert.equal(result, "continue");
|
|
173
|
+
assert.equal(pauseAutoMock.mock.callCount(), 0);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("returns continue when verification passes", async () => {
|
|
177
|
+
createBasicTask();
|
|
178
|
+
writePreferences({
|
|
179
|
+
enhanced_verification: true,
|
|
180
|
+
enhanced_verification_post: true,
|
|
181
|
+
verification_auto_fix: true,
|
|
182
|
+
verification_max_retries: 3,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const ctx = makeMockCtx();
|
|
186
|
+
const pi = makeMockPi();
|
|
187
|
+
const pauseAutoMock = mock.fn(async () => {});
|
|
188
|
+
const s = makeMockSession(tempDir, { type: "execute-task", id: "M001/S01/T01" });
|
|
189
|
+
|
|
190
|
+
const vctx: VerificationContext = { s, ctx, pi };
|
|
191
|
+
const result = await runPostUnitVerification(vctx, pauseAutoMock);
|
|
192
|
+
|
|
193
|
+
// When verification passes, should return "continue" and not call pauseAuto
|
|
194
|
+
assert.equal(result, "continue");
|
|
195
|
+
assert.equal(pauseAutoMock.mock.callCount(), 0);
|
|
196
|
+
|
|
197
|
+
// Retry state should be cleared
|
|
198
|
+
assert.equal(s.pendingVerificationRetry, null);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("verification retry count is cleared on success", async () => {
|
|
202
|
+
createBasicTask();
|
|
203
|
+
writePreferences({
|
|
204
|
+
enhanced_verification: true,
|
|
205
|
+
enhanced_verification_post: true,
|
|
206
|
+
verification_auto_fix: true,
|
|
207
|
+
verification_max_retries: 3,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const ctx = makeMockCtx();
|
|
211
|
+
const pi = makeMockPi();
|
|
212
|
+
const pauseAutoMock = mock.fn(async () => {});
|
|
213
|
+
const s = makeMockSession(tempDir, { type: "execute-task", id: "M001/S01/T01" });
|
|
214
|
+
|
|
215
|
+
// Pre-set some retry state
|
|
216
|
+
s.verificationRetryCount.set("M001/S01/T01", 2);
|
|
217
|
+
|
|
218
|
+
const vctx: VerificationContext = { s, ctx, pi };
|
|
219
|
+
const result = await runPostUnitVerification(vctx, pauseAutoMock);
|
|
220
|
+
|
|
221
|
+
// On success, retry count should be cleared
|
|
222
|
+
assert.equal(result, "continue");
|
|
223
|
+
assert.equal(s.verificationRetryCount.has("M001/S01/T01"), false);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("post-exec failure notification mentions cross-task consistency", async () => {
|
|
227
|
+
// This test verifies that the notification for post-exec failures includes
|
|
228
|
+
// the appropriate message about cross-task consistency issues.
|
|
229
|
+
// The actual post-exec failure would require specific file/output state
|
|
230
|
+
// that's harder to set up in a unit test, but we can verify the code path exists.
|
|
231
|
+
|
|
232
|
+
createBasicTask();
|
|
233
|
+
writePreferences({
|
|
234
|
+
enhanced_verification: true,
|
|
235
|
+
enhanced_verification_post: true,
|
|
236
|
+
verification_auto_fix: true,
|
|
237
|
+
verification_max_retries: 3,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const ctx = makeMockCtx();
|
|
241
|
+
const pi = makeMockPi();
|
|
242
|
+
const pauseAutoMock = mock.fn(async () => {});
|
|
243
|
+
const s = makeMockSession(tempDir, { type: "execute-task", id: "M001/S01/T01" });
|
|
244
|
+
|
|
245
|
+
const vctx: VerificationContext = { s, ctx, pi };
|
|
246
|
+
const result = await runPostUnitVerification(vctx, pauseAutoMock);
|
|
247
|
+
|
|
248
|
+
// The verification should pass with our simple "echo pass" task
|
|
249
|
+
// This test mainly confirms the wiring is correct
|
|
250
|
+
assert.equal(result, "continue");
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe("Post-execution retry behavior", () => {
|
|
255
|
+
beforeEach(() => {
|
|
256
|
+
setupTestEnvironment();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
afterEach(() => {
|
|
260
|
+
cleanupTestEnvironment();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("when autofix is disabled, failure pauses immediately without retry", async () => {
|
|
264
|
+
// Create a task with a verify command that will fail
|
|
265
|
+
insertMilestone({ id: "M001" });
|
|
266
|
+
insertSlice({
|
|
267
|
+
id: "S01",
|
|
268
|
+
milestoneId: "M001",
|
|
269
|
+
title: "Test Slice",
|
|
270
|
+
risk: "low",
|
|
271
|
+
});
|
|
272
|
+
insertTask({
|
|
273
|
+
id: "T01",
|
|
274
|
+
sliceId: "S01",
|
|
275
|
+
milestoneId: "M001",
|
|
276
|
+
title: "Failing task",
|
|
277
|
+
status: "pending",
|
|
278
|
+
planning: {
|
|
279
|
+
description: "Task with failing verification",
|
|
280
|
+
estimate: "1h",
|
|
281
|
+
files: [],
|
|
282
|
+
verify: "exit 1", // This will fail
|
|
283
|
+
inputs: [],
|
|
284
|
+
expectedOutput: [],
|
|
285
|
+
observabilityImpact: "",
|
|
286
|
+
},
|
|
287
|
+
sequence: 0,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
writePreferences({
|
|
291
|
+
enhanced_verification: true,
|
|
292
|
+
enhanced_verification_post: true,
|
|
293
|
+
verification_auto_fix: false, // Autofix disabled
|
|
294
|
+
verification_max_retries: 3,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const ctx = makeMockCtx();
|
|
298
|
+
const pi = makeMockPi();
|
|
299
|
+
const pauseAutoMock = mock.fn(async () => {});
|
|
300
|
+
const s = makeMockSession(tempDir, { type: "execute-task", id: "M001/S01/T01" });
|
|
301
|
+
|
|
302
|
+
const vctx: VerificationContext = { s, ctx, pi };
|
|
303
|
+
const result = await runPostUnitVerification(vctx, pauseAutoMock);
|
|
304
|
+
|
|
305
|
+
// When autofix is disabled and verification fails, should pause
|
|
306
|
+
assert.equal(result, "pause");
|
|
307
|
+
assert.equal(pauseAutoMock.mock.callCount(), 1);
|
|
308
|
+
|
|
309
|
+
// Should NOT set up a retry
|
|
310
|
+
assert.equal(s.pendingVerificationRetry, null);
|
|
311
|
+
});
|
|
312
|
+
});
|