groove-dev 0.24.15 → 0.25.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/node_modules/@groove-dev/daemon/src/api.js +44 -14
- package/node_modules/@groove-dev/daemon/src/introducer.js +10 -0
- package/node_modules/@groove-dev/daemon/src/process.js +34 -7
- package/node_modules/@groove-dev/daemon/src/tokentracker.js +2 -1
- package/node_modules/@groove-dev/gui/dist/assets/{index-v1z73h9P.js → index-B8wkZgk8.js} +2 -2
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/src/stores/groove.js +5 -1
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +8 -0
- package/package.json +1 -1
- package/packages/daemon/src/api.js +44 -14
- package/packages/daemon/src/introducer.js +10 -0
- package/packages/daemon/src/process.js +34 -7
- package/packages/daemon/src/tokentracker.js +2 -1
- package/packages/gui/dist/assets/{index-v1z73h9P.js → index-B8wkZgk8.js} +2 -2
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/src/stores/groove.js +5 -1
- package/packages/gui/src/views/agents.jsx +8 -0
|
@@ -1563,8 +1563,15 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
1563
1563
|
return res.json({ exists: false, agents: [] });
|
|
1564
1564
|
}
|
|
1565
1565
|
try {
|
|
1566
|
-
const
|
|
1567
|
-
|
|
1566
|
+
const raw = JSON.parse(readFileSync(teamPath, 'utf8'));
|
|
1567
|
+
// Support both old format (bare array) and new format ({ projectDir, agents })
|
|
1568
|
+
if (Array.isArray(raw)) {
|
|
1569
|
+
res.json({ exists: true, agents: raw });
|
|
1570
|
+
} else if (raw && Array.isArray(raw.agents)) {
|
|
1571
|
+
res.json({ exists: true, agents: raw.agents, projectDir: raw.projectDir || null });
|
|
1572
|
+
} else {
|
|
1573
|
+
res.json({ exists: false, agents: [] });
|
|
1574
|
+
}
|
|
1568
1575
|
} catch {
|
|
1569
1576
|
res.json({ exists: false, agents: [] });
|
|
1570
1577
|
}
|
|
@@ -1576,28 +1583,51 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
1576
1583
|
return res.status(404).json({ error: 'No recommended team found. Run a planner first.' });
|
|
1577
1584
|
}
|
|
1578
1585
|
try {
|
|
1579
|
-
const
|
|
1580
|
-
|
|
1586
|
+
const raw = JSON.parse(readFileSync(teamPath, 'utf8'));
|
|
1587
|
+
|
|
1588
|
+
// Support both old format (bare array) and new format ({ projectDir, agents })
|
|
1589
|
+
let agentConfigs;
|
|
1590
|
+
let projectDir = null;
|
|
1591
|
+
if (Array.isArray(raw)) {
|
|
1592
|
+
agentConfigs = raw;
|
|
1593
|
+
} else if (raw && Array.isArray(raw.agents)) {
|
|
1594
|
+
agentConfigs = raw.agents;
|
|
1595
|
+
projectDir = raw.projectDir || null;
|
|
1596
|
+
} else {
|
|
1597
|
+
return res.status(400).json({ error: 'Invalid recommended team format' });
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
if (agentConfigs.length === 0) {
|
|
1581
1601
|
return res.status(400).json({ error: 'Recommended team is empty' });
|
|
1582
1602
|
}
|
|
1583
1603
|
|
|
1584
|
-
const
|
|
1604
|
+
const baseDir = daemon.config?.defaultWorkingDir || daemon.projectDir;
|
|
1585
1605
|
const defaultTeamId = daemon.teams.getDefault()?.id || null;
|
|
1586
1606
|
|
|
1607
|
+
// If planner specified a project directory, create it and use it as workingDir
|
|
1608
|
+
let projectWorkingDir = baseDir;
|
|
1609
|
+
if (projectDir) {
|
|
1610
|
+
// Sanitize: kebab-case, no path traversal
|
|
1611
|
+
const safeName = String(projectDir).replace(/[^a-zA-Z0-9_-]/g, '-').slice(0, 64);
|
|
1612
|
+
projectWorkingDir = resolve(baseDir, safeName);
|
|
1613
|
+
mkdirSync(projectWorkingDir, { recursive: true });
|
|
1614
|
+
console.log(`[Groove] Project directory: ${projectWorkingDir}`);
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1587
1617
|
// Separate phase 1 (builders) and phase 2 (QC/finisher)
|
|
1588
|
-
const phase1 =
|
|
1589
|
-
let phase2 =
|
|
1618
|
+
const phase1 = agentConfigs.filter((a) => !a.phase || a.phase === 1);
|
|
1619
|
+
let phase2 = agentConfigs.filter((a) => a.phase === 2);
|
|
1590
1620
|
|
|
1591
1621
|
// Safety net: if planner forgot the QC agent, auto-add one
|
|
1592
1622
|
if (phase2.length === 0 && phase1.length >= 2) {
|
|
1593
1623
|
phase2 = [{
|
|
1594
1624
|
name: 'qc-agent',
|
|
1595
1625
|
role: 'fullstack', phase: 2, scope: [],
|
|
1596
|
-
prompt: 'QC Senior Dev: All builder agents have completed. Audit their changes for correctness, fix any issues, run tests, build the project, commit all changes, and launch the dev server. Output the localhost URL where the app can be accessed.',
|
|
1626
|
+
prompt: 'QC Senior Dev: All builder agents have completed. Audit their changes for correctness, fix any issues, run tests, build the project, commit all changes, and launch the dev server. Output the localhost URL where the app can be accessed. IMPORTANT: Do NOT delete files from other projects or directories outside this project.',
|
|
1597
1627
|
}];
|
|
1598
1628
|
}
|
|
1599
1629
|
|
|
1600
|
-
// Spawn phase 1 agents immediately
|
|
1630
|
+
// Spawn phase 1 agents immediately — all scoped to projectWorkingDir
|
|
1601
1631
|
const spawned = [];
|
|
1602
1632
|
const failed = [];
|
|
1603
1633
|
const phase1Ids = [];
|
|
@@ -1610,7 +1640,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
1610
1640
|
provider: config.provider || 'claude-code',
|
|
1611
1641
|
model: config.model || 'auto',
|
|
1612
1642
|
permission: config.permission || 'auto',
|
|
1613
|
-
workingDir: config.workingDir ||
|
|
1643
|
+
workingDir: config.workingDir || projectWorkingDir,
|
|
1614
1644
|
name: config.name || undefined,
|
|
1615
1645
|
});
|
|
1616
1646
|
validated.teamId = defaultTeamId;
|
|
@@ -1623,7 +1653,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
1623
1653
|
}
|
|
1624
1654
|
}
|
|
1625
1655
|
|
|
1626
|
-
//
|
|
1656
|
+
// Phase 2 agents also scoped to projectWorkingDir
|
|
1627
1657
|
if (phase2.length > 0 && phase1Ids.length > 0) {
|
|
1628
1658
|
daemon._pendingPhase2 = daemon._pendingPhase2 || [];
|
|
1629
1659
|
daemon._pendingPhase2.push({
|
|
@@ -1632,7 +1662,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
1632
1662
|
role: c.role, scope: c.scope || [], prompt: c.prompt || '',
|
|
1633
1663
|
provider: c.provider || 'claude-code', model: c.model || 'auto',
|
|
1634
1664
|
permission: c.permission || 'auto',
|
|
1635
|
-
workingDir: c.workingDir ||
|
|
1665
|
+
workingDir: c.workingDir || projectWorkingDir,
|
|
1636
1666
|
name: c.name || undefined,
|
|
1637
1667
|
teamId: defaultTeamId,
|
|
1638
1668
|
})),
|
|
@@ -1641,9 +1671,9 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
1641
1671
|
|
|
1642
1672
|
daemon.audit.log('team.launch', {
|
|
1643
1673
|
phase1: spawned.length, phase2Pending: phase2.length, failed: failed.length,
|
|
1644
|
-
agents: spawned.map((a) => a.role),
|
|
1674
|
+
agents: spawned.map((a) => a.role), projectDir: projectDir || null,
|
|
1645
1675
|
});
|
|
1646
|
-
res.json({ launched: spawned.length, phase2Pending: phase2.length, agents: spawned, failed });
|
|
1676
|
+
res.json({ launched: spawned.length, phase2Pending: phase2.length, agents: spawned, failed, projectDir: projectDir || null });
|
|
1647
1677
|
} catch (err) {
|
|
1648
1678
|
res.status(500).json({ error: err.message });
|
|
1649
1679
|
}
|
|
@@ -127,6 +127,16 @@ export class Introducer {
|
|
|
127
127
|
lines.push(`4. Clear your entry from \`.groove/coordination.md\` when done`);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
// File safety — prevent agents from deleting files they didn't create
|
|
131
|
+
lines.push('');
|
|
132
|
+
lines.push(`## File Safety`);
|
|
133
|
+
lines.push('');
|
|
134
|
+
lines.push(`CRITICAL: NEVER delete files you did not create in this session. Do NOT remove files from other projects, previous work, or unrelated directories.`);
|
|
135
|
+
if (newAgent.workingDir) {
|
|
136
|
+
lines.push(`Your working directory is \`${newAgent.workingDir}\`. Stay inside it. Do NOT modify or delete files outside this directory.`);
|
|
137
|
+
}
|
|
138
|
+
lines.push(`If you see files that seem unrelated to your task, leave them alone — they belong to another project or agent.`);
|
|
139
|
+
|
|
130
140
|
// Memory containment — prevent agents from reading/writing auto-memory
|
|
131
141
|
// which can contain stale context from unrelated sessions in the same dir
|
|
132
142
|
lines.push('');
|
|
@@ -74,15 +74,33 @@ After completing your plan, you MUST do two things:
|
|
|
74
74
|
1. Write your team recommendation as a clear summary in your output so the user can review it.
|
|
75
75
|
|
|
76
76
|
2. Save a machine-readable team config to .groove/recommended-team.json using this EXACT format:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
|
|
78
|
+
For NEW projects (building something from scratch):
|
|
79
|
+
{
|
|
80
|
+
"projectDir": "my-project-name",
|
|
81
|
+
"agents": [
|
|
82
|
+
{ "role": "frontend", "phase": 1, "scope": ["src/components/**", "src/views/**"], "prompt": "Build the frontend: [specific tasks]" },
|
|
83
|
+
{ "role": "backend", "phase": 1, "scope": ["src/api/**", "src/server/**"], "prompt": "Build the backend: [specific tasks]" },
|
|
84
|
+
{ "role": "fullstack", "phase": 2, "scope": [], "prompt": "QC Senior Dev: Audit all changes from phase 1 agents. Verify correctness, fix issues, run tests, build the project, commit, and launch. Output the localhost URL." }
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
For EXISTING codebases (modifying/extending an existing project):
|
|
89
|
+
{
|
|
90
|
+
"agents": [
|
|
91
|
+
{ "role": "frontend", "phase": 1, "scope": ["src/components/**"], "prompt": "Update the frontend: [specific tasks]" },
|
|
92
|
+
{ "role": "fullstack", "phase": 2, "scope": [], "prompt": "QC Senior Dev: Audit all changes, fix issues, run tests, build, commit, and launch." }
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
PROJECT DIRECTORY RULES:
|
|
97
|
+
- For NEW projects: ALWAYS include "projectDir" with a short, clean directory name (kebab-case, e.g. "cat-website", "landing-page", "api-service"). All agents will be spawned inside this directory so each project stays isolated.
|
|
98
|
+
- For EXISTING codebases: Do NOT include "projectDir". Agents work in the current repo root. You can tell an existing codebase by the presence of package.json, .git, or established source directories.
|
|
99
|
+
- NEVER mix projects. Each new project gets its own directory.
|
|
82
100
|
|
|
83
101
|
MANDATORY RULES — NEVER SKIP THESE:
|
|
84
102
|
|
|
85
|
-
1. The LAST entry in the array MUST be: { "role": "fullstack", "phase": 2, ... }
|
|
103
|
+
1. The LAST entry in the agents array MUST be: { "role": "fullstack", "phase": 2, ... }
|
|
86
104
|
This is the QC Senior Dev. It auto-spawns after all other agents finish.
|
|
87
105
|
Its prompt: audit changes, fix issues, run tests, build, commit, launch.
|
|
88
106
|
NEVER omit this agent. Every team needs a QC.
|
|
@@ -93,7 +111,7 @@ MANDATORY RULES — NEVER SKIP THESE:
|
|
|
93
111
|
|
|
94
112
|
4. Set appropriate scopes. Write detailed prompts so each agent knows exactly what to build.
|
|
95
113
|
|
|
96
|
-
5.
|
|
114
|
+
5. NEVER instruct any agent to delete files from other projects or clean up unrelated code. Each agent must ONLY create and modify files relevant to its assigned tasks.
|
|
97
115
|
|
|
98
116
|
IMPORTANT: Do not use markdown formatting like ** or ### in your output. Write in plain text with clean formatting. Use line breaks, dashes, and indentation for structure.
|
|
99
117
|
|
|
@@ -183,6 +201,15 @@ export class ProcessManager {
|
|
|
183
201
|
// Generate introduction context (team awareness + negotiation)
|
|
184
202
|
const introContext = introducer.generateContext(agent, { taskNegotiation });
|
|
185
203
|
|
|
204
|
+
// Track cold-start savings — agent gets context from planner/journalist/team
|
|
205
|
+
// instead of exploring the codebase from scratch
|
|
206
|
+
const otherAgents = registry.getAll().filter((a) => a.id !== agent.id);
|
|
207
|
+
const hasTeamContext = otherAgents.length > 0;
|
|
208
|
+
const hasJournalistContext = this.daemon.journalist?.getLastSynthesis()?.projectMap;
|
|
209
|
+
if (hasTeamContext || hasJournalistContext) {
|
|
210
|
+
this.daemon.tokens.recordColdStartSkipped();
|
|
211
|
+
}
|
|
212
|
+
|
|
186
213
|
// Update AGENTS_REGISTRY.md (other agents can see this new agent)
|
|
187
214
|
introducer.writeRegistryFile(this.daemon.projectDir);
|
|
188
215
|
|
|
@@ -5,7 +5,8 @@ import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
|
5
5
|
import { resolve } from 'path';
|
|
6
6
|
|
|
7
7
|
// Estimated tokens wasted per cold-start without GROOVE context
|
|
8
|
-
|
|
8
|
+
// (agent explores codebase, reads files, discovers structure, builds mental model)
|
|
9
|
+
const COLD_START_OVERHEAD = 8000;
|
|
9
10
|
// Estimated tokens wasted per file conflict (agent discovers, backs off, retries)
|
|
10
11
|
const CONFLICT_OVERHEAD = 500;
|
|
11
12
|
|