groove-dev 0.10.10 → 0.11.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.
- package/README.md +24 -16
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +22 -0
- package/node_modules/@groove-dev/daemon/src/index.js +5 -0
- package/node_modules/@groove-dev/daemon/src/indexer.js +324 -0
- package/node_modules/@groove-dev/daemon/src/introducer.js +55 -4
- package/node_modules/@groove-dev/daemon/src/journalist.js +140 -51
- package/node_modules/@groove-dev/daemon/src/process.js +3 -2
- package/node_modules/@groove-dev/gui/dist/assets/index-BqZnnVJF.js +73 -0
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/AgentNode.jsx +6 -4
- package/node_modules/@groove-dev/gui/src/components/AgentStats.jsx +1 -0
- package/node_modules/@groove-dev/gui/src/components/SpawnPanel.jsx +70 -0
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +22 -0
- package/packages/daemon/src/index.js +5 -0
- package/packages/daemon/src/indexer.js +324 -0
- package/packages/daemon/src/introducer.js +55 -4
- package/packages/daemon/src/journalist.js +140 -51
- package/packages/daemon/src/process.js +3 -2
- package/packages/gui/dist/assets/index-BqZnnVJF.js +73 -0
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/AgentNode.jsx +6 -4
- package/packages/gui/src/components/AgentStats.jsx +1 -0
- package/packages/gui/src/components/SpawnPanel.jsx +70 -0
- package/groove-logo.png +0 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BPVh7Oqk.js +0 -73
- 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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
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
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
399
|
-
|
|
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
|
|
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
|
|
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
|
|