From 9172122faf8571e430fc7c88d396233ff132704a Mon Sep 17 00:00:00 2001 From: NagyVikt Date: Wed, 22 Apr 2026 16:48:32 +0200 Subject: [PATCH 1/3] Make Active Agents plugin edits surface a new VS Code version The extension stayed at 0.0.1 across local reinstalls because nothing enforced a version bump when shipped plugin files changed. This bumps the manifests to 0.0.2 and adds a focused base-branch regression so future plugin edits fail without a higher version. Constraint: Installed VS Code folder names come from manifest.version Rejected: Derive a transient install-time version | hides stale source metadata and lets live/template manifests drift Confidence: high Scope-risk: narrow Directive: Bump both live and template Active Agents manifests whenever shipped plugin files change Tested: node --test test/vscode-active-agents-session-state.test.js Tested: openspec validate agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18 --type change --strict Tested: openspec validate --specs Tested: node scripts/install-vscode-active-agents-extension.js Not-tested: PR merge and sandbox cleanup for agent/codex/always-active-vscode-extension-2026-04-22-16-18 --- .../.openspec.yaml | 2 + .../proposal.md | 16 +++ .../vscode-active-agents-extension/spec.md | 12 ++ .../tasks.md | 30 +++++ .../vscode/guardex-active-agents/package.json | 2 +- ...vscode-active-agents-session-state.test.js | 125 +++++++++++++++++- vscode/guardex-active-agents/package.json | 2 +- 7 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/.openspec.yaml create mode 100644 openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/proposal.md create mode 100644 openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/specs/vscode-active-agents-extension/spec.md create mode 100644 openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/tasks.md diff --git a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/.openspec.yaml b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/.openspec.yaml new file mode 100644 index 0000000..25345f4 --- /dev/null +++ b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-04-22 diff --git a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/proposal.md b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/proposal.md new file mode 100644 index 0000000..849933c --- /dev/null +++ b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/proposal.md @@ -0,0 +1,16 @@ +## Why + +- The Active Agents VS Code companion still showed version `0.0.1` after plugin edits because nothing enforced a visible version bump when the shipped extension changed. +- That makes local reinstall verification ambiguous in VS Code and makes it too easy to ship plugin edits behind a stale installed version label. + +## What Changes + +- Bump the shipped Active Agents extension manifest version. +- Add a focused regression that requires a higher extension version whenever plugin-shipping files change on a branch. +- Keep the live and template extension manifests aligned so installs and scaffolds report the same version. + +## Impact + +- Local VS Code installs show a new extension version after plugin edits. +- Future plugin branches fail fast in tests if they forget to bump the extension version. +- No runtime behavior changes beyond extension metadata and install-path visibility. diff --git a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/specs/vscode-active-agents-extension/spec.md b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/specs/vscode-active-agents-extension/spec.md new file mode 100644 index 0000000..c83de1e --- /dev/null +++ b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/specs/vscode-active-agents-extension/spec.md @@ -0,0 +1,12 @@ +## MODIFIED Requirements + +### Requirement: Active Agents extension installs a visible current plugin version + +The Active Agents VS Code companion SHALL expose a higher extension version whenever shipped plugin files change on a branch, and the live/template manifests SHALL stay aligned. + +#### Scenario: Plugin edits require a version bump + +- **GIVEN** a branch changes any shipped Active Agents extension files under `vscode/guardex-active-agents/**`, `templates/vscode/guardex-active-agents/**`, or `scripts/install-vscode-active-agents-extension.js` +- **WHEN** the focused extension regression suite runs +- **THEN** `vscode/guardex-active-agents/package.json` SHALL declare a version greater than the base branch version +- **AND** `templates/vscode/guardex-active-agents/package.json` SHALL match that same version exactly diff --git a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/tasks.md b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/tasks.md new file mode 100644 index 0000000..72ad69e --- /dev/null +++ b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/tasks.md @@ -0,0 +1,30 @@ +## Definition of Done + +This change is complete only when **all** of the following are true: + +- Every checkbox below is checked. +- The agent branch reaches `MERGED` state on `origin` and the PR URL + state are recorded in the completion handoff. +- If any step blocks (test failure, conflict, ambiguous result), append a `BLOCKED:` line under section 4 explaining the blocker and **STOP**. Do not tick remaining cleanup boxes; do not silently skip the cleanup pipeline. + +## 1. Specification + +- [x] 1.1 Finalize the scope around bumping the VS Code extension version whenever shipped plugin files change. +- [x] 1.2 Define the normative guard in `specs/vscode-active-agents-extension/spec.md`. + +## 2. Implementation + +- [x] 2.1 Bump the live/template Active Agents extension manifests from `0.0.1` to `0.0.2`. +- [x] 2.2 Make the extension install regression read the current manifest version instead of hardcoding `0.0.1`. +- [x] 2.3 Add a focused regression that fails when extension-shipping files change without a higher version than the base branch. + +## 3. Verification + +- [x] 3.1 Run `node --test test/vscode-active-agents-session-state.test.js`. +- [x] 3.2 Run `openspec validate agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18 --type change --strict`. +- [x] 3.3 Run `openspec validate --specs`. + +## 4. Cleanup (mandatory; run before claiming completion) + +- [ ] 4.1 Run `gx branch finish --branch agent/codex/always-active-vscode-extension-2026-04-22-16-18 --base main --via-pr --wait-for-merge --cleanup`. +- [ ] 4.2 Record the PR URL and final merge state (`MERGED`) in the completion handoff. +- [ ] 4.3 Confirm the sandbox worktree is gone (`git worktree list` no longer shows the agent path; `git branch -a` shows no surviving local/remote refs for the branch). diff --git a/templates/vscode/guardex-active-agents/package.json b/templates/vscode/guardex-active-agents/package.json index 56c3698..6f09cad 100644 --- a/templates/vscode/guardex-active-agents/package.json +++ b/templates/vscode/guardex-active-agents/package.json @@ -3,7 +3,7 @@ "displayName": "GitGuardex Active Agents", "description": "Shows live Guardex sandbox sessions and repo changes inside VS Code Source Control.", "publisher": "recodeee", - "version": "0.0.1", + "version": "0.0.2", "license": "MIT", "icon": "icon.png", "engines": { diff --git a/test/vscode-active-agents-session-state.test.js b/test/vscode-active-agents-session-state.test.js index 6dd0077..b2d229a 100644 --- a/test/vscode-active-agents-session-state.test.js +++ b/test/vscode-active-agents-session-state.test.js @@ -8,6 +8,19 @@ const cp = require('node:child_process'); const repoRoot = path.resolve(__dirname, '..'); const sessionScript = path.join(repoRoot, 'scripts', 'agent-session-state.js'); const installScript = path.join(repoRoot, 'scripts', 'install-vscode-active-agents-extension.js'); +const extensionManifestPath = path.join( + repoRoot, + 'vscode', + 'guardex-active-agents', + 'package.json', +); +const templateExtensionManifestPath = path.join( + repoRoot, + 'templates', + 'vscode', + 'guardex-active-agents', + 'package.json', +); const sessionSchema = require(path.join( repoRoot, 'templates', @@ -40,6 +53,83 @@ function initGitRepo(repoPath) { runGit(repoPath, ['config', 'user.name', 'Guardex Tests']); } +function readJson(filePath) { + return JSON.parse(fs.readFileSync(filePath, 'utf8')); +} + +function parseSimpleSemver(version) { + const parts = version.split('.').map((part) => Number.parseInt(part, 10)); + assert.equal(parts.length, 3, `Expected simple semver, received ${version}`); + for (const part of parts) { + assert.equal(Number.isNaN(part), false, `Expected numeric semver, received ${version}`); + } + return parts; +} + +function compareSimpleSemver(left, right) { + const leftParts = parseSimpleSemver(left); + const rightParts = parseSimpleSemver(right); + for (let index = 0; index < leftParts.length; index += 1) { + if (leftParts[index] !== rightParts[index]) { + return leftParts[index] - rightParts[index]; + } + } + return 0; +} + +function resolveRepoBaseRef() { + for (const candidate of ['origin/main', 'main']) { + const result = cp.spawnSync('git', ['-C', repoRoot, 'rev-parse', '--verify', candidate], { + encoding: 'utf8', + }); + if (result.status === 0) { + return candidate; + } + } + throw new Error('Could not resolve a base ref for the extension version guard.'); +} + +function readExtensionManifest(filePath = extensionManifestPath) { + return readJson(filePath); +} + +function readBaseExtensionManifest(baseRef) { + const result = cp.spawnSync( + 'git', + ['-C', repoRoot, 'show', `${baseRef}:vscode/guardex-active-agents/package.json`], + { + encoding: 'utf8', + }, + ); + assert.equal(result.status, 0, result.stderr || result.stdout); + return JSON.parse(result.stdout); +} + +function readChangedExtensionPaths(baseRef) { + const result = cp.spawnSync( + 'git', + [ + '-C', + repoRoot, + 'diff', + '--name-only', + `${baseRef}...HEAD`, + '--', + 'vscode/guardex-active-agents', + 'templates/vscode/guardex-active-agents', + 'scripts/install-vscode-active-agents-extension.js', + ], + { + encoding: 'utf8', + }, + ); + assert.equal(result.status, 0, result.stderr || result.stdout); + return result.stdout + .split('\n') + .map((entry) => entry.trim()) + .filter(Boolean); +} + function setPathMtime(filePath, whenMs) { const when = new Date(whenMs); fs.utimesSync(filePath, when, when); @@ -728,6 +818,7 @@ test('session-schema derives repo change rows from root git status', () => { test('install-vscode-active-agents-extension installs the current extension version and prunes older copies', () => { const tempExtensionsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'guardex-vscode-ext-')); + const manifest = readExtensionManifest(); const staleDir = path.join(tempExtensionsDir, 'recodeee.gitguardex-active-agents-0.0.0'); fs.mkdirSync(staleDir, { recursive: true }); fs.writeFileSync(path.join(staleDir, 'stale.txt'), 'old', 'utf8'); @@ -737,19 +828,47 @@ test('install-vscode-active-agents-extension installs the current extension vers }); assert.equal(result.status, 0, result.stderr); - const installedDir = path.join(tempExtensionsDir, 'recodeee.gitguardex-active-agents-0.0.1'); - const installedManifest = JSON.parse( - fs.readFileSync(path.join(installedDir, 'package.json'), 'utf8'), + const installedDir = path.join( + tempExtensionsDir, + `recodeee.gitguardex-active-agents-${manifest.version}`, ); + const installedManifest = readJson(path.join(installedDir, 'package.json')); assert.equal(fs.existsSync(installedDir), true); assert.equal(fs.existsSync(path.join(installedDir, 'extension.js')), true); assert.equal(fs.existsSync(path.join(installedDir, 'session-schema.js')), true); assert.equal(installedManifest.icon, 'icon.png'); + assert.equal(installedManifest.version, manifest.version); assert.equal(fs.existsSync(path.join(installedDir, 'icon.png')), true); assert.equal(fs.existsSync(staleDir), false); assert.match(result.stdout, /Reload the VS Code window/); }); +test('active-agents extension edits require a higher manifest version than the base branch', () => { + const baseRef = resolveRepoBaseRef(); + const changedPaths = readChangedExtensionPaths(baseRef); + + if (changedPaths.length === 0) { + return; + } + + const liveManifest = readExtensionManifest(); + const templateManifest = readExtensionManifest(templateExtensionManifestPath); + const baseManifest = readBaseExtensionManifest(baseRef); + + assert.equal( + liveManifest.version, + templateManifest.version, + 'Live and template Active Agents manifests must stay in sync.', + ); + assert.ok( + compareSimpleSemver(liveManifest.version, baseManifest.version) > 0, + [ + `Active Agents extension files changed (${changedPaths.join(', ')})`, + `but version ${liveManifest.version} did not increase above ${baseManifest.version}.`, + ].join(' '), + ); +}); + test('active-agents extension registers tree and decoration providers', async () => { const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'guardex-vscode-view-')); const { registrations, vscode } = createMockVscode(tempRoot); diff --git a/vscode/guardex-active-agents/package.json b/vscode/guardex-active-agents/package.json index 56c3698..6f09cad 100644 --- a/vscode/guardex-active-agents/package.json +++ b/vscode/guardex-active-agents/package.json @@ -3,7 +3,7 @@ "displayName": "GitGuardex Active Agents", "description": "Shows live Guardex sandbox sessions and repo changes inside VS Code Source Control.", "publisher": "recodeee", - "version": "0.0.1", + "version": "0.0.2", "license": "MIT", "icon": "icon.png", "engines": { From 33a8461b8610422d3bec3603490797879b46a68e Mon Sep 17 00:00:00 2001 From: NagyVikt Date: Wed, 22 Apr 2026 16:50:24 +0200 Subject: [PATCH 2/3] Start the Active Agents companion as soon as VS Code opens The branch also needed the extension to wake up on startup so the newly versioned install shows live state immediately instead of waiting for a view-open trigger. This adds onStartupFinished to both manifests, keeps the live/template activation contracts aligned, and extends the focused regression to lock that startup behavior in place. Constraint: The shipped live and template manifests must stay identical or local installs and repo scaffolds drift Rejected: Leave activation view-only | delays the companion until the user manually opens the SCM view and weakens the always-active expectation Confidence: high Scope-risk: narrow Directive: Keep the startup activation event and manifest parity covered whenever the Active Agents extension install surface changes Tested: node --test test/vscode-active-agents-session-state.test.js Tested: openspec validate agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18 --type change --strict Tested: openspec validate --specs Not-tested: PR merge and sandbox cleanup for agent/codex/always-active-vscode-extension-2026-04-22-16-18 --- .../proposal.md | 5 ++++- .../specs/vscode-active-agents-extension/spec.md | 11 +++++++++++ .../tasks.md | 5 +++-- templates/vscode/guardex-active-agents/package.json | 1 + test/vscode-active-agents-session-state.test.js | 12 ++++++++++++ vscode/guardex-active-agents/package.json | 1 + 6 files changed, 32 insertions(+), 3 deletions(-) diff --git a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/proposal.md b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/proposal.md index 849933c..db6dd5c 100644 --- a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/proposal.md +++ b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/proposal.md @@ -2,15 +2,18 @@ - The Active Agents VS Code companion still showed version `0.0.1` after plugin edits because nothing enforced a visible version bump when the shipped extension changed. - That makes local reinstall verification ambiguous in VS Code and makes it too easy to ship plugin edits behind a stale installed version label. +- The companion also depended on workspace marker discovery or the first view open before activation, so a freshly reloaded VS Code window could still look stale even after installing the newest extension files. ## What Changes - Bump the shipped Active Agents extension manifest version. - Add a focused regression that requires a higher extension version whenever plugin-shipping files change on a branch. - Keep the live and template extension manifests aligned so installs and scaffolds report the same version. +- Add `onStartupFinished` to the shipped Active Agents manifests and lock that startup activation contract in the focused regression suite. ## Impact - Local VS Code installs show a new extension version after plugin edits. +- Reloaded VS Code windows activate the Active Agents companion immediately instead of waiting for view-open or marker-discovery triggers. - Future plugin branches fail fast in tests if they forget to bump the extension version. -- No runtime behavior changes beyond extension metadata and install-path visibility. +- Runtime behavior changes are limited to earlier extension activation after startup plus extension metadata/install-path visibility. diff --git a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/specs/vscode-active-agents-extension/spec.md b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/specs/vscode-active-agents-extension/spec.md index c83de1e..4ddee7e 100644 --- a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/specs/vscode-active-agents-extension/spec.md +++ b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/specs/vscode-active-agents-extension/spec.md @@ -10,3 +10,14 @@ The Active Agents VS Code companion SHALL expose a higher extension version when - **WHEN** the focused extension regression suite runs - **THEN** `vscode/guardex-active-agents/package.json` SHALL declare a version greater than the base branch version - **AND** `templates/vscode/guardex-active-agents/package.json` SHALL match that same version exactly + +### Requirement: Active Agents extension activates on VS Code startup + +The Active Agents VS Code companion SHALL declare startup activation in both shipped manifests so the companion can initialize immediately after the VS Code window reloads. + +#### Scenario: Startup activation stays in sync across shipped manifests + +- **GIVEN** the live and template Active Agents extension manifests ship in the repo +- **WHEN** the extension install regression suite reads those manifests +- **THEN** both manifests SHALL include `onStartupFinished` +- **AND** the installed extension manifest SHALL preserve that same activation event list diff --git a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/tasks.md b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/tasks.md index 72ad69e..35c3d06 100644 --- a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/tasks.md +++ b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/tasks.md @@ -8,14 +8,15 @@ This change is complete only when **all** of the following are true: ## 1. Specification -- [x] 1.1 Finalize the scope around bumping the VS Code extension version whenever shipped plugin files change. -- [x] 1.2 Define the normative guard in `specs/vscode-active-agents-extension/spec.md`. +- [x] 1.1 Finalize the scope around bumping the VS Code extension version whenever shipped plugin files change and activating the companion on VS Code startup. +- [x] 1.2 Define the normative version + startup requirements in `specs/vscode-active-agents-extension/spec.md`. ## 2. Implementation - [x] 2.1 Bump the live/template Active Agents extension manifests from `0.0.1` to `0.0.2`. - [x] 2.2 Make the extension install regression read the current manifest version instead of hardcoding `0.0.1`. - [x] 2.3 Add a focused regression that fails when extension-shipping files change without a higher version than the base branch. +- [x] 2.4 Add `onStartupFinished` to the live/template manifests and lock the installed-manifest/startup contract in the focused regression suite. ## 3. Verification diff --git a/templates/vscode/guardex-active-agents/package.json b/templates/vscode/guardex-active-agents/package.json index 6f09cad..60f61fa 100644 --- a/templates/vscode/guardex-active-agents/package.json +++ b/templates/vscode/guardex-active-agents/package.json @@ -14,6 +14,7 @@ "Other" ], "activationEvents": [ + "onStartupFinished", "workspaceContains:.omx/state/active-sessions", "workspaceContains:.omx/agent-worktrees", "workspaceContains:.omc/agent-worktrees", diff --git a/test/vscode-active-agents-session-state.test.js b/test/vscode-active-agents-session-state.test.js index b2d229a..e160a0b 100644 --- a/test/vscode-active-agents-session-state.test.js +++ b/test/vscode-active-agents-session-state.test.js @@ -838,6 +838,8 @@ test('install-vscode-active-agents-extension installs the current extension vers assert.equal(fs.existsSync(path.join(installedDir, 'session-schema.js')), true); assert.equal(installedManifest.icon, 'icon.png'); assert.equal(installedManifest.version, manifest.version); + assert.deepEqual(installedManifest.activationEvents, manifest.activationEvents); + assert.equal(installedManifest.activationEvents.includes('onStartupFinished'), true); assert.equal(fs.existsSync(path.join(installedDir, 'icon.png')), true); assert.equal(fs.existsSync(staleDir), false); assert.match(result.stdout, /Reload the VS Code window/); @@ -860,6 +862,16 @@ test('active-agents extension edits require a higher manifest version than the b templateManifest.version, 'Live and template Active Agents manifests must stay in sync.', ); + assert.deepEqual( + liveManifest.activationEvents, + templateManifest.activationEvents, + 'Live and template Active Agents activation events must stay in sync.', + ); + assert.equal( + liveManifest.activationEvents.includes('onStartupFinished'), + true, + 'Active Agents manifests must activate on VS Code startup.', + ); assert.ok( compareSimpleSemver(liveManifest.version, baseManifest.version) > 0, [ diff --git a/vscode/guardex-active-agents/package.json b/vscode/guardex-active-agents/package.json index 6f09cad..60f61fa 100644 --- a/vscode/guardex-active-agents/package.json +++ b/vscode/guardex-active-agents/package.json @@ -14,6 +14,7 @@ "Other" ], "activationEvents": [ + "onStartupFinished", "workspaceContains:.omx/state/active-sessions", "workspaceContains:.omx/agent-worktrees", "workspaceContains:.omc/agent-worktrees", From 5e5b6e45a9bb03ffcb15669c9045b2da2b271348 Mon Sep 17 00:00:00 2001 From: NagyVikt Date: Wed, 22 Apr 2026 16:55:00 +0200 Subject: [PATCH 3/3] Keep the Active Agents companion current inside VS Code The extension now boots on startup, auto-installs a newer workspace build on activate, and offers Reload Window so the newest local companion takes effect without a manual script pass. The focused test suite now locks manifest parity, version bumps, startup activation, and the reload prompt path. Constraint: A running VS Code extension still needs a window reload before newly installed code becomes active Rejected: Silent background install with no reload prompt | leaves the user on stale code with no visible handoff Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep live/template Active Agents manifests and extension runtime behavior aligned whenever shipped companion files change Tested: node --test test/vscode-active-agents-session-state.test.js; openspec validate agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18 --type change --strict; openspec validate --specs; node scripts/install-vscode-active-agents-extension.js Not-tested: Real VS Code notification interaction outside the mocked extension harness --- .../proposal.md | 5 +- .../vscode-active-agents-extension/spec.md | 12 ++ .../tasks.md | 7 +- .../vscode/guardex-active-agents/extension.js | 119 ++++++++++++++++++ .../vscode/guardex-active-agents/package.json | 2 +- ...vscode-active-agents-session-state.test.js | 60 ++++++++- vscode/guardex-active-agents/extension.js | 119 ++++++++++++++++++ vscode/guardex-active-agents/package.json | 2 +- 8 files changed, 319 insertions(+), 7 deletions(-) diff --git a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/proposal.md b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/proposal.md index db6dd5c..079d712 100644 --- a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/proposal.md +++ b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/proposal.md @@ -3,6 +3,7 @@ - The Active Agents VS Code companion still showed version `0.0.1` after plugin edits because nothing enforced a visible version bump when the shipped extension changed. - That makes local reinstall verification ambiguous in VS Code and makes it too easy to ship plugin edits behind a stale installed version label. - The companion also depended on workspace marker discovery or the first view open before activation, so a freshly reloaded VS Code window could still look stale even after installing the newest extension files. +- Even after the repo had a newer companion build, the installed extension still needed a manual install-script run, so the newest repo copy was easy to miss. ## What Changes @@ -10,10 +11,12 @@ - Add a focused regression that requires a higher extension version whenever plugin-shipping files change on a branch. - Keep the live and template extension manifests aligned so installs and scaffolds report the same version. - Add `onStartupFinished` to the shipped Active Agents manifests and lock that startup activation contract in the focused regression suite. +- Auto-install the newest workspace companion build when the running extension version is older, then offer a Reload Window prompt so the new version boots immediately. ## Impact - Local VS Code installs show a new extension version after plugin edits. - Reloaded VS Code windows activate the Active Agents companion immediately instead of waiting for view-open or marker-discovery triggers. +- When a workspace ships a newer companion version than the running extension, the companion updates itself and offers an immediate reload path. - Future plugin branches fail fast in tests if they forget to bump the extension version. -- Runtime behavior changes are limited to earlier extension activation after startup plus extension metadata/install-path visibility. +- Runtime behavior changes are limited to earlier extension activation after startup, one-shot auto-update/reload prompting, and extension metadata/install-path visibility. diff --git a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/specs/vscode-active-agents-extension/spec.md b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/specs/vscode-active-agents-extension/spec.md index 4ddee7e..5b0e43f 100644 --- a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/specs/vscode-active-agents-extension/spec.md +++ b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/specs/vscode-active-agents-extension/spec.md @@ -21,3 +21,15 @@ The Active Agents VS Code companion SHALL declare startup activation in both shi - **WHEN** the extension install regression suite reads those manifests - **THEN** both manifests SHALL include `onStartupFinished` - **AND** the installed extension manifest SHALL preserve that same activation event list + +### Requirement: Active Agents extension installs a newer workspace build and prompts reload + +The Active Agents VS Code companion SHALL install a newer workspace-shipped build of itself when one is available and SHALL offer a reload action so the updated companion takes effect immediately. + +#### Scenario: Running companion sees a newer workspace build + +- **GIVEN** the running Active Agents extension version is older than `vscode/guardex-active-agents/package.json` in the open workspace +- **AND** the workspace contains `scripts/install-vscode-active-agents-extension.js` +- **WHEN** the companion activates +- **THEN** it SHALL run that install script with the workspace root as the current working directory +- **AND** it SHALL show a message offering `Reload Window` diff --git a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/tasks.md b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/tasks.md index 35c3d06..69d5163 100644 --- a/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/tasks.md +++ b/openspec/changes/agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18/tasks.md @@ -8,15 +8,16 @@ This change is complete only when **all** of the following are true: ## 1. Specification -- [x] 1.1 Finalize the scope around bumping the VS Code extension version whenever shipped plugin files change and activating the companion on VS Code startup. -- [x] 1.2 Define the normative version + startup requirements in `specs/vscode-active-agents-extension/spec.md`. +- [x] 1.1 Finalize the scope around bumping the VS Code extension version whenever shipped plugin files change, activating the companion on VS Code startup, and auto-installing newer workspace builds. +- [x] 1.2 Define the normative version + startup + auto-update requirements in `specs/vscode-active-agents-extension/spec.md`. ## 2. Implementation -- [x] 2.1 Bump the live/template Active Agents extension manifests from `0.0.1` to `0.0.2`. +- [x] 2.1 Bump the live/template Active Agents extension manifests from `0.0.1` to `0.0.3`. - [x] 2.2 Make the extension install regression read the current manifest version instead of hardcoding `0.0.1`. - [x] 2.3 Add a focused regression that fails when extension-shipping files change without a higher version than the base branch. - [x] 2.4 Add `onStartupFinished` to the live/template manifests and lock the installed-manifest/startup contract in the focused regression suite. +- [x] 2.5 Auto-install a newer workspace companion build on activate and offer a `Reload Window` action after the update lands. ## 3. Verification diff --git a/templates/vscode/guardex-active-agents/extension.js b/templates/vscode/guardex-active-agents/extension.js index 947e008..f5ef86e 100644 --- a/templates/vscode/guardex-active-agents/extension.js +++ b/templates/vscode/guardex-active-agents/extension.js @@ -20,6 +20,10 @@ const SESSION_SCAN_EXCLUDE_GLOB = '**/{node_modules,.git,.omx/agent-worktrees,.o const WORKTREE_LOCK_SCAN_EXCLUDE_GLOB = '**/{node_modules,.git}/**'; const SESSION_SCAN_LIMIT = 200; const REFRESH_DEBOUNCE_MS = 250; +const ACTIVE_AGENTS_MANIFEST_RELATIVE = path.join('vscode', 'guardex-active-agents', 'package.json'); +const ACTIVE_AGENTS_INSTALL_SCRIPT_RELATIVE = path.join('scripts', 'install-vscode-active-agents-extension.js'); +const RELOAD_WINDOW_ACTION = 'Reload Window'; +const UPDATE_LATER_ACTION = 'Later'; const SESSION_ACTIVITY_GROUPS = [ { kind: 'blocked', label: 'BLOCKED' }, { kind: 'working', label: 'WORKING NOW' }, @@ -565,6 +569,120 @@ function readCurrentBranch(repoRoot) { } } +function parseSimpleSemver(version) { + const parts = String(version || '') + .split('.') + .map((part) => Number.parseInt(part, 10)); + if (parts.length !== 3 || parts.some((part) => Number.isNaN(part))) { + return null; + } + return parts; +} + +function compareSimpleSemver(left, right) { + const leftParts = parseSimpleSemver(left); + const rightParts = parseSimpleSemver(right); + if (!leftParts || !rightParts) { + return 0; + } + + for (let index = 0; index < leftParts.length; index += 1) { + if (leftParts[index] !== rightParts[index]) { + return leftParts[index] - rightParts[index]; + } + } + + return 0; +} + +function readJsonFile(filePath) { + try { + return JSON.parse(fs.readFileSync(filePath, 'utf8')); + } catch (_error) { + return null; + } +} + +function resolveActiveAgentsAutoUpdateCandidate(installedVersion) { + const candidates = []; + + for (const workspaceFolder of vscode.workspace.workspaceFolders || []) { + const repoRoot = workspaceFolder?.uri?.fsPath; + if (!repoRoot) { + continue; + } + + const manifestPath = path.join(repoRoot, ACTIVE_AGENTS_MANIFEST_RELATIVE); + const installScriptPath = path.join(repoRoot, ACTIVE_AGENTS_INSTALL_SCRIPT_RELATIVE); + if (!fs.existsSync(manifestPath) || !fs.existsSync(installScriptPath)) { + continue; + } + + const manifest = readJsonFile(manifestPath); + const nextVersion = typeof manifest?.version === 'string' ? manifest.version.trim() : ''; + if (!nextVersion || compareSimpleSemver(nextVersion, installedVersion) <= 0) { + continue; + } + + candidates.push({ repoRoot, installScriptPath, version: nextVersion }); + } + + candidates.sort((left, right) => compareSimpleSemver(right.version, left.version)); + return candidates[0] || null; +} + +function runActiveAgentsInstallScript(repoRoot, installScriptPath) { + return new Promise((resolve, reject) => { + cp.execFile( + process.execPath, + [installScriptPath], + { cwd: repoRoot, encoding: 'utf8' }, + (error, stdout, stderr) => { + if (error) { + reject(new Error(String(stderr || stdout || error.message || '').trim() || 'install failed')); + return; + } + resolve({ stdout, stderr }); + }, + ); + }); +} + +async function maybeAutoUpdateActiveAgentsExtension(context) { + const installedVersion = typeof context?.extension?.packageJSON?.version === 'string' + ? context.extension.packageJSON.version.trim() + : ''; + if (!installedVersion) { + return; + } + + const candidate = resolveActiveAgentsAutoUpdateCandidate(installedVersion); + if (!candidate) { + return; + } + + try { + await runActiveAgentsInstallScript(candidate.repoRoot, candidate.installScriptPath); + } catch (error) { + const failure = typeof error?.message === 'string' && error.message.trim() + ? error.message.trim() + : 'install failed'; + vscode.window.showWarningMessage?.( + `GitGuardex Active Agents could not auto-update to ${candidate.version}: ${failure}`, + ); + return; + } + + const selection = await vscode.window.showInformationMessage?.( + `GitGuardex Active Agents updated to ${candidate.version}. Reload Window to use the newest companion.`, + RELOAD_WINDOW_ACTION, + UPDATE_LATER_ACTION, + ); + if (selection === RELOAD_WINDOW_ACTION) { + await vscode.commands.executeCommand('workbench.action.reloadWindow'); + } +} + function decorateSession(session, lockRegistry) { return { ...session, @@ -1349,6 +1467,7 @@ function activate(context) { ...bindRefreshWatcher(worktreeLockWatcher, scheduleRefresh), ); void refreshController.refreshNow(); + void maybeAutoUpdateActiveAgentsExtension(context); } function deactivate() {} diff --git a/templates/vscode/guardex-active-agents/package.json b/templates/vscode/guardex-active-agents/package.json index 60f61fa..feaf5b0 100644 --- a/templates/vscode/guardex-active-agents/package.json +++ b/templates/vscode/guardex-active-agents/package.json @@ -3,7 +3,7 @@ "displayName": "GitGuardex Active Agents", "description": "Shows live Guardex sandbox sessions and repo changes inside VS Code Source Control.", "publisher": "recodeee", - "version": "0.0.2", + "version": "0.0.3", "license": "MIT", "icon": "icon.png", "engines": { diff --git a/test/vscode-active-agents-session-state.test.js b/test/vscode-active-agents-session-state.test.js index e160a0b..bd75733 100644 --- a/test/vscode-active-agents-session-state.test.js +++ b/test/vscode-active-agents-session-state.test.js @@ -221,6 +221,7 @@ function createMockVscode(tempRoot) { openedDocuments: [], shownDocuments: [], infoMessages: [], + infoResponses: [], inputResponses: [], quickPickCalls: [], quickPickResponse: undefined, @@ -402,7 +403,7 @@ function createMockVscode(tempRoot) { if (typeof args[0] === 'string') { registrations.informationMessages.push(args[0]); } - return undefined; + return registrations.infoResponses.shift(); }, showErrorMessage: async (message) => { registrations.errorMessages.push(message); @@ -881,6 +882,63 @@ test('active-agents extension edits require a higher manifest version than the b ); }); +test('active-agents extension auto-installs a newer workspace build and offers reload', async () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'guardex-vscode-autoupdate-')); + const repoManifest = { + ...readExtensionManifest(), + version: '9.9.9', + }; + const repoManifestPath = path.join(tempRoot, 'vscode', 'guardex-active-agents', 'package.json'); + const repoInstallScriptPath = path.join(tempRoot, 'scripts', 'install-vscode-active-agents-extension.js'); + fs.mkdirSync(path.dirname(repoManifestPath), { recursive: true }); + fs.writeFileSync(repoManifestPath, `${JSON.stringify(repoManifest, null, 2)}\n`, 'utf8'); + fs.mkdirSync(path.dirname(repoInstallScriptPath), { recursive: true }); + fs.writeFileSync(repoInstallScriptPath, '#!/usr/bin/env node\n', 'utf8'); + + const execCalls = []; + const originalExecFile = cp.execFile; + cp.execFile = (file, args, options, callback) => { + execCalls.push({ file, args, options }); + callback(null, '[guardex-active-agents] ok\n', ''); + }; + + try { + const { registrations, vscode } = createMockVscode(tempRoot); + registrations.infoResponses.push('Reload Window'); + const extension = loadExtensionWithMockVscode(vscode); + const context = { + subscriptions: [], + extension: { + packageJSON: { + version: '0.0.2', + }, + }, + }; + + extension.activate(context); + await flushAsyncWork(); + + assert.equal(execCalls.length, 1); + assert.equal(execCalls[0].file, process.execPath); + assert.deepEqual(execCalls[0].args, [repoInstallScriptPath]); + assert.equal(execCalls[0].options.cwd, tempRoot); + assert.equal(execCalls[0].options.encoding, 'utf8'); + assert.match( + registrations.informationMessages.at(-1), + /GitGuardex Active Agents updated to 9\.9\.9/, + ); + assert.deepEqual(registrations.infoMessages.at(-1).slice(1), ['Reload Window', 'Later']); + assert.equal( + registrations.executedCommands.some( + (entry) => entry.command === 'workbench.action.reloadWindow', + ), + true, + ); + } finally { + cp.execFile = originalExecFile; + } +}); + test('active-agents extension registers tree and decoration providers', async () => { const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'guardex-vscode-view-')); const { registrations, vscode } = createMockVscode(tempRoot); diff --git a/vscode/guardex-active-agents/extension.js b/vscode/guardex-active-agents/extension.js index 4718b6c..be733a2 100644 --- a/vscode/guardex-active-agents/extension.js +++ b/vscode/guardex-active-agents/extension.js @@ -20,6 +20,10 @@ const SESSION_SCAN_EXCLUDE_GLOB = '**/{node_modules,.git,.omx/agent-worktrees,.o const WORKTREE_LOCK_SCAN_EXCLUDE_GLOB = '**/{node_modules,.git}/**'; const SESSION_SCAN_LIMIT = 200; const REFRESH_DEBOUNCE_MS = 250; +const ACTIVE_AGENTS_MANIFEST_RELATIVE = path.join('vscode', 'guardex-active-agents', 'package.json'); +const ACTIVE_AGENTS_INSTALL_SCRIPT_RELATIVE = path.join('scripts', 'install-vscode-active-agents-extension.js'); +const RELOAD_WINDOW_ACTION = 'Reload Window'; +const UPDATE_LATER_ACTION = 'Later'; const SESSION_ACTIVITY_GROUPS = [ { kind: 'blocked', label: 'BLOCKED' }, { kind: 'working', label: 'WORKING NOW' }, @@ -451,6 +455,120 @@ function readCurrentBranch(repoRoot) { } } +function parseSimpleSemver(version) { + const parts = String(version || '') + .split('.') + .map((part) => Number.parseInt(part, 10)); + if (parts.length !== 3 || parts.some((part) => Number.isNaN(part))) { + return null; + } + return parts; +} + +function compareSimpleSemver(left, right) { + const leftParts = parseSimpleSemver(left); + const rightParts = parseSimpleSemver(right); + if (!leftParts || !rightParts) { + return 0; + } + + for (let index = 0; index < leftParts.length; index += 1) { + if (leftParts[index] !== rightParts[index]) { + return leftParts[index] - rightParts[index]; + } + } + + return 0; +} + +function readJsonFile(filePath) { + try { + return JSON.parse(fs.readFileSync(filePath, 'utf8')); + } catch (_error) { + return null; + } +} + +function resolveActiveAgentsAutoUpdateCandidate(installedVersion) { + const candidates = []; + + for (const workspaceFolder of vscode.workspace.workspaceFolders || []) { + const repoRoot = workspaceFolder?.uri?.fsPath; + if (!repoRoot) { + continue; + } + + const manifestPath = path.join(repoRoot, ACTIVE_AGENTS_MANIFEST_RELATIVE); + const installScriptPath = path.join(repoRoot, ACTIVE_AGENTS_INSTALL_SCRIPT_RELATIVE); + if (!fs.existsSync(manifestPath) || !fs.existsSync(installScriptPath)) { + continue; + } + + const manifest = readJsonFile(manifestPath); + const nextVersion = typeof manifest?.version === 'string' ? manifest.version.trim() : ''; + if (!nextVersion || compareSimpleSemver(nextVersion, installedVersion) <= 0) { + continue; + } + + candidates.push({ repoRoot, installScriptPath, version: nextVersion }); + } + + candidates.sort((left, right) => compareSimpleSemver(right.version, left.version)); + return candidates[0] || null; +} + +function runActiveAgentsInstallScript(repoRoot, installScriptPath) { + return new Promise((resolve, reject) => { + cp.execFile( + process.execPath, + [installScriptPath], + { cwd: repoRoot, encoding: 'utf8' }, + (error, stdout, stderr) => { + if (error) { + reject(new Error(String(stderr || stdout || error.message || '').trim() || 'install failed')); + return; + } + resolve({ stdout, stderr }); + }, + ); + }); +} + +async function maybeAutoUpdateActiveAgentsExtension(context) { + const installedVersion = typeof context?.extension?.packageJSON?.version === 'string' + ? context.extension.packageJSON.version.trim() + : ''; + if (!installedVersion) { + return; + } + + const candidate = resolveActiveAgentsAutoUpdateCandidate(installedVersion); + if (!candidate) { + return; + } + + try { + await runActiveAgentsInstallScript(candidate.repoRoot, candidate.installScriptPath); + } catch (error) { + const failure = typeof error?.message === 'string' && error.message.trim() + ? error.message.trim() + : 'install failed'; + vscode.window.showWarningMessage?.( + `GitGuardex Active Agents could not auto-update to ${candidate.version}: ${failure}`, + ); + return; + } + + const selection = await vscode.window.showInformationMessage?.( + `GitGuardex Active Agents updated to ${candidate.version}. Reload Window to use the newest companion.`, + RELOAD_WINDOW_ACTION, + UPDATE_LATER_ACTION, + ); + if (selection === RELOAD_WINDOW_ACTION) { + await vscode.commands.executeCommand('workbench.action.reloadWindow'); + } +} + function decorateSession(session, lockRegistry) { return { ...session, @@ -1190,6 +1308,7 @@ function activate(context) { ...bindRefreshWatcher(worktreeLockWatcher, scheduleRefresh), ); void refreshController.refreshNow(); + void maybeAutoUpdateActiveAgentsExtension(context); } function deactivate() {} diff --git a/vscode/guardex-active-agents/package.json b/vscode/guardex-active-agents/package.json index 60f61fa..feaf5b0 100644 --- a/vscode/guardex-active-agents/package.json +++ b/vscode/guardex-active-agents/package.json @@ -3,7 +3,7 @@ "displayName": "GitGuardex Active Agents", "description": "Shows live Guardex sandbox sessions and repo changes inside VS Code Source Control.", "publisher": "recodeee", - "version": "0.0.2", + "version": "0.0.3", "license": "MIT", "icon": "icon.png", "engines": {