groove-dev 0.17.8 → 0.18.2

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 (131) hide show
  1. package/node_modules/@groove-dev/cli/package.json +4 -3
  2. package/node_modules/@groove-dev/daemon/google-oauth.json +5 -0
  3. package/node_modules/@groove-dev/daemon/integrations-registry.json +0 -40
  4. package/node_modules/@groove-dev/daemon/package.json +4 -3
  5. package/node_modules/@groove-dev/daemon/src/api.js +212 -21
  6. package/node_modules/@groove-dev/daemon/src/index.js +68 -1
  7. package/node_modules/@groove-dev/daemon/src/integrations.js +59 -20
  8. package/node_modules/@groove-dev/daemon/src/process.js +83 -11
  9. package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +4 -0
  10. package/node_modules/@groove-dev/daemon/src/registry.js +1 -1
  11. package/node_modules/@groove-dev/gui/.groove/audit.log +1 -0
  12. package/node_modules/@groove-dev/gui/.groove/codebase-index.json +64 -0
  13. package/node_modules/@groove-dev/gui/.groove/config.json +10 -0
  14. package/node_modules/@groove-dev/gui/.groove/coordination.md +5 -0
  15. package/node_modules/@groove-dev/gui/.groove/credentials.json +6 -0
  16. package/node_modules/@groove-dev/gui/.groove/daemon.port +1 -0
  17. package/node_modules/@groove-dev/gui/.groove/federation/identity.key +3 -0
  18. package/node_modules/@groove-dev/gui/.groove/federation/identity.pub +3 -0
  19. package/node_modules/@groove-dev/gui/.groove/integrations/package.json +6 -0
  20. package/node_modules/@groove-dev/gui/.groove/state.json +3 -0
  21. package/node_modules/@groove-dev/gui/dist/assets/index-x5suAiK7.js +182 -0
  22. package/node_modules/@groove-dev/gui/dist/index.html +1 -1
  23. package/node_modules/@groove-dev/gui/package.json +5 -4
  24. package/node_modules/@groove-dev/gui/src/App.jsx +149 -76
  25. package/node_modules/@groove-dev/gui/src/components/AgentActions.jsx +130 -1
  26. package/node_modules/@groove-dev/gui/src/components/AgentChat.jsx +47 -7
  27. package/node_modules/@groove-dev/gui/src/components/AgentNode.jsx +13 -83
  28. package/node_modules/@groove-dev/gui/src/components/SpawnPanel.jsx +918 -580
  29. package/node_modules/@groove-dev/gui/src/stores/groove.js +31 -2
  30. package/node_modules/@groove-dev/gui/src/views/AgentTree.jsx +133 -67
  31. package/node_modules/@groove-dev/gui/src/views/FileEditor.jsx +85 -1
  32. package/node_modules/@groove-dev/gui/src/views/IntegrationsStore.jsx +121 -44
  33. package/package.json +1 -2
  34. package/packages/cli/package.json +4 -3
  35. package/packages/daemon/integrations-registry.json +0 -40
  36. package/packages/daemon/package.json +4 -3
  37. package/packages/daemon/src/api.js +212 -21
  38. package/packages/daemon/src/index.js +68 -1
  39. package/packages/daemon/src/integrations.js +59 -20
  40. package/packages/daemon/src/process.js +83 -11
  41. package/packages/daemon/src/providers/claude-code.js +4 -0
  42. package/packages/daemon/src/registry.js +1 -1
  43. package/packages/gui/dist/assets/index-x5suAiK7.js +182 -0
  44. package/packages/gui/dist/index.html +1 -1
  45. package/packages/gui/node_modules/.vite/deps/@codemirror_autocomplete.js +68 -0
  46. package/packages/gui/node_modules/.vite/deps/@codemirror_autocomplete.js.map +7 -0
  47. package/packages/gui/node_modules/.vite/deps/@codemirror_commands.js +1420 -0
  48. package/packages/gui/node_modules/.vite/deps/@codemirror_commands.js.map +7 -0
  49. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-css.js +17 -0
  50. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-css.js.map +7 -0
  51. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js +22 -0
  52. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js.map +7 -0
  53. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js +34 -0
  54. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js.map +7 -0
  55. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-json.js +101 -0
  56. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-json.js.map +7 -0
  57. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js +2534 -0
  58. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js.map +7 -0
  59. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js +789 -0
  60. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js.map +7 -0
  61. package/packages/gui/node_modules/.vite/deps/@codemirror_language.js +115 -0
  62. package/packages/gui/node_modules/.vite/deps/@codemirror_language.js.map +7 -0
  63. package/packages/gui/node_modules/.vite/deps/@codemirror_search.js +1136 -0
  64. package/packages/gui/node_modules/.vite/deps/@codemirror_search.js.map +7 -0
  65. package/packages/gui/node_modules/.vite/deps/@codemirror_state.js +63 -0
  66. package/packages/gui/node_modules/.vite/deps/@codemirror_state.js.map +7 -0
  67. package/packages/gui/node_modules/.vite/deps/@codemirror_theme-one-dark.js +179 -0
  68. package/packages/gui/node_modules/.vite/deps/@codemirror_theme-one-dark.js.map +7 -0
  69. package/packages/gui/node_modules/.vite/deps/@codemirror_view.js +104 -0
  70. package/packages/gui/node_modules/.vite/deps/@codemirror_view.js.map +7 -0
  71. package/packages/gui/node_modules/.vite/deps/@xterm_addon-fit.js +46 -0
  72. package/packages/gui/node_modules/.vite/deps/@xterm_addon-fit.js.map +7 -0
  73. package/packages/gui/node_modules/.vite/deps/@xterm_addon-web-links.js +121 -0
  74. package/packages/gui/node_modules/.vite/deps/@xterm_addon-web-links.js.map +7 -0
  75. package/packages/gui/node_modules/.vite/deps/@xterm_xterm.js +9237 -0
  76. package/packages/gui/node_modules/.vite/deps/@xterm_xterm.js.map +7 -0
  77. package/packages/gui/node_modules/.vite/deps/@xyflow_react.js +9934 -0
  78. package/packages/gui/node_modules/.vite/deps/@xyflow_react.js.map +7 -0
  79. package/packages/gui/node_modules/.vite/deps/_metadata.json +184 -0
  80. package/packages/gui/node_modules/.vite/deps/chunk-3EE34IFC.js +5169 -0
  81. package/packages/gui/node_modules/.vite/deps/chunk-3EE34IFC.js.map +7 -0
  82. package/packages/gui/node_modules/.vite/deps/chunk-3IB5EUP7.js +2000 -0
  83. package/packages/gui/node_modules/.vite/deps/chunk-3IB5EUP7.js.map +7 -0
  84. package/packages/gui/node_modules/.vite/deps/chunk-3LBP22MX.js +1115 -0
  85. package/packages/gui/node_modules/.vite/deps/chunk-3LBP22MX.js.map +7 -0
  86. package/packages/gui/node_modules/.vite/deps/chunk-3Q7HT7ZF.js +701 -0
  87. package/packages/gui/node_modules/.vite/deps/chunk-3Q7HT7ZF.js.map +7 -0
  88. package/packages/gui/node_modules/.vite/deps/chunk-44CLUOQE.js +1776 -0
  89. package/packages/gui/node_modules/.vite/deps/chunk-44CLUOQE.js.map +7 -0
  90. package/packages/gui/node_modules/.vite/deps/chunk-5RZAEUNX.js +280 -0
  91. package/packages/gui/node_modules/.vite/deps/chunk-5RZAEUNX.js.map +7 -0
  92. package/packages/gui/node_modules/.vite/deps/chunk-5WRI5ZAA.js +30 -0
  93. package/packages/gui/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +7 -0
  94. package/packages/gui/node_modules/.vite/deps/chunk-7FYDPZIO.js +1004 -0
  95. package/packages/gui/node_modules/.vite/deps/chunk-7FYDPZIO.js.map +7 -0
  96. package/packages/gui/node_modules/.vite/deps/chunk-BX6POZPY.js +292 -0
  97. package/packages/gui/node_modules/.vite/deps/chunk-BX6POZPY.js.map +7 -0
  98. package/packages/gui/node_modules/.vite/deps/chunk-HVFOBSCQ.js +1062 -0
  99. package/packages/gui/node_modules/.vite/deps/chunk-HVFOBSCQ.js.map +7 -0
  100. package/packages/gui/node_modules/.vite/deps/chunk-RE2FU7ZU.js +10985 -0
  101. package/packages/gui/node_modules/.vite/deps/chunk-RE2FU7ZU.js.map +7 -0
  102. package/packages/gui/node_modules/.vite/deps/chunk-YYJMNVCJ.js +3459 -0
  103. package/packages/gui/node_modules/.vite/deps/chunk-YYJMNVCJ.js.map +7 -0
  104. package/packages/gui/node_modules/.vite/deps/package.json +3 -0
  105. package/packages/gui/node_modules/.vite/deps/react-dom.js +6 -0
  106. package/packages/gui/node_modules/.vite/deps/react-dom.js.map +7 -0
  107. package/packages/gui/node_modules/.vite/deps/react-dom_client.js +20217 -0
  108. package/packages/gui/node_modules/.vite/deps/react-dom_client.js.map +7 -0
  109. package/packages/gui/node_modules/.vite/deps/react.js +5 -0
  110. package/packages/gui/node_modules/.vite/deps/react.js.map +7 -0
  111. package/packages/gui/node_modules/.vite/deps/react_jsx-dev-runtime.js +278 -0
  112. package/packages/gui/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
  113. package/packages/gui/node_modules/.vite/deps/react_jsx-runtime.js +6 -0
  114. package/packages/gui/node_modules/.vite/deps/react_jsx-runtime.js.map +7 -0
  115. package/packages/gui/node_modules/.vite/deps/zustand.js +56 -0
  116. package/packages/gui/node_modules/.vite/deps/zustand.js.map +7 -0
  117. package/packages/gui/package.json +5 -4
  118. package/packages/gui/src/App.jsx +149 -76
  119. package/packages/gui/src/components/AgentActions.jsx +130 -1
  120. package/packages/gui/src/components/AgentChat.jsx +47 -7
  121. package/packages/gui/src/components/AgentNode.jsx +13 -83
  122. package/packages/gui/src/components/SpawnPanel.jsx +918 -580
  123. package/packages/gui/src/stores/groove.js +31 -2
  124. package/packages/gui/src/views/AgentTree.jsx +133 -67
  125. package/packages/gui/src/views/FileEditor.jsx +85 -1
  126. package/packages/gui/src/views/IntegrationsStore.jsx +121 -44
  127. package/docs/FILE-EDITOR-PLAN.md +0 -253
  128. package/docs/GUI_DESIGN_SPEC.md +0 -402
  129. package/docs/SKILLS-API-SPEC.md +0 -277
  130. package/node_modules/@groove-dev/gui/dist/assets/index-D5dtDQf0.js +0 -156
  131. package/packages/gui/dist/assets/index-D5dtDQf0.js +0 -156
@@ -380,10 +380,8 @@ export class IntegrationStore {
380
380
  if (!entry) throw new Error(`Integration not found: ${integrationId}`);
381
381
  if (entry.authType !== 'oauth-google') throw new Error('Integration does not use OAuth');
382
382
 
383
- // Check if user has provided their own Google OAuth client (stored globally)
384
- const clientId = this.getCredential('google-oauth', 'GOOGLE_CLIENT_ID');
385
- const clientSecret = this.getCredential('google-oauth', 'GOOGLE_CLIENT_SECRET');
386
- if (!clientId || !clientSecret) {
383
+ const creds = this._getGoogleOAuthCredentials();
384
+ if (!creds) {
387
385
  throw new Error('Google OAuth not configured. Set up your Google Cloud project first.');
388
386
  }
389
387
 
@@ -392,7 +390,7 @@ export class IntegrationStore {
392
390
  const scopes = entry.oauthScopes || [];
393
391
 
394
392
  const params = new URLSearchParams({
395
- client_id: clientId,
393
+ client_id: creds.clientId,
396
394
  redirect_uri: redirectUri,
397
395
  response_type: 'code',
398
396
  scope: scopes.join(' '),
@@ -408,11 +406,11 @@ export class IntegrationStore {
408
406
  * Handle OAuth callback — exchange code for tokens.
409
407
  */
410
408
  async handleOAuthCallback(code, integrationId) {
411
- const clientId = this.getCredential('google-oauth', 'GOOGLE_CLIENT_ID');
412
- const clientSecret = this.getCredential('google-oauth', 'GOOGLE_CLIENT_SECRET');
413
- if (!clientId || !clientSecret) {
409
+ const creds = this._getGoogleOAuthCredentials();
410
+ if (!creds) {
414
411
  throw new Error('Google OAuth credentials not found');
415
412
  }
413
+ const { clientId, clientSecret } = creds;
416
414
 
417
415
  const port = this.daemon.port || 31415;
418
416
  const redirectUri = `http://localhost:${port}/api/integrations/oauth/callback`;
@@ -451,10 +449,32 @@ export class IntegrationStore {
451
449
  /**
452
450
  * Check if Google OAuth is configured (user has set up their Cloud project).
453
451
  */
452
+ /**
453
+ * Get Google OAuth credentials from user config OR bundled defaults.
454
+ * User-configured credentials take priority over bundled defaults.
455
+ */
456
+ _getGoogleOAuthCredentials() {
457
+ // 1. Check user-configured credentials (encrypted store)
458
+ let clientId = this.getCredential('google-oauth', 'GOOGLE_CLIENT_ID');
459
+ let clientSecret = this.getCredential('google-oauth', 'GOOGLE_CLIENT_SECRET');
460
+ if (clientId && clientSecret) return { clientId, clientSecret, source: 'user' };
461
+
462
+ // 2. Check bundled defaults (shipped with Groove)
463
+ try {
464
+ const defaultsPath = resolve(__dirname, '../google-oauth.json');
465
+ if (existsSync(defaultsPath)) {
466
+ const defaults = JSON.parse(readFileSync(defaultsPath, 'utf8'));
467
+ if (defaults.client_id && defaults.client_secret) {
468
+ return { clientId: defaults.client_id, clientSecret: defaults.client_secret, source: 'bundled' };
469
+ }
470
+ }
471
+ } catch { /* no bundled defaults */ }
472
+
473
+ return null;
474
+ }
475
+
454
476
  isGoogleOAuthConfigured() {
455
- const clientId = this.getCredential('google-oauth', 'GOOGLE_CLIENT_ID');
456
- const clientSecret = this.getCredential('google-oauth', 'GOOGLE_CLIENT_SECRET');
457
- return !!(clientId && clientSecret);
477
+ return !!this._getGoogleOAuthCredentials();
458
478
  }
459
479
 
460
480
  /**
@@ -467,14 +487,18 @@ export class IntegrationStore {
467
487
  const entry = this.registry.find((s) => s.id === integrationId);
468
488
  if (!entry) throw new Error(`Integration not found: ${integrationId}`);
469
489
 
490
+ console.log(`[Groove:Integrations] authenticate(${integrationId}) — authType: ${entry.authType}`);
491
+
470
492
  // For google-autoauth integrations, write the gcp-oauth.keys.json file
471
493
  // that the MCP server expects before it can start the OAuth browser flow
472
494
  if (entry.authType === 'google-autoauth') {
495
+ console.log(`[Groove:Integrations] Writing gcp-oauth.keys.json for ${integrationId}`);
473
496
  this._writeGoogleOAuthKeys(entry);
474
497
  }
475
498
 
476
499
  const command = entry.command || 'npx';
477
500
  const args = entry.args || ['-y', entry.npmPackage];
501
+ console.log(`[Groove:Integrations] Spawning: ${command} ${args.join(' ')}`);
478
502
 
479
503
  // Build env with any configured credentials
480
504
  const env = {};
@@ -491,6 +515,8 @@ export class IntegrationStore {
491
515
  detached: false,
492
516
  });
493
517
 
518
+ console.log(`[Groove:Integrations] Process spawned, PID: ${proc.pid}`);
519
+
494
520
  // Send MCP handshake to initialize the server — this triggers auth
495
521
  const initMsg = JSON.stringify({
496
522
  jsonrpc: '2.0', id: 1, method: 'initialize',
@@ -507,28 +533,38 @@ export class IntegrationStore {
507
533
  jsonrpc: '2.0', method: 'notifications/initialized',
508
534
  });
509
535
 
510
- // Wait a moment for npx to download + start, then send handshake
511
536
  proc.stdout.on('data', (chunk) => {
512
537
  const text = chunk.toString();
538
+ console.log(`[Groove:Integrations] MCP stdout: ${text.slice(0, 200)}`);
513
539
  // After initialize response, send initialized notification + tools/list
514
540
  if (text.includes('"id":1') || text.includes('"id": 1')) {
541
+ console.log('[Groove:Integrations] Got initialize response, sending initialized + tools/list');
515
542
  proc.stdin.write(initializedNotif + '\n');
516
543
  setTimeout(() => proc.stdin.write(listToolsMsg + '\n'), 500);
517
544
  }
518
545
  });
519
546
 
547
+ proc.on('error', (err) => {
548
+ console.log(`[Groove:Integrations] Process error: ${err.message}`);
549
+ });
550
+
551
+ proc.on('exit', (code, signal) => {
552
+ console.log(`[Groove:Integrations] Process exited: code=${code} signal=${signal}`);
553
+ clearTimeout(timeout);
554
+ });
555
+
520
556
  // Send initialize after a brief delay for npx startup
521
557
  setTimeout(() => {
522
- try { proc.stdin.write(initMsg + '\n'); } catch { /* process may have exited */ }
558
+ console.log('[Groove:Integrations] Sending MCP initialize message');
559
+ try { proc.stdin.write(initMsg + '\n'); } catch (e) { console.log('[Groove:Integrations] stdin write failed:', e.message); }
523
560
  }, 3000);
524
561
 
525
- // Auto-kill after 2 minutes (auth should complete well before that)
562
+ // Auto-kill after 2 minutes
526
563
  const timeout = setTimeout(() => {
564
+ console.log('[Groove:Integrations] Auth timeout — killing process');
527
565
  try { proc.kill('SIGTERM'); } catch { /* ignore */ }
528
566
  }, 120_000);
529
567
 
530
- proc.on('exit', () => clearTimeout(timeout));
531
-
532
568
  this.daemon.audit.log('integration.authenticate', { id: integrationId });
533
569
 
534
570
  return {
@@ -543,11 +579,11 @@ export class IntegrationStore {
543
579
  * before they can open the browser for user consent.
544
580
  */
545
581
  _writeGoogleOAuthKeys(entry) {
546
- const clientId = this.getCredential('google-oauth', 'GOOGLE_CLIENT_ID');
547
- const clientSecret = this.getCredential('google-oauth', 'GOOGLE_CLIENT_SECRET');
548
- if (!clientId || !clientSecret) {
549
- throw new Error('Google OAuth not configured. Click "Sign in with Google" to set up your Google Cloud credentials first.');
582
+ const creds = this._getGoogleOAuthCredentials();
583
+ if (!creds) {
584
+ throw new Error('Google OAuth not configured. Set up your Google Cloud credentials first.');
550
585
  }
586
+ const { clientId, clientSecret } = creds;
551
587
 
552
588
  const keysContent = JSON.stringify({
553
589
  installed: {
@@ -567,6 +603,9 @@ export class IntegrationStore {
567
603
  mkdirSync(dirPath, { recursive: true });
568
604
  const keysPath = resolve(dirPath, 'gcp-oauth.keys.json');
569
605
  writeFileSync(keysPath, keysContent, { mode: 0o600 });
606
+ console.log(`[Groove:Integrations] Wrote OAuth keys to: ${keysPath}`);
607
+ } else {
608
+ console.log(`[Groove:Integrations] WARNING: No oauthKeysDir for ${entry.id}`);
570
609
  }
571
610
  }
572
611
 
@@ -2,9 +2,10 @@
2
2
  // FSL-1.1-Apache-2.0 — see LICENSE
3
3
 
4
4
  import { spawn as cpSpawn } from 'child_process';
5
- import { createWriteStream, mkdirSync, chmodSync } from 'fs';
5
+ import { createWriteStream, mkdirSync, chmodSync, existsSync, readFileSync, unlinkSync } from 'fs';
6
6
  import { resolve } from 'path';
7
7
  import { getProvider } from './providers/index.js';
8
+ import { validateAgentConfig } from './validate.js';
8
9
 
9
10
  // Role-specific prompt prefixes — applied during spawn regardless of entry point
10
11
  // (SpawnPanel, chat continue, CLI, API) for consistency
@@ -58,11 +59,15 @@ Do NOT write code unless explicitly asked. Use your MCP tools (database queries,
58
59
  Do NOT write code unless explicitly asked. Use your MCP tools to interact with Home Assistant.
59
60
 
60
61
  `,
61
- planner: `You are a planning and architecture agent. Research, analyze, and create plans do NOT implement code unless explicitly asked. Focus on:
62
+ planner: `You are a PLANNING ONLY agent. You create plans. You do NOT write code, edit files, or run commands.
63
+
64
+ ABSOLUTE RULE: Never use the Edit, Write, or Bash tools to modify source code. You ONLY use Read, Glob, and Grep to understand the codebase, then output a written plan. If the user says "build this" or "redesign this", create a PLAN for how other agents should build it — do NOT build it yourself.
65
+
66
+ Focus on:
62
67
  - Understanding requirements
63
- - Exploring the codebase
68
+ - Exploring the codebase to understand current architecture
64
69
  - Identifying approaches and trade-offs
65
- - Writing structured plans
70
+ - Writing structured plans with agent assignments
66
71
 
67
72
  After completing your plan, you MUST do two things:
68
73
 
@@ -70,15 +75,25 @@ After completing your plan, you MUST do two things:
70
75
 
71
76
  2. Save a machine-readable team config to .groove/recommended-team.json using this EXACT format:
72
77
  [
73
- { "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." },
74
- { "role": "backend", "scope": ["src/api/**", "src/server/**", "src/db/**", "src/lib/**"], "workingDir": "packages/backend", "prompt": "Build the backend: [specific tasks from your plan]" },
75
- { "role": "frontend", "scope": ["src/components/**", "src/views/**", "src/pages/**", "src/styles/**"], "workingDir": "packages/frontend", "prompt": "Build the frontend: [specific tasks from your plan]" }
78
+ { "role": "frontend", "phase": 1, "scope": ["src/components/**", "src/views/**"], "prompt": "Build the frontend: [specific tasks]" },
79
+ { "role": "backend", "phase": 1, "scope": ["src/api/**", "src/server/**"], "prompt": "Build the backend: [specific tasks]" },
80
+ { "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." }
76
81
  ]
77
82
 
78
- 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.
79
- 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).
83
+ MANDATORY RULES NEVER SKIP THESE:
84
+
85
+ 1. The LAST entry in the array MUST be: { "role": "fullstack", "phase": 2, ... }
86
+ This is the QC Senior Dev. It auto-spawns after all other agents finish.
87
+ Its prompt: audit changes, fix issues, run tests, build, commit, launch.
88
+ NEVER omit this agent. Every team needs a QC.
89
+
90
+ 2. ALL other agents are phase: 1 — they run in parallel.
91
+
92
+ 3. Do NOT tell any agent to "wait for" another agent. Phase 2 handles sequencing automatically.
93
+
94
+ 4. Set appropriate scopes. Write detailed prompts so each agent knows exactly what to build.
80
95
 
81
- 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.
96
+ 5. If the project is a monorepo, set "workingDir" for agents that need specific subdirectories.
82
97
 
83
98
  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.
84
99
 
@@ -105,6 +120,17 @@ export class ProcessManager {
105
120
  async spawn(config) {
106
121
  const { registry, locks, introducer } = this.daemon;
107
122
 
123
+ // Clean stale recommended-team.json when spawning a new planner
124
+ if (config.role === 'planner') {
125
+ const dirs = [this.daemon.grooveDir];
126
+ if (config.workingDir) dirs.push(resolve(config.workingDir, '.groove'));
127
+ if (this.daemon.config?.defaultWorkingDir) dirs.push(resolve(this.daemon.config.defaultWorkingDir, '.groove'));
128
+ for (const dir of dirs) {
129
+ const p = resolve(dir, 'recommended-team.json');
130
+ if (existsSync(p)) try { unlinkSync(p); } catch { /* */ }
131
+ }
132
+ }
133
+
108
134
  // Validate provider exists and is installed
109
135
  const provider = getProvider(config.provider || 'claude-code');
110
136
  if (!provider) {
@@ -325,6 +351,9 @@ For normal file edits within your scope, proceed without review.
325
351
  if (finalStatus === 'completed' && this.daemon.journalist) {
326
352
  this.daemon.journalist.cycle().catch(() => {});
327
353
  }
354
+
355
+ // Phase 2 auto-spawn: check if all phase 1 agents for a team are done
356
+ this._checkPhase2(agent.id);
328
357
  });
329
358
 
330
359
  proc.on('error', (err) => {
@@ -338,6 +367,49 @@ For normal file edits within your scope, proceed without review.
338
367
  return agent;
339
368
  }
340
369
 
370
+ /**
371
+ * Check if a completed/crashed agent was the last phase 1 agent in a team.
372
+ * If so, auto-spawn the phase 2 (QC/finisher) agents.
373
+ */
374
+ _checkPhase2(completedAgentId) {
375
+ const pending = this.daemon._pendingPhase2;
376
+ if (!pending || pending.length === 0) return;
377
+
378
+ const registry = this.daemon.registry;
379
+
380
+ for (let i = pending.length - 1; i >= 0; i--) {
381
+ const group = pending[i];
382
+ if (!group.waitFor.includes(completedAgentId)) continue;
383
+
384
+ // Check if ALL phase 1 agents in this group are done
385
+ const allDone = group.waitFor.every((id) => {
386
+ const a = registry.get(id);
387
+ return !a || a.status === 'completed' || a.status === 'crashed' || a.status === 'stopped' || a.status === 'killed';
388
+ });
389
+
390
+ if (allDone) {
391
+ // Remove from pending
392
+ pending.splice(i, 1);
393
+
394
+ // Auto-spawn phase 2 agents
395
+ for (const config of group.agents) {
396
+ try {
397
+ const validated = validateAgentConfig(config);
398
+ this.spawn(validated).then((agent) => {
399
+ this.daemon.broadcast({
400
+ type: 'phase2:spawned',
401
+ agentId: agent.id,
402
+ name: agent.name,
403
+ role: agent.role,
404
+ });
405
+ this.daemon.audit.log('phase2.autoSpawn', { id: agent.id, name: agent.name, role: agent.role });
406
+ }).catch(() => {});
407
+ } catch { /* skip invalid configs */ }
408
+ }
409
+ }
410
+ }
411
+ }
412
+
341
413
  /**
342
414
  * Resume a completed agent's session with a new message.
343
415
  * Uses --resume SESSION_ID for zero cold-start continuation.
@@ -390,7 +462,7 @@ For normal file edits within your scope, proceed without review.
390
462
  model: config.model,
391
463
  prompt: config.prompt,
392
464
  permission: config.permission,
393
- workingDir: config.workingDir,
465
+ workingDir: config.workingDir || this.daemon.config?.defaultWorkingDir || undefined,
394
466
  name: config.name,
395
467
  });
396
468
 
@@ -48,6 +48,10 @@ export class ClaudeCodeProvider extends Provider {
48
48
  args.push('--model', agent.model);
49
49
  }
50
50
 
51
+ if (agent.effort) {
52
+ args.push('--effort', agent.effort);
53
+ }
54
+
51
55
  // Pass the initial prompt as positional arg (includes GROOVE context)
52
56
  const fullPrompt = this.buildFullPrompt(agent);
53
57
  if (fullPrompt) {
@@ -50,7 +50,7 @@ export class Registry extends EventEmitter {
50
50
  if (!agent) return null;
51
51
 
52
52
  // Only allow known fields to prevent prototype pollution
53
- const SAFE_FIELDS = ['status', 'pid', 'tokensUsed', 'contextUsage', 'lastActivity', 'model', 'name', 'routingMode', 'routingReason', 'sessionId', 'skills', 'integrations'];
53
+ const SAFE_FIELDS = ['status', 'pid', 'tokensUsed', 'contextUsage', 'lastActivity', 'model', 'name', 'routingMode', 'routingReason', 'sessionId', 'skills', 'integrations', 'workingDir', 'effort'];
54
54
  for (const key of Object.keys(updates)) {
55
55
  if (SAFE_FIELDS.includes(key)) {
56
56
  agent[key] = updates[key];
@@ -0,0 +1 @@
1
+ {"t":"2026-04-08T02:20:01.108Z","action":"credential.set","provider":"anthropic-api"}
@@ -0,0 +1,64 @@
1
+ {
2
+ "projectName": "gui",
3
+ "scannedAt": "2026-04-08T01:26:36.485Z",
4
+ "stats": {
5
+ "totalFiles": 32,
6
+ "totalDirs": 5,
7
+ "treeDepth": 4
8
+ },
9
+ "workspaces": [],
10
+ "keyFiles": [
11
+ "package.json"
12
+ ],
13
+ "tree": [
14
+ {
15
+ "path": ".",
16
+ "depth": 0,
17
+ "dirs": 2,
18
+ "files": 3,
19
+ "children": [
20
+ "public",
21
+ "src"
22
+ ]
23
+ },
24
+ {
25
+ "path": "public",
26
+ "depth": 1,
27
+ "dirs": 0,
28
+ "files": 3,
29
+ "children": []
30
+ },
31
+ {
32
+ "path": "src",
33
+ "depth": 1,
34
+ "dirs": 3,
35
+ "files": 3,
36
+ "children": [
37
+ "components",
38
+ "stores",
39
+ "views"
40
+ ]
41
+ },
42
+ {
43
+ "path": "src/components",
44
+ "depth": 2,
45
+ "dirs": 0,
46
+ "files": 15,
47
+ "children": []
48
+ },
49
+ {
50
+ "path": "src/stores",
51
+ "depth": 2,
52
+ "dirs": 0,
53
+ "files": 1,
54
+ "children": []
55
+ },
56
+ {
57
+ "path": "src/views",
58
+ "depth": 2,
59
+ "dirs": 0,
60
+ "files": 7,
61
+ "children": []
62
+ }
63
+ ]
64
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": "0.1.0",
3
+ "port": 31415,
4
+ "journalistInterval": 120,
5
+ "rotationThreshold": 0.75,
6
+ "autoRotation": true,
7
+ "qcThreshold": 4,
8
+ "maxAgents": 10,
9
+ "defaultProvider": "claude-code"
10
+ }
@@ -0,0 +1,5 @@
1
+ # GROOVE Coordination
2
+
3
+ *Agents write their intent here before shared/destructive actions.*
4
+
5
+ <!-- No active operations -->
@@ -0,0 +1,6 @@
1
+ {
2
+ "anthropic-api": {
3
+ "key": "bee44e798e85b64b04e5198bd44074f0:a0de1b79cca1a00842ca83193e383128:fb7b942b82313c940f802fd2c54f5945756ce6b65dae652b1534acd38d9cca19cdfaad8eee267f56e11f2c2ec6cd8db16c4a4139ddfe0777517437a3a4b5beb53e89210e31c1cfc1f964bba8b26ca75b93f62d36ea1dbc19d3910a91c8a976a2c4369b233a5d00f799e33ebd",
4
+ "setAt": "2026-04-08T02:20:01.107Z"
5
+ }
6
+ }
@@ -0,0 +1,3 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MC4CAQAwBQYDK2VwBCIEIDgv9qJFjRft4Jh4sUoIe1GtmtckzOSsVvbi8WNRdSwf
3
+ -----END PRIVATE KEY-----
@@ -0,0 +1,3 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MCowBQYDK2VwAyEAHZ3D0IChsPmavZorg3hwhdMZN4W3Q0geH9TR5NVMnI0=
3
+ -----END PUBLIC KEY-----
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "groove-integrations",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "description": "MCP server packages managed by Groove"
6
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "agents": []
3
+ }