@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.
Files changed (73) hide show
  1. package/dashboard/dist/assets/index-BEs2fHhy.js +131 -0
  2. package/dashboard/dist/assets/index-BEs2fHhy.js.map +1 -0
  3. package/dashboard/dist/index.html +1 -1
  4. package/dist/agent-farm/commands/attach.d.ts.map +1 -1
  5. package/dist/agent-farm/commands/attach.js +31 -8
  6. package/dist/agent-farm/commands/attach.js.map +1 -1
  7. package/dist/agent-farm/commands/send.d.ts.map +1 -1
  8. package/dist/agent-farm/commands/send.js +55 -17
  9. package/dist/agent-farm/commands/send.js.map +1 -1
  10. package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
  11. package/dist/agent-farm/commands/spawn.js +37 -27
  12. package/dist/agent-farm/commands/spawn.js.map +1 -1
  13. package/dist/agent-farm/commands/start.d.ts.map +1 -1
  14. package/dist/agent-farm/commands/start.js +3 -226
  15. package/dist/agent-farm/commands/start.js.map +1 -1
  16. package/dist/agent-farm/commands/status.d.ts.map +1 -1
  17. package/dist/agent-farm/commands/status.js +5 -2
  18. package/dist/agent-farm/commands/status.js.map +1 -1
  19. package/dist/agent-farm/db/index.d.ts.map +1 -1
  20. package/dist/agent-farm/db/index.js +45 -0
  21. package/dist/agent-farm/db/index.js.map +1 -1
  22. package/dist/agent-farm/db/schema.d.ts +1 -1
  23. package/dist/agent-farm/db/schema.d.ts.map +1 -1
  24. package/dist/agent-farm/db/schema.js +2 -2
  25. package/dist/agent-farm/hq-connector.d.ts +0 -4
  26. package/dist/agent-farm/hq-connector.d.ts.map +1 -1
  27. package/dist/agent-farm/hq-connector.js +0 -15
  28. package/dist/agent-farm/hq-connector.js.map +1 -1
  29. package/dist/agent-farm/servers/tower-server.js +228 -13
  30. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  31. package/dist/commands/porch/protocol.d.ts.map +1 -1
  32. package/dist/commands/porch/protocol.js +26 -1
  33. package/dist/commands/porch/protocol.js.map +1 -1
  34. package/package.json +4 -6
  35. package/skeleton/porch/prompts/defend.md +1 -1
  36. package/skeleton/porch/prompts/evaluate.md +2 -2
  37. package/skeleton/porch/prompts/implement.md +1 -1
  38. package/skeleton/porch/prompts/plan.md +1 -1
  39. package/skeleton/porch/prompts/review.md +4 -4
  40. package/skeleton/porch/prompts/specify.md +1 -1
  41. package/skeleton/porch/prompts/understand.md +2 -2
  42. package/skeleton/protocol-schema.json +3 -3
  43. package/skeleton/protocols/bugfix/builder-prompt.md +1 -1
  44. package/skeleton/protocols/bugfix/prompts/pr.md +2 -17
  45. package/skeleton/protocols/experiment/protocol.md +3 -3
  46. package/skeleton/protocols/experiment/templates/notes.md +1 -1
  47. package/skeleton/protocols/maintain/protocol.md +1 -1
  48. package/skeleton/protocols/protocol-schema.json +1 -1
  49. package/skeleton/protocols/{spider → spir}/builder-prompt.md +1 -1
  50. package/skeleton/protocols/{spider → spir}/prompts/implement.md +1 -1
  51. package/skeleton/protocols/{spider → spir}/prompts/plan.md +6 -70
  52. package/skeleton/protocols/{spider → spir}/prompts/review.md +7 -25
  53. package/skeleton/protocols/{spider → spir}/prompts/specify.md +5 -58
  54. package/skeleton/protocols/{spider → spir}/protocol.json +2 -2
  55. package/skeleton/protocols/{spider → spir}/protocol.md +6 -8
  56. package/skeleton/protocols/{spider → spir}/templates/review.md +1 -1
  57. package/skeleton/protocols/tick/builder-prompt.md +1 -1
  58. package/skeleton/protocols/tick/protocol.md +18 -18
  59. package/skeleton/protocols/tick/templates/review.md +1 -1
  60. package/skeleton/resources/commands/overview.md +1 -1
  61. package/skeleton/resources/workflow-reference.md +2 -2
  62. package/skeleton/roles/architect.md +2 -2
  63. package/skeleton/roles/builder.md +2 -2
  64. package/skeleton/templates/AGENTS.md +1 -1
  65. package/skeleton/templates/CLAUDE.md +1 -1
  66. package/skeleton/templates/cheatsheet.md +3 -3
  67. package/skeleton/templates/projectlist.md +1 -1
  68. package/templates/dashboard/js/main.js +1 -1
  69. package/templates/open.html +26 -0
  70. package/dashboard/dist/assets/index-xOaDIZ0l.js +0 -132
  71. package/dashboard/dist/assets/index-xOaDIZ0l.js.map +0 -1
  72. /package/skeleton/protocols/{spider → spir}/templates/plan.md +0 -0
  73. /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 }));