gsd-pi 2.33.1-dev.ee47f1b → 2.34.0-dev.bbb5216
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/bundled-resource-path.d.ts +8 -0
- package/dist/bundled-resource-path.js +14 -0
- package/dist/headless-query.js +6 -6
- package/dist/resources/extensions/gsd/auto/session.js +27 -32
- package/dist/resources/extensions/gsd/auto-dashboard.js +29 -109
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +6 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +52 -81
- package/dist/resources/extensions/gsd/auto-loop.js +956 -0
- package/dist/resources/extensions/gsd/auto-observability.js +4 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +75 -185
- package/dist/resources/extensions/gsd/auto-prompts.js +133 -101
- package/dist/resources/extensions/gsd/auto-recovery.js +59 -97
- package/dist/resources/extensions/gsd/auto-start.js +330 -309
- package/dist/resources/extensions/gsd/auto-supervisor.js +5 -11
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +7 -7
- package/dist/resources/extensions/gsd/auto-timers.js +3 -4
- package/dist/resources/extensions/gsd/auto-verification.js +35 -73
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +167 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +291 -126
- package/dist/resources/extensions/gsd/auto.js +283 -1013
- package/dist/resources/extensions/gsd/captures.js +10 -4
- package/dist/resources/extensions/gsd/dispatch-guard.js +7 -8
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -18
- package/dist/resources/extensions/gsd/doctor-checks.js +3 -4
- package/dist/resources/extensions/gsd/git-service.js +1 -1
- package/dist/resources/extensions/gsd/gsd-db.js +296 -151
- package/dist/resources/extensions/gsd/index.js +92 -228
- package/dist/resources/extensions/gsd/post-unit-hooks.js +13 -13
- package/dist/resources/extensions/gsd/progress-score.js +61 -156
- package/dist/resources/extensions/gsd/quick.js +98 -122
- package/dist/resources/extensions/gsd/session-lock.js +13 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
- package/dist/resources/extensions/gsd/undo.js +43 -48
- package/dist/resources/extensions/gsd/unit-runtime.js +16 -15
- package/dist/resources/extensions/gsd/verification-evidence.js +0 -1
- package/dist/resources/extensions/gsd/verification-gate.js +6 -35
- package/dist/resources/extensions/gsd/worktree-command.js +30 -24
- package/dist/resources/extensions/gsd/worktree-manager.js +2 -3
- package/dist/resources/extensions/gsd/worktree-resolver.js +344 -0
- package/dist/resources/extensions/gsd/worktree.js +7 -44
- package/dist/tool-bootstrap.js +59 -11
- package/dist/worktree-cli.js +7 -7
- package/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +3630 -5483
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +735 -2588
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/src/models.generated.ts +1039 -2892
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/session.ts +47 -30
- package/src/resources/extensions/gsd/auto-dashboard.ts +28 -131
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +6 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +135 -91
- package/src/resources/extensions/gsd/auto-loop.ts +1665 -0
- package/src/resources/extensions/gsd/auto-observability.ts +4 -2
- package/src/resources/extensions/gsd/auto-post-unit.ts +85 -228
- package/src/resources/extensions/gsd/auto-prompts.ts +138 -109
- package/src/resources/extensions/gsd/auto-recovery.ts +124 -118
- package/src/resources/extensions/gsd/auto-start.ts +440 -354
- package/src/resources/extensions/gsd/auto-supervisor.ts +5 -12
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +8 -8
- package/src/resources/extensions/gsd/auto-timers.ts +3 -4
- package/src/resources/extensions/gsd/auto-verification.ts +76 -90
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +204 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +389 -141
- package/src/resources/extensions/gsd/auto.ts +515 -1199
- package/src/resources/extensions/gsd/captures.ts +10 -4
- package/src/resources/extensions/gsd/dispatch-guard.ts +13 -9
- package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -18
- package/src/resources/extensions/gsd/doctor-checks.ts +3 -4
- package/src/resources/extensions/gsd/git-service.ts +8 -1
- package/src/resources/extensions/gsd/gitignore.ts +4 -2
- package/src/resources/extensions/gsd/gsd-db.ts +375 -180
- package/src/resources/extensions/gsd/index.ts +104 -263
- package/src/resources/extensions/gsd/post-unit-hooks.ts +13 -13
- package/src/resources/extensions/gsd/progress-score.ts +65 -200
- package/src/resources/extensions/gsd/quick.ts +121 -125
- package/src/resources/extensions/gsd/session-lock.ts +11 -0
- package/src/resources/extensions/gsd/templates/preferences.md +1 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +32 -59
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +75 -27
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1458 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +8 -162
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -108
- package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +1 -3
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -55
- package/src/resources/extensions/gsd/tests/headless-query.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +8 -11
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +4 -6
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/undo.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +7 -201
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +705 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +57 -106
- package/src/resources/extensions/gsd/tests/worktree.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +43 -132
- package/src/resources/extensions/gsd/types.ts +90 -81
- package/src/resources/extensions/gsd/undo.ts +42 -46
- package/src/resources/extensions/gsd/unit-runtime.ts +14 -18
- package/src/resources/extensions/gsd/verification-evidence.ts +1 -3
- package/src/resources/extensions/gsd/verification-gate.ts +6 -39
- package/src/resources/extensions/gsd/worktree-command.ts +36 -24
- package/src/resources/extensions/gsd/worktree-manager.ts +2 -3
- package/src/resources/extensions/gsd/worktree-resolver.ts +485 -0
- package/src/resources/extensions/gsd/worktree.ts +7 -44
- package/dist/resources/extensions/gsd/auto-constants.js +0 -5
- package/dist/resources/extensions/gsd/auto-idempotency.js +0 -106
- package/dist/resources/extensions/gsd/auto-stuck-detection.js +0 -165
- package/dist/resources/extensions/gsd/mechanical-completion.js +0 -351
- package/src/resources/extensions/gsd/auto-constants.ts +0 -6
- package/src/resources/extensions/gsd/auto-idempotency.ts +0 -151
- package/src/resources/extensions/gsd/auto-stuck-detection.ts +0 -221
- package/src/resources/extensions/gsd/mechanical-completion.ts +0 -430
- package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +0 -691
- package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +0 -127
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +0 -123
- package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +0 -126
- package/src/resources/extensions/gsd/tests/loop-regression.test.ts +0 -874
- package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +0 -356
- package/src/resources/extensions/gsd/tests/progress-score.test.ts +0 -206
- package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -434
|
@@ -261,71 +261,6 @@ test("verification-gate: each check has durationMs", () => {
|
|
|
261
261
|
}
|
|
262
262
|
});
|
|
263
263
|
|
|
264
|
-
// ─── Infra Error Tagging Tests ───────────────────────────────────────────────
|
|
265
|
-
|
|
266
|
-
test("verification-gate: spawnSync ETIMEDOUT → infraError: true on the check", () => {
|
|
267
|
-
const tmp = makeTempDir("vg-etimedout");
|
|
268
|
-
try {
|
|
269
|
-
// Use a short timeout against a long sleep to guarantee ETIMEDOUT
|
|
270
|
-
const result = runVerificationGate({
|
|
271
|
-
basePath: tmp,
|
|
272
|
-
unitId: "T01",
|
|
273
|
-
cwd: tmp,
|
|
274
|
-
preferenceCommands: ["sleep 60"],
|
|
275
|
-
commandTimeoutMs: 200,
|
|
276
|
-
});
|
|
277
|
-
assert.equal(result.passed, false);
|
|
278
|
-
assert.equal(result.checks.length, 1);
|
|
279
|
-
assert.ok(result.checks[0].exitCode !== 0, "should have non-zero exit code");
|
|
280
|
-
assert.equal(result.checks[0].infraError, true, "ETIMEDOUT should be tagged as infraError");
|
|
281
|
-
} finally {
|
|
282
|
-
rmSync(tmp, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
test("verification-gate: real command failure does NOT have infraError", () => {
|
|
287
|
-
const tmp = makeTempDir("vg-real-fail");
|
|
288
|
-
try {
|
|
289
|
-
const result = runVerificationGate({
|
|
290
|
-
basePath: tmp,
|
|
291
|
-
unitId: "T01",
|
|
292
|
-
cwd: tmp,
|
|
293
|
-
// Cross-platform: node with --eval flag and no shell-sensitive characters
|
|
294
|
-
preferenceCommands: ["node --eval \"process.exitCode=1\""],
|
|
295
|
-
});
|
|
296
|
-
assert.equal(result.passed, false);
|
|
297
|
-
assert.equal(result.checks.length, 1);
|
|
298
|
-
assert.equal(result.checks[0].exitCode, 1);
|
|
299
|
-
assert.equal(result.checks[0].infraError, undefined, "real failure should not be tagged as infraError");
|
|
300
|
-
} finally {
|
|
301
|
-
rmSync(tmp, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
test("verification-gate: mixed infra + real failure — only infra check is tagged", () => {
|
|
306
|
-
const tmp = makeTempDir("vg-mixed-infra");
|
|
307
|
-
try {
|
|
308
|
-
// Use a timeout that kills "sleep 60" but lets "node --eval" complete (~80ms).
|
|
309
|
-
// The gate applies the same timeout to each command sequentially.
|
|
310
|
-
const result = runVerificationGate({
|
|
311
|
-
basePath: tmp,
|
|
312
|
-
unitId: "T01",
|
|
313
|
-
cwd: tmp,
|
|
314
|
-
preferenceCommands: ["sleep 60", "node --eval \"process.exitCode=2\""],
|
|
315
|
-
commandTimeoutMs: 500,
|
|
316
|
-
});
|
|
317
|
-
assert.equal(result.passed, false);
|
|
318
|
-
assert.equal(result.checks.length, 2);
|
|
319
|
-
// First check: ETIMEDOUT → infraError
|
|
320
|
-
assert.equal(result.checks[0].infraError, true, "timed-out command should be infraError");
|
|
321
|
-
// Second check: real exit 2 → no infraError
|
|
322
|
-
assert.equal(result.checks[1].exitCode, 2);
|
|
323
|
-
assert.equal(result.checks[1].infraError, undefined, "real failure should not be infraError");
|
|
324
|
-
} finally {
|
|
325
|
-
rmSync(tmp, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
|
|
329
264
|
// ─── Preference Validation Tests ─────────────────────────────────────────────
|
|
330
265
|
|
|
331
266
|
test("verification-gate: validatePreferences accepts valid verification keys", () => {
|
|
@@ -646,7 +581,7 @@ test("formatFailureContext: formats a single failure with command, exit code, st
|
|
|
646
581
|
const result: import("../types.ts").VerificationResult = {
|
|
647
582
|
passed: false,
|
|
648
583
|
checks: [
|
|
649
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "error: unused var", durationMs: 500
|
|
584
|
+
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "error: unused var", durationMs: 500 },
|
|
650
585
|
],
|
|
651
586
|
discoverySource: "preference",
|
|
652
587
|
timestamp: Date.now(),
|
|
@@ -663,9 +598,9 @@ test("formatFailureContext: formats multiple failures", () => {
|
|
|
663
598
|
const result: import("../types.ts").VerificationResult = {
|
|
664
599
|
passed: false,
|
|
665
600
|
checks: [
|
|
666
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint error", durationMs: 100
|
|
667
|
-
{ command: "npm run test", exitCode: 2, stdout: "", stderr: "test failure", durationMs: 200
|
|
668
|
-
{ command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 50
|
|
601
|
+
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint error", durationMs: 100 },
|
|
602
|
+
{ command: "npm run test", exitCode: 2, stdout: "", stderr: "test failure", durationMs: 200 },
|
|
603
|
+
{ command: "npm run typecheck", exitCode: 0, stdout: "ok", stderr: "", durationMs: 50 },
|
|
669
604
|
],
|
|
670
605
|
discoverySource: "preference",
|
|
671
606
|
timestamp: Date.now(),
|
|
@@ -684,7 +619,7 @@ test("formatFailureContext: truncates stderr longer than 2000 chars", () => {
|
|
|
684
619
|
const result: import("../types.ts").VerificationResult = {
|
|
685
620
|
passed: false,
|
|
686
621
|
checks: [
|
|
687
|
-
{ command: "big-err", exitCode: 1, stdout: "", stderr: longStderr, durationMs: 100
|
|
622
|
+
{ command: "big-err", exitCode: 1, stdout: "", stderr: longStderr, durationMs: 100 },
|
|
688
623
|
],
|
|
689
624
|
discoverySource: "preference",
|
|
690
625
|
timestamp: Date.now(),
|
|
@@ -699,8 +634,8 @@ test("formatFailureContext: returns empty string when all checks pass", () => {
|
|
|
699
634
|
const result: import("../types.ts").VerificationResult = {
|
|
700
635
|
passed: true,
|
|
701
636
|
checks: [
|
|
702
|
-
{ command: "npm run lint", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100
|
|
703
|
-
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 200
|
|
637
|
+
{ command: "npm run lint", exitCode: 0, stdout: "ok", stderr: "", durationMs: 100 },
|
|
638
|
+
{ command: "npm run test", exitCode: 0, stdout: "ok", stderr: "", durationMs: 200 },
|
|
704
639
|
],
|
|
705
640
|
discoverySource: "preference",
|
|
706
641
|
timestamp: Date.now(),
|
|
@@ -728,7 +663,6 @@ test("formatFailureContext: caps total output at 10,000 chars", () => {
|
|
|
728
663
|
stdout: "",
|
|
729
664
|
stderr: "e".repeat(1000), // 1000 chars each, 20 * ~1050 (with formatting) > 10,000
|
|
730
665
|
durationMs: 100,
|
|
731
|
-
blocking: true,
|
|
732
666
|
});
|
|
733
667
|
}
|
|
734
668
|
const result: import("../types.ts").VerificationResult = {
|
|
@@ -1143,131 +1077,3 @@ test("dependency-audit: subdirectory package.json does not trigger audit", () =>
|
|
|
1143
1077
|
assert.equal(npmAuditCalled, false, "subdirectory dependency files should not trigger audit");
|
|
1144
1078
|
assert.deepStrictEqual(result, []);
|
|
1145
1079
|
});
|
|
1146
|
-
|
|
1147
|
-
// ─── Non-Blocking Discovery Tests ────────────────────────────────────────────
|
|
1148
|
-
|
|
1149
|
-
test("non-blocking: package-json discovered commands failing → result.passed is still true", () => {
|
|
1150
|
-
const tmp = makeTempDir("vg-nb-pkg-fail");
|
|
1151
|
-
try {
|
|
1152
|
-
writeFileSync(
|
|
1153
|
-
join(tmp, "package.json"),
|
|
1154
|
-
JSON.stringify({ scripts: { lint: "eslint .", test: "vitest" } }),
|
|
1155
|
-
);
|
|
1156
|
-
// These commands will fail because eslint/vitest don't exist in the temp dir
|
|
1157
|
-
const result = runVerificationGate({
|
|
1158
|
-
basePath: tmp,
|
|
1159
|
-
unitId: "T01",
|
|
1160
|
-
cwd: tmp,
|
|
1161
|
-
// No preference commands — discovery falls through to package.json
|
|
1162
|
-
});
|
|
1163
|
-
assert.equal(result.discoverySource, "package-json");
|
|
1164
|
-
assert.ok(result.checks.length > 0, "should have discovered package.json checks");
|
|
1165
|
-
assert.equal(result.passed, true, "package-json failures should not block the gate");
|
|
1166
|
-
for (const check of result.checks) {
|
|
1167
|
-
assert.equal(check.blocking, false, "package-json checks should be non-blocking");
|
|
1168
|
-
}
|
|
1169
|
-
} finally {
|
|
1170
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
1171
|
-
}
|
|
1172
|
-
});
|
|
1173
|
-
|
|
1174
|
-
test("non-blocking: preference commands failing → result.passed is false", () => {
|
|
1175
|
-
const tmp = makeTempDir("vg-nb-pref-fail");
|
|
1176
|
-
try {
|
|
1177
|
-
const result = runVerificationGate({
|
|
1178
|
-
basePath: tmp,
|
|
1179
|
-
unitId: "T01",
|
|
1180
|
-
cwd: tmp,
|
|
1181
|
-
preferenceCommands: ["sh -c 'exit 1'"],
|
|
1182
|
-
});
|
|
1183
|
-
assert.equal(result.discoverySource, "preference");
|
|
1184
|
-
assert.equal(result.passed, false, "preference failures should block the gate");
|
|
1185
|
-
assert.equal(result.checks[0].blocking, true, "preference checks should be blocking");
|
|
1186
|
-
} finally {
|
|
1187
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
1188
|
-
}
|
|
1189
|
-
});
|
|
1190
|
-
|
|
1191
|
-
test("non-blocking: task-plan commands failing → result.passed is false", () => {
|
|
1192
|
-
const tmp = makeTempDir("vg-nb-tp-fail");
|
|
1193
|
-
try {
|
|
1194
|
-
const result = runVerificationGate({
|
|
1195
|
-
basePath: tmp,
|
|
1196
|
-
unitId: "T01",
|
|
1197
|
-
cwd: tmp,
|
|
1198
|
-
taskPlanVerify: "sh -c 'exit 1'",
|
|
1199
|
-
});
|
|
1200
|
-
assert.equal(result.discoverySource, "task-plan");
|
|
1201
|
-
assert.equal(result.passed, false, "task-plan failures should block the gate");
|
|
1202
|
-
assert.equal(result.checks[0].blocking, true, "task-plan checks should be blocking");
|
|
1203
|
-
} finally {
|
|
1204
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
1205
|
-
}
|
|
1206
|
-
});
|
|
1207
|
-
|
|
1208
|
-
test("non-blocking: blocking field is set correctly based on discovery source", () => {
|
|
1209
|
-
const tmp = makeTempDir("vg-nb-field");
|
|
1210
|
-
try {
|
|
1211
|
-
// preference → blocking
|
|
1212
|
-
const prefResult = runVerificationGate({
|
|
1213
|
-
basePath: tmp,
|
|
1214
|
-
unitId: "T01",
|
|
1215
|
-
cwd: tmp,
|
|
1216
|
-
preferenceCommands: ["echo ok"],
|
|
1217
|
-
});
|
|
1218
|
-
assert.equal(prefResult.checks[0].blocking, true);
|
|
1219
|
-
|
|
1220
|
-
// task-plan → blocking
|
|
1221
|
-
const tpResult = runVerificationGate({
|
|
1222
|
-
basePath: tmp,
|
|
1223
|
-
unitId: "T01",
|
|
1224
|
-
cwd: tmp,
|
|
1225
|
-
taskPlanVerify: "echo ok",
|
|
1226
|
-
});
|
|
1227
|
-
assert.equal(tpResult.checks[0].blocking, true);
|
|
1228
|
-
|
|
1229
|
-
// package-json → non-blocking
|
|
1230
|
-
writeFileSync(
|
|
1231
|
-
join(tmp, "package.json"),
|
|
1232
|
-
JSON.stringify({ scripts: { test: "echo ok" } }),
|
|
1233
|
-
);
|
|
1234
|
-
const pkgResult = runVerificationGate({
|
|
1235
|
-
basePath: tmp,
|
|
1236
|
-
unitId: "T01",
|
|
1237
|
-
cwd: tmp,
|
|
1238
|
-
});
|
|
1239
|
-
assert.equal(pkgResult.checks[0].blocking, false);
|
|
1240
|
-
} finally {
|
|
1241
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
1242
|
-
}
|
|
1243
|
-
});
|
|
1244
|
-
|
|
1245
|
-
test("non-blocking: formatFailureContext only includes blocking failures", () => {
|
|
1246
|
-
const result: import("../types.ts").VerificationResult = {
|
|
1247
|
-
passed: true,
|
|
1248
|
-
checks: [
|
|
1249
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint warning", durationMs: 100, blocking: false },
|
|
1250
|
-
{ command: "npm run test", exitCode: 1, stdout: "", stderr: "test error", durationMs: 200, blocking: true },
|
|
1251
|
-
{ command: "npm run typecheck", exitCode: 1, stdout: "", stderr: "type error", durationMs: 50, blocking: false },
|
|
1252
|
-
],
|
|
1253
|
-
discoverySource: "preference",
|
|
1254
|
-
timestamp: Date.now(),
|
|
1255
|
-
};
|
|
1256
|
-
const output = formatFailureContext(result);
|
|
1257
|
-
assert.ok(output.includes("`npm run test`"), "should include blocking failure");
|
|
1258
|
-
assert.ok(!output.includes("npm run lint"), "should not include non-blocking failure");
|
|
1259
|
-
assert.ok(!output.includes("npm run typecheck"), "should not include non-blocking failure");
|
|
1260
|
-
});
|
|
1261
|
-
|
|
1262
|
-
test("non-blocking: formatFailureContext returns empty when only non-blocking failures exist", () => {
|
|
1263
|
-
const result: import("../types.ts").VerificationResult = {
|
|
1264
|
-
passed: true,
|
|
1265
|
-
checks: [
|
|
1266
|
-
{ command: "npm run lint", exitCode: 1, stdout: "", stderr: "lint warning", durationMs: 100, blocking: false },
|
|
1267
|
-
{ command: "npm run test", exitCode: 1, stdout: "", stderr: "test warning", durationMs: 200, blocking: false },
|
|
1268
|
-
],
|
|
1269
|
-
discoverySource: "package-json",
|
|
1270
|
-
timestamp: Date.now(),
|
|
1271
|
-
};
|
|
1272
|
-
assert.equal(formatFailureContext(result), "", "should return empty when only non-blocking failures");
|
|
1273
|
-
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worktree-db-integration.test.ts
|
|
3
|
+
*
|
|
4
|
+
* Integration tests for the worktree DB copy and reconcile hooks.
|
|
5
|
+
* Uses real temp git repos and real SQLite databases.
|
|
6
|
+
*
|
|
7
|
+
* Test cases:
|
|
8
|
+
* 1. Copy: createAutoWorktree seeds .gsd/gsd.db into the worktree when main has one
|
|
9
|
+
* 2. Copy-skip: createAutoWorktree silently skips when main has no gsd.db
|
|
10
|
+
* 3. Reconcile: reconcileWorktreeDb merges worktree rows into main DB
|
|
11
|
+
* 4. Reconcile-skip: reconcileWorktreeDb is non-fatal when both paths are nonexistent
|
|
12
|
+
* 5. Failure path: reconcileWorktreeDb emits to stderr on open failure (observable)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, realpathSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import { execSync } from "node:child_process";
|
|
19
|
+
|
|
20
|
+
import { createAutoWorktree } from "../auto-worktree.ts";
|
|
21
|
+
import { worktreePath } from "../worktree-manager.ts";
|
|
22
|
+
import {
|
|
23
|
+
copyWorktreeDb,
|
|
24
|
+
reconcileWorktreeDb,
|
|
25
|
+
openDatabase,
|
|
26
|
+
closeDatabase,
|
|
27
|
+
upsertDecision,
|
|
28
|
+
getActiveDecisions,
|
|
29
|
+
isDbAvailable,
|
|
30
|
+
} from "../gsd-db.ts";
|
|
31
|
+
|
|
32
|
+
import { createTestContext } from "./test-helpers.ts";
|
|
33
|
+
|
|
34
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
35
|
+
|
|
36
|
+
function run(command: string, cwd: string): string {
|
|
37
|
+
return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createTempRepo(): string {
|
|
41
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "wt-db-int-test-")));
|
|
42
|
+
run("git init", dir);
|
|
43
|
+
run("git config user.email test@test.com", dir);
|
|
44
|
+
run("git config user.name Test", dir);
|
|
45
|
+
writeFileSync(join(dir, "README.md"), "# test\n");
|
|
46
|
+
run("git add .", dir);
|
|
47
|
+
run("git commit -m init", dir);
|
|
48
|
+
run("git branch -M main", dir);
|
|
49
|
+
return dir;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function main(): Promise<void> {
|
|
53
|
+
const savedCwd = process.cwd();
|
|
54
|
+
const tempDirs: string[] = [];
|
|
55
|
+
|
|
56
|
+
function makeTempDir(): string {
|
|
57
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "wt-db-int-")));
|
|
58
|
+
tempDirs.push(dir);
|
|
59
|
+
return dir;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
|
|
64
|
+
// ─── Test 1: copy on worktree creation ───────────────────────────
|
|
65
|
+
console.log("\n=== Test 1: copy on worktree creation ===");
|
|
66
|
+
{
|
|
67
|
+
const tempDir = createTempRepo();
|
|
68
|
+
tempDirs.push(tempDir);
|
|
69
|
+
|
|
70
|
+
// Seed a gsd.db in the main repo
|
|
71
|
+
const gsdDir = join(tempDir, ".gsd");
|
|
72
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
73
|
+
const mainDbPath = join(gsdDir, "gsd.db");
|
|
74
|
+
openDatabase(mainDbPath);
|
|
75
|
+
closeDatabase();
|
|
76
|
+
|
|
77
|
+
// Commit so createAutoWorktree can copy planning artifacts
|
|
78
|
+
run("git add .", tempDir);
|
|
79
|
+
run('git commit -m "add gsd dir"', tempDir);
|
|
80
|
+
|
|
81
|
+
// createAutoWorktree should copy the DB into the worktree
|
|
82
|
+
const wtPath = createAutoWorktree(tempDir, "M004");
|
|
83
|
+
|
|
84
|
+
const worktreeDbPath = join(worktreePath(tempDir, "M004"), ".gsd", "gsd.db");
|
|
85
|
+
assertTrue(
|
|
86
|
+
existsSync(worktreeDbPath),
|
|
87
|
+
"gsd.db exists in worktree .gsd after createAutoWorktree",
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Restore cwd for next test
|
|
91
|
+
process.chdir(savedCwd);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── Test 2: copy skip when no source DB ─────────────────────────
|
|
95
|
+
console.log("\n=== Test 2: copy skip when no source DB ===");
|
|
96
|
+
{
|
|
97
|
+
const tempDir = createTempRepo();
|
|
98
|
+
tempDirs.push(tempDir);
|
|
99
|
+
|
|
100
|
+
// No gsd.db — just a bare repo
|
|
101
|
+
let threw = false;
|
|
102
|
+
let wtPath: string | null = null;
|
|
103
|
+
try {
|
|
104
|
+
wtPath = createAutoWorktree(tempDir, "M004");
|
|
105
|
+
} catch (err) {
|
|
106
|
+
threw = true;
|
|
107
|
+
console.error(" Unexpected throw:", err);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
assertTrue(!threw, "createAutoWorktree does not throw when no source DB");
|
|
111
|
+
|
|
112
|
+
const worktreeDbPath = join(worktreePath(tempDir, "M004"), ".gsd", "gsd.db");
|
|
113
|
+
assertTrue(
|
|
114
|
+
!existsSync(worktreeDbPath),
|
|
115
|
+
"gsd.db is absent in worktree when source had none",
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
process.chdir(savedCwd);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ─── Test 3: reconcile inserts worktree rows into main ───────────
|
|
122
|
+
console.log("\n=== Test 3: reconcile merges worktree rows into main ===");
|
|
123
|
+
{
|
|
124
|
+
const mainDbPath = join(makeTempDir(), "main.db");
|
|
125
|
+
const worktreeDbPath = join(makeTempDir(), "wt.db");
|
|
126
|
+
|
|
127
|
+
// Seed main DB (empty schema)
|
|
128
|
+
openDatabase(mainDbPath);
|
|
129
|
+
closeDatabase();
|
|
130
|
+
|
|
131
|
+
// Seed worktree DB with one decision
|
|
132
|
+
openDatabase(worktreeDbPath);
|
|
133
|
+
upsertDecision({
|
|
134
|
+
id: "D-WT-001",
|
|
135
|
+
when_context: "integration test",
|
|
136
|
+
scope: "test",
|
|
137
|
+
decision: "use reconcile",
|
|
138
|
+
choice: "reconcile on merge",
|
|
139
|
+
rationale: "test coverage",
|
|
140
|
+
revisable: "no",
|
|
141
|
+
superseded_by: null,
|
|
142
|
+
});
|
|
143
|
+
closeDatabase();
|
|
144
|
+
|
|
145
|
+
// Reconcile worktree → main
|
|
146
|
+
const result = reconcileWorktreeDb(mainDbPath, worktreeDbPath);
|
|
147
|
+
assertTrue(result.decisions >= 1, "reconcile reports at least 1 decision merged");
|
|
148
|
+
|
|
149
|
+
// Open main DB and verify the row is present
|
|
150
|
+
openDatabase(mainDbPath);
|
|
151
|
+
const decisions = getActiveDecisions();
|
|
152
|
+
closeDatabase();
|
|
153
|
+
|
|
154
|
+
const found = decisions.some((d) => d.id === "D-WT-001");
|
|
155
|
+
assertTrue(found, "worktree decision D-WT-001 present in main DB after reconcile");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ─── Test 4: reconcile non-fatal when both paths nonexistent ─────
|
|
159
|
+
console.log("\n=== Test 4: reconcile non-fatal on nonexistent paths ===");
|
|
160
|
+
{
|
|
161
|
+
let threw = false;
|
|
162
|
+
try {
|
|
163
|
+
reconcileWorktreeDb("/nonexistent/path/gsd.db", "/also/nonexistent/gsd.db");
|
|
164
|
+
} catch {
|
|
165
|
+
threw = true;
|
|
166
|
+
}
|
|
167
|
+
assertTrue(!threw, "reconcileWorktreeDb does not throw when worktree DB is absent");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ─── Test 5: failure path observable via stderr (diagnostic) ─────
|
|
171
|
+
// reconcileWorktreeDb emits to stderr on reconciliation failures.
|
|
172
|
+
// We can't easily intercept stderr in this test harness, but we verify
|
|
173
|
+
// that the function returns the zero-result shape (not undefined/throws)
|
|
174
|
+
// when the worktree DB is missing — confirming the failure path is non-fatal
|
|
175
|
+
// and returns a structured result.
|
|
176
|
+
console.log("\n=== Test 5: reconcile returns zero-shape when worktree DB absent ===");
|
|
177
|
+
{
|
|
178
|
+
const mainDbPath = join(makeTempDir(), "main2.db");
|
|
179
|
+
openDatabase(mainDbPath);
|
|
180
|
+
closeDatabase();
|
|
181
|
+
|
|
182
|
+
const result = reconcileWorktreeDb(mainDbPath, "/definitely/does/not/exist.db");
|
|
183
|
+
assertEq(result.decisions, 0, "decisions is 0 when worktree DB absent");
|
|
184
|
+
assertEq(result.requirements, 0, "requirements is 0 when worktree DB absent");
|
|
185
|
+
assertEq(result.artifacts, 0, "artifacts is 0 when worktree DB absent");
|
|
186
|
+
assertEq(result.conflicts.length, 0, "conflicts is empty when worktree DB absent");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
} finally {
|
|
190
|
+
// Always restore cwd
|
|
191
|
+
process.chdir(savedCwd);
|
|
192
|
+
// Ensure DB is closed
|
|
193
|
+
if (isDbAvailable()) closeDatabase();
|
|
194
|
+
// Remove all temp dirs
|
|
195
|
+
for (const dir of tempDirs) {
|
|
196
|
+
if (existsSync(dir)) {
|
|
197
|
+
rmSync(dir, { recursive: true, force: true });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
report();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
main();
|