From e1f51592b1a7c39288334393fd5663f0d16e62af Mon Sep 17 00:00:00 2001 From: Lex Christopherson Date: Wed, 25 Mar 2026 22:20:43 -0600 Subject: [PATCH 1/2] fix(gsd): handle session_switch event so /resume restores GSD state (#2587) The GSD extension only listened for session_start, not session_switch. When /resume switched to a previous session, GSD's write-gate, loop guard, discussion flow, service tier, and tool API keys were never re-initialized, leaving GSD in stale state from the prior session. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/bootstrap/register-hooks.ts | 8 ++++++++ src/resources/extensions/gsd/extension-manifest.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/resources/extensions/gsd/bootstrap/register-hooks.ts b/src/resources/extensions/gsd/bootstrap/register-hooks.ts index 0cdc0353f..9de7759e8 100644 --- a/src/resources/extensions/gsd/bootstrap/register-hooks.ts +++ b/src/resources/extensions/gsd/bootstrap/register-hooks.ts @@ -69,6 +69,14 @@ export function registerHooks(pi: ExtensionAPI): void { } }); + pi.on("session_switch", async (_event, ctx) => { + resetWriteGateState(); + resetToolCallLoopGuard(); + clearDiscussionFlowState(); + await syncServiceTierStatus(ctx); + loadToolApiKeys(); + }); + pi.on("before_agent_start", async (event, ctx: ExtensionContext) => { return buildBeforeAgentStartResult(event, ctx); }); diff --git a/src/resources/extensions/gsd/extension-manifest.json b/src/resources/extensions/gsd/extension-manifest.json index a1b2877be..2c01ab4ee 100644 --- a/src/resources/extensions/gsd/extension-manifest.json +++ b/src/resources/extensions/gsd/extension-manifest.json @@ -12,7 +12,7 @@ "gsd_requirement_update", "gsd_milestone_generate_id" ], "commands": ["gsd", "kill", "worktree", "exit"], - "hooks": ["session_start"], + "hooks": ["session_start", "session_switch"], "shortcuts": ["Ctrl+Alt+G"] } } From f00b69621fd73ec357de92e1661f63cd2a08c408 Mon Sep 17 00:00:00 2001 From: Lex Christopherson Date: Wed, 25 Mar 2026 22:26:35 -0600 Subject: [PATCH 2/2] fix(tests): replace undefined assertTrue/assertEq with assert.ok/deepStrictEqual These test files imported `assert` from node:assert/strict but used assertTrue/assertEq (from test-helpers.ts createTestContext) without importing them, breaking typecheck:extensions on main and all PRs. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../gsd/tests/doctor-environment.test.ts | 16 ++++++++-------- .../extensions/gsd/tests/doctor-git.test.ts | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/resources/extensions/gsd/tests/doctor-environment.test.ts b/src/resources/extensions/gsd/tests/doctor-environment.test.ts index 59263f2b7..de53efa6a 100644 --- a/src/resources/extensions/gsd/tests/doctor-environment.test.ts +++ b/src/resources/extensions/gsd/tests/doctor-environment.test.ts @@ -143,8 +143,8 @@ describe('doctor-environment', async () => { cleanups.push(dir); const results = runEnvironmentChecks(dir); const depsCheck = results.find(r => r.name === "dependencies"); - assertTrue(depsCheck !== undefined, "dependencies check runs"); - assertEq(depsCheck!.status, "ok", "npm marker newer than lockfile → not stale"); + assert.ok(depsCheck !== undefined, "dependencies check runs"); + assert.deepStrictEqual(depsCheck!.status, "ok", "npm marker newer than lockfile → not stale"); } console.log("\n=== env: yarn marker file newer than lockfile → ok (#1974) ==="); @@ -167,8 +167,8 @@ describe('doctor-environment', async () => { cleanups.push(dir); const results = runEnvironmentChecks(dir); const depsCheck = results.find(r => r.name === "dependencies"); - assertTrue(depsCheck !== undefined, "dependencies check runs"); - assertEq(depsCheck!.status, "ok", "yarn marker newer than lockfile → not stale"); + assert.ok(depsCheck !== undefined, "dependencies check runs"); + assert.deepStrictEqual(depsCheck!.status, "ok", "yarn marker newer than lockfile → not stale"); } console.log("\n=== env: pnpm marker file newer than lockfile → ok (#1974) ==="); @@ -191,8 +191,8 @@ describe('doctor-environment', async () => { cleanups.push(dir); const results = runEnvironmentChecks(dir); const depsCheck = results.find(r => r.name === "dependencies"); - assertTrue(depsCheck !== undefined, "dependencies check runs"); - assertEq(depsCheck!.status, "ok", "pnpm marker newer than lockfile → not stale"); + assert.ok(depsCheck !== undefined, "dependencies check runs"); + assert.deepStrictEqual(depsCheck!.status, "ok", "pnpm marker newer than lockfile → not stale"); } console.log("\n=== env: no marker file falls back to dir mtime → stale warning (#1974) ==="); @@ -212,8 +212,8 @@ describe('doctor-environment', async () => { cleanups.push(dir); const results = runEnvironmentChecks(dir); const depsCheck = results.find(r => r.name === "dependencies"); - assertTrue(depsCheck !== undefined, "dependencies check runs"); - assertEq(depsCheck!.status, "warning", "no marker + lockfile newer → stale warning"); + assert.ok(depsCheck !== undefined, "dependencies check runs"); + assert.deepStrictEqual(depsCheck!.status, "warning", "no marker + lockfile newer → stale warning"); } // ── Env File Check ───────────────────────────────────────────────── diff --git a/src/resources/extensions/gsd/tests/doctor-git.test.ts b/src/resources/extensions/gsd/tests/doctor-git.test.ts index eabb2daf5..cdffe17ae 100644 --- a/src/resources/extensions/gsd/tests/doctor-git.test.ts +++ b/src/resources/extensions/gsd/tests/doctor-git.test.ts @@ -167,22 +167,22 @@ describe('doctor-git', async () => { const fixed = await runGSDDoctor(dir, { fix: true, isolationMode: "worktree" }); // The fix must NOT skip removal — it should chdir out and remove - assertTrue( + assert.ok( !fixed.fixesApplied.some(f => f.includes("skipped removing worktree")), "does NOT skip removal when cwd is inside worktree", ); - assertTrue( + assert.ok( fixed.fixesApplied.some(f => f.includes("removed orphaned worktree")), "removes orphaned worktree even when cwd was inside it", ); // Verify worktree is gone const wtList = run("git worktree list", dir); - assertTrue(!wtList.includes("milestone/M001"), "worktree removed after fix with cwd inside"); + assert.ok(!wtList.includes("milestone/M001"), "worktree removed after fix with cwd inside"); // Verify cwd was moved out (should be basePath, not still inside worktree) const newCwd = process.cwd(); - assertTrue( + assert.ok( !newCwd.startsWith(wtPath), "cwd moved out of worktree after fix", );