Skip to content

Commit a799a80

Browse files
authored
Merge pull request #665 from cluesmith/builder/bugfix-664-dashboard-extract-issue-number
[Bugfix #664] Read issue number from DB, not builder name
2 parents 2720e63 + 068bb59 commit a799a80

2 files changed

Lines changed: 92 additions & 0 deletions

File tree

packages/codev/src/agent-farm/__tests__/overview.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
99
import fs from 'node:fs';
1010
import path from 'node:path';
1111
import os from 'node:os';
12+
import Database from 'better-sqlite3';
1213
import {
1314
OverviewCache,
1415
parseStatusYaml,
@@ -100,6 +101,19 @@ function issueItem(number: number, title: string, labels: Array<{ name: string }
100101
return { number, title, url: `https://github.com/org/repo/issues/${number}`, labels, createdAt: '2026-01-01T00:00:00Z' };
101102
}
102103

104+
/** Create a state.db in the workspace's .agent-farm/ with builder issue_number rows. */
105+
function createStateDb(root: string, rows: Array<{ worktree: string; issue_number: number }>): void {
106+
const agentFarmDir = path.join(root, '.agent-farm');
107+
fs.mkdirSync(agentFarmDir, { recursive: true });
108+
const db = new Database(path.join(agentFarmDir, 'state.db'));
109+
db.exec('CREATE TABLE IF NOT EXISTS builders (worktree TEXT, issue_number INTEGER)');
110+
const insert = db.prepare('INSERT INTO builders (worktree, issue_number) VALUES (?, ?)');
111+
for (const row of rows) {
112+
insert.run(row.worktree, row.issue_number);
113+
}
114+
db.close();
115+
}
116+
103117
// ============================================================================
104118
// Tests
105119
// ============================================================================
@@ -1724,5 +1738,56 @@ describe('overview', () => {
17241738
expect(data.recentlyClosed).toHaveLength(1);
17251739
expect(data.recentlyClosed[0].prUrl).toBeUndefined();
17261740
});
1741+
1742+
it('enriches issueId from DB issue_number for unknown protocols (#664)', async () => {
1743+
// research-533 doesn't match any protocol regex → soft mode, issueId null
1744+
const worktreePath = createBuilderWorktree(tmpDir, 'research-533-context-window');
1745+
1746+
// Create a real DB with issue_number for this worktree
1747+
createStateDb(tmpDir, [{ worktree: worktreePath, issue_number: 533 }]);
1748+
1749+
const cache = new OverviewCache();
1750+
const data = await cache.getOverview(tmpDir);
1751+
1752+
expect(data.builders).toHaveLength(1);
1753+
expect(data.builders[0].issueId).toBe('533');
1754+
});
1755+
1756+
it('DB issue_number overrides regex-parsed issueId (#664)', async () => {
1757+
// spir-42 matches the regex → issueId '42' from regex
1758+
// DB also has issue_number 42 → should still be '42'
1759+
const worktreePath = createBuilderWorktree(tmpDir, 'spir-42-feature', [
1760+
"id: '0042'",
1761+
'title: feature',
1762+
'protocol: spir',
1763+
'phase: implement',
1764+
'gates:',
1765+
].join('\n'), '0042-feature');
1766+
1767+
createStateDb(tmpDir, [{ worktree: worktreePath, issue_number: 42 }]);
1768+
1769+
const cache = new OverviewCache();
1770+
const data = await cache.getOverview(tmpDir);
1771+
1772+
expect(data.builders).toHaveLength(1);
1773+
expect(data.builders[0].issueId).toBe('42');
1774+
});
1775+
1776+
it('falls back to regex-parsed issueId when DB has no issue_number (#664)', async () => {
1777+
createBuilderWorktree(tmpDir, 'spir-42-feature', [
1778+
"id: '0042'",
1779+
'title: feature',
1780+
'protocol: spir',
1781+
'phase: implement',
1782+
'gates:',
1783+
].join('\n'), '0042-feature');
1784+
1785+
// No state.db → regex-parsed issueId preserved
1786+
const cache = new OverviewCache();
1787+
const data = await cache.getOverview(tmpDir);
1788+
1789+
expect(data.builders).toHaveLength(1);
1790+
expect(data.builders[0].issueId).toBe('42');
1791+
});
17271792
});
17281793
});

packages/codev/src/agent-farm/servers/overview.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from '../../lib/github.js';
2020
import type { ForgePR, ForgeIssueListItem } from '../../lib/github.js';
2121
import { loadProtocol } from '../../commands/porch/protocol.js';
22+
import Database from 'better-sqlite3';
2223

2324
// =============================================================================
2425
// Types
@@ -692,6 +693,32 @@ export class OverviewCache {
692693
return roleId !== null && activeBuilderRoleIds.has(roleId);
693694
});
694695
}
696+
697+
// Enrich issueId from DB issue_number — protocol-agnostic (fixes #664)
698+
// Open DB directly using workspaceRoot to avoid singleton path issues
699+
// when Tower serves multiple workspaces.
700+
try {
701+
const dbPath = path.join(workspaceRoot, '.agent-farm', 'state.db');
702+
if (fs.existsSync(dbPath)) {
703+
const db = new Database(dbPath, { readonly: true });
704+
try {
705+
const rows = db.prepare(
706+
'SELECT worktree, issue_number FROM builders WHERE issue_number IS NOT NULL',
707+
).all() as Array<{ worktree: string; issue_number: number }>;
708+
for (const row of rows) {
709+
const builder = builders.find(b => b.worktreePath === row.worktree);
710+
if (builder) {
711+
builder.issueId = String(row.issue_number);
712+
}
713+
}
714+
} finally {
715+
db.close();
716+
}
717+
}
718+
} catch {
719+
// DB not available — keep regex-parsed issueId
720+
}
721+
695722
const activeBuilderIssues = new Set(
696723
builders
697724
.map(b => b.issueId)

0 commit comments

Comments
 (0)