@cluesmith/codev 2.0.0-rc.56 → 2.0.0-rc.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard/dist/assets/index-BEs2fHhy.js +131 -0
- package/dashboard/dist/assets/index-BEs2fHhy.js.map +1 -0
- package/dashboard/dist/index.html +1 -1
- package/dist/agent-farm/commands/attach.d.ts.map +1 -1
- package/dist/agent-farm/commands/attach.js +31 -8
- package/dist/agent-farm/commands/attach.js.map +1 -1
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +55 -17
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +37 -27
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/start.d.ts.map +1 -1
- package/dist/agent-farm/commands/start.js +3 -226
- package/dist/agent-farm/commands/start.js.map +1 -1
- package/dist/agent-farm/commands/status.d.ts.map +1 -1
- package/dist/agent-farm/commands/status.js +5 -2
- package/dist/agent-farm/commands/status.js.map +1 -1
- package/dist/agent-farm/db/index.d.ts.map +1 -1
- package/dist/agent-farm/db/index.js +45 -0
- package/dist/agent-farm/db/index.js.map +1 -1
- package/dist/agent-farm/db/schema.d.ts +1 -1
- package/dist/agent-farm/db/schema.d.ts.map +1 -1
- package/dist/agent-farm/db/schema.js +2 -2
- package/dist/agent-farm/hq-connector.d.ts +0 -4
- package/dist/agent-farm/hq-connector.d.ts.map +1 -1
- package/dist/agent-farm/hq-connector.js +0 -15
- package/dist/agent-farm/hq-connector.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +228 -13
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/commands/porch/protocol.d.ts.map +1 -1
- package/dist/commands/porch/protocol.js +26 -1
- package/dist/commands/porch/protocol.js.map +1 -1
- package/package.json +4 -6
- package/skeleton/porch/prompts/defend.md +1 -1
- package/skeleton/porch/prompts/evaluate.md +2 -2
- package/skeleton/porch/prompts/implement.md +1 -1
- package/skeleton/porch/prompts/plan.md +1 -1
- package/skeleton/porch/prompts/review.md +4 -4
- package/skeleton/porch/prompts/specify.md +1 -1
- package/skeleton/porch/prompts/understand.md +2 -2
- package/skeleton/protocol-schema.json +3 -3
- package/skeleton/protocols/bugfix/builder-prompt.md +1 -1
- package/skeleton/protocols/bugfix/prompts/pr.md +2 -17
- package/skeleton/protocols/experiment/protocol.md +3 -3
- package/skeleton/protocols/experiment/templates/notes.md +1 -1
- package/skeleton/protocols/maintain/protocol.md +1 -1
- package/skeleton/protocols/protocol-schema.json +1 -1
- package/skeleton/protocols/{spider → spir}/builder-prompt.md +1 -1
- package/skeleton/protocols/{spider → spir}/prompts/implement.md +1 -1
- package/skeleton/protocols/{spider → spir}/prompts/plan.md +6 -70
- package/skeleton/protocols/{spider → spir}/prompts/review.md +7 -25
- package/skeleton/protocols/{spider → spir}/prompts/specify.md +5 -58
- package/skeleton/protocols/{spider → spir}/protocol.json +2 -2
- package/skeleton/protocols/{spider → spir}/protocol.md +6 -8
- package/skeleton/protocols/{spider → spir}/templates/review.md +1 -1
- package/skeleton/protocols/tick/builder-prompt.md +1 -1
- package/skeleton/protocols/tick/protocol.md +18 -18
- package/skeleton/protocols/tick/templates/review.md +1 -1
- package/skeleton/resources/commands/overview.md +1 -1
- package/skeleton/resources/workflow-reference.md +2 -2
- package/skeleton/roles/architect.md +2 -2
- package/skeleton/roles/builder.md +2 -2
- package/skeleton/templates/AGENTS.md +1 -1
- package/skeleton/templates/CLAUDE.md +1 -1
- package/skeleton/templates/cheatsheet.md +3 -3
- package/skeleton/templates/projectlist.md +1 -1
- package/templates/dashboard/js/main.js +1 -1
- package/templates/open.html +26 -0
- package/dashboard/dist/assets/index-xOaDIZ0l.js +0 -132
- package/dashboard/dist/assets/index-xOaDIZ0l.js.map +0 -1
- /package/skeleton/protocols/{spider → spir}/templates/plan.md +0 -0
- /package/skeleton/protocols/{spider → spir}/templates/spec.md +0 -0
|
@@ -242,6 +242,11 @@ function createTmuxSession(sessionName, command, args, cwd, cols, rows) {
|
|
|
242
242
|
log('WARN', `tmux new-session exited with code ${result.status} for "${sessionName}"`);
|
|
243
243
|
return false;
|
|
244
244
|
}
|
|
245
|
+
// Hide tmux status bar (dashboard has its own tabs), enable mouse, and
|
|
246
|
+
// use aggressive-resize so tmux sizes to the largest client (not smallest)
|
|
247
|
+
spawnSync('tmux', ['set-option', '-t', sessionName, 'status', 'off'], { stdio: 'ignore' });
|
|
248
|
+
spawnSync('tmux', ['set-option', '-t', sessionName, 'mouse', 'on'], { stdio: 'ignore' });
|
|
249
|
+
spawnSync('tmux', ['set-option', '-t', sessionName, 'aggressive-resize', 'on'], { stdio: 'ignore' });
|
|
245
250
|
return true;
|
|
246
251
|
}
|
|
247
252
|
catch (err) {
|
|
@@ -1054,6 +1059,27 @@ async function launchInstance(projectPath) {
|
|
|
1054
1059
|
entry.architect = session.id;
|
|
1055
1060
|
// TICK-001: Save to SQLite for persistence (with tmux session name)
|
|
1056
1061
|
saveTerminalSession(session.id, resolvedPath, 'architect', null, session.pid, activeTmuxSession);
|
|
1062
|
+
// Auto-restart architect on exit (restored from pre-Phase 4 dashboard-server.ts)
|
|
1063
|
+
const ptySession = manager.getSession(session.id);
|
|
1064
|
+
if (ptySession) {
|
|
1065
|
+
const startedAt = Date.now();
|
|
1066
|
+
ptySession.on('exit', () => {
|
|
1067
|
+
entry.architect = undefined;
|
|
1068
|
+
// Only restart if the architect ran for at least 5s (prevents crash loops)
|
|
1069
|
+
const uptime = Date.now() - startedAt;
|
|
1070
|
+
if (uptime < 5000) {
|
|
1071
|
+
log('INFO', `Architect exited after ${uptime}ms for ${projectPath}, not restarting (too short)`);
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
log('INFO', `Architect exited for ${projectPath}, restarting in 2s...`);
|
|
1075
|
+
deleteTerminalSession(session.id);
|
|
1076
|
+
setTimeout(() => {
|
|
1077
|
+
launchInstance(projectPath).catch((err) => {
|
|
1078
|
+
log('WARN', `Failed to restart architect: ${err.message}`);
|
|
1079
|
+
});
|
|
1080
|
+
}, 2000);
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1057
1083
|
log('INFO', `Created architect terminal for project: ${projectPath}`);
|
|
1058
1084
|
}
|
|
1059
1085
|
catch (err) {
|
|
@@ -1067,19 +1093,6 @@ async function launchInstance(projectPath) {
|
|
|
1067
1093
|
return { success: false, error: `Failed to launch: ${err.message}` };
|
|
1068
1094
|
}
|
|
1069
1095
|
}
|
|
1070
|
-
/**
|
|
1071
|
-
* Get PID of process listening on a port
|
|
1072
|
-
*/
|
|
1073
|
-
function getProcessOnPort(targetPort) {
|
|
1074
|
-
try {
|
|
1075
|
-
const result = execSync(`lsof -ti :${targetPort} 2>/dev/null`, { encoding: 'utf-8' });
|
|
1076
|
-
const pid = parseInt(result.trim().split('\n')[0], 10);
|
|
1077
|
-
return isNaN(pid) ? null : pid;
|
|
1078
|
-
}
|
|
1079
|
-
catch {
|
|
1080
|
-
return null;
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
1096
|
/**
|
|
1084
1097
|
* Stop an agent-farm instance by killing all its terminals
|
|
1085
1098
|
* Phase 4 (Spec 0090): Tower manages terminals directly
|
|
@@ -1736,6 +1749,27 @@ const server = http.createServer(async (req, res) => {
|
|
|
1736
1749
|
// Phase 4 (Spec 0090): Tower handles everything directly
|
|
1737
1750
|
const isApiCall = subPath.startsWith('api/') || subPath === 'api';
|
|
1738
1751
|
const isWsPath = subPath.startsWith('ws/') || subPath === 'ws';
|
|
1752
|
+
// GET /file?path=<relative-path> — Read project file by path (for StatusPanel project list)
|
|
1753
|
+
if (req.method === 'GET' && subPath === 'file' && url.searchParams.has('path')) {
|
|
1754
|
+
const relPath = url.searchParams.get('path');
|
|
1755
|
+
const fullPath = path.resolve(projectPath, relPath);
|
|
1756
|
+
// Security: ensure resolved path stays within project directory
|
|
1757
|
+
if (!fullPath.startsWith(projectPath + path.sep) && fullPath !== projectPath) {
|
|
1758
|
+
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
1759
|
+
res.end('Forbidden');
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
try {
|
|
1763
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
1764
|
+
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
1765
|
+
res.end(content);
|
|
1766
|
+
}
|
|
1767
|
+
catch {
|
|
1768
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
1769
|
+
res.end('Not found');
|
|
1770
|
+
}
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1739
1773
|
// Serve React dashboard static files directly if:
|
|
1740
1774
|
// 1. Not an API call
|
|
1741
1775
|
// 2. Not a WebSocket path
|
|
@@ -2113,6 +2147,44 @@ const server = http.createServer(async (req, res) => {
|
|
|
2113
2147
|
res.end(JSON.stringify({ ok: true }));
|
|
2114
2148
|
return;
|
|
2115
2149
|
}
|
|
2150
|
+
// GET /api/files - Return project directory tree for file browser (Spec 0092)
|
|
2151
|
+
if (req.method === 'GET' && apiPath === 'files') {
|
|
2152
|
+
const maxDepth = parseInt(url.searchParams.get('depth') || '3', 10);
|
|
2153
|
+
const ignore = new Set(['.git', 'node_modules', '.builders', 'dist', '.agent-farm', '.next', '.cache', '__pycache__']);
|
|
2154
|
+
function readTree(dir, depth) {
|
|
2155
|
+
if (depth <= 0)
|
|
2156
|
+
return [];
|
|
2157
|
+
try {
|
|
2158
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
2159
|
+
return entries
|
|
2160
|
+
.filter(e => !e.name.startsWith('.') || e.name === '.env.example')
|
|
2161
|
+
.filter(e => !ignore.has(e.name))
|
|
2162
|
+
.sort((a, b) => {
|
|
2163
|
+
// Directories first, then alphabetical
|
|
2164
|
+
if (a.isDirectory() && !b.isDirectory())
|
|
2165
|
+
return -1;
|
|
2166
|
+
if (!a.isDirectory() && b.isDirectory())
|
|
2167
|
+
return 1;
|
|
2168
|
+
return a.name.localeCompare(b.name);
|
|
2169
|
+
})
|
|
2170
|
+
.map(e => {
|
|
2171
|
+
const fullPath = path.join(dir, e.name);
|
|
2172
|
+
const relativePath = path.relative(projectPath, fullPath);
|
|
2173
|
+
if (e.isDirectory()) {
|
|
2174
|
+
return { name: e.name, path: relativePath, type: 'directory', children: readTree(fullPath, depth - 1) };
|
|
2175
|
+
}
|
|
2176
|
+
return { name: e.name, path: relativePath, type: 'file' };
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
2179
|
+
catch {
|
|
2180
|
+
return [];
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
const tree = readTree(projectPath, maxDepth);
|
|
2184
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2185
|
+
res.end(JSON.stringify(tree));
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2116
2188
|
// GET /api/git/status - Return git status for file browser (Spec 0092)
|
|
2117
2189
|
if (req.method === 'GET' && apiPath === 'git/status') {
|
|
2118
2190
|
try {
|
|
@@ -2172,6 +2244,149 @@ const server = http.createServer(async (req, res) => {
|
|
|
2172
2244
|
res.end(JSON.stringify(recentFiles));
|
|
2173
2245
|
return;
|
|
2174
2246
|
}
|
|
2247
|
+
// GET /api/annotate/:tabId/* — Serve rich annotator template and sub-APIs
|
|
2248
|
+
const annotateMatch = apiPath.match(/^annotate\/([^/]+)(\/(.*))?$/);
|
|
2249
|
+
if (annotateMatch) {
|
|
2250
|
+
const tabId = annotateMatch[1];
|
|
2251
|
+
const subRoute = annotateMatch[3] || '';
|
|
2252
|
+
const entry = getProjectTerminalsEntry(projectPath);
|
|
2253
|
+
const tab = entry.fileTabs.get(tabId);
|
|
2254
|
+
if (!tab) {
|
|
2255
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
2256
|
+
res.end('File tab not found');
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
const filePath = tab.path;
|
|
2260
|
+
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
2261
|
+
const isImage = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'].includes(ext);
|
|
2262
|
+
const isVideo = ['mp4', 'webm', 'mov'].includes(ext);
|
|
2263
|
+
const is3D = ['stl', '3mf'].includes(ext);
|
|
2264
|
+
const isPdf = ext === 'pdf';
|
|
2265
|
+
const isMarkdown = ext === 'md';
|
|
2266
|
+
// Sub-route: GET /file — re-read file content from disk
|
|
2267
|
+
if (req.method === 'GET' && subRoute === 'file') {
|
|
2268
|
+
try {
|
|
2269
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
2270
|
+
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
2271
|
+
res.end(content);
|
|
2272
|
+
}
|
|
2273
|
+
catch (err) {
|
|
2274
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
2275
|
+
res.end(err.message);
|
|
2276
|
+
}
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
// Sub-route: POST /save — save file content
|
|
2280
|
+
if (req.method === 'POST' && subRoute === 'save') {
|
|
2281
|
+
try {
|
|
2282
|
+
const body = await new Promise((resolve) => {
|
|
2283
|
+
let data = '';
|
|
2284
|
+
req.on('data', (chunk) => data += chunk.toString());
|
|
2285
|
+
req.on('end', () => resolve(data));
|
|
2286
|
+
});
|
|
2287
|
+
const parsed = JSON.parse(body || '{}');
|
|
2288
|
+
const fileContent = parsed.content;
|
|
2289
|
+
if (typeof fileContent !== 'string') {
|
|
2290
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
2291
|
+
res.end('Missing content');
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
fs.writeFileSync(filePath, fileContent, 'utf-8');
|
|
2295
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2296
|
+
res.end(JSON.stringify({ ok: true }));
|
|
2297
|
+
}
|
|
2298
|
+
catch (err) {
|
|
2299
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
2300
|
+
res.end(err.message);
|
|
2301
|
+
}
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
// Sub-route: GET /api/mtime — file modification time
|
|
2305
|
+
if (req.method === 'GET' && subRoute === 'api/mtime') {
|
|
2306
|
+
try {
|
|
2307
|
+
const stat = fs.statSync(filePath);
|
|
2308
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2309
|
+
res.end(JSON.stringify({ mtime: stat.mtimeMs }));
|
|
2310
|
+
}
|
|
2311
|
+
catch (err) {
|
|
2312
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
2313
|
+
res.end(err.message);
|
|
2314
|
+
}
|
|
2315
|
+
return;
|
|
2316
|
+
}
|
|
2317
|
+
// Sub-route: GET /api/image, /api/video, /api/model, /api/pdf — raw binary content
|
|
2318
|
+
if (req.method === 'GET' && (subRoute === 'api/image' || subRoute === 'api/video' || subRoute === 'api/model' || subRoute === 'api/pdf')) {
|
|
2319
|
+
try {
|
|
2320
|
+
const data = fs.readFileSync(filePath);
|
|
2321
|
+
const mimeType = getMimeTypeForFile(filePath);
|
|
2322
|
+
res.writeHead(200, {
|
|
2323
|
+
'Content-Type': mimeType,
|
|
2324
|
+
'Content-Length': data.length,
|
|
2325
|
+
'Cache-Control': 'no-cache',
|
|
2326
|
+
});
|
|
2327
|
+
res.end(data);
|
|
2328
|
+
}
|
|
2329
|
+
catch (err) {
|
|
2330
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
2331
|
+
res.end(err.message);
|
|
2332
|
+
}
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
// Default: serve the annotator HTML template
|
|
2336
|
+
if (req.method === 'GET' && (subRoute === '' || subRoute === undefined)) {
|
|
2337
|
+
try {
|
|
2338
|
+
const templateFile = is3D ? '3d-viewer.html' : 'open.html';
|
|
2339
|
+
const tplPath = path.resolve(__dirname, `../../../templates/${templateFile}`);
|
|
2340
|
+
let html = fs.readFileSync(tplPath, 'utf-8');
|
|
2341
|
+
const fileName = path.basename(filePath);
|
|
2342
|
+
const fileSize = fs.statSync(filePath).size;
|
|
2343
|
+
if (is3D) {
|
|
2344
|
+
html = html.replace(/\{\{FILE\}\}/g, fileName);
|
|
2345
|
+
html = html.replace(/\{\{FILE_PATH_JSON\}\}/g, JSON.stringify(filePath));
|
|
2346
|
+
html = html.replace(/\{\{FORMAT\}\}/g, ext);
|
|
2347
|
+
}
|
|
2348
|
+
else {
|
|
2349
|
+
html = html.replace(/\{\{FILE\}\}/g, fileName);
|
|
2350
|
+
html = html.replace(/\{\{FILE_PATH\}\}/g, filePath);
|
|
2351
|
+
html = html.replace(/\{\{BUILDER_ID\}\}/g, '');
|
|
2352
|
+
html = html.replace(/\{\{LANG\}\}/g, getLanguageForExt(ext));
|
|
2353
|
+
html = html.replace(/\{\{IS_MARKDOWN\}\}/g, String(isMarkdown));
|
|
2354
|
+
html = html.replace(/\{\{IS_IMAGE\}\}/g, String(isImage));
|
|
2355
|
+
html = html.replace(/\{\{IS_VIDEO\}\}/g, String(isVideo));
|
|
2356
|
+
html = html.replace(/\{\{IS_PDF\}\}/g, String(isPdf));
|
|
2357
|
+
html = html.replace(/\{\{FILE_SIZE\}\}/g, String(fileSize));
|
|
2358
|
+
// Inject initialization script (template loads content via fetch)
|
|
2359
|
+
let initScript;
|
|
2360
|
+
if (isImage) {
|
|
2361
|
+
initScript = `initImage(${fileSize});`;
|
|
2362
|
+
}
|
|
2363
|
+
else if (isVideo) {
|
|
2364
|
+
initScript = `initVideo(${fileSize});`;
|
|
2365
|
+
}
|
|
2366
|
+
else if (isPdf) {
|
|
2367
|
+
initScript = `initPdf(${fileSize});`;
|
|
2368
|
+
}
|
|
2369
|
+
else {
|
|
2370
|
+
initScript = `fetch('file').then(r=>r.text()).then(init);`;
|
|
2371
|
+
}
|
|
2372
|
+
html = html.replace('// FILE_CONTENT will be injected by the server', initScript);
|
|
2373
|
+
}
|
|
2374
|
+
// Handle ?line= query param for scroll-to-line
|
|
2375
|
+
const lineParam = url.searchParams.get('line');
|
|
2376
|
+
if (lineParam) {
|
|
2377
|
+
const scrollScript = `<script>window.addEventListener('load',()=>{setTimeout(()=>{const el=document.querySelector('[data-line="${lineParam}"]');if(el){el.scrollIntoView({block:'center'});el.classList.add('highlighted-line');}},200);})</script>`;
|
|
2378
|
+
html = html.replace('</body>', `${scrollScript}</body>`);
|
|
2379
|
+
}
|
|
2380
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
2381
|
+
res.end(html);
|
|
2382
|
+
}
|
|
2383
|
+
catch (err) {
|
|
2384
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
2385
|
+
res.end(`Failed to serve annotator: ${err.message}`);
|
|
2386
|
+
}
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2175
2390
|
// Unhandled API route
|
|
2176
2391
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
2177
2392
|
res.end(JSON.stringify({ error: 'API endpoint not found', path: apiPath }));
|