gsd-pi 2.82.0-dev.9d5798940 → 2.82.0-dev.dfbc5f58f
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/README.md +2 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +7 -0
- package/dist/resources/extensions/gsd/auto/infra-errors.js +9 -3
- package/dist/resources/extensions/gsd/auto/loop.js +5 -5
- package/dist/resources/extensions/gsd/auto/orchestrator.js +11 -0
- package/dist/resources/extensions/gsd/auto/phases.js +8 -1
- package/dist/resources/extensions/gsd/auto/workflow-memory-pressure.js +12 -0
- package/dist/resources/extensions/gsd/auto-model-selection.js +2 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +1 -1
- package/dist/resources/extensions/gsd/auto-start.js +78 -9
- package/dist/resources/extensions/gsd/auto-worktree.js +15 -1
- package/dist/resources/extensions/gsd/auto.js +30 -3
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +9 -8
- package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +5 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +31 -5
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +1 -0
- package/dist/resources/extensions/gsd/dispatch-guard.js +2 -2
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +28 -11
- package/dist/resources/extensions/gsd/doctor.js +2 -28
- package/dist/resources/extensions/gsd/git-service.js +39 -1
- package/dist/resources/extensions/gsd/gsd-db.js +1 -0
- package/dist/resources/extensions/gsd/guided-flow.js +6 -0
- package/dist/resources/extensions/gsd/migrate/parsers.js +10 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +40 -9
- package/dist/resources/extensions/gsd/post-execution-checks.js +73 -2
- package/dist/resources/extensions/gsd/pre-execution-checks.js +28 -1
- package/dist/resources/extensions/gsd/prompt-loader.js +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +3 -3
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/dist/resources/extensions/gsd/status-guards.js +4 -0
- package/dist/resources/extensions/gsd/templates/plan.md +8 -5
- package/dist/resources/extensions/gsd/templates/task-plan.md +4 -2
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -8
- package/dist/resources/extensions/gsd/tools/complete-slice.js +6 -8
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +7 -1
- package/dist/resources/extensions/gsd/tools/plan-slice.js +88 -14
- package/dist/resources/extensions/gsd/validation.js +23 -1
- package/dist/resources/extensions/gsd/verification-gate.js +68 -7
- package/dist/resources/extensions/gsd/workflow-projections.js +6 -8
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +5 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- 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/server/app/_global-error.html +1 -1
- 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/api/git/route.js +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 +12 -12
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +2 -2
- package/packages/mcp-server/src/workflow-tools.test.ts +1 -1
- package/packages/native/tsconfig.json +2 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +82 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +52 -0
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/simple-options.d.ts +2 -4
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +5 -6
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/simple-options.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/simple-options.test.js +50 -0
- package/packages/pi-ai/dist/providers/simple-options.test.js.map +1 -0
- package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +63 -0
- package/packages/pi-ai/src/providers/openai-codex-responses.ts +91 -1
- package/packages/pi-ai/src/providers/simple-options.test.ts +60 -0
- package/packages/pi-ai/src/providers/simple-options.ts +5 -6
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.js +66 -0
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-thinking-level.test.ts +79 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/GSD-WORKFLOW.md +7 -0
- package/src/resources/extensions/gsd/auto/contracts.ts +14 -6
- package/src/resources/extensions/gsd/auto/infra-errors.ts +9 -3
- package/src/resources/extensions/gsd/auto/loop.ts +8 -5
- package/src/resources/extensions/gsd/auto/orchestrator.ts +11 -0
- package/src/resources/extensions/gsd/auto/phases.ts +7 -1
- package/src/resources/extensions/gsd/auto/workflow-memory-pressure.ts +13 -0
- package/src/resources/extensions/gsd/auto-model-selection.ts +2 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +1 -1
- package/src/resources/extensions/gsd/auto-start.ts +85 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +15 -1
- package/src/resources/extensions/gsd/auto.ts +32 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +9 -8
- package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +3 -1
- package/src/resources/extensions/gsd/crash-recovery.ts +30 -4
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +1 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +2 -2
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +25 -13
- package/src/resources/extensions/gsd/doctor.ts +2 -27
- package/src/resources/extensions/gsd/git-service.ts +45 -1
- package/src/resources/extensions/gsd/gsd-db.ts +3 -0
- package/src/resources/extensions/gsd/guided-flow.ts +6 -0
- package/src/resources/extensions/gsd/migrate/parsers.ts +11 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +46 -9
- package/src/resources/extensions/gsd/post-execution-checks.ts +87 -2
- package/src/resources/extensions/gsd/pre-execution-checks.ts +32 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/src/resources/extensions/gsd/status-guards.ts +5 -0
- package/src/resources/extensions/gsd/templates/plan.md +8 -5
- package/src/resources/extensions/gsd/templates/task-plan.md +4 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +80 -1
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -2
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/guided-flow.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/hook-model-resolution.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/infra-error.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +103 -1
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +24 -1
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +121 -1
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +200 -1
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +86 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/prompt-loader.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +31 -1
- package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +26 -2
- package/src/resources/extensions/gsd/tests/summary-render-parity.test.ts +7 -3
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +110 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/workflow-memory-pressure.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +7 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +8 -10
- package/src/resources/extensions/gsd/tools/complete-slice.ts +6 -8
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +5 -1
- package/src/resources/extensions/gsd/tools/plan-slice.ts +96 -12
- package/src/resources/extensions/gsd/types.ts +1 -1
- package/src/resources/extensions/gsd/validation.ts +23 -1
- package/src/resources/extensions/gsd/verification-gate.ts +78 -6
- package/src/resources/extensions/gsd/workflow-projections.ts +6 -8
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +7 -1
- /package/dist/web/standalone/.next/static/{BdZQhe8yKl6bdKLiXVEzh → q0WYuDVbHeFFYbdd-fei2}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{BdZQhe8yKl6bdKLiXVEzh → q0WYuDVbHeFFYbdd-fei2}/_ssgManifest.js +0 -0
|
@@ -3353,6 +3353,60 @@ test("runDispatch runs stuck detection while artifact verification retry is pend
|
|
|
3353
3353
|
);
|
|
3354
3354
|
});
|
|
3355
3355
|
|
|
3356
|
+
test("runDispatch falls back to main when dispatch guard cannot read main branch (#5530)", async (t) => {
|
|
3357
|
+
_resetPendingResolve();
|
|
3358
|
+
|
|
3359
|
+
const ctx = makeMockCtx();
|
|
3360
|
+
const pi = makeMockPi();
|
|
3361
|
+
const basePath = mkdtempSync(join(tmpdir(), "gsd-5530-main-branch-fallback-"));
|
|
3362
|
+
t.after(() => rmSync(basePath, { recursive: true, force: true }));
|
|
3363
|
+
|
|
3364
|
+
let guardBranch: string | null = null;
|
|
3365
|
+
const s = makeLoopSession({ basePath });
|
|
3366
|
+
const deps = makeMockDeps({
|
|
3367
|
+
getMainBranch: () => {
|
|
3368
|
+
throw new Error("fatal: detected dubious ownership");
|
|
3369
|
+
},
|
|
3370
|
+
getPriorSliceCompletionBlocker: (_basePath, mainBranch) => {
|
|
3371
|
+
guardBranch = mainBranch;
|
|
3372
|
+
return null;
|
|
3373
|
+
},
|
|
3374
|
+
});
|
|
3375
|
+
|
|
3376
|
+
const result = await runDispatch(
|
|
3377
|
+
{
|
|
3378
|
+
ctx,
|
|
3379
|
+
pi,
|
|
3380
|
+
s,
|
|
3381
|
+
deps,
|
|
3382
|
+
prefs: undefined,
|
|
3383
|
+
iteration: 1,
|
|
3384
|
+
flowId: "test-flow",
|
|
3385
|
+
nextSeq: () => 1,
|
|
3386
|
+
},
|
|
3387
|
+
{
|
|
3388
|
+
state: {
|
|
3389
|
+
phase: "executing",
|
|
3390
|
+
activeMilestone: { id: "M001", title: "Test", status: "active" },
|
|
3391
|
+
activeSlice: { id: "S01", title: "Slice 1" },
|
|
3392
|
+
activeTask: { id: "T01" },
|
|
3393
|
+
registry: [{ id: "M001", status: "active" }],
|
|
3394
|
+
blockers: [],
|
|
3395
|
+
} as any,
|
|
3396
|
+
mid: "M001",
|
|
3397
|
+
midTitle: "Test",
|
|
3398
|
+
},
|
|
3399
|
+
{
|
|
3400
|
+
recentUnits: [],
|
|
3401
|
+
stuckRecoveryAttempts: 0,
|
|
3402
|
+
consecutiveFinalizeTimeouts: 0,
|
|
3403
|
+
},
|
|
3404
|
+
);
|
|
3405
|
+
|
|
3406
|
+
assert.equal(guardBranch, "main");
|
|
3407
|
+
assert.equal(result.action, "next");
|
|
3408
|
+
});
|
|
3409
|
+
|
|
3356
3410
|
test("dispatch Worktree Safety stops unknown unit types with missing Tool Contract", async (t) => {
|
|
3357
3411
|
_resetPendingResolve();
|
|
3358
3412
|
|
|
@@ -288,6 +288,51 @@ test("advance() stops when dispatch has no next unit", async () => {
|
|
|
288
288
|
assert.equal(orchestrator.getStatus().phase, "stopped");
|
|
289
289
|
});
|
|
290
290
|
|
|
291
|
+
test("advance() surfaces dispatch blocker reason instead of generic no remaining units", async () => {
|
|
292
|
+
const { deps, calls } = makeDeps({
|
|
293
|
+
dispatch: {
|
|
294
|
+
async decideNextUnit() {
|
|
295
|
+
return {
|
|
296
|
+
kind: "blocked",
|
|
297
|
+
reason: "Milestone M001 validation verdict is needs-remediation but all slices are complete.",
|
|
298
|
+
action: "pause",
|
|
299
|
+
};
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
const orchestrator = createAutoOrchestrator(deps);
|
|
304
|
+
|
|
305
|
+
const result = await orchestrator.advance();
|
|
306
|
+
|
|
307
|
+
assert.equal(result.kind, "blocked");
|
|
308
|
+
if (result.kind !== "blocked") return;
|
|
309
|
+
assert.equal(result.reason, "Milestone M001 validation verdict is needs-remediation but all slices are complete.");
|
|
310
|
+
assert.equal(result.action, "pause");
|
|
311
|
+
assert.ok(calls.includes("journal:advance-blocked"));
|
|
312
|
+
assert.ok(!calls.includes("journal:advance-stopped"));
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("resume() returns blocked when advance detects a dispatch blocker", async () => {
|
|
316
|
+
const { deps } = makeDeps({
|
|
317
|
+
dispatch: {
|
|
318
|
+
async decideNextUnit() {
|
|
319
|
+
return {
|
|
320
|
+
kind: "blocked",
|
|
321
|
+
reason: "remediation required",
|
|
322
|
+
action: "pause",
|
|
323
|
+
};
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
const orchestrator = createAutoOrchestrator(deps);
|
|
328
|
+
|
|
329
|
+
const result = await orchestrator.resume();
|
|
330
|
+
|
|
331
|
+
assert.equal(result.kind, "blocked");
|
|
332
|
+
if (result.kind !== "blocked") return;
|
|
333
|
+
assert.equal(result.reason, "remediation required");
|
|
334
|
+
});
|
|
335
|
+
|
|
291
336
|
test("advance() uses recovery on error", async () => {
|
|
292
337
|
const { deps, calls } = makeDeps({
|
|
293
338
|
runtime: {
|
|
@@ -797,7 +842,9 @@ test("wired DispatchAdapter forwards session-derived dispatch inputs identically
|
|
|
797
842
|
assert.equal(adapterCtx.midTitle, directCtx.midTitle);
|
|
798
843
|
|
|
799
844
|
// Dispatch action equality: both flows reach the same dispatch decision.
|
|
800
|
-
|
|
845
|
+
if (!adapterResult || !("unitType" in adapterResult)) {
|
|
846
|
+
assert.fail("expected adapter result to be a dispatch decision");
|
|
847
|
+
}
|
|
801
848
|
assert.equal(adapterResult.unitType, "execute-task");
|
|
802
849
|
assert.equal(adapterResult.unitId, "T01");
|
|
803
850
|
assert.equal(adapterResult.reason, "test-capture");
|
|
@@ -872,3 +919,35 @@ test("wired DispatchAdapter prefers caller-supplied dispatch inputs over ctx-der
|
|
|
872
919
|
resetRegistry();
|
|
873
920
|
}
|
|
874
921
|
});
|
|
922
|
+
|
|
923
|
+
test("wired DispatchAdapter preserves stop reason as a blocked decision", async () => {
|
|
924
|
+
const stateSnapshot = makeState();
|
|
925
|
+
const stopRule: UnifiedRule = {
|
|
926
|
+
name: "test-stop",
|
|
927
|
+
when: "dispatch",
|
|
928
|
+
evaluation: "first-match",
|
|
929
|
+
where: async () => ({
|
|
930
|
+
action: "stop" as const,
|
|
931
|
+
reason: "remediation blocker",
|
|
932
|
+
level: "warning" as const,
|
|
933
|
+
}),
|
|
934
|
+
then: (r: unknown) => r,
|
|
935
|
+
};
|
|
936
|
+
setRegistry(new RuleRegistry([stopRule]));
|
|
937
|
+
|
|
938
|
+
try {
|
|
939
|
+
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as any;
|
|
940
|
+
const pi = { getActiveTools: () => [] } as any;
|
|
941
|
+
const adapter = createWiredDispatchAdapter(ctx, pi, "/tmp/parity-fixture");
|
|
942
|
+
|
|
943
|
+
const result = await adapter.decideNextUnit({ stateSnapshot });
|
|
944
|
+
|
|
945
|
+
assert.deepEqual(result, {
|
|
946
|
+
kind: "blocked",
|
|
947
|
+
reason: "remediation blocker",
|
|
948
|
+
action: "pause",
|
|
949
|
+
});
|
|
950
|
+
} finally {
|
|
951
|
+
resetRegistry();
|
|
952
|
+
}
|
|
953
|
+
});
|
|
@@ -43,7 +43,7 @@ test("cleanupAfterLoopExit preserves paused auto badge after provider pause", as
|
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
test("cleanupAfterLoopExit clears status without replacing
|
|
46
|
+
test("cleanupAfterLoopExit clears status and progress widget without replacing outcome surface", async () => {
|
|
47
47
|
const statusCalls: unknown[] = [];
|
|
48
48
|
const widgetCalls: unknown[] = [];
|
|
49
49
|
|
|
@@ -64,8 +64,8 @@ test("cleanupAfterLoopExit clears status without replacing the last auto surface
|
|
|
64
64
|
assert.deepEqual(statusCalls, [["gsd-auto", undefined]]);
|
|
65
65
|
assert.equal(
|
|
66
66
|
widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-progress" && args[1] === undefined),
|
|
67
|
-
|
|
68
|
-
"cleanup must
|
|
67
|
+
true,
|
|
68
|
+
"cleanup must clear the stale auto progress widget",
|
|
69
69
|
);
|
|
70
70
|
assert.equal(
|
|
71
71
|
widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-outcome"),
|
|
@@ -79,7 +79,7 @@ test("cleanupAfterLoopExit clears status without replacing the last auto surface
|
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
test("cleanupAfterLoopExit
|
|
82
|
+
test("cleanupAfterLoopExit clears progress widget after stopAuto reset", async () => {
|
|
83
83
|
const statusCalls: unknown[] = [];
|
|
84
84
|
const widgetCalls: unknown[] = [];
|
|
85
85
|
|
|
@@ -103,8 +103,8 @@ test("cleanupAfterLoopExit preserves completion roll-up after stopAuto reset", a
|
|
|
103
103
|
assert.deepEqual(statusCalls, [["gsd-auto", undefined]]);
|
|
104
104
|
assert.equal(
|
|
105
105
|
widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-progress" && args[1] === undefined),
|
|
106
|
-
|
|
107
|
-
"completion cleanup must
|
|
106
|
+
true,
|
|
107
|
+
"completion cleanup must clear the stale progress widget",
|
|
108
108
|
);
|
|
109
109
|
assert.equal(
|
|
110
110
|
widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-outcome"),
|
|
@@ -98,6 +98,7 @@ test("bootstrap aborts before starting next milestone when completed orphan merg
|
|
|
98
98
|
{
|
|
99
99
|
shouldUseWorktreeIsolation: () => true,
|
|
100
100
|
registerSigtermHandler: () => {},
|
|
101
|
+
registerAutoWorkerForSession: () => {},
|
|
101
102
|
lockBase: () => base,
|
|
102
103
|
buildLifecycle: () => ({
|
|
103
104
|
adoptSessionRoot: (sessionBase: string, originalBase?: string) => {
|
|
@@ -645,7 +645,7 @@ describe("complete-milestone", () => {
|
|
|
645
645
|
assert.strictEqual(sanitized.triggerReason, undefined);
|
|
646
646
|
});
|
|
647
647
|
|
|
648
|
-
test("rendered SUMMARY.md uses
|
|
648
|
+
test("rendered SUMMARY.md uses empty frontmatter lists for empty key fields", async () => {
|
|
649
649
|
const { handleCompleteMilestone } = await import("../tools/complete-milestone.ts");
|
|
650
650
|
const base = createFixtureBase();
|
|
651
651
|
const mid = "M001";
|
|
@@ -678,6 +678,9 @@ describe("complete-milestone", () => {
|
|
|
678
678
|
assert.match(summary, /## Success Criteria Results\n\nNot provided\./);
|
|
679
679
|
assert.match(summary, /## Definition of Done Results\n\nNot provided\./);
|
|
680
680
|
assert.match(summary, /## Requirement Outcomes\n\nNot provided\./);
|
|
681
|
+
assert.match(summary, /key_decisions:\s*\[\]/);
|
|
682
|
+
assert.match(summary, /key_files:\s*\[\]/);
|
|
683
|
+
assert.doesNotMatch(summary, /key_(?:decisions|files):\n - \(none\)/);
|
|
681
684
|
assert.match(summary, /## Deviations\n\nNone\./);
|
|
682
685
|
assert.match(summary, /## Follow-ups\n\nNone\./);
|
|
683
686
|
} finally {
|
|
@@ -491,7 +491,9 @@ console.log('\n=== complete-task: minimal params (no keyFiles, keyDecisions, ver
|
|
|
491
491
|
assertTrue(fs.existsSync(result.summaryPath), 'summary file should be written with minimal params');
|
|
492
492
|
const summaryContent = fs.readFileSync(result.summaryPath, 'utf-8');
|
|
493
493
|
assertMatch(summaryContent, /blocker_discovered:\s*false/, 'blocker_discovered should default to false');
|
|
494
|
-
assertMatch(summaryContent,
|
|
494
|
+
assertMatch(summaryContent, /key_files:\s*\[\]/, 'key_files should render as an empty frontmatter list');
|
|
495
|
+
assertMatch(summaryContent, /key_decisions:\s*\[\]/, 'key_decisions should render as an empty frontmatter list');
|
|
496
|
+
assertTrue(!summaryContent.includes(' - (none)'), 'empty frontmatter lists should not render (none) as a list item');
|
|
495
497
|
}
|
|
496
498
|
|
|
497
499
|
cleanupDir(basePath);
|
|
@@ -19,14 +19,15 @@ import {
|
|
|
19
19
|
insertMilestone,
|
|
20
20
|
_getAdapter,
|
|
21
21
|
} from "../gsd-db.ts";
|
|
22
|
-
import { registerAutoWorker } from "../db/auto-workers.ts";
|
|
22
|
+
import { getAutoWorker, registerAutoWorker } from "../db/auto-workers.ts";
|
|
23
23
|
import { claimMilestoneLease } from "../db/milestone-leases.ts";
|
|
24
|
-
import { recordDispatchClaim } from "../db/unit-dispatches.ts";
|
|
24
|
+
import { getLatestForUnit, markRunning, recordDispatchClaim } from "../db/unit-dispatches.ts";
|
|
25
25
|
import { setRuntimeKv, getRuntimeKv } from "../db/runtime-kv.ts";
|
|
26
26
|
import {
|
|
27
27
|
writeLock,
|
|
28
28
|
readCrashLock,
|
|
29
29
|
clearLock,
|
|
30
|
+
clearStaleWorkerLock,
|
|
30
31
|
isLockProcessAlive,
|
|
31
32
|
} from "../crash-recovery.ts";
|
|
32
33
|
import { normalizeRealPath } from "../paths.ts";
|
|
@@ -223,3 +224,43 @@ test("clearLock removes the session_file row for the active worker", (t) => {
|
|
|
223
224
|
assert.equal(getRuntimeKv("worker", workerId, "session_file"), null,
|
|
224
225
|
"session_file row deleted by clearLock");
|
|
225
226
|
});
|
|
227
|
+
|
|
228
|
+
test("clearStaleWorkerLock crashes stale worker and cancels latest active dispatch", (t) => {
|
|
229
|
+
const base = makeBase();
|
|
230
|
+
t.after(() => cleanup(base));
|
|
231
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
232
|
+
insertMilestone({ id: "M001", title: "T", status: "active" });
|
|
233
|
+
const projectRoot = normalizeRealPath(base);
|
|
234
|
+
const workerId = registerAutoWorker({ projectRootRealpath: projectRoot });
|
|
235
|
+
const lease = claimMilestoneLease(workerId, "M001");
|
|
236
|
+
assert.equal(lease.ok, true);
|
|
237
|
+
if (!lease.ok) return;
|
|
238
|
+
const claim = recordDispatchClaim({
|
|
239
|
+
traceId: "t1",
|
|
240
|
+
workerId,
|
|
241
|
+
milestoneLeaseToken: lease.token,
|
|
242
|
+
milestoneId: "M001",
|
|
243
|
+
sliceId: "S01",
|
|
244
|
+
taskId: "T02",
|
|
245
|
+
unitType: "hook/codex-review",
|
|
246
|
+
unitId: "M001/S01/T02",
|
|
247
|
+
});
|
|
248
|
+
assert.equal(claim.ok, true);
|
|
249
|
+
if (!claim.ok) return;
|
|
250
|
+
markRunning(claim.dispatchId);
|
|
251
|
+
setRuntimeKv("worker", workerId, "session_file", "/tmp/pi-session-hook.jsonl");
|
|
252
|
+
setWorkerPid(workerId, 99999);
|
|
253
|
+
expireWorker(workerId);
|
|
254
|
+
|
|
255
|
+
assert.ok(readCrashLock(base), "stale worker is detected before cleanup");
|
|
256
|
+
|
|
257
|
+
clearStaleWorkerLock(base);
|
|
258
|
+
|
|
259
|
+
assert.equal(getAutoWorker(workerId)?.status, "crashed");
|
|
260
|
+
const dispatch = getLatestForUnit("M001/S01/T02");
|
|
261
|
+
assert.ok(dispatch);
|
|
262
|
+
assert.equal(dispatch!.status, "canceled");
|
|
263
|
+
assert.equal(dispatch!.exit_reason, "crash-recovered");
|
|
264
|
+
assert.equal(getRuntimeKv("worker", workerId, "session_file"), null);
|
|
265
|
+
assert.equal(readCrashLock(base), null);
|
|
266
|
+
});
|
|
@@ -272,6 +272,7 @@ test("deep project setup: bootstrap can start auto-mode without an active milest
|
|
|
272
272
|
{
|
|
273
273
|
shouldUseWorktreeIsolation: () => false,
|
|
274
274
|
registerSigtermHandler: () => {},
|
|
275
|
+
registerAutoWorkerForSession: () => {},
|
|
275
276
|
lockBase: () => base,
|
|
276
277
|
buildLifecycle: () => ({
|
|
277
278
|
adoptSessionRoot: (sessionBase: string, originalBase?: string) => {
|
|
@@ -386,6 +387,7 @@ test("deep project setup: bootstrap continues queued M002 without milestone cont
|
|
|
386
387
|
{
|
|
387
388
|
shouldUseWorktreeIsolation: () => false,
|
|
388
389
|
registerSigtermHandler: () => {},
|
|
390
|
+
registerAutoWorkerForSession: () => {},
|
|
389
391
|
lockBase: () => base,
|
|
390
392
|
buildLifecycle: () => ({
|
|
391
393
|
adoptSessionRoot: (sessionBase: string, originalBase?: string) => {
|
|
@@ -47,6 +47,33 @@ test("dispatch guard blocks when prior milestone has incomplete slices", (t) =>
|
|
|
47
47
|
);
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
+
test("dispatch guard skips prior DB parked or deferred milestones without marker files", (t) => {
|
|
51
|
+
const repo = setupRepo();
|
|
52
|
+
t.after(() => teardownRepo(repo));
|
|
53
|
+
|
|
54
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M001"), { recursive: true });
|
|
55
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M002"), { recursive: true });
|
|
56
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M003"), { recursive: true });
|
|
57
|
+
|
|
58
|
+
insertMilestone({ id: "M001", title: "Parked", status: "parked" });
|
|
59
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Incomplete", status: "pending", depends: [], sequence: 1 });
|
|
60
|
+
|
|
61
|
+
insertMilestone({ id: "M002", title: "Deferred", status: "deferred" });
|
|
62
|
+
insertSlice({ id: "S01", milestoneId: "M002", title: "Incomplete", status: "pending", depends: [], sequence: 1 });
|
|
63
|
+
|
|
64
|
+
insertMilestone({ id: "M003", title: "Current", status: "active" });
|
|
65
|
+
insertSlice({ id: "S01", milestoneId: "M003", title: "First", status: "pending", depends: [], sequence: 1 });
|
|
66
|
+
|
|
67
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "# M001\n");
|
|
68
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M002", "M002-ROADMAP.md"), "# M002\n");
|
|
69
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M003", "M003-ROADMAP.md"), "# M003\n");
|
|
70
|
+
|
|
71
|
+
assert.equal(
|
|
72
|
+
getPriorSliceCompletionBlocker(repo, "main", "plan-slice", "M003/S01"),
|
|
73
|
+
null,
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
50
77
|
test("dispatch guard blocks later slice in same milestone when earlier incomplete", (t) => {
|
|
51
78
|
const repo = setupRepo();
|
|
52
79
|
t.after(() => teardownRepo(repo));
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
test("guided milestone discussion callsites pass workingDirectory to loadPrompt", () => {
|
|
10
|
+
const source = readFileSync(join(__dirname, "..", "guided-flow.ts"), "utf-8");
|
|
11
|
+
const calls = [...source.matchAll(/loadPrompt\("guided-discuss-milestone",\s*\{([\s\S]*?)\}\)/g)];
|
|
12
|
+
|
|
13
|
+
assert.equal(calls.length, 6, "all guided-flow guided-discuss-milestone callsites should be covered");
|
|
14
|
+
for (const call of calls) {
|
|
15
|
+
assert.match(
|
|
16
|
+
call[1] ?? "",
|
|
17
|
+
/\bworkingDirectory:\s*basePath\b/,
|
|
18
|
+
"guided-discuss-milestone prompts need workingDirectory so template validation does not crash",
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
@@ -92,6 +92,11 @@ test("resolveModelId: returns undefined for unknown model", () => {
|
|
|
92
92
|
assert.equal(match, undefined);
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
+
test("resolveModelId: returns undefined for missing model ID", () => {
|
|
96
|
+
const match = resolveModelId(undefined, AVAILABLE_MODELS, "anthropic");
|
|
97
|
+
assert.equal(match, undefined);
|
|
98
|
+
});
|
|
99
|
+
|
|
95
100
|
test("resolveModelId: returns undefined for unknown provider/model combo", () => {
|
|
96
101
|
const match = resolveModelId("fakeprovider/fake-model", AVAILABLE_MODELS, undefined);
|
|
97
102
|
assert.equal(match, undefined);
|
|
@@ -9,11 +9,11 @@ import { isInfrastructureError, INFRA_ERROR_CODES } from "../auto/infra-errors.j
|
|
|
9
9
|
test("INFRA_ERROR_CODES contains the expected codes", () => {
|
|
10
10
|
for (const code of [
|
|
11
11
|
"ENOSPC", "ENOMEM", "EROFS", "EDQUOT", "EMFILE", "ENFILE",
|
|
12
|
-
"EAGAIN", "ECONNREFUSED", "ENOTFOUND", "ENETUNREACH",
|
|
12
|
+
"EAGAIN", "ENOBUFS", "ECONNREFUSED", "ENOTFOUND", "ENETUNREACH",
|
|
13
13
|
]) {
|
|
14
14
|
assert.ok(INFRA_ERROR_CODES.has(code), `missing ${code}`);
|
|
15
15
|
}
|
|
16
|
-
assert.equal(INFRA_ERROR_CODES.size,
|
|
16
|
+
assert.equal(INFRA_ERROR_CODES.size, 11, "unexpected extra codes");
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
// ── isInfrastructureError: code property detection ───────────────────────────
|
|
@@ -5,6 +5,8 @@ import test, { describe } from "node:test";
|
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
+
INFRA_ERROR_CODES,
|
|
9
|
+
isInfrastructureError,
|
|
8
10
|
isTransientCooldownError,
|
|
9
11
|
getCooldownRetryAfterMs,
|
|
10
12
|
MAX_COOLDOWN_RETRIES,
|
|
@@ -13,6 +15,13 @@ import {
|
|
|
13
15
|
|
|
14
16
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
15
17
|
|
|
18
|
+
describe("infra error classification", () => {
|
|
19
|
+
test("ENOBUFS is treated as infrastructure exhaustion", () => {
|
|
20
|
+
assert.equal(INFRA_ERROR_CODES.has("ENOBUFS"), true);
|
|
21
|
+
assert.equal(isInfrastructureError(Object.assign(new Error("spawnSync git ENOBUFS"), { code: "ENOBUFS" })), "ENOBUFS");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
16
25
|
describe("infra-errors cooldown constants", () => {
|
|
17
26
|
test("COOLDOWN_FALLBACK_WAIT_MS is a positive number greater than the 30s rate-limit backoff", () => {
|
|
18
27
|
assert.ok(typeof COOLDOWN_FALLBACK_WAIT_MS === "number");
|
|
@@ -446,6 +446,26 @@ node_modules/
|
|
|
446
446
|
const strandedIssues = detect.issues.filter(i => i.code === "stranded_lock_directory");
|
|
447
447
|
assert.deepStrictEqual(strandedIssues.length, 0, "live lock holder: stranded_lock_directory NOT detected");
|
|
448
448
|
});
|
|
449
|
+
|
|
450
|
+
test('stranded_lock_directory still reports when worker lookup fails', async () => {
|
|
451
|
+
const dir = createMinimalProject();
|
|
452
|
+
cleanups.push(dir);
|
|
453
|
+
|
|
454
|
+
const lockDir = join(dir, ".gsd.lock");
|
|
455
|
+
mkdirSync(lockDir, { recursive: true });
|
|
456
|
+
const { openDatabase, _getAdapter, closeDatabase } = await import("../../gsd-db.ts");
|
|
457
|
+
openDatabase(join(dir, ".gsd", "gsd.db"));
|
|
458
|
+
const db = _getAdapter()!;
|
|
459
|
+
db.exec("DROP TABLE workers");
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
const detect = await runGSDDoctor(dir);
|
|
463
|
+
const strandedIssues = detect.issues.filter(i => i.code === "stranded_lock_directory");
|
|
464
|
+
assert.ok(strandedIssues.length > 0, "reports stranded lock directory even when active worker lookup fails");
|
|
465
|
+
} finally {
|
|
466
|
+
closeDatabase();
|
|
467
|
+
}
|
|
468
|
+
});
|
|
449
469
|
} else {
|
|
450
470
|
}
|
|
451
471
|
|
|
@@ -5,7 +5,7 @@ import assert from 'node:assert/strict';
|
|
|
5
5
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, symlinkSync, readFileSync } from "node:fs";
|
|
6
6
|
import { join, dirname } from "node:path";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
|
-
import { execSync } from "node:child_process";
|
|
8
|
+
import { execFileSync, execSync } from "node:child_process";
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
inferCommitType,
|
|
@@ -371,6 +371,18 @@ describe('git-service', async () => {
|
|
|
371
371
|
return dir;
|
|
372
372
|
}
|
|
373
373
|
|
|
374
|
+
function gitRun(args: string[], cwd: string): string {
|
|
375
|
+
return execFileSync("git", args, {
|
|
376
|
+
cwd,
|
|
377
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
378
|
+
encoding: "utf-8",
|
|
379
|
+
env: {
|
|
380
|
+
...process.env,
|
|
381
|
+
GIT_ALLOW_PROTOCOL: "file",
|
|
382
|
+
},
|
|
383
|
+
}).trim();
|
|
384
|
+
}
|
|
385
|
+
|
|
374
386
|
// ─── GitServiceImpl: smart staging ─────────────────────────────────────
|
|
375
387
|
|
|
376
388
|
test('GitServiceImpl: smart staging', () => {
|
|
@@ -413,6 +425,96 @@ describe('git-service', async () => {
|
|
|
413
425
|
rmSync(repo, { recursive: true, force: true });
|
|
414
426
|
});
|
|
415
427
|
|
|
428
|
+
test('GitServiceImpl: task autoCommit skips keyFiles inside submodules', () => {
|
|
429
|
+
const repo = initTempRepo();
|
|
430
|
+
const subSrc = mkdtempSync(join(tmpdir(), "gsd-git-submodule-src-"));
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
gitRun(["init", "-b", "main"], subSrc);
|
|
434
|
+
gitRun(["config", "user.name", "Pi Test"], subSrc);
|
|
435
|
+
gitRun(["config", "user.email", "pi@example.com"], subSrc);
|
|
436
|
+
createFile(subSrc, "tracked.txt", "initial\n");
|
|
437
|
+
gitRun(["add", "-A"], subSrc);
|
|
438
|
+
gitRun(["commit", "-m", "init submodule"], subSrc);
|
|
439
|
+
|
|
440
|
+
gitRun(["-c", "protocol.file.allow=always", "submodule", "add", `file://${subSrc}`, "sub"], repo);
|
|
441
|
+
gitRun(["commit", "-m", "add submodule"], repo);
|
|
442
|
+
|
|
443
|
+
createFile(repo, "sub/copied.txt", "copied from source\n");
|
|
444
|
+
createFile(repo, "src/feature.ts", "export const feature = true;\n");
|
|
445
|
+
createFile(repo, "src/unrelated.ts", "export const unrelated = true;\n");
|
|
446
|
+
|
|
447
|
+
const svc = new GitServiceImpl(repo);
|
|
448
|
+
const taskContext: TaskCommitContext = {
|
|
449
|
+
taskId: "S01/T01",
|
|
450
|
+
taskDisplayId: "T01",
|
|
451
|
+
taskTitle: "fix submodule staging",
|
|
452
|
+
milestoneId: "M001",
|
|
453
|
+
milestoneTitle: "Submodule auto commit",
|
|
454
|
+
sliceId: "S01",
|
|
455
|
+
sliceTitle: "Commit scoped files",
|
|
456
|
+
oneLiner: "Fixed auto commit when key files include submodule paths",
|
|
457
|
+
keyFiles: ["sub/copied.txt", "src/feature.ts"],
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const result = svc.autoCommit("execute-task", "M001/S01/T01", [], taskContext);
|
|
461
|
+
|
|
462
|
+
assert.ok(result !== null, "autoCommit should commit non-submodule changes");
|
|
463
|
+
const committed = gitRun(["show", "--name-only", "--format=", "HEAD"], repo);
|
|
464
|
+
assert.ok(committed.includes("src/feature.ts"), "non-submodule keyFile is committed");
|
|
465
|
+
assert.ok(!committed.includes("sub/copied.txt"), "submodule inner keyFile is not pathspec-staged");
|
|
466
|
+
assert.ok(!committed.includes("src/unrelated.ts"), "scoped staging does not fall back to smartStage");
|
|
467
|
+
} finally {
|
|
468
|
+
rmSync(repo, { recursive: true, force: true });
|
|
469
|
+
rmSync(subSrc, { recursive: true, force: true });
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test('GitServiceImpl: all keyFiles inside submodules falls back to smartStage', () => {
|
|
474
|
+
const repo = initTempRepo();
|
|
475
|
+
const subSrc = mkdtempSync(join(tmpdir(), "gsd-git-all-submodule-src-"));
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
gitRun(["init", "-b", "main"], subSrc);
|
|
479
|
+
gitRun(["config", "user.name", "Pi Test"], subSrc);
|
|
480
|
+
gitRun(["config", "user.email", "pi@example.com"], subSrc);
|
|
481
|
+
createFile(subSrc, "tracked.txt", "initial\n");
|
|
482
|
+
gitRun(["add", "-A"], subSrc);
|
|
483
|
+
gitRun(["commit", "-m", "init submodule"], subSrc);
|
|
484
|
+
|
|
485
|
+
gitRun(["-c", "protocol.file.allow=always", "submodule", "add", `file://${subSrc}`, "sub"], repo);
|
|
486
|
+
gitRun(["commit", "-m", "add submodule"], repo);
|
|
487
|
+
|
|
488
|
+
createFile(repo, "sub/file1.txt", "inside submodule\n");
|
|
489
|
+
createFile(repo, "sub/file2.txt", "also inside\n");
|
|
490
|
+
createFile(repo, "src/real.ts", "export const real = true;\n");
|
|
491
|
+
|
|
492
|
+
const svc = new GitServiceImpl(repo);
|
|
493
|
+
const taskContext: TaskCommitContext = {
|
|
494
|
+
taskId: "S01/T02",
|
|
495
|
+
taskDisplayId: "T02",
|
|
496
|
+
taskTitle: "all keyFiles inside submodule",
|
|
497
|
+
milestoneId: "M001",
|
|
498
|
+
milestoneTitle: "Submodule auto commit",
|
|
499
|
+
sliceId: "S01",
|
|
500
|
+
sliceTitle: "Commit scoped files",
|
|
501
|
+
oneLiner: "Fell back when all key files are inside submodules",
|
|
502
|
+
keyFiles: ["sub", "sub/file1.txt", "sub/file2.txt"],
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
const result = svc.autoCommit("execute-task", "M001/S01/T02", [], taskContext);
|
|
506
|
+
|
|
507
|
+
assert.ok(result !== null, "autoCommit falls back to smartStage when all keyFiles are filtered");
|
|
508
|
+
const committed = gitRun(["show", "--name-only", "--format=", "HEAD"], repo);
|
|
509
|
+
assert.ok(!committed.includes("sub/file1.txt"), "submodule keyFile is not committed");
|
|
510
|
+
assert.ok(!committed.includes("sub/file2.txt"), "submodule keyFile is not committed");
|
|
511
|
+
assert.ok(committed.includes("src/real.ts"), "smartStage fallback commits other dirty files");
|
|
512
|
+
} finally {
|
|
513
|
+
rmSync(repo, { recursive: true, force: true });
|
|
514
|
+
rmSync(subSrc, { recursive: true, force: true });
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
|
|
416
518
|
// ─── GitServiceImpl: smart staging excludes tracked runtime files ──────
|
|
417
519
|
|
|
418
520
|
test('GitServiceImpl: smart staging excludes tracked runtime files', () => {
|
package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts
CHANGED
|
@@ -195,6 +195,11 @@ describe("infrastructure error detection", () => {
|
|
|
195
195
|
assert.equal(isInfrastructureError(err), "EAGAIN");
|
|
196
196
|
});
|
|
197
197
|
|
|
198
|
+
test("ENOBUFS (no buffer space available) is detected", () => {
|
|
199
|
+
const err = Object.assign(new Error("spawnSync git ENOBUFS"), { code: "ENOBUFS" });
|
|
200
|
+
assert.equal(isInfrastructureError(err), "ENOBUFS");
|
|
201
|
+
});
|
|
202
|
+
|
|
198
203
|
test("SQLite WAL corruption is detected via message scan", () => {
|
|
199
204
|
const err = new Error("database disk image is malformed");
|
|
200
205
|
assert.equal(isInfrastructureError(err), "SQLITE_CORRUPT");
|
|
@@ -223,7 +228,7 @@ describe("infrastructure error detection", () => {
|
|
|
223
228
|
test("all INFRA_ERROR_CODES are covered", () => {
|
|
224
229
|
const expectedCodes = [
|
|
225
230
|
"ENOSPC", "ENOMEM", "EROFS", "EDQUOT", "EMFILE",
|
|
226
|
-
"ENFILE", "EAGAIN", "ECONNREFUSED", "ENOTFOUND", "ENETUNREACH",
|
|
231
|
+
"ENFILE", "EAGAIN", "ENOBUFS", "ECONNREFUSED", "ENOTFOUND", "ENETUNREACH",
|
|
227
232
|
];
|
|
228
233
|
for (const code of expectedCodes) {
|
|
229
234
|
assert.ok(INFRA_ERROR_CODES.has(code), `${code} should be in INFRA_ERROR_CODES`);
|
|
@@ -41,6 +41,15 @@ const SAMPLE_ROADMAP = `# Project Roadmap
|
|
|
41
41
|
- [ ] 31 — Notifications
|
|
42
42
|
`;
|
|
43
43
|
|
|
44
|
+
const SAMPLE_VERSION_PREFIX_ROADMAP = `# Project Roadmap
|
|
45
|
+
|
|
46
|
+
## Phases
|
|
47
|
+
|
|
48
|
+
- ✅ **v1.0 MVP** — Phases 1-6 (shipped 2026-02-24)
|
|
49
|
+
- ✅ **v1.1 Onboarding** — Phases 7-9 (shipped 2026-03-01)
|
|
50
|
+
- 🚧 **v1.8 Production** — Phases 44-53
|
|
51
|
+
`;
|
|
52
|
+
|
|
44
53
|
const SAMPLE_PROJECT = `# My Project
|
|
45
54
|
|
|
46
55
|
A sample project for testing the migration parser.
|
|
@@ -246,6 +255,21 @@ test('parseOldRoadmap: flat format', () => {
|
|
|
246
255
|
assert.deepStrictEqual(roadmap.phases[1].done, false, 'flat roadmap: second phase not done');
|
|
247
256
|
});
|
|
248
257
|
|
|
258
|
+
test('parseOldRoadmap: emoji version-prefix phase ranges', () => {
|
|
259
|
+
const roadmap = parseOldRoadmap(SAMPLE_VERSION_PREFIX_ROADMAP);
|
|
260
|
+
assert.deepStrictEqual(roadmap.milestones.length, 0, 'version roadmap: no milestone sections');
|
|
261
|
+
assert.deepStrictEqual(roadmap.phases.length, 3, 'version roadmap: 3 phase ranges');
|
|
262
|
+
assert.deepStrictEqual(roadmap.phases[0].number, 1, 'version roadmap: first range starts at phase 1');
|
|
263
|
+
assert.deepStrictEqual(roadmap.phases[0].title, 'MVP', 'version roadmap: first title');
|
|
264
|
+
assert.deepStrictEqual(roadmap.phases[0].done, true, 'version roadmap: first range done');
|
|
265
|
+
assert.deepStrictEqual(roadmap.phases[1].number, 7, 'version roadmap: second range starts at phase 7');
|
|
266
|
+
assert.deepStrictEqual(roadmap.phases[1].title, 'Onboarding', 'version roadmap: second title');
|
|
267
|
+
assert.deepStrictEqual(roadmap.phases[1].done, true, 'version roadmap: second range done');
|
|
268
|
+
assert.deepStrictEqual(roadmap.phases[2].number, 44, 'version roadmap: third range starts at phase 44');
|
|
269
|
+
assert.deepStrictEqual(roadmap.phases[2].title, 'Production', 'version roadmap: third title');
|
|
270
|
+
assert.deepStrictEqual(roadmap.phases[2].done, false, 'version roadmap: third range in progress');
|
|
271
|
+
});
|
|
272
|
+
|
|
249
273
|
test('parseOldRoadmap: milestone-sectioned with <details>', () => {
|
|
250
274
|
const roadmap = parseOldRoadmap(SAMPLE_MILESTONE_SECTIONED_ROADMAP);
|
|
251
275
|
assert.ok(roadmap.milestones.length >= 2, 'ms roadmap: has milestone sections');
|
|
@@ -387,4 +411,3 @@ test('parseOldProject', () => {
|
|
|
387
411
|
const project = parseOldProject(SAMPLE_PROJECT);
|
|
388
412
|
assert.deepStrictEqual(project, SAMPLE_PROJECT, 'project: returns raw content');
|
|
389
413
|
});
|
|
390
|
-
|