groove-dev 0.10.10 → 0.12.0

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 (61) hide show
  1. package/README.md +24 -16
  2. package/node_modules/@groove-dev/cli/bin/groove.js +32 -0
  3. package/node_modules/@groove-dev/cli/package.json +1 -1
  4. package/node_modules/@groove-dev/cli/src/commands/audit.js +60 -0
  5. package/node_modules/@groove-dev/cli/src/commands/connect.js +279 -0
  6. package/node_modules/@groove-dev/cli/src/commands/disconnect.js +91 -0
  7. package/node_modules/@groove-dev/cli/src/commands/federation.js +84 -0
  8. package/node_modules/@groove-dev/cli/src/commands/start.js +7 -2
  9. package/node_modules/@groove-dev/cli/src/commands/status.js +4 -1
  10. package/node_modules/@groove-dev/daemon/package.json +1 -1
  11. package/node_modules/@groove-dev/daemon/src/api.js +128 -2
  12. package/node_modules/@groove-dev/daemon/src/audit.js +65 -0
  13. package/node_modules/@groove-dev/daemon/src/federation.js +352 -0
  14. package/node_modules/@groove-dev/daemon/src/firstrun.js +27 -2
  15. package/node_modules/@groove-dev/daemon/src/index.js +64 -6
  16. package/node_modules/@groove-dev/daemon/src/indexer.js +324 -0
  17. package/node_modules/@groove-dev/daemon/src/introducer.js +55 -4
  18. package/node_modules/@groove-dev/daemon/src/journalist.js +140 -51
  19. package/node_modules/@groove-dev/daemon/src/process.js +3 -2
  20. package/node_modules/@groove-dev/gui/dist/assets/index-B49YqEXS.js +73 -0
  21. package/{packages/gui/dist/assets/index-CPzm9ZE9.css → node_modules/@groove-dev/gui/dist/assets/index-Gfb8Zxy9.css} +1 -1
  22. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  23. package/node_modules/@groove-dev/gui/package.json +1 -1
  24. package/node_modules/@groove-dev/gui/src/App.jsx +24 -1
  25. package/node_modules/@groove-dev/gui/src/components/AgentNode.jsx +6 -4
  26. package/node_modules/@groove-dev/gui/src/components/AgentStats.jsx +1 -0
  27. package/node_modules/@groove-dev/gui/src/components/SpawnPanel.jsx +71 -1
  28. package/node_modules/@groove-dev/gui/src/stores/groove.js +19 -2
  29. package/node_modules/@groove-dev/gui/src/theme.css +2 -2
  30. package/package.json +1 -1
  31. package/packages/cli/bin/groove.js +32 -0
  32. package/packages/cli/package.json +1 -1
  33. package/packages/cli/src/commands/audit.js +60 -0
  34. package/packages/cli/src/commands/connect.js +279 -0
  35. package/packages/cli/src/commands/disconnect.js +91 -0
  36. package/packages/cli/src/commands/federation.js +84 -0
  37. package/packages/cli/src/commands/start.js +7 -2
  38. package/packages/cli/src/commands/status.js +4 -1
  39. package/packages/daemon/package.json +1 -1
  40. package/packages/daemon/src/api.js +128 -2
  41. package/packages/daemon/src/audit.js +65 -0
  42. package/packages/daemon/src/federation.js +352 -0
  43. package/packages/daemon/src/firstrun.js +27 -2
  44. package/packages/daemon/src/index.js +64 -6
  45. package/packages/daemon/src/indexer.js +324 -0
  46. package/packages/daemon/src/introducer.js +55 -4
  47. package/packages/daemon/src/journalist.js +140 -51
  48. package/packages/daemon/src/process.js +3 -2
  49. package/packages/gui/dist/assets/index-B49YqEXS.js +73 -0
  50. package/{node_modules/@groove-dev/gui/dist/assets/index-CPzm9ZE9.css → packages/gui/dist/assets/index-Gfb8Zxy9.css} +1 -1
  51. package/packages/gui/dist/index.html +2 -2
  52. package/packages/gui/package.json +1 -1
  53. package/packages/gui/src/App.jsx +24 -1
  54. package/packages/gui/src/components/AgentNode.jsx +6 -4
  55. package/packages/gui/src/components/AgentStats.jsx +1 -0
  56. package/packages/gui/src/components/SpawnPanel.jsx +71 -1
  57. package/packages/gui/src/stores/groove.js +19 -2
  58. package/packages/gui/src/theme.css +2 -2
  59. package/groove-logo.png +0 -0
  60. package/node_modules/@groove-dev/gui/dist/assets/index-BPVh7Oqk.js +0 -73
  61. package/packages/gui/dist/assets/index-BPVh7Oqk.js +0 -73
@@ -224,6 +224,8 @@ export class Journalist {
224
224
  }
225
225
 
226
226
  buildSynthesisPrompt(agents, filteredLogs) {
227
+ const hasWorkspaces = agents.some((a) => a.workingDir);
228
+
227
229
  const parts = [
228
230
  'You are The Journalist for GROOVE, an agent orchestration system.',
229
231
  'Analyze the following agent activity logs and produce a synthesis.',
@@ -231,7 +233,9 @@ export class Journalist {
231
233
  'Output EXACTLY this format (use these exact headers):',
232
234
  '',
233
235
  '## Project Map',
234
- '(What has been built/changed, organized by area. Be specific about files and functions.)',
236
+ hasWorkspaces
237
+ ? '(What has been built/changed, organized by workspace/directory. Use ### subheadings for each workspace.)'
238
+ : '(What has been built/changed, organized by area. Be specific about files and functions.)',
235
239
  '',
236
240
  '## Decisions',
237
241
  '(Key architectural and implementation decisions agents made, with reasoning.)',
@@ -243,20 +247,31 @@ export class Journalist {
243
247
  '',
244
248
  ];
245
249
 
246
- // Add agent logs, budget-trimmed
250
+ // Group agents by workspace for clarity
251
+ const grouped = this.groupByWorkspace(agents);
247
252
  let totalChars = 0;
248
- for (const [agentId, { agent, entries }] of Object.entries(filteredLogs)) {
249
- if (entries.length === 0) continue;
250
253
 
251
- parts.push(`### Agent: ${agent.name} (${agent.role}, scope: ${agent.scope?.join(', ') || 'unrestricted'})`);
254
+ for (const [workspace, wsAgents] of grouped) {
255
+ if (hasWorkspaces) {
256
+ parts.push(`--- Workspace: ${workspace} ---`);
257
+ parts.push('');
258
+ }
259
+
260
+ for (const agent of wsAgents) {
261
+ const log = filteredLogs[agent.id];
262
+ if (!log || log.entries.length === 0) continue;
252
263
 
253
- for (const entry of entries.slice(-100)) { // Last 100 events per agent
254
- const line = this.formatEntry(entry);
255
- if (totalChars + line.length > MAX_LOG_CHARS) break;
256
- parts.push(line);
257
- totalChars += line.length;
264
+ const dir = agent.workingDir ? `, dir: ${agent.workingDir}` : '';
265
+ parts.push(`### Agent: ${agent.name} (${agent.role}${dir}, scope: ${agent.scope?.join(', ') || 'unrestricted'})`);
266
+
267
+ for (const entry of log.entries.slice(-100)) {
268
+ const line = this.formatEntry(entry);
269
+ if (totalChars + line.length > MAX_LOG_CHARS) break;
270
+ parts.push(line);
271
+ totalChars += line.length;
272
+ }
273
+ parts.push('');
258
274
  }
259
- parts.push('');
260
275
  }
261
276
 
262
277
  return parts.join('\n');
@@ -337,66 +352,94 @@ export class Journalist {
337
352
  // --- Fallback structural summary (no AI) ---
338
353
 
339
354
  buildStructuralSummary(agents, filteredLogs) {
340
- const mapParts = [];
341
355
  const decisions = [];
342
356
  const summaryParts = [];
357
+ const grouped = this.groupByWorkspace(agents);
358
+ const workspaceSections = {};
359
+
360
+ for (const [workspace, wsAgents] of grouped) {
361
+ const mapParts = [];
362
+
363
+ for (const agent of wsAgents) {
364
+ const log = filteredLogs[agent.id];
365
+ if (!log) continue;
366
+ const entries = log.entries;
367
+ const tools = entries.filter((e) => e.type === 'tool');
368
+ const errors = entries.filter((e) => e.type === 'error');
369
+ const writes = tools.filter((t) => t.tool === 'Write' || t.tool === 'Edit');
370
+ const reads = tools.filter((t) => t.tool === 'Read');
371
+
372
+ mapParts.push(`### ${agent.name} (${agent.role})`);
373
+ if (writes.length > 0) {
374
+ mapParts.push(`Files modified:`);
375
+ const files = [...new Set(writes.map((w) => w.input).filter(Boolean))];
376
+ files.forEach((f) => mapParts.push(`- ${f}`));
377
+ }
378
+ if (reads.length > 0) {
379
+ const files = [...new Set(reads.map((r) => r.input).filter(Boolean))];
380
+ mapParts.push(`Files read: ${files.slice(0, 10).join(', ')}`);
381
+ }
382
+ if (errors.length > 0) {
383
+ mapParts.push(`Errors: ${errors.length}`);
384
+ }
385
+ mapParts.push(`Activity: ${entries.length} events, ${tools.length} tool calls`);
386
+ mapParts.push('');
343
387
 
344
- for (const [agentId, { agent, entries }] of Object.entries(filteredLogs)) {
345
- const tools = entries.filter((e) => e.type === 'tool');
346
- const errors = entries.filter((e) => e.type === 'error');
347
- const writes = tools.filter((t) => t.tool === 'Write' || t.tool === 'Edit');
348
- const reads = tools.filter((t) => t.tool === 'Read');
349
-
350
- mapParts.push(`### ${agent.name} (${agent.role})`);
351
- if (writes.length > 0) {
352
- mapParts.push(`Files modified:`);
353
- const files = [...new Set(writes.map((w) => w.input).filter(Boolean))];
354
- files.forEach((f) => mapParts.push(`- ${f}`));
355
- }
356
- if (reads.length > 0) {
357
- const files = [...new Set(reads.map((r) => r.input).filter(Boolean))];
358
- mapParts.push(`Files read: ${files.slice(0, 10).join(', ')}`);
359
- }
360
- if (errors.length > 0) {
361
- mapParts.push(`Errors: ${errors.length}`);
362
- }
363
- mapParts.push(`Activity: ${entries.length} events, ${tools.length} tool calls`);
364
- mapParts.push('');
365
-
366
- summaryParts.push(`${agent.name}: ${tools.length} tool calls, ${writes.length} writes`);
388
+ summaryParts.push(`${agent.name}: ${tools.length} tool calls, ${writes.length} writes`);
367
389
 
368
- // Extract decisions from thinking entries
369
- const thoughts = entries.filter((e) => e.type === 'thinking');
370
- for (const t of thoughts.slice(-3)) {
371
- if (t.text.length > 100) {
372
- decisions.push(`- **${agent.name}**: ${t.text.slice(0, 200)}`);
390
+ const thoughts = entries.filter((e) => e.type === 'thinking');
391
+ for (const t of thoughts.slice(-3)) {
392
+ if (t.text.length > 100) {
393
+ decisions.push(`- **${agent.name}**: ${t.text.slice(0, 200)}`);
394
+ }
373
395
  }
374
396
  }
397
+
398
+ workspaceSections[workspace] = mapParts.join('\n');
375
399
  }
376
400
 
377
401
  return {
378
- projectMap: this.buildProjectMapMd(agents, mapParts.join('\n')),
402
+ projectMap: this.buildProjectMapMd(agents, workspaceSections),
379
403
  decisions: decisions.join('\n'),
380
404
  summary: summaryParts.join('. ') || 'No activity.',
381
405
  };
382
406
  }
383
407
 
384
408
  buildProjectMapMd(agents, content) {
385
- return [
409
+ const hasWorkspaces = agents.some((a) => a.workingDir);
410
+
411
+ const lines = [
386
412
  `# GROOVE Project Map`,
387
413
  ``,
388
414
  `*Auto-generated by The Journalist. Cycle ${this.cycleCount}. Updated: ${new Date().toISOString()}*`,
389
415
  ``,
390
416
  `## Active Agents`,
391
417
  ``,
392
- ...agents.map((a) =>
393
- `- **${a.name}** (${a.role}) — ${a.provider} ${a.scope?.join(', ') || 'unrestricted'} tokens: ${a.tokensUsed}`
394
- ),
395
- ``,
396
- `## Activity`,
418
+ ...agents.map((a) => {
419
+ const dir = a.workingDir ? `dir: ${a.workingDir}` : '';
420
+ return `- **${a.name}** (${a.role}) — ${a.provider}${dir} — ${a.scope?.join(', ') || 'unrestricted'} — tokens: ${a.tokensUsed}`;
421
+ }),
397
422
  ``,
398
- content,
399
- ].join('\n');
423
+ ];
424
+
425
+ // Content can be a string (from AI synthesis) or an object of workspace sections (from structural)
426
+ if (typeof content === 'string') {
427
+ lines.push(`## Activity`, ``, content);
428
+ } else {
429
+ // Workspace-sectioned content: { workspaceName: markdownContent }
430
+ const entries = Object.entries(content);
431
+ if (entries.length === 1 && entries[0][0] === 'project root') {
432
+ // Single workspace = flat layout (backwards compat)
433
+ lines.push(`## Activity`, ``, entries[0][1]);
434
+ } else {
435
+ for (const [workspace, sectionContent] of entries) {
436
+ if (!sectionContent.trim()) continue;
437
+ lines.push(`## ${workspace}`, ``, sectionContent);
438
+ }
439
+ }
440
+ }
441
+
442
+ return lines.join('\n');
400
443
  }
401
444
 
402
445
  // --- File outputs ---
@@ -459,9 +502,12 @@ export class Journalist {
459
502
  const agentLog = filteredLogs[agent.id];
460
503
  const entries = agentLog?.entries || [];
461
504
 
462
- // Get current project map for context
505
+ // Get current project map scoped to agent's workspace if applicable
463
506
  const mapPath = resolve(this.daemon.projectDir, 'GROOVE_PROJECT_MAP.md');
464
- const projectMap = existsSync(mapPath) ? readFileSync(mapPath, 'utf8') : '';
507
+ const fullMap = existsSync(mapPath) ? readFileSync(mapPath, 'utf8') : '';
508
+ const projectMap = agent.workingDir
509
+ ? this.extractWorkspaceSection(fullMap, agent.workingDir)
510
+ : fullMap;
465
511
 
466
512
  // Build a focused handoff brief
467
513
  const toolSummary = entries
@@ -492,6 +538,7 @@ export class Journalist {
492
538
  `- Role: ${agent.role}`,
493
539
  `- Scope: ${agent.scope?.join(', ') || 'unrestricted'}`,
494
540
  `- Provider: ${agent.provider}`,
541
+ agent.workingDir ? `- Working directory: ${agent.workingDir}` : '',
495
542
  ``,
496
543
  `## Previous Session`,
497
544
  `- Tokens used: ${agent.tokensUsed}`,
@@ -508,10 +555,52 @@ export class Journalist {
508
555
  ``,
509
556
  `Continue the work from where the previous session left off.`,
510
557
  `Review AGENTS_REGISTRY.md for team awareness.`,
558
+ agent.workingDir ? `Stay within your working directory: ${agent.workingDir}` : '',
511
559
  agent.prompt ? `\nOriginal task: ${agent.prompt}` : '',
512
560
  ].filter(Boolean).join('\n');
513
561
  }
514
562
 
563
+ // --- Workspace Grouping ---
564
+
565
+ /**
566
+ * Group agents by workingDir. Returns a Map of workspace → agents[].
567
+ * Agents with no workingDir go under "project root".
568
+ */
569
+ groupByWorkspace(agents) {
570
+ const groups = new Map();
571
+ for (const agent of agents) {
572
+ const key = agent.workingDir || 'project root';
573
+ if (!groups.has(key)) groups.set(key, []);
574
+ groups.get(key).push(agent);
575
+ }
576
+ return groups;
577
+ }
578
+
579
+ /**
580
+ * Extract the workspace-specific section from a project map for handoff briefs.
581
+ * Returns the full map if no workspace match or no workspace sections.
582
+ */
583
+ extractWorkspaceSection(projectMap, workingDir) {
584
+ if (!workingDir || !projectMap) return projectMap;
585
+
586
+ // Try to find a section header matching this workspace
587
+ // Looks for "## packages/frontend" or "## workspace-name" patterns
588
+ const sectionPattern = new RegExp(
589
+ `^## ${workingDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b[^\\n]*\\n([\\s\\S]*?)(?=^## |$)`,
590
+ 'm'
591
+ );
592
+ const match = projectMap.match(sectionPattern);
593
+ if (match) {
594
+ // Include the agents header + the matched workspace section
595
+ const agentsSection = projectMap.match(/^# GROOVE Project Map[\s\S]*?(?=^## (?!Active))/m);
596
+ const header = agentsSection ? agentsSection[0] : '';
597
+ return header + `## ${workingDir}\n` + match[1];
598
+ }
599
+
600
+ // No workspace section found — return full map (truncated)
601
+ return projectMap;
602
+ }
603
+
515
604
  // --- Agent File Tracking ---
516
605
 
517
606
  /**
@@ -22,11 +22,12 @@ After completing your plan, you MUST do two things:
22
22
  2. Save a machine-readable team config to .groove/recommended-team.json using this EXACT format:
23
23
  [
24
24
  { "role": "fullstack", "scope": [], "prompt": "Set up project infrastructure: package.json, tsconfig, vite config, dependencies. Once all other agents finish, audit and QC their work, fix any issues, then launch the dev server. Output the localhost URL where the app can be accessed." },
25
- { "role": "backend", "scope": ["src/api/**", "src/server/**", "src/db/**", "src/lib/**"], "prompt": "Build the backend: [specific tasks from your plan]" },
26
- { "role": "frontend", "scope": ["src/components/**", "src/views/**", "src/pages/**", "src/styles/**"], "prompt": "Build the frontend: [specific tasks from your plan]" }
25
+ { "role": "backend", "scope": ["src/api/**", "src/server/**", "src/db/**", "src/lib/**"], "workingDir": "packages/backend", "prompt": "Build the backend: [specific tasks from your plan]" },
26
+ { "role": "frontend", "scope": ["src/components/**", "src/views/**", "src/pages/**", "src/styles/**"], "workingDir": "packages/frontend", "prompt": "Build the frontend: [specific tasks from your plan]" }
27
27
  ]
28
28
 
29
29
  Include only the agents needed. Set appropriate scopes for each role. Write detailed prompts based on your plan so each agent knows exactly what to build.
30
+ If the project is a monorepo or has distinct subdirectories (e.g. packages/, apps/), set "workingDir" to the relative path for each agent so it spawns inside its subdirectory. Omit workingDir for agents that need full project access (like fullstack or planner).
30
31
 
31
32
  Always include a fullstack agent. Its job: set up infrastructure first, then after all other agents finish, audit their work, fix issues, build the project, launch the dev server, and output the localhost URL so the user can immediately see the result. Include testing/devops only if the project needs them.
32
33