groove-dev 0.8.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 (84) hide show
  1. package/CLAUDE.md +197 -0
  2. package/LICENSE +40 -0
  3. package/README.md +115 -0
  4. package/docs/GUI_DESIGN_SPEC.md +402 -0
  5. package/favicon.png +0 -0
  6. package/groove-logo-short.png +0 -0
  7. package/groove-logo.png +0 -0
  8. package/package.json +70 -0
  9. package/packages/cli/bin/groove.js +98 -0
  10. package/packages/cli/package.json +15 -0
  11. package/packages/cli/src/client.js +25 -0
  12. package/packages/cli/src/commands/agents.js +38 -0
  13. package/packages/cli/src/commands/approve.js +50 -0
  14. package/packages/cli/src/commands/config.js +35 -0
  15. package/packages/cli/src/commands/kill.js +15 -0
  16. package/packages/cli/src/commands/nuke.js +19 -0
  17. package/packages/cli/src/commands/providers.js +40 -0
  18. package/packages/cli/src/commands/rotate.js +16 -0
  19. package/packages/cli/src/commands/spawn.js +91 -0
  20. package/packages/cli/src/commands/start.js +31 -0
  21. package/packages/cli/src/commands/status.js +38 -0
  22. package/packages/cli/src/commands/stop.js +15 -0
  23. package/packages/cli/src/commands/team.js +77 -0
  24. package/packages/daemon/package.json +18 -0
  25. package/packages/daemon/src/adaptive.js +237 -0
  26. package/packages/daemon/src/api.js +533 -0
  27. package/packages/daemon/src/classifier.js +126 -0
  28. package/packages/daemon/src/credentials.js +121 -0
  29. package/packages/daemon/src/firstrun.js +93 -0
  30. package/packages/daemon/src/index.js +208 -0
  31. package/packages/daemon/src/introducer.js +238 -0
  32. package/packages/daemon/src/journalist.js +600 -0
  33. package/packages/daemon/src/lockmanager.js +58 -0
  34. package/packages/daemon/src/pm.js +108 -0
  35. package/packages/daemon/src/process.js +361 -0
  36. package/packages/daemon/src/providers/aider.js +72 -0
  37. package/packages/daemon/src/providers/base.js +38 -0
  38. package/packages/daemon/src/providers/claude-code.js +167 -0
  39. package/packages/daemon/src/providers/codex.js +68 -0
  40. package/packages/daemon/src/providers/gemini.js +62 -0
  41. package/packages/daemon/src/providers/index.js +38 -0
  42. package/packages/daemon/src/providers/ollama.js +94 -0
  43. package/packages/daemon/src/registry.js +89 -0
  44. package/packages/daemon/src/rotator.js +185 -0
  45. package/packages/daemon/src/router.js +132 -0
  46. package/packages/daemon/src/state.js +34 -0
  47. package/packages/daemon/src/supervisor.js +178 -0
  48. package/packages/daemon/src/teams.js +203 -0
  49. package/packages/daemon/src/terminal/base.js +27 -0
  50. package/packages/daemon/src/terminal/generic.js +27 -0
  51. package/packages/daemon/src/terminal/tmux.js +64 -0
  52. package/packages/daemon/src/tokentracker.js +124 -0
  53. package/packages/daemon/src/validate.js +122 -0
  54. package/packages/daemon/templates/api-builder.json +18 -0
  55. package/packages/daemon/templates/fullstack.json +18 -0
  56. package/packages/daemon/templates/monorepo.json +24 -0
  57. package/packages/gui/dist/assets/index-BO95Rm1F.js +73 -0
  58. package/packages/gui/dist/assets/index-CPzm9ZE9.css +1 -0
  59. package/packages/gui/dist/favicon.png +0 -0
  60. package/packages/gui/dist/groove-logo-short.png +0 -0
  61. package/packages/gui/dist/groove-logo.png +0 -0
  62. package/packages/gui/dist/index.html +13 -0
  63. package/packages/gui/index.html +12 -0
  64. package/packages/gui/package.json +22 -0
  65. package/packages/gui/public/favicon.png +0 -0
  66. package/packages/gui/public/groove-logo-short.png +0 -0
  67. package/packages/gui/public/groove-logo.png +0 -0
  68. package/packages/gui/src/App.jsx +215 -0
  69. package/packages/gui/src/components/AgentActions.jsx +347 -0
  70. package/packages/gui/src/components/AgentChat.jsx +479 -0
  71. package/packages/gui/src/components/AgentNode.jsx +117 -0
  72. package/packages/gui/src/components/AgentPanel.jsx +115 -0
  73. package/packages/gui/src/components/AgentStats.jsx +333 -0
  74. package/packages/gui/src/components/ApprovalQueue.jsx +156 -0
  75. package/packages/gui/src/components/EmptyState.jsx +100 -0
  76. package/packages/gui/src/components/SpawnPanel.jsx +515 -0
  77. package/packages/gui/src/components/TeamSelector.jsx +162 -0
  78. package/packages/gui/src/main.jsx +9 -0
  79. package/packages/gui/src/stores/groove.js +247 -0
  80. package/packages/gui/src/theme.css +67 -0
  81. package/packages/gui/src/views/AgentTree.jsx +148 -0
  82. package/packages/gui/src/views/CommandCenter.jsx +620 -0
  83. package/packages/gui/src/views/JournalistFeed.jsx +149 -0
  84. package/packages/gui/vite.config.js +19 -0
@@ -0,0 +1,237 @@
1
+ // GROOVE — Adaptive Rotation Threshold System
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
5
+ import { resolve } from 'path';
6
+
7
+ const DEFAULT_THRESHOLD = 0.75;
8
+ const NUDGE_UP = 0.02; // Good session → allow more context
9
+ const NUDGE_DOWN = 0.05; // Bad session → rotate sooner
10
+ const MIN_THRESHOLD = 0.40;
11
+ const MAX_THRESHOLD = 0.95;
12
+ const CONVERGENCE_WINDOW = 10; // Stable if last N adjustments < 1%
13
+
14
+ export class AdaptiveThresholds {
15
+ constructor(grooveDir) {
16
+ this.path = resolve(grooveDir, 'rotation-profiles.json');
17
+ this.profiles = {}; // key: `${provider}:${role}` -> { threshold, history[], converged }
18
+ this.load();
19
+ }
20
+
21
+ load() {
22
+ if (existsSync(this.path)) {
23
+ try {
24
+ this.profiles = JSON.parse(readFileSync(this.path, 'utf8'));
25
+ } catch {
26
+ this.profiles = {};
27
+ }
28
+ }
29
+ }
30
+
31
+ save() {
32
+ writeFileSync(this.path, JSON.stringify(this.profiles, null, 2));
33
+ }
34
+
35
+ profileKey(provider, role) {
36
+ return `${provider}:${role}`;
37
+ }
38
+
39
+ getProfile(provider, role) {
40
+ const key = this.profileKey(provider, role);
41
+ if (!this.profiles[key]) {
42
+ this.profiles[key] = {
43
+ threshold: DEFAULT_THRESHOLD,
44
+ history: [],
45
+ converged: false,
46
+ adjustmentCount: 0,
47
+ };
48
+ }
49
+ return this.profiles[key];
50
+ }
51
+
52
+ getThreshold(provider, role) {
53
+ return this.getProfile(provider, role).threshold;
54
+ }
55
+
56
+ // Score a completed session and adjust the threshold
57
+ recordSession(provider, role, signals) {
58
+ const profile = this.getProfile(provider, role);
59
+ const score = this.scoreSession(signals);
60
+
61
+ const oldThreshold = profile.threshold;
62
+ let newThreshold;
63
+
64
+ if (score >= 70) {
65
+ // Good session — nudge threshold up (allow more context before rotating)
66
+ newThreshold = Math.min(profile.threshold + NUDGE_UP, MAX_THRESHOLD);
67
+ } else if (score < 40) {
68
+ // Bad session — nudge threshold down (rotate sooner)
69
+ newThreshold = Math.max(profile.threshold - NUDGE_DOWN, MIN_THRESHOLD);
70
+ } else {
71
+ // Neutral — no change
72
+ newThreshold = profile.threshold;
73
+ }
74
+
75
+ profile.threshold = newThreshold;
76
+ profile.adjustmentCount++;
77
+
78
+ const record = {
79
+ score,
80
+ oldThreshold,
81
+ newThreshold,
82
+ signals,
83
+ timestamp: new Date().toISOString(),
84
+ };
85
+
86
+ profile.history.push(record);
87
+ if (profile.history.length > 100) profile.history = profile.history.slice(-100);
88
+
89
+ // Check convergence
90
+ profile.converged = this.checkConvergence(profile);
91
+
92
+ this.save();
93
+ return { score, threshold: newThreshold, converged: profile.converged };
94
+ }
95
+
96
+ scoreSession(signals) {
97
+ // Score 0-100 based on quality signals.
98
+ // Low score → rotate sooner (threshold decreases)
99
+ // High score → allow more context (threshold increases)
100
+ let score = 70; // Baseline: decent session
101
+
102
+ // Error rate: each error costs 5 points
103
+ const errorCount = signals.errorCount || 0;
104
+ score -= errorCount * 5;
105
+
106
+ // Repetitions: each detected repetition costs 8 points (agent repeating itself)
107
+ const repetitions = signals.repetitions || 0;
108
+ score -= repetitions * 8;
109
+
110
+ // Out-of-scope access: each violation costs 10 points
111
+ const scopeViolations = signals.scopeViolations || 0;
112
+ score -= scopeViolations * 10;
113
+
114
+ // Tool call success rate: bonus for high success, penalty for failures
115
+ const toolCalls = signals.toolCalls || 0;
116
+ const toolFailures = signals.toolFailures || 0;
117
+ if (toolCalls > 0) {
118
+ const successRate = (toolCalls - toolFailures) / toolCalls;
119
+ score += Math.round((successRate - 0.8) * 20); // Bonus/penalty around 80%
120
+ }
121
+
122
+ // File churn: editing same file 3+ times signals circular refactoring
123
+ const fileChurn = signals.fileChurn || 0;
124
+ score -= fileChurn * 12;
125
+
126
+ // Error trend: increasing errors in second half = degradation
127
+ const errorTrend = signals.errorTrend || 0;
128
+ if (errorTrend > 0) score -= errorTrend * 6; // getting worse
129
+ if (errorTrend < 0) score += 3; // getting better (small bonus)
130
+
131
+ // Files written: productivity signal
132
+ const filesWritten = signals.filesWritten || 0;
133
+ score += Math.min(filesWritten * 2, 10); // Cap at +10
134
+
135
+ // Clamp to 0-100
136
+ return Math.max(0, Math.min(100, score));
137
+ }
138
+
139
+ checkConvergence(profile) {
140
+ const history = profile.history;
141
+ if (history.length < CONVERGENCE_WINDOW) return false;
142
+
143
+ const recent = history.slice(-CONVERGENCE_WINDOW);
144
+ const maxDelta = Math.max(
145
+ ...recent.map((r) => Math.abs(r.newThreshold - r.oldThreshold))
146
+ );
147
+
148
+ return maxDelta < 0.01; // All recent adjustments < 1%
149
+ }
150
+
151
+ // Extract quality signals from classifier events and filtered log entries.
152
+ // These signals drive the scoring model that adapts rotation thresholds.
153
+ extractSignals(entries, agentScope) {
154
+ const signals = {
155
+ errorCount: 0,
156
+ repetitions: 0,
157
+ scopeViolations: 0,
158
+ toolCalls: 0,
159
+ toolFailures: 0,
160
+ filesWritten: 0,
161
+ fileChurn: 0, // same file written 3+ times → possible circular refactoring
162
+ errorTrend: 0, // errors increasing in recent window → degradation signal
163
+ };
164
+
165
+ const recentTools = [];
166
+ const writtenFiles = new Set();
167
+ const fileWriteCounts = {}; // track how many times each file is written
168
+ const errorTimestamps = [];
169
+
170
+ for (const entry of entries) {
171
+ if (entry.type === 'error') {
172
+ signals.errorCount++;
173
+ errorTimestamps.push(entry.timestamp || Date.now());
174
+ }
175
+
176
+ if (entry.type === 'tool') {
177
+ signals.toolCalls++;
178
+
179
+ // Track file writes and churn
180
+ if (entry.tool === 'Write' || entry.tool === 'Edit') {
181
+ if (entry.input) {
182
+ writtenFiles.add(entry.input);
183
+ fileWriteCounts[entry.input] = (fileWriteCounts[entry.input] || 0) + 1;
184
+ }
185
+ }
186
+
187
+ // Track tool failures — errors that follow tool calls indicate failure
188
+ // (stream-json emits tool_use then tool_result with is_error)
189
+ if (entry.type === 'tool' && entry.isError) {
190
+ signals.toolFailures++;
191
+ }
192
+
193
+ // Detect repetitions: same tool+input within last 5 calls
194
+ const key = `${entry.tool}:${entry.input}`;
195
+ if (recentTools.includes(key)) {
196
+ signals.repetitions++;
197
+ }
198
+ recentTools.push(key);
199
+ if (recentTools.length > 5) recentTools.shift();
200
+
201
+ // Detect scope violations (simplified check)
202
+ if (agentScope && agentScope.length > 0 && entry.input) {
203
+ const file = entry.input;
204
+ if (entry.tool === 'Write' || entry.tool === 'Edit') {
205
+ const inScope = agentScope.some((pattern) =>
206
+ file.includes(pattern.replace('/**', '').replace('**/', ''))
207
+ );
208
+ if (!inScope) signals.scopeViolations++;
209
+ }
210
+ }
211
+ }
212
+ }
213
+
214
+ signals.filesWritten = writtenFiles.size;
215
+
216
+ // File churn: count files written 3+ times (circular refactoring signal)
217
+ for (const count of Object.values(fileWriteCounts)) {
218
+ if (count >= 3) signals.fileChurn++;
219
+ }
220
+
221
+ // Error trend: compare error rate in first half vs second half of session
222
+ // Increasing errors = degradation signal
223
+ if (entries.length >= 6) {
224
+ const mid = Math.floor(entries.length / 2);
225
+ const firstHalfErrors = entries.slice(0, mid).filter((e) => e.type === 'error').length;
226
+ const secondHalfErrors = entries.slice(mid).filter((e) => e.type === 'error').length;
227
+ // Positive = errors increasing (bad), negative = errors decreasing (good)
228
+ signals.errorTrend = secondHalfErrors - firstHalfErrors;
229
+ }
230
+
231
+ return signals;
232
+ }
233
+
234
+ getAllProfiles() {
235
+ return this.profiles;
236
+ }
237
+ }