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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,14 @@ Being honest about where this still has issues:
<details open>
<summary><strong>v7.x</strong></summary>

### v7.0.37
- Bumped `@imdeadpool/guardex` from `7.0.36` to `7.0.37` so the current
package can publish under a fresh npm version after `7.0.36` reached the
registry.
- Synced the shipped Active Agents template with the canonical VS Code
extension source so Colony task counts and details install with the package.
- No new CLI command behavior is introduced in this release lane.

### v7.0.36
- Bumped `@imdeadpool/guardex` from `7.0.35` to `7.0.36` so the latest
branch-finish cwd-prune fix can ship under a fresh npm version after PR #424.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-04-25
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Release notes

- Bump `@imdeadpool/guardex` from `7.0.36` to `7.0.37` for the next publishable release.
- Registry baseline before the bump: `@imdeadpool/guardex@7.0.36` with `latest` pointing to `7.0.36`.
- GitHub release target: `v7.0.37`.
- Synced `templates/vscode/guardex-active-agents/extension.js` with the canonical `vscode/guardex-active-agents/extension.js` bundle so package install parity tests pass.
- No CLI behavior changes are introduced in this release lane.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@imdeadpool/guardex",
"version": "7.0.36",
"version": "7.0.37",
"description": "Guardian T-Rex for your multi-agent repo. Isolated worktrees, file locks, and PR-only merges stop parallel Codex & Claude agents from overwriting each other's work. Auto-wires Oh My Codex, Oh My Claude, OpenSpec, and Caveman.",
"license": "MIT",
"preferGlobal": true,
Expand Down
168 changes: 151 additions & 17 deletions templates/vscode/guardex-active-agents/extension.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const fs = require('node:fs');
const path = require('node:path');
const cp = require('node:child_process');
const http = require('node:http');
const os = require('node:os');
const vscode = require('vscode');
const {
clearWorktreeActivityCache,
Expand Down Expand Up @@ -40,6 +42,84 @@ const ACTIVE_AGENTS_EXTENSION_ID = 'Recodee.gitguardex-active-agents';
const RESTART_EXTENSION_HOST_COMMAND = 'workbench.action.restartExtensionHost';
const REFRESH_POLL_INTERVAL_MS = 30_000;
const INSPECT_PANEL_VIEW_TYPE = 'gitguardex.activeAgents.inspect';
const COLONY_DEFAULT_PORT = 37777;
const COLONY_SNAPSHOT_TTL_MS = 5_000;
const COLONY_FETCH_TIMEOUT_MS = 800;

function colonyDataDir() {
return process.env.COLONY_HOME
|| process.env.CAVEMEM_HOME
|| path.join(os.homedir(), '.colony');
}

function readColonyPort() {
try {
const raw = fs.readFileSync(path.join(colonyDataDir(), 'settings.json'), 'utf8');
const parsed = JSON.parse(raw);
const port = Number(parsed?.workerPort);
return Number.isFinite(port) && port > 0 ? port : COLONY_DEFAULT_PORT;
} catch (_error) {
return COLONY_DEFAULT_PORT;
}
}

function fetchColonyJson(urlPath) {
return new Promise((resolve) => {
const req = http.get(
{
hostname: '127.0.0.1',
port: readColonyPort(),
path: urlPath,
timeout: COLONY_FETCH_TIMEOUT_MS,
},
(res) => {
if (res.statusCode !== 200) {
res.resume();
resolve(null);
return;
}
let body = '';
res.setEncoding('utf8');
res.on('data', (chunk) => {
body += chunk;
});
res.on('end', () => {
try {
resolve(JSON.parse(body));
} catch (_error) {
resolve(null);
}
});
},
);
req.on('error', () => resolve(null));
req.on('timeout', () => {
req.destroy();
resolve(null);
});
});
}

const colonyTasksCache = new Map();

async function readColonyTasksForRepo(repoRoot) {
const cached = colonyTasksCache.get(repoRoot);
if (cached && Date.now() - cached.at < COLONY_SNAPSHOT_TTL_MS) {
return cached.tasks;
}
const tasks = await fetchColonyJson(
`/api/colony/tasks?repo_root=${encodeURIComponent(repoRoot)}`,
);
const resolved = Array.isArray(tasks) ? tasks : [];
colonyTasksCache.set(repoRoot, { at: Date.now(), tasks: resolved });
return resolved;
}

function compactColonyBranchLabel(branch) {
if (typeof branch !== 'string' || !branch) return 'unknown';
const parts = branch.split('/').filter(Boolean);
return parts.length > 2 ? parts.slice(-2).join('/') : branch;
}
const GIT_CONFIGURATION_SECTION = 'git';
const REPO_SCAN_IGNORED_FOLDERS_SETTING = 'repositoryScanIgnoredFolders';
const BUNDLED_FILE_ICONS_MANIFEST_RELATIVE = path.join('fileicons', 'gitguardex-fileicons.json');
Expand Down Expand Up @@ -840,10 +920,18 @@ function buildOverviewDescription(summary) {
formatCountLabel(summary?.workingCount || 0, 'working agent'),
formatCountLabel(summary?.finishedCount || 0, 'finished agent'),
formatCountLabel(summary?.idleCount || 0, 'idle agent'),
summary?.colonyTaskCount
? formatCountLabel(summary.colonyTaskCount, 'colony task')
: '',
summary?.pendingHandoffCount
? formatCountLabel(summary.pendingHandoffCount, 'pending handoff')
: '',
formatCountLabel(summary?.unassignedChangeCount || 0, 'unassigned change'),
formatCountLabel(summary?.lockedFileCount || 0, 'locked file'),
formatCountLabel(summary?.conflictCount || 0, 'conflict'),
].join(' · ');
]
.filter(Boolean)
.join(' · ');
}

function buildRepoDescription(summary) {
Expand Down Expand Up @@ -1252,7 +1340,9 @@ class RepoItem extends vscode.TreeItem {
this.changes = changes;
this.unassignedChanges = options.unassignedChanges || [];
this.lockEntries = options.lockEntries || [];
this.overview = options.overview || buildRepoOverview(sessions, this.unassignedChanges, this.lockEntries);
this.colonyTasks = Array.isArray(options.colonyTasks) ? options.colonyTasks : [];
this.overview = options.overview
|| buildRepoOverview(sessions, this.unassignedChanges, this.lockEntries, this.colonyTasks);
this.description = buildRepoDescription(this.overview);
this.tooltip = buildRepoTooltip(repoRoot, this.overview);
this.iconPath = themeIcon('repo');
Expand Down Expand Up @@ -2524,7 +2614,8 @@ function countChangedPaths(repoRoot, sessions, changes) {
return changedKeys.size;
}

function buildRepoOverview(sessions, unassignedChanges, lockEntries) {
function buildRepoOverview(sessions, unassignedChanges, lockEntries, colonyTasks = []) {
const colonyTaskList = Array.isArray(colonyTasks) ? colonyTasks : [];
return {
sessionCount: sessions.length,
workingCount: countWorkingSessions(sessions),
Expand All @@ -2536,6 +2627,11 @@ function buildRepoOverview(sessions, unassignedChanges, lockEntries) {
(total, session) => total + (session.conflictCount || 0),
0,
) + (unassignedChanges || []).filter((change) => change.hasForeignLock).length,
colonyTaskCount: colonyTaskList.length,
pendingHandoffCount: colonyTaskList.reduce(
(total, task) => total + (task.pending_handoff_count || 0),
0,
),
};
}

Expand Down Expand Up @@ -3023,12 +3119,14 @@ class ActiveAgentsProvider {

const { repoRootChanges } = partitionChangesByOwnership(sessions, changes);
const unassignedChanges = sortUnassignedChanges(repoRootChanges);
const colonyTasks = Array.isArray(entry.colonyTasks) ? entry.colonyTasks : [];
return {
...entry,
sessions,
changes,
unassignedChanges,
overview: buildRepoOverview(sessions, unassignedChanges, entry.lockEntries),
colonyTasks,
overview: buildRepoOverview(sessions, unassignedChanges, entry.lockEntries, colonyTasks),
};
});

Expand Down Expand Up @@ -3140,6 +3238,37 @@ class ActiveAgentsProvider {
iconId: 'file-directory',
}));
}
const colonyTaskList = Array.isArray(element.colonyTasks) ? element.colonyTasks : [];
if (colonyTaskList.length > 0) {
const colonyItems = colonyTaskList.map((task) => {
const pendingLabel = task.pending_handoff_count > 0
? formatCountLabel(task.pending_handoff_count, 'pending handoff')
: 'quiet';
const participantLabel =
(task.participants || []).map((p) => p.agent).filter(Boolean).join(', ')
|| 'no participants';
return new DetailItem(
`#${task.id} · ${compactColonyBranchLabel(task.branch)}`,
`${participantLabel} · ${pendingLabel}`,
{
iconId: task.pending_handoff_count > 0 ? 'warning' : 'comment-discussion',
tooltip: [
task.branch,
`task #${task.id}`,
participantLabel,
task.pending_handoff_count > 0
? formatCountLabel(task.pending_handoff_count, 'pending handoff')
: '',
].filter(Boolean).join('\n'),
},
);
});
advancedItems.push(new SectionItem('Colony tasks', colonyItems, {
description: String(colonyItems.length),
collapsedState: vscode.TreeItemCollapsibleState.Collapsed,
iconId: 'organization',
}));
}
if (advancedItems.length > 0) {
sectionItems.push(new SectionItem('Advanced details', advancedItems, {
description: String(advancedItems.length),
Expand All @@ -3166,24 +3295,29 @@ class ActiveAgentsProvider {
overview: entry.overview,
unassignedChanges: entry.unassignedChanges,
lockEntries: entry.lockEntries,
colonyTasks: entry.colonyTasks,
}));
}

async loadRepoEntries() {
const repoEntries = await findRepoSessionEntries();
return repoEntries.map((entry) => {
const repoRoot = entry.repoRoot;
const lockRegistry = this.getLockRegistryForRepo(repoRoot);
const currentBranch = readCurrentBranch(repoRoot);
return {
repoRoot,
sessions: entry.sessions.map((session) => decorateSession(session, lockRegistry)),
changes: readRepoChanges(repoRoot).map((change) => (
decorateChange(change, lockRegistry, currentBranch)
)),
lockEntries: Array.from(lockRegistry.entriesByPath.entries()),
};
});
return Promise.all(
repoEntries.map(async (entry) => {
const repoRoot = entry.repoRoot;
const lockRegistry = this.getLockRegistryForRepo(repoRoot);
const currentBranch = readCurrentBranch(repoRoot);
const colonyTasks = await readColonyTasksForRepo(repoRoot);
return {
repoRoot,
sessions: entry.sessions.map((session) => decorateSession(session, lockRegistry)),
changes: readRepoChanges(repoRoot).map((change) => (
decorateChange(change, lockRegistry, currentBranch)
)),
lockEntries: Array.from(lockRegistry.entriesByPath.entries()),
colonyTasks,
};
}),
);
}
}

Expand Down
Loading