gsd-pi 2.81.0-dev.3cddbbba2 → 2.81.0-dev.72a81bdf3

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.
Files changed (89) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/gsd/auto/phases.js +100 -95
  3. package/dist/resources/extensions/gsd/auto-recovery.js +6 -181
  4. package/dist/resources/extensions/gsd/auto.js +6 -3
  5. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +2 -5
  6. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +9 -0
  7. package/dist/resources/extensions/gsd/gsd-db.js +7 -23
  8. package/dist/resources/extensions/gsd/markdown-renderer.js +0 -95
  9. package/dist/resources/extensions/gsd/recovery-classification.js +15 -1
  10. package/dist/resources/extensions/gsd/session-lock.js +40 -0
  11. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +131 -0
  12. package/dist/resources/extensions/gsd/state-reconciliation/drift/merge-state.js +247 -0
  13. package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +50 -0
  14. package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +87 -0
  15. package/dist/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.js +50 -0
  16. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +124 -0
  17. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +32 -0
  18. package/dist/resources/extensions/gsd/state-reconciliation/errors.js +41 -0
  19. package/dist/resources/extensions/gsd/state-reconciliation/index.js +99 -0
  20. package/dist/resources/extensions/gsd/state-reconciliation/registry.js +24 -0
  21. package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +43 -0
  22. package/dist/resources/extensions/gsd/state-reconciliation/types.js +3 -0
  23. package/dist/resources/extensions/gsd/state-reconciliation.js +5 -26
  24. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  25. package/dist/web/standalone/.next/BUILD_ID +1 -1
  26. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  27. package/dist/web/standalone/.next/build-manifest.json +2 -2
  28. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  29. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/index.html +1 -1
  46. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  53. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  54. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  55. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  56. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  57. package/package.json +1 -1
  58. package/src/resources/extensions/gsd/auto/phases.ts +25 -17
  59. package/src/resources/extensions/gsd/auto-recovery.ts +7 -209
  60. package/src/resources/extensions/gsd/auto.ts +7 -3
  61. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +2 -5
  62. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +12 -0
  63. package/src/resources/extensions/gsd/gsd-db.ts +7 -23
  64. package/src/resources/extensions/gsd/markdown-renderer.ts +4 -95
  65. package/src/resources/extensions/gsd/recovery-classification.ts +18 -1
  66. package/src/resources/extensions/gsd/session-lock.ts +41 -0
  67. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +172 -0
  68. package/src/resources/extensions/gsd/state-reconciliation/drift/merge-state.ts +337 -0
  69. package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +69 -0
  70. package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +109 -0
  71. package/src/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.ts +68 -0
  72. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +185 -0
  73. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +46 -0
  74. package/src/resources/extensions/gsd/state-reconciliation/errors.ts +67 -0
  75. package/src/resources/extensions/gsd/state-reconciliation/index.ts +142 -0
  76. package/src/resources/extensions/gsd/state-reconciliation/registry.ts +27 -0
  77. package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +60 -0
  78. package/src/resources/extensions/gsd/state-reconciliation/types.ts +83 -0
  79. package/src/resources/extensions/gsd/state-reconciliation.ts +21 -53
  80. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +1 -1
  81. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +81 -10
  82. package/src/resources/extensions/gsd/tests/integration/integration-proof.test.ts +1 -1
  83. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +1 -1
  84. package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +1 -1
  85. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +6 -3
  86. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +24 -0
  87. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +952 -0
  88. /package/dist/web/standalone/.next/static/{F5x9E6H9k_52fjqyql93y → rIkMv4YSNlfSeqmGqWVns}/_buildManifest.js +0 -0
  89. /package/dist/web/standalone/.next/static/{F5x9E6H9k_52fjqyql93y → rIkMv4YSNlfSeqmGqWVns}/_ssgManifest.js +0 -0
@@ -1,57 +1,25 @@
1
1
  // Project/App: GSD-2
2
- // File Purpose: ADR-015 State Reconciliation module for pre-dispatch runtime invariants.
2
+ // File Purpose: ADR-017 State Reconciliation Module public entry point.
3
+ // Re-exports the drift-driven implementation from the state-reconciliation/
4
+ // folder so existing import paths (./state-reconciliation.js) keep working.
3
5
 
4
- import { deriveState, invalidateStateCache, type DeriveStateOptions } from "./state.js";
5
- import type { GSDState } from "./types.js";
6
+ export {
7
+ reconcileBeforeDispatch,
8
+ ReconciliationFailedError,
9
+ DRIFT_REGISTRY,
10
+ } from "./state-reconciliation/index.js";
6
11
 
7
- export type StateReconciliationResult =
8
- | {
9
- ok: true;
10
- stateSnapshot: GSDState;
11
- repaired: readonly string[];
12
- blockers: readonly string[];
13
- }
14
- | {
15
- ok: false;
16
- reason: string;
17
- stateSnapshot?: GSDState;
18
- repaired: readonly string[];
19
- blockers: readonly string[];
20
- };
12
+ export type {
13
+ DriftContext,
14
+ DriftHandler,
15
+ DriftRecord,
16
+ ReconciliationDeps,
17
+ ReconciliationFailureDetail,
18
+ ReconciliationResult,
19
+ } from "./state-reconciliation/index.js";
21
20
 
22
- export interface StateReconciliationDeps {
23
- invalidateStateCache: () => void;
24
- deriveState: (basePath: string, opts?: DeriveStateOptions) => Promise<GSDState>;
25
- }
26
-
27
- const defaultDeps: StateReconciliationDeps = {
28
- invalidateStateCache,
29
- deriveState,
30
- };
31
-
32
- export async function reconcileBeforeDispatch(
33
- basePath: string,
34
- deps: StateReconciliationDeps = defaultDeps,
35
- opts?: DeriveStateOptions,
36
- ): Promise<StateReconciliationResult> {
37
- deps.invalidateStateCache();
38
- const stateSnapshot = await deps.deriveState(basePath, opts);
39
- const blockers = stateSnapshot.blockers ?? [];
40
-
41
- if (blockers.length > 0 || stateSnapshot.phase === "blocked") {
42
- return {
43
- ok: false,
44
- reason: blockers[0] ?? `State reconciliation blocked in phase ${stateSnapshot.phase}`,
45
- stateSnapshot,
46
- repaired: ["derive-state-cache-invalidated"],
47
- blockers,
48
- };
49
- }
50
-
51
- return {
52
- ok: true,
53
- stateSnapshot,
54
- repaired: ["derive-state-cache-invalidated"],
55
- blockers,
56
- };
57
- }
21
+ export { reconcileBeforeSpawn } from "./state-reconciliation/spawn-gate.js";
22
+ export type {
23
+ SpawnGateDeps,
24
+ SpawnGateResult,
25
+ } from "./state-reconciliation/spawn-gate.js";
@@ -62,7 +62,7 @@ test("#2007 bug 2: recentUnits.push is unconditional — not gated on pendingVer
62
62
  );
63
63
  });
64
64
 
65
- test("#2007 bug 2: detectStuck is still inside the pendingVerificationRetry guard", () => {
65
+ test("#2007 bug 2: pendingVerificationRetry state is available for dispatch regression coverage", () => {
66
66
  const s = new AutoSession();
67
67
  s.pendingVerificationRetry = {
68
68
  unitId: "M001/S01/T01",
@@ -2144,7 +2144,7 @@ test("stuck detection: window resets recovery when deriveState returns a differe
2144
2144
  );
2145
2145
  });
2146
2146
 
2147
- test("stuck detection: does not push to window during verification retry", async () => {
2147
+ test("stuck detection: verification retries remain visible to the sliding window", async () => {
2148
2148
  _resetPendingResolve();
2149
2149
  mock.timers.enable({ apis: ["Date", "setTimeout"], now: 20_000 });
2150
2150
 
@@ -2199,8 +2199,9 @@ test("stuck detection: does not push to window during verification retry", async
2199
2199
 
2200
2200
  const loopPromise = autoLoop(ctx, pi, s, deps);
2201
2201
 
2202
- // Resolve agent_end for 4 iterations (1 initial + 3 retries)
2203
- for (let i = 1; i <= 4; i++) {
2202
+ // Resolve agent_end for 3 attempts. The 4th iteration should stop before
2203
+ // dispatch because retry dispatches stay visible to stuck detection.
2204
+ for (let i = 1; i <= 3; i++) {
2204
2205
  await waitForMicrotasks(() => pi.calls.length === i, `dispatch ${i}`);
2205
2206
  resolveAgentEnd(makeEvent());
2206
2207
  await drainMicrotasks(100);
@@ -2209,16 +2210,14 @@ test("stuck detection: does not push to window during verification retry", async
2209
2210
 
2210
2211
  await loopPromise;
2211
2212
 
2212
- // Even though same unit was derived 4 times, verification retries should
2213
- // not push to the sliding window, so stuck detection should not have fired
2214
2213
  assert.ok(
2215
- !stopReason.includes("Stuck"),
2216
- `stuck detection should not fire during verification retries, got: ${stopReason}`,
2214
+ stopReason.includes("Stuck"),
2215
+ `stuck detection should fire during repeated verification retries, got: ${stopReason}`,
2217
2216
  );
2218
2217
  assert.equal(
2219
2218
  verifyCallCount,
2220
- 4,
2221
- "verification should have been called 4 times (1 initial + 3 retries)",
2219
+ 3,
2220
+ "verification should stop before a 4th repeated retry dispatch",
2222
2221
  );
2223
2222
  } finally {
2224
2223
  mock.timers.reset();
@@ -2304,7 +2303,8 @@ test("detectStuck: truncates long error strings", () => {
2304
2303
  { key: "A", error: longError },
2305
2304
  ]);
2306
2305
  assert.ok(result?.stuck);
2307
- assert.ok(result!.reason.length < 300, "reason should be truncated");
2306
+ assert.ok(result!.reason.includes(longError.slice(0, 200)), "reason should include the truncated error prefix");
2307
+ assert.equal(result!.reason.includes(longError), false, "reason should not include the full long error");
2308
2308
  });
2309
2309
 
2310
2310
  // NOTE: the "stuck-detected" / "stuck-counter-reset" debug-log grep was
@@ -3207,6 +3207,77 @@ test("dispatch Worktree Safety wins before stuck detection for execute-task with
3207
3207
  );
3208
3208
  });
3209
3209
 
3210
+ test("runDispatch runs stuck detection while artifact verification retry is pending (#5719)", async (t) => {
3211
+ _resetPendingResolve();
3212
+
3213
+ const ctx = makeMockCtx();
3214
+ const pi = makeMockPi();
3215
+ const notifications: string[] = [];
3216
+ ctx.ui.notify = (msg: string) => { notifications.push(msg); };
3217
+
3218
+ const basePath = mkdtempSync(join(tmpdir(), "gsd-5719-retry-stuck-"));
3219
+ t.after(() => rmSync(basePath, { recursive: true, force: true }));
3220
+
3221
+ const s = makeLoopSession({
3222
+ basePath,
3223
+ pendingVerificationRetry: {
3224
+ unitId: "M001/S01/T01",
3225
+ failureContext: "ENOENT: no such file or directory, access '/tmp/missing-plan.md'",
3226
+ attempt: 1,
3227
+ },
3228
+ });
3229
+ const deps = makeMockDeps();
3230
+ const loopState = {
3231
+ recentUnits: [
3232
+ {
3233
+ key: "execute-task/M001/S01/T01",
3234
+ error: "ENOENT: no such file or directory, access '/tmp/missing-plan.md'",
3235
+ },
3236
+ { key: "plan-slice/M001/S02", error: "other failure" },
3237
+ {
3238
+ key: "complete-slice/M001/S01",
3239
+ error: "ENOENT: no such file or directory, access '/tmp/missing-plan.md'",
3240
+ },
3241
+ ],
3242
+ stuckRecoveryAttempts: 0,
3243
+ consecutiveFinalizeTimeouts: 0,
3244
+ };
3245
+
3246
+ const result = await runDispatch(
3247
+ {
3248
+ ctx,
3249
+ pi,
3250
+ s,
3251
+ deps,
3252
+ prefs: undefined,
3253
+ iteration: 1,
3254
+ flowId: "test-flow",
3255
+ nextSeq: () => 1,
3256
+ },
3257
+ {
3258
+ state: {
3259
+ phase: "executing",
3260
+ activeMilestone: { id: "M001", title: "Test", status: "active" },
3261
+ activeSlice: { id: "S01", title: "Slice 1" },
3262
+ activeTask: { id: "T01" },
3263
+ registry: [{ id: "M001", status: "active" }],
3264
+ blockers: [],
3265
+ } as any,
3266
+ mid: "M001",
3267
+ midTitle: "Test",
3268
+ },
3269
+ loopState,
3270
+ );
3271
+
3272
+ assert.equal(result.action, "next", "level-1 stuck recovery should still allow the recovery dispatch");
3273
+ assert.equal(loopState.stuckRecoveryAttempts, 1, "stuck recovery should record the first recovery attempt");
3274
+ assert.ok(deps.callLog.includes("invalidateAllCaches"), "stuck recovery should invalidate caches");
3275
+ assert.ok(
3276
+ notifications.some((n) => n.includes("Missing file referenced twice")),
3277
+ "notification should surface the repeated ENOENT stuck reason",
3278
+ );
3279
+ });
3280
+
3210
3281
  test("dispatch Worktree Safety stops unknown unit types with missing Tool Contract", async (t) => {
3211
3282
  _resetPendingResolve();
3212
3283
 
@@ -62,8 +62,8 @@ import {
62
62
  renderRoadmapCheckboxes,
63
63
  renderAllFromDb,
64
64
  detectStaleRenders,
65
- repairStaleRenders,
66
65
  } from "../../markdown-renderer.ts";
66
+ import { repairStaleRenders } from "../../state-reconciliation/drift/stale-render.ts";
67
67
 
68
68
  // ── State derivation ──────────────────────────────────────────────────────
69
69
  import {
@@ -24,8 +24,8 @@ import {
24
24
  renderPlanFromDb,
25
25
  renderTaskPlanFromDb,
26
26
  detectStaleRenders,
27
- repairStaleRenders,
28
27
  } from '../markdown-renderer.ts';
28
+ import { repairStaleRenders } from '../state-reconciliation/drift/stale-render.ts';
29
29
  import {
30
30
  parseRoadmap,
31
31
  parsePlan,
@@ -14,9 +14,9 @@ import {
14
14
  insertMilestone,
15
15
  insertSlice,
16
16
  setSliceSketchFlag,
17
- autoHealSketchFlags,
18
17
  getSlice,
19
18
  } from "../gsd-db.ts";
19
+ import { autoHealSketchFlags } from "../state-reconciliation/drift/sketch-flag.ts";
20
20
  import { deriveStateFromDb } from "../state.ts";
21
21
  import { resolveDispatch } from "../auto-dispatch.ts";
22
22
  import type { DispatchContext } from "../auto-dispatch.ts";
@@ -42,16 +42,19 @@ test("State Reconciliation invalidates cache and returns reconciled state", asyn
42
42
  assert.equal(result.ok && result.stateSnapshot, state);
43
43
  });
44
44
 
45
- test("State Reconciliation blocks when derived state carries blockers", async () => {
45
+ test("State Reconciliation surfaces terminal blockers in result (ADR-017)", async () => {
46
+ // Under ADR-017, blockers are terminal but do not throw — they ride along
47
+ // in the result so the orchestrator adapter can map them to ok=false.
46
48
  const result = await reconcileBeforeDispatch("/project", {
47
49
  invalidateStateCache() {},
48
50
  async deriveState() {
49
51
  return makeState({ phase: "blocked", blockers: ["slice lock missing"] });
50
52
  },
53
+ registry: [],
51
54
  });
52
55
 
53
- assert.equal(result.ok, false);
54
- assert.equal(!result.ok && result.reason, "slice lock missing");
56
+ assert.equal(result.ok, true);
57
+ assert.deepEqual(result.blockers, ["slice lock missing"]);
55
58
  });
56
59
 
57
60
  test("Tool Contract compiles known Unit prompt and tool policy", () => {
@@ -188,6 +188,30 @@ test("empty-content aborted during session-switch is silently ignored", () => {
188
188
  assert.equal(cancelledWith, null);
189
189
  });
190
190
 
191
+ test("completed assistant content with aborted stopReason during session-switch is ignored", () => {
192
+ // newSession() can abort the just-finished provider stream while the last
193
+ // assistant message still carries the completed unit summary. That is a
194
+ // session-transition artifact, not a cancellation for the next unit.
195
+ let cancelledWith: unknown = null;
196
+ const resolveCancelled = (ctx: ErrorContext) => {
197
+ cancelledWith = ctx;
198
+ return true;
199
+ };
200
+
201
+ _handleSessionSwitchAgentEnd(
202
+ {
203
+ stopReason: "aborted",
204
+ content: [{
205
+ type: "text",
206
+ text: "Implemented T01 and verified the slice task is complete.",
207
+ }],
208
+ },
209
+ resolveCancelled,
210
+ );
211
+
212
+ assert.equal(cancelledWith, null);
213
+ });
214
+
191
215
  test("non-abort errors during session-switch are not propagated through this helper", () => {
192
216
  // Real provider errors (rate-limit, network, unsupported-model) are handled
193
217
  // by the post-switch retry pipeline — not by the in-flight switch handler.