gsd-pi 2.65.0-dev.16e10d7 → 2.65.0-dev.6cc5110

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 (71) hide show
  1. package/dist/resources/extensions/gsd/auto/session.js +4 -0
  2. package/dist/resources/extensions/gsd/auto-dispatch.js +5 -1
  3. package/dist/resources/extensions/gsd/auto-recovery.js +28 -14
  4. package/dist/resources/extensions/gsd/auto-start.js +7 -10
  5. package/dist/resources/extensions/gsd/auto.js +19 -13
  6. package/dist/resources/extensions/gsd/db-writer.js +13 -3
  7. package/dist/resources/extensions/gsd/json-persistence.js +5 -2
  8. package/dist/resources/extensions/gsd/state.js +12 -10
  9. package/dist/resources/extensions/gsd/tools/complete-milestone.js +15 -3
  10. package/dist/resources/extensions/gsd/tools/complete-slice.js +15 -3
  11. package/dist/resources/extensions/gsd/tools/complete-task.js +15 -3
  12. package/dist/resources/extensions/gsd/triage-resolution.js +8 -7
  13. package/dist/resources/extensions/gsd/undo.js +3 -2
  14. package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
  15. package/dist/resources/extensions/gsd/workflow-reconcile.js +99 -6
  16. package/dist/web/standalone/.next/BUILD_ID +1 -1
  17. package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -20
  18. package/dist/web/standalone/.next/build-manifest.json +2 -2
  19. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  20. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  21. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  29. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/index.html +1 -1
  37. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app-paths-manifest.json +20 -20
  44. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  45. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  46. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  47. package/package.json +1 -1
  48. package/src/resources/extensions/gsd/auto/session.ts +4 -0
  49. package/src/resources/extensions/gsd/auto-dispatch.ts +5 -1
  50. package/src/resources/extensions/gsd/auto-recovery.ts +19 -15
  51. package/src/resources/extensions/gsd/auto-start.ts +7 -10
  52. package/src/resources/extensions/gsd/auto.ts +17 -7
  53. package/src/resources/extensions/gsd/db-writer.ts +11 -3
  54. package/src/resources/extensions/gsd/json-persistence.ts +6 -3
  55. package/src/resources/extensions/gsd/state.ts +11 -9
  56. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +6 -6
  57. package/src/resources/extensions/gsd/tests/wave1-critical-regressions.test.ts +49 -0
  58. package/src/resources/extensions/gsd/tests/wave2-events-regressions.test.ts +48 -0
  59. package/src/resources/extensions/gsd/tests/wave3-session-regressions.test.ts +47 -0
  60. package/src/resources/extensions/gsd/tests/wave4-write-safety-regressions.test.ts +70 -0
  61. package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +6 -3
  62. package/src/resources/extensions/gsd/tools/complete-milestone.ts +13 -3
  63. package/src/resources/extensions/gsd/tools/complete-slice.ts +13 -3
  64. package/src/resources/extensions/gsd/tools/complete-task.ts +13 -3
  65. package/src/resources/extensions/gsd/triage-resolution.ts +8 -7
  66. package/src/resources/extensions/gsd/undo.ts +3 -2
  67. package/src/resources/extensions/gsd/workflow-events.ts +1 -1
  68. package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
  69. package/src/resources/extensions/gsd/workflow-reconcile.ts +107 -5
  70. /package/dist/web/standalone/.next/static/{Z3TgDP0c7kG9j8CVQVGcl → iueakR5x5bQbax2sGz8Yr}/_buildManifest.js +0 -0
  71. /package/dist/web/standalone/.next/static/{Z3TgDP0c7kG9j8CVQVGcl → iueakR5x5bQbax2sGz8Yr}/_ssgManifest.js +0 -0
@@ -295,7 +295,7 @@ function _sanitizeForAudit(entry: LogEntry): LogEntry {
295
295
  };
296
296
  if (entry.context) {
297
297
  // Allowlist: only persist known-safe structured keys
298
- const SAFE_KEYS = new Set(["fn", "tool", "mid", "sid", "tid", "worktree"]);
298
+ const SAFE_KEYS = new Set(["fn", "tool", "mid", "sid", "tid", "worktree", "id", "error", "count"]);
299
299
  const filtered: Record<string, string> = {};
300
300
  for (const [k, v] of Object.entries(entry.context)) {
301
301
  if (SAFE_KEYS.has(k)) {
@@ -7,13 +7,20 @@ import {
7
7
  transaction,
8
8
  updateTaskStatus,
9
9
  updateSliceStatus,
10
+ updateMilestoneStatus,
10
11
  getSliceTasks,
12
+ insertMilestone,
13
+ _getAdapter,
14
+ getMilestoneSlices,
11
15
  insertVerificationEvidence,
12
16
  upsertDecision,
13
17
  openDatabase,
14
18
  setTaskBlockerDiscovered,
15
19
  } from "./gsd-db.js";
16
20
  import { isClosedStatus } from "./status-guards.js";
21
+ import { invalidateStateCache } from "./state.js";
22
+ import { clearPathCache } from "./paths.js";
23
+ import { clearParseCache } from "./files.js";
17
24
  import { writeManifest } from "./workflow-manifest.js";
18
25
  import { atomicWriteSync } from "./atomic-write.js";
19
26
  import { acquireSyncLock, releaseSyncLock } from "./sync-lock.js";
@@ -74,7 +81,15 @@ function replayEvents(events: WorkflowEvent[]): void {
74
81
  transaction(() => {
75
82
  for (const event of events) {
76
83
  const p = event.params;
77
- switch (event.cmd) {
84
+ // Normalize cmd format: completion tools write hyphens ("complete-task"),
85
+ // legacy logs use underscores ("complete_task"). Accept both formats.
86
+ // Type guard: malformed event lines with non-string cmd are skipped.
87
+ if (typeof event.cmd !== "string") {
88
+ logWarning("reconcile", `Event with non-string cmd skipped: ${JSON.stringify(event.cmd)}`);
89
+ continue;
90
+ }
91
+ const cmd = event.cmd.replace(/-/g, "_");
92
+ switch (cmd) {
78
93
  case "complete_task": {
79
94
  const milestoneId = p["milestoneId"] as string;
80
95
  const sliceId = p["sliceId"] as string;
@@ -119,9 +134,66 @@ function replayEvents(events: WorkflowEvent[]): void {
119
134
  replaySliceComplete(milestoneId, sliceId, event.ts);
120
135
  break;
121
136
  }
137
+ case "complete_milestone": {
138
+ const milestoneId = p["milestoneId"] as string;
139
+ if (!milestoneId) break;
140
+ // Invariant check: only mark complete if all slices are closed.
141
+ // Without this guard, a reordered/partial event stream could close
142
+ // a milestone while work is still incomplete.
143
+ const mSlices = getMilestoneSlices(milestoneId);
144
+ const allClosed = mSlices.length === 0 || mSlices.every(s => isClosedStatus(s.status));
145
+ if (allClosed) {
146
+ updateMilestoneStatus(milestoneId, "complete", event.ts);
147
+ } else {
148
+ logWarning("reconcile", `Skipping complete_milestone replay for ${milestoneId}: not all slices are closed`);
149
+ }
150
+ break;
151
+ }
152
+ case "plan_milestone": {
153
+ // Replay milestone creation — uses INSERT OR IGNORE (gsd-db's insertMilestone is safe)
154
+ const mId = p["milestoneId"] as string;
155
+ if (mId) {
156
+ insertMilestone({ id: mId, title: (p["title"] as string) ?? mId });
157
+ }
158
+ break;
159
+ }
122
160
  case "plan_slice": {
123
- // plan_slice events are informational slice should already exist.
124
- // No DB mutation needed during replay (the slice was inserted at plan time).
161
+ // Replay slice creationstrict INSERT OR IGNORE to avoid overwriting
162
+ // progressed status. insertSlice() uses ON CONFLICT DO UPDATE which
163
+ // could downgrade a completed slice back to pending.
164
+ const milestoneId = p["milestoneId"] as string;
165
+ const sliceId = p["sliceId"] as string;
166
+ if (milestoneId && sliceId) {
167
+ const adapter = _getAdapter();
168
+ if (adapter) {
169
+ adapter.prepare(
170
+ `INSERT OR IGNORE INTO slices (milestone_id, id, title, status, created_at)
171
+ VALUES (:mid, :sid, :title, 'pending', :ts)`,
172
+ ).run({ ":mid": milestoneId, ":sid": sliceId, ":title": (p["title"] as string) ?? sliceId, ":ts": event.ts });
173
+ }
174
+ }
175
+ break;
176
+ }
177
+ case "plan_task": {
178
+ // Replay task creation — strict INSERT OR IGNORE to avoid overwriting
179
+ // progressed status. insertTask() uses ON CONFLICT DO UPDATE which
180
+ // could downgrade a done/in-progress task back to pending.
181
+ const milestoneId = p["milestoneId"] as string;
182
+ const sliceId = p["sliceId"] as string;
183
+ const taskId = p["taskId"] as string;
184
+ if (milestoneId && sliceId && taskId) {
185
+ const adapter = _getAdapter();
186
+ if (adapter) {
187
+ adapter.prepare(
188
+ `INSERT OR IGNORE INTO tasks (milestone_id, slice_id, id, title, status, created_at)
189
+ VALUES (:mid, :sid, :tid, :title, 'pending', :ts)`,
190
+ ).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId, ":title": (p["title"] as string) ?? taskId, ":ts": event.ts });
191
+ }
192
+ }
193
+ break;
194
+ }
195
+ case "replan_slice": {
196
+ // Informational — replan events don't mutate DB during replay
125
197
  break;
126
198
  }
127
199
  case "save_decision": {
@@ -139,7 +211,7 @@ function replayEvents(events: WorkflowEvent[]): void {
139
211
  break;
140
212
  }
141
213
  default:
142
- // Unknown commands are silently skipped during replay
214
+ logWarning("reconcile", `Unknown event cmd during replay: "${event.cmd}" — skipped`);
143
215
  break;
144
216
  }
145
217
  }
@@ -157,26 +229,42 @@ export function extractEntityKey(
157
229
  event: WorkflowEvent,
158
230
  ): { type: string; id: string } | null {
159
231
  const p = event.params;
232
+ // Normalize cmd format: accept both hyphens and underscores
233
+ if (typeof event.cmd !== "string") return null;
234
+ const cmd = event.cmd.replace(/-/g, "_");
160
235
 
161
- switch (event.cmd) {
236
+ switch (cmd) {
162
237
  case "complete_task":
163
238
  case "start_task":
164
239
  case "report_blocker":
165
240
  case "record_verification":
241
+ case "plan_task":
166
242
  return typeof p["taskId"] === "string"
167
243
  ? { type: "task", id: p["taskId"] }
168
244
  : null;
169
245
 
170
246
  case "complete_slice":
247
+ case "replan_slice":
171
248
  return typeof p["sliceId"] === "string"
172
249
  ? { type: "slice", id: p["sliceId"] }
173
250
  : null;
174
251
 
252
+ case "complete_milestone":
253
+ return typeof p["milestoneId"] === "string"
254
+ ? { type: "milestone", id: p["milestoneId"] }
255
+ : null;
256
+
175
257
  case "plan_slice":
176
258
  return typeof p["sliceId"] === "string"
177
259
  ? { type: "slice_plan", id: p["sliceId"] }
178
260
  : null;
179
261
 
262
+ case "complete_milestone":
263
+ case "plan_milestone":
264
+ return typeof p["milestoneId"] === "string"
265
+ ? { type: "milestone", id: p["milestoneId"] }
266
+ : null;
267
+
180
268
  case "save_decision":
181
269
  if (typeof p["scope"] === "string" && typeof p["decision"] === "string") {
182
270
  return { type: "decision", id: `${p["scope"]}:${p["decision"]}` };
@@ -359,6 +447,14 @@ function _reconcileWorktreeLogsInner(
359
447
  const merged = indexed.map(({ e }) => e);
360
448
 
361
449
  // Step 7: Write merged event log FIRST (so crash recovery can re-derive DB state)
450
+ // Guard: detect concurrent appendEvent calls between our read (step 1) and
451
+ // this rewrite. If the log grew, re-read and retry to avoid dropping events.
452
+ const preWriteEvents = readEvents(mainLogPath);
453
+ if (preWriteEvents.length > mainEvents.length) {
454
+ logWarning("reconcile", `Event log grew during reconcile (${mainEvents.length} → ${preWriteEvents.length}), retrying with fresh read`);
455
+ return _reconcileWorktreeLogsInner(mainBasePath, worktreeBasePath);
456
+ }
457
+
362
458
  const baseEvents = mainEvents.slice(0, forkPoint + 1);
363
459
  const mergedLog = baseEvents.concat(merged);
364
460
  const logContent = mergedLog.map((e) => JSON.stringify(e)).join("\n") + (mergedLog.length > 0 ? "\n" : "");
@@ -376,6 +472,12 @@ function _reconcileWorktreeLogsInner(
376
472
  logWarning("reconcile", "manifest write failed (non-fatal)", { error: (err as Error).message });
377
473
  }
378
474
 
475
+ // Step 10: Invalidate caches so deriveState() sees post-reconcile DB state.
476
+ // Use targeted invalidation (not invalidateAllCaches) to avoid wiping artifacts table.
477
+ invalidateStateCache();
478
+ clearPathCache();
479
+ clearParseCache();
480
+
379
481
  return { autoMerged: merged.length, conflicts: [] };
380
482
  }
381
483