Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-04-21
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## Why

- The Guardex Active Agents VS Code companion currently throws `this._dataProvider.getTreeItem is not a function` at render time.
- That crash keeps the Source Control view from showing live sandbox sessions even when `.omx/state/active-sessions/*.json` exists.

## What Changes

- Add the missing `getTreeItem` implementation to the Active Agents tree provider in the extension bundle this branch installs.
- Add focused regression coverage that activates the extension against a mocked VS Code host and asserts the provider satisfies the tree-data contract.

## Impact

- Affected surfaces: `templates/vscode/guardex-active-agents/extension.js`, `test/vscode-active-agents-session-state.test.js`, and this change workspace.
- Risk is narrow because the fix only restores the provider method VS Code already expects and the new test covers the exact failure mode.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## ADDED Requirements

### Requirement: Active Agents tree provider satisfies the VS Code contract
The Guardex Active Agents extension SHALL register a tree data provider that implements the tree item resolution contract required by `gitguardex.activeAgents`.

#### Scenario: Provider exposes tree item resolution
- **WHEN** the Active Agents extension activates
- **THEN** the registered provider exposes a callable `getTreeItem`
- **AND** rendering tree items does not throw `this._dataProvider.getTreeItem is not a function`.

#### Scenario: Regression coverage locks the provider contract
- **WHEN** the extension is exercised under the repo test harness with a mocked VS Code host
- **THEN** activation succeeds
- **AND** the regression test fails if the provider is registered without `getTreeItem`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## 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 Capture the Active Agents VS Code failure mode and acceptance criteria for `agent-codex-demo-vscode-active-agents-2026-04-21-18-04`.
- [x] 1.2 Define the tree-provider requirements in `specs/demo-vscode-active-agents/spec.md`.

## 2. Implementation

- [x] 2.1 Add the missing `getTreeItem` implementation to the Active Agents extension bundle this branch installs.
- [x] 2.2 Add focused regression coverage for extension activation/provider registration.

## 3. Verification

- [x] 3.1 Run `node --test test/vscode-active-agents-session-state.test.js`.
- [x] 3.2 Run `openspec validate agent-codex-demo-vscode-active-agents-2026-04-21-18-04 --type change --strict`.
- [x] 3.3 Run `openspec validate --specs`.

## 4. Cleanup (mandatory; run before claiming completion)

- [ ] 4.1 Run the cleanup pipeline: `bash scripts/agent-branch-finish.sh --branch agent/<your-name>/<branch-slug> --base dev --via-pr --wait-for-merge --cleanup`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation.
- [ ] 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).
4 changes: 4 additions & 0 deletions templates/vscode/guardex-active-agents/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ class ActiveAgentsProvider {
this.onDidChangeTreeData = this.onDidChangeTreeDataEmitter.event;
}

getTreeItem(element) {
return element;
}

refresh() {
this.onDidChangeTreeDataEmitter.fire();
}
Expand Down
110 changes: 110 additions & 0 deletions test/vscode-active-agents-session-state.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const sessionSchema = require(path.join(
'guardex-active-agents',
'session-schema.js',
));
const extensionEntry = path.join(repoRoot, 'templates', 'vscode', 'guardex-active-agents', 'extension.js');

function runNode(scriptPath, args, options = {}) {
return cp.spawnSync('node', [scriptPath, ...args], {
Expand All @@ -23,6 +24,92 @@ function runNode(scriptPath, args, options = {}) {
});
}

function loadExtensionWithMockVscode(mockVscode) {
const Module = require('node:module');
const originalLoad = Module._load;
delete require.cache[require.resolve(extensionEntry)];

Module._load = function patchedModuleLoad(request, parent, isMain) {
if (request === 'vscode') {
return mockVscode;
}
return originalLoad.call(this, request, parent, isMain);
};

try {
return require(extensionEntry);
} finally {
Module._load = originalLoad;
}
}

function createMockVscode(tempRoot) {
const registrations = {
providers: [],
};

class TreeItem {
constructor(label, collapsibleState) {
this.label = label;
this.collapsibleState = collapsibleState;
}
}

class ThemeIcon {
constructor(id) {
this.id = id;
}
}

class EventEmitter {
constructor() {
this.event = () => {};
}

fire() {}
}

const disposable = () => ({ dispose() {} });
const fileWatcher = {
onDidCreate() {},
onDidChange() {},
onDidDelete() {},
dispose() {},
};

return {
registrations,
vscode: {
TreeItem,
ThemeIcon,
EventEmitter,
TreeItemCollapsibleState: {
None: 0,
Expanded: 1,
},
commands: {
executeCommand: async () => {},
registerCommand: () => disposable(),
},
Uri: {
file: (fsPath) => ({ fsPath }),
},
window: {
registerTreeDataProvider: (viewId, provider) => {
registrations.providers.push({ viewId, provider });
return disposable();
},
},
workspace: {
createFileSystemWatcher: () => fileWatcher,
findFiles: async () => [],
onDidChangeWorkspaceFolders: () => disposable(),
workspaceFolders: [{ uri: { fsPath: tempRoot } }],
},
},
};
}

test('agent-session-state writes and removes active session records', () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'guardex-active-session-'));
const branch = 'agent/codex/demo-task';
Expand Down Expand Up @@ -132,3 +219,26 @@ test('install-vscode-active-agents-extension installs the current extension vers
assert.equal(fs.existsSync(staleDir), false);
assert.match(result.stdout, /Reload the VS Code window/);
});

test('active-agents extension registers a provider with getTreeItem', async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'guardex-vscode-view-'));
const { registrations, vscode } = createMockVscode(tempRoot);
const extension = loadExtensionWithMockVscode(vscode);
const context = { subscriptions: [] };

extension.activate(context);

assert.equal(registrations.providers.length, 1);
assert.equal(registrations.providers[0].viewId, 'gitguardex.activeAgents');

const provider = registrations.providers[0].provider;
assert.equal(typeof provider.getTreeItem, 'function');

const [rootItem] = await provider.getChildren();
assert.equal(rootItem.label, 'No active Guardex agents');
assert.equal(provider.getTreeItem(rootItem), rootItem);

for (const subscription of context.subscriptions) {
subscription.dispose?.();
}
});