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.
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +5 -1
- package/dist/resources/extensions/gsd/auto-recovery.js +28 -14
- package/dist/resources/extensions/gsd/auto-start.js +7 -10
- package/dist/resources/extensions/gsd/auto.js +19 -13
- package/dist/resources/extensions/gsd/db-writer.js +13 -3
- package/dist/resources/extensions/gsd/json-persistence.js +5 -2
- package/dist/resources/extensions/gsd/state.js +12 -10
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +15 -3
- package/dist/resources/extensions/gsd/tools/complete-slice.js +15 -3
- package/dist/resources/extensions/gsd/tools/complete-task.js +15 -3
- package/dist/resources/extensions/gsd/triage-resolution.js +8 -7
- package/dist/resources/extensions/gsd/undo.js +3 -2
- package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-reconcile.js +99 -6
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -20
- 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 +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +20 -20
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +5 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +19 -15
- package/src/resources/extensions/gsd/auto-start.ts +7 -10
- package/src/resources/extensions/gsd/auto.ts +17 -7
- package/src/resources/extensions/gsd/db-writer.ts +11 -3
- package/src/resources/extensions/gsd/json-persistence.ts +6 -3
- package/src/resources/extensions/gsd/state.ts +11 -9
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/wave1-critical-regressions.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/wave2-events-regressions.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/wave3-session-regressions.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/wave4-write-safety-regressions.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +6 -3
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +13 -3
- package/src/resources/extensions/gsd/tools/complete-slice.ts +13 -3
- package/src/resources/extensions/gsd/tools/complete-task.ts +13 -3
- package/src/resources/extensions/gsd/triage-resolution.ts +8 -7
- package/src/resources/extensions/gsd/undo.ts +3 -2
- package/src/resources/extensions/gsd/workflow-events.ts +1 -1
- package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-reconcile.ts +107 -5
- /package/dist/web/standalone/.next/static/{Z3TgDP0c7kG9j8CVQVGcl → iueakR5x5bQbax2sGz8Yr}/_buildManifest.js +0 -0
- /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
|
-
|
|
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
|
-
//
|
|
124
|
-
//
|
|
161
|
+
// Replay slice creation — strict 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
|
-
|
|
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 (
|
|
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
|
|
|
File without changes
|
|
File without changes
|