gsd-pi 2.33.1-dev.9bafd68 → 2.33.1-dev.c0b2610

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.
@@ -1359,6 +1359,15 @@ export class AgentSession {
1359
1359
  this.abortRetry();
1360
1360
  this.agent.abort();
1361
1361
  await this.agent.waitForIdle();
1362
+ // Ensure agent_end is emitted even when abort interrupts a tool call (#1414).
1363
+ // The agent may go idle without emitting agent_end if the abort happens
1364
+ // between tool execution and response processing.
1365
+ if (!this.isStreaming && this._extensionRunner) {
1366
+ await this._extensionRunner.emit({
1367
+ type: "agent_end",
1368
+ messages: this.agent.state.messages,
1369
+ });
1370
+ }
1362
1371
  }
1363
1372
 
1364
1373
  /**
@@ -162,6 +162,77 @@ export function syncGsdStateToWorktree(mainBasePath: string, worktreePath_: stri
162
162
  return { synced };
163
163
  }
164
164
 
165
+ /**
166
+ * Sync milestone artifacts from worktree back to the main external state directory.
167
+ * Called before milestone merge to ensure completion artifacts (SUMMARY, VALIDATION,
168
+ * updated ROADMAP) are visible from the project root (#1412).
169
+ *
170
+ * Only syncs .gsd/milestones/ content — root-level files (DECISIONS, REQUIREMENTS, etc.)
171
+ * are handled by the merge itself.
172
+ */
173
+ export function syncWorktreeStateBack(mainBasePath: string, worktreePath: string, milestoneId: string): { synced: string[] } {
174
+ const mainGsd = gsdRoot(mainBasePath);
175
+ const wtGsd = gsdRoot(worktreePath);
176
+ const synced: string[] = [];
177
+
178
+ // If both resolve to the same directory (symlink), no sync needed
179
+ try {
180
+ const mainResolved = realpathSync(mainGsd);
181
+ const wtResolved = realpathSync(wtGsd);
182
+ if (mainResolved === wtResolved) return { synced };
183
+ } catch {
184
+ // Can't resolve — proceed with sync
185
+ }
186
+
187
+ const wtMilestoneDir = join(wtGsd, "milestones", milestoneId);
188
+ const mainMilestoneDir = join(mainGsd, "milestones", milestoneId);
189
+
190
+ if (!existsSync(wtMilestoneDir)) return { synced };
191
+ mkdirSync(mainMilestoneDir, { recursive: true });
192
+
193
+ // Sync milestone-level files (SUMMARY, VALIDATION, ROADMAP, CONTEXT)
194
+ try {
195
+ for (const entry of readdirSync(wtMilestoneDir, { withFileTypes: true })) {
196
+ if (entry.isFile() && entry.name.endsWith(".md")) {
197
+ const src = join(wtMilestoneDir, entry.name);
198
+ const dst = join(mainMilestoneDir, entry.name);
199
+ try {
200
+ cpSync(src, dst, { force: true });
201
+ synced.push(`milestones/${milestoneId}/${entry.name}`);
202
+ } catch { /* non-fatal */ }
203
+ }
204
+ }
205
+ } catch { /* non-fatal */ }
206
+
207
+ // Sync slice-level files (summaries, UATs)
208
+ const wtSlicesDir = join(wtMilestoneDir, "slices");
209
+ const mainSlicesDir = join(mainMilestoneDir, "slices");
210
+ if (existsSync(wtSlicesDir)) {
211
+ try {
212
+ for (const sliceEntry of readdirSync(wtSlicesDir, { withFileTypes: true })) {
213
+ if (!sliceEntry.isDirectory()) continue;
214
+ const sid = sliceEntry.name;
215
+ const wtSliceDir = join(wtSlicesDir, sid);
216
+ const mainSliceDir = join(mainSlicesDir, sid);
217
+ mkdirSync(mainSliceDir, { recursive: true });
218
+
219
+ for (const fileEntry of readdirSync(wtSliceDir, { withFileTypes: true })) {
220
+ if (fileEntry.isFile() && fileEntry.name.endsWith(".md")) {
221
+ const src = join(wtSliceDir, fileEntry.name);
222
+ const dst = join(mainSliceDir, fileEntry.name);
223
+ try {
224
+ cpSync(src, dst, { force: true });
225
+ synced.push(`milestones/${milestoneId}/slices/${sid}/${fileEntry.name}`);
226
+ } catch { /* non-fatal */ }
227
+ }
228
+ }
229
+ }
230
+ } catch { /* non-fatal */ }
231
+ }
232
+
233
+ return { synced };
234
+ }
235
+
165
236
  // ─── Worktree Post-Create Hook (#597) ────────────────────────────────────────
166
237
 
167
238
  /**
@@ -131,6 +131,7 @@ import {
131
131
  getAutoWorktreeOriginalBase,
132
132
  mergeMilestoneToMain,
133
133
  autoWorktreeBranch,
134
+ syncWorktreeStateBack,
134
135
  } from "./auto-worktree.js";
135
136
  import { pruneQueueOrder } from "./queue-order.js";
136
137
  import { consumeSignal } from "./session-status-io.js";
@@ -377,6 +378,16 @@ function tryMergeMilestone(ctx: ExtensionContext, milestoneId: string, mode: "tr
377
378
  // Worktree merge path
378
379
  if (isInAutoWorktree(s.basePath) && s.originalBasePath) {
379
380
  try {
381
+ // Sync completion artifacts from worktree → external state before merge (#1412)
382
+ try {
383
+ const { synced } = syncWorktreeStateBack(s.originalBasePath, s.basePath, milestoneId);
384
+ if (synced.length > 0) {
385
+ debugLog("worktree-reverse-sync", { milestoneId, synced: synced.length });
386
+ }
387
+ } catch (syncErr) {
388
+ debugLog("worktree-reverse-sync-failed", { milestoneId, error: getErrorMessage(syncErr) });
389
+ }
390
+
380
391
  const roadmapPath = resolveMilestoneFile(s.originalBasePath, milestoneId, "ROADMAP");
381
392
  if (!roadmapPath) {
382
393
  teardownAutoWorktree(s.originalBasePath, milestoneId);
@@ -13,8 +13,8 @@ function run(command: string, cwd: string): string {
13
13
  }
14
14
 
15
15
  async function main(): Promise<void> {
16
- const base = mkdtempSync(join(tmpdir(), "gsd-repo-identity-"));
17
- const stateDir = mkdtempSync(join(tmpdir(), "gsd-state-"));
16
+ const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-repo-identity-")));
17
+ const stateDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-state-")));
18
18
 
19
19
  try {
20
20
  process.env.GSD_STATE_DIR = stateDir;
@@ -38,7 +38,7 @@ async function main(): Promise<void> {
38
38
  assertEq(worktreeState, expectedExternalState, "worktree symlink target matches main repo external state dir");
39
39
  assertTrue(existsSync(join(worktreePath, ".gsd")), "worktree .gsd exists");
40
40
  assertTrue(lstatSync(join(worktreePath, ".gsd")).isSymbolicLink(), "worktree .gsd is a symlink");
41
- assertEq(realpathSync(join(worktreePath, ".gsd")), expectedExternalState, "worktree .gsd symlink resolves to main repo external state dir");
41
+ assertEq(realpathSync(join(worktreePath, ".gsd")), realpathSync(expectedExternalState), "worktree .gsd symlink resolves to main repo external state dir");
42
42
 
43
43
  console.log("\n=== ensureGsdSymlink heals stale worktree symlinks ===");
44
44
  const staleState = join(stateDir, "projects", "stale-worktree-state");
@@ -47,7 +47,7 @@ async function main(): Promise<void> {
47
47
  symlinkSync(staleState, join(worktreePath, ".gsd"), "junction");
48
48
  const healedState = ensureGsdSymlink(worktreePath);
49
49
  assertEq(healedState, expectedExternalState, "stale worktree symlink is repaired to canonical external state dir");
50
- assertEq(realpathSync(join(worktreePath, ".gsd")), expectedExternalState, "healed worktree symlink resolves to canonical external state dir");
50
+ assertEq(realpathSync(join(worktreePath, ".gsd")), realpathSync(expectedExternalState), "healed worktree symlink resolves to canonical external state dir");
51
51
 
52
52
  console.log("\n=== ensureGsdSymlink preserves worktree .gsd directories ===");
53
53
  rmSync(join(worktreePath, ".gsd"), { recursive: true, force: true });