tlc-claude-code 1.2.27 → 1.2.29

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 (179) hide show
  1. package/README.md +9 -4
  2. package/dashboard/dist/components/ActivityFeed.d.ts +17 -0
  3. package/dashboard/dist/components/ActivityFeed.js +42 -0
  4. package/dashboard/dist/components/ActivityFeed.test.d.ts +1 -0
  5. package/dashboard/dist/components/ActivityFeed.test.js +162 -0
  6. package/dashboard/dist/components/BranchSelector.d.ts +16 -0
  7. package/dashboard/dist/components/BranchSelector.js +49 -0
  8. package/dashboard/dist/components/BranchSelector.test.d.ts +1 -0
  9. package/dashboard/dist/components/BranchSelector.test.js +166 -0
  10. package/dashboard/dist/components/CommandPalette.d.ts +17 -0
  11. package/dashboard/dist/components/CommandPalette.js +118 -0
  12. package/dashboard/dist/components/CommandPalette.test.d.ts +1 -0
  13. package/dashboard/dist/components/CommandPalette.test.js +181 -0
  14. package/dashboard/dist/components/ConnectionStatus.d.ts +16 -0
  15. package/dashboard/dist/components/ConnectionStatus.js +27 -0
  16. package/dashboard/dist/components/ConnectionStatus.test.d.ts +1 -0
  17. package/dashboard/dist/components/ConnectionStatus.test.js +121 -0
  18. package/dashboard/dist/components/DeviceFrame.d.ts +19 -0
  19. package/dashboard/dist/components/DeviceFrame.js +52 -0
  20. package/dashboard/dist/components/DeviceFrame.test.d.ts +1 -0
  21. package/dashboard/dist/components/DeviceFrame.test.js +118 -0
  22. package/dashboard/dist/components/EnvironmentBadge.d.ts +11 -0
  23. package/dashboard/dist/components/EnvironmentBadge.js +16 -0
  24. package/dashboard/dist/components/EnvironmentBadge.test.d.ts +1 -0
  25. package/dashboard/dist/components/EnvironmentBadge.test.js +102 -0
  26. package/dashboard/dist/components/FocusIndicator.d.ts +19 -0
  27. package/dashboard/dist/components/FocusIndicator.js +47 -0
  28. package/dashboard/dist/components/FocusIndicator.test.d.ts +1 -0
  29. package/dashboard/dist/components/FocusIndicator.test.js +117 -0
  30. package/dashboard/dist/components/KeyboardHelp.d.ts +15 -0
  31. package/dashboard/dist/components/KeyboardHelp.js +61 -0
  32. package/dashboard/dist/components/KeyboardHelp.test.d.ts +1 -0
  33. package/dashboard/dist/components/KeyboardHelp.test.js +131 -0
  34. package/dashboard/dist/components/LogSearch.d.ts +13 -0
  35. package/dashboard/dist/components/LogSearch.js +43 -0
  36. package/dashboard/dist/components/LogSearch.test.d.ts +1 -0
  37. package/dashboard/dist/components/LogSearch.test.js +100 -0
  38. package/dashboard/dist/components/LogStream.d.ts +21 -0
  39. package/dashboard/dist/components/LogStream.js +123 -0
  40. package/dashboard/dist/components/LogStream.test.d.ts +1 -0
  41. package/dashboard/dist/components/LogStream.test.js +159 -0
  42. package/dashboard/dist/components/PreviewPanel.d.ts +18 -0
  43. package/dashboard/dist/components/PreviewPanel.js +73 -0
  44. package/dashboard/dist/components/PreviewPanel.test.d.ts +1 -0
  45. package/dashboard/dist/components/PreviewPanel.test.js +124 -0
  46. package/dashboard/dist/components/ProjectCard.d.ts +18 -0
  47. package/dashboard/dist/components/ProjectCard.js +19 -0
  48. package/dashboard/dist/components/ProjectCard.test.d.ts +1 -0
  49. package/dashboard/dist/components/ProjectCard.test.js +53 -0
  50. package/dashboard/dist/components/ProjectDetail.d.ts +44 -0
  51. package/dashboard/dist/components/ProjectDetail.js +65 -0
  52. package/dashboard/dist/components/ProjectDetail.test.d.ts +1 -0
  53. package/dashboard/dist/components/ProjectDetail.test.js +196 -0
  54. package/dashboard/dist/components/ProjectList.d.ts +11 -0
  55. package/dashboard/dist/components/ProjectList.js +62 -0
  56. package/dashboard/dist/components/ProjectList.test.d.ts +1 -0
  57. package/dashboard/dist/components/ProjectList.test.js +93 -0
  58. package/dashboard/dist/components/SettingsPanel.d.ts +32 -0
  59. package/dashboard/dist/components/SettingsPanel.js +154 -0
  60. package/dashboard/dist/components/SettingsPanel.test.d.ts +1 -0
  61. package/dashboard/dist/components/SettingsPanel.test.js +196 -0
  62. package/dashboard/dist/components/StatusBar.d.ts +16 -0
  63. package/dashboard/dist/components/StatusBar.js +47 -0
  64. package/dashboard/dist/components/StatusBar.test.d.ts +1 -0
  65. package/dashboard/dist/components/StatusBar.test.js +123 -0
  66. package/dashboard/dist/components/TaskBoard.d.ts +22 -0
  67. package/dashboard/dist/components/TaskBoard.js +102 -0
  68. package/dashboard/dist/components/TaskBoard.test.d.ts +1 -0
  69. package/dashboard/dist/components/TaskBoard.test.js +113 -0
  70. package/dashboard/dist/components/TaskCard.d.ts +17 -0
  71. package/dashboard/dist/components/TaskCard.js +29 -0
  72. package/dashboard/dist/components/TaskCard.test.d.ts +1 -0
  73. package/dashboard/dist/components/TaskCard.test.js +109 -0
  74. package/dashboard/dist/components/TaskDetail.d.ts +36 -0
  75. package/dashboard/dist/components/TaskDetail.js +41 -0
  76. package/dashboard/dist/components/TaskDetail.test.d.ts +1 -0
  77. package/dashboard/dist/components/TaskDetail.test.js +164 -0
  78. package/dashboard/dist/components/TaskFilter.d.ts +12 -0
  79. package/dashboard/dist/components/TaskFilter.js +138 -0
  80. package/dashboard/dist/components/TaskFilter.test.d.ts +1 -0
  81. package/dashboard/dist/components/TaskFilter.test.js +109 -0
  82. package/dashboard/dist/components/TeamPanel.d.ts +15 -0
  83. package/dashboard/dist/components/TeamPanel.js +24 -0
  84. package/dashboard/dist/components/TeamPanel.test.d.ts +1 -0
  85. package/dashboard/dist/components/TeamPanel.test.js +109 -0
  86. package/dashboard/dist/components/TeamPresence.d.ts +14 -0
  87. package/dashboard/dist/components/TeamPresence.js +31 -0
  88. package/dashboard/dist/components/TeamPresence.test.d.ts +1 -0
  89. package/dashboard/dist/components/TeamPresence.test.js +144 -0
  90. package/dashboard/dist/components/layout/Header.d.ts +9 -0
  91. package/dashboard/dist/components/layout/Header.js +11 -0
  92. package/dashboard/dist/components/layout/Header.test.d.ts +1 -0
  93. package/dashboard/dist/components/layout/Header.test.js +35 -0
  94. package/dashboard/dist/components/layout/Shell.d.ts +10 -0
  95. package/dashboard/dist/components/layout/Shell.js +5 -0
  96. package/dashboard/dist/components/layout/Shell.test.d.ts +1 -0
  97. package/dashboard/dist/components/layout/Shell.test.js +34 -0
  98. package/dashboard/dist/components/layout/Sidebar.d.ts +14 -0
  99. package/dashboard/dist/components/layout/Sidebar.js +8 -0
  100. package/dashboard/dist/components/layout/Sidebar.test.d.ts +1 -0
  101. package/dashboard/dist/components/layout/Sidebar.test.js +40 -0
  102. package/dashboard/dist/components/ui/Badge.d.ts +9 -0
  103. package/dashboard/dist/components/ui/Badge.js +13 -0
  104. package/dashboard/dist/components/ui/Badge.test.d.ts +1 -0
  105. package/dashboard/dist/components/ui/Badge.test.js +69 -0
  106. package/dashboard/dist/components/ui/Button.d.ts +12 -0
  107. package/dashboard/dist/components/ui/Button.js +14 -0
  108. package/dashboard/dist/components/ui/Button.test.d.ts +1 -0
  109. package/dashboard/dist/components/ui/Button.test.js +81 -0
  110. package/dashboard/dist/components/ui/Card.d.ts +21 -0
  111. package/dashboard/dist/components/ui/Card.js +20 -0
  112. package/dashboard/dist/components/ui/Card.test.d.ts +1 -0
  113. package/dashboard/dist/components/ui/Card.test.js +82 -0
  114. package/dashboard/dist/components/ui/Input.d.ts +13 -0
  115. package/dashboard/dist/components/ui/Input.js +8 -0
  116. package/dashboard/dist/components/ui/Input.test.d.ts +1 -0
  117. package/dashboard/dist/components/ui/Input.test.js +68 -0
  118. package/dashboard/dist/styles/tokens.d.ts +150 -0
  119. package/dashboard/dist/styles/tokens.js +184 -0
  120. package/dashboard/dist/styles/tokens.test.d.ts +1 -0
  121. package/dashboard/dist/styles/tokens.test.js +95 -0
  122. package/dashboard/dist/test/setup.d.ts +1 -0
  123. package/dashboard/dist/test/setup.js +1 -0
  124. package/dashboard/package.json +3 -0
  125. package/package.json +15 -4
  126. package/scripts/capture-screenshots.js +170 -0
  127. package/scripts/docs-update.js +253 -0
  128. package/scripts/generate-screenshots.js +321 -0
  129. package/scripts/project-docs.js +377 -0
  130. package/scripts/vps-setup.sh +477 -0
  131. package/server/lib/adapters/base-adapter.js +114 -0
  132. package/server/lib/adapters/base-adapter.test.js +90 -0
  133. package/server/lib/adapters/claude-adapter.js +141 -0
  134. package/server/lib/adapters/claude-adapter.test.js +180 -0
  135. package/server/lib/adapters/deepseek-adapter.js +153 -0
  136. package/server/lib/adapters/deepseek-adapter.test.js +193 -0
  137. package/server/lib/adapters/openai-adapter.js +190 -0
  138. package/server/lib/adapters/openai-adapter.test.js +231 -0
  139. package/server/lib/budget-tracker.js +169 -0
  140. package/server/lib/budget-tracker.test.js +165 -0
  141. package/server/lib/claude-injector.js +85 -0
  142. package/server/lib/claude-injector.test.js +161 -0
  143. package/server/lib/consensus-engine.js +135 -0
  144. package/server/lib/consensus-engine.test.js +152 -0
  145. package/server/lib/context-builder.js +112 -0
  146. package/server/lib/context-builder.test.js +120 -0
  147. package/server/lib/file-collector.js +322 -0
  148. package/server/lib/file-collector.test.js +307 -0
  149. package/server/lib/memory-classifier.js +175 -0
  150. package/server/lib/memory-classifier.test.js +169 -0
  151. package/server/lib/memory-committer.js +138 -0
  152. package/server/lib/memory-committer.test.js +136 -0
  153. package/server/lib/memory-hooks.js +127 -0
  154. package/server/lib/memory-hooks.test.js +136 -0
  155. package/server/lib/memory-init.js +104 -0
  156. package/server/lib/memory-init.test.js +119 -0
  157. package/server/lib/memory-observer.js +149 -0
  158. package/server/lib/memory-observer.test.js +158 -0
  159. package/server/lib/memory-reader.js +243 -0
  160. package/server/lib/memory-reader.test.js +216 -0
  161. package/server/lib/memory-storage.js +120 -0
  162. package/server/lib/memory-storage.test.js +136 -0
  163. package/server/lib/memory-writer.js +176 -0
  164. package/server/lib/memory-writer.test.js +231 -0
  165. package/server/lib/overdrive-command.js +30 -6
  166. package/server/lib/overdrive-command.test.js +8 -1
  167. package/server/lib/pattern-detector.js +216 -0
  168. package/server/lib/pattern-detector.test.js +241 -0
  169. package/server/lib/relevance-scorer.js +175 -0
  170. package/server/lib/relevance-scorer.test.js +107 -0
  171. package/server/lib/review-command.js +238 -0
  172. package/server/lib/review-command.test.js +245 -0
  173. package/server/lib/review-orchestrator.js +273 -0
  174. package/server/lib/review-orchestrator.test.js +300 -0
  175. package/server/lib/review-reporter.js +288 -0
  176. package/server/lib/review-reporter.test.js +240 -0
  177. package/server/lib/session-summary.js +90 -0
  178. package/server/lib/session-summary.test.js +156 -0
  179. package/templates/docs-sync.yml +91 -0
@@ -0,0 +1,231 @@
1
+ import { describe, it, beforeEach, afterEach, expect } from 'vitest';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import {
6
+ writeTeamDecision,
7
+ writeTeamGotcha,
8
+ writePersonalPreference,
9
+ appendSessionLog,
10
+ } from './memory-writer.js';
11
+ import { initMemoryStructure, MEMORY_PATHS } from './memory-storage.js';
12
+
13
+ describe('memory-writer', () => {
14
+ let testDir;
15
+
16
+ beforeEach(async () => {
17
+ testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tlc-memory-writer-test-'));
18
+ await initMemoryStructure(testDir);
19
+ });
20
+
21
+ afterEach(() => {
22
+ fs.rmSync(testDir, { recursive: true, force: true });
23
+ });
24
+
25
+ describe('writeTeamDecision', () => {
26
+ it('creates decision file with auto-increment ID', async () => {
27
+ await writeTeamDecision(testDir, {
28
+ title: 'Use Postgres over MySQL',
29
+ reasoning: 'JSONB support for flexible schemas',
30
+ context: 'database selection',
31
+ });
32
+
33
+ const decisionsDir = path.join(testDir, MEMORY_PATHS.DECISIONS);
34
+ const files = fs.readdirSync(decisionsDir);
35
+
36
+ expect(files).toHaveLength(1);
37
+ expect(files[0]).toMatch(/^001-use-postgres-over-mysql\.md$/);
38
+ });
39
+
40
+ it('increments ID for multiple decisions', async () => {
41
+ await writeTeamDecision(testDir, { title: 'First Decision', reasoning: 'test' });
42
+ await writeTeamDecision(testDir, { title: 'Second Decision', reasoning: 'test' });
43
+ await writeTeamDecision(testDir, { title: 'Third Decision', reasoning: 'test' });
44
+
45
+ const decisionsDir = path.join(testDir, MEMORY_PATHS.DECISIONS);
46
+ const files = fs.readdirSync(decisionsDir).sort();
47
+
48
+ expect(files).toHaveLength(3);
49
+ expect(files[0]).toMatch(/^001-/);
50
+ expect(files[1]).toMatch(/^002-/);
51
+ expect(files[2]).toMatch(/^003-/);
52
+ });
53
+
54
+ it('includes decision content in markdown format', async () => {
55
+ await writeTeamDecision(testDir, {
56
+ title: 'Use Postgres',
57
+ reasoning: 'JSONB support',
58
+ context: 'database selection',
59
+ alternatives: ['MySQL', 'MongoDB'],
60
+ });
61
+
62
+ const decisionsDir = path.join(testDir, MEMORY_PATHS.DECISIONS);
63
+ const files = fs.readdirSync(decisionsDir);
64
+ const content = fs.readFileSync(path.join(decisionsDir, files[0]), 'utf8');
65
+
66
+ expect(content).toContain('# Decision: Use Postgres');
67
+ expect(content).toContain('JSONB support');
68
+ expect(content).toContain('database selection');
69
+ expect(content).toContain('MySQL');
70
+ });
71
+
72
+ it('includes date in decision file', async () => {
73
+ await writeTeamDecision(testDir, { title: 'Test', reasoning: 'test' });
74
+
75
+ const decisionsDir = path.join(testDir, MEMORY_PATHS.DECISIONS);
76
+ const files = fs.readdirSync(decisionsDir);
77
+ const content = fs.readFileSync(path.join(decisionsDir, files[0]), 'utf8');
78
+
79
+ const today = new Date().toISOString().split('T')[0];
80
+ expect(content).toContain(`**Date:** ${today}`);
81
+ });
82
+
83
+ it('slugifies title for filename', async () => {
84
+ await writeTeamDecision(testDir, {
85
+ title: 'Use REST API instead of GraphQL!',
86
+ reasoning: 'simpler',
87
+ });
88
+
89
+ const decisionsDir = path.join(testDir, MEMORY_PATHS.DECISIONS);
90
+ const files = fs.readdirSync(decisionsDir);
91
+
92
+ expect(files[0]).toBe('001-use-rest-api-instead-of-graphql.md');
93
+ });
94
+ });
95
+
96
+ describe('writeTeamGotcha', () => {
97
+ it('creates gotcha file in gotchas directory', async () => {
98
+ await writeTeamGotcha(testDir, {
99
+ title: 'Auth Service Warmup',
100
+ issue: 'Auth service needs 2 seconds to warm up',
101
+ workaround: 'Add delay in test setup',
102
+ severity: 'medium',
103
+ });
104
+
105
+ const gotchasDir = path.join(testDir, MEMORY_PATHS.GOTCHAS);
106
+ const files = fs.readdirSync(gotchasDir);
107
+
108
+ expect(files).toHaveLength(1);
109
+ expect(files[0]).toContain('auth-service-warmup');
110
+ });
111
+
112
+ it('includes gotcha content in markdown format', async () => {
113
+ await writeTeamGotcha(testDir, {
114
+ title: 'Auth Service Warmup',
115
+ issue: 'Needs 2 seconds to warm up',
116
+ workaround: 'Add delay',
117
+ severity: 'high',
118
+ affected: ['src/auth/*'],
119
+ });
120
+
121
+ const gotchasDir = path.join(testDir, MEMORY_PATHS.GOTCHAS);
122
+ const files = fs.readdirSync(gotchasDir);
123
+ const content = fs.readFileSync(path.join(gotchasDir, files[0]), 'utf8');
124
+
125
+ expect(content).toContain('# Gotcha: Auth Service Warmup');
126
+ expect(content).toContain('Needs 2 seconds');
127
+ expect(content).toContain('Add delay');
128
+ expect(content).toContain('**Severity:** high');
129
+ });
130
+ });
131
+
132
+ describe('writePersonalPreference', () => {
133
+ it('writes preference to preferences.json', async () => {
134
+ await writePersonalPreference(testDir, 'codeStyle', 'functional');
135
+
136
+ const prefsPath = path.join(testDir, MEMORY_PATHS.LOCAL, 'preferences.json');
137
+ const prefs = JSON.parse(fs.readFileSync(prefsPath, 'utf8'));
138
+
139
+ expect(prefs.codeStyle).toBe('functional');
140
+ });
141
+
142
+ it('merges with existing preferences', async () => {
143
+ await writePersonalPreference(testDir, 'codeStyle', 'functional');
144
+ await writePersonalPreference(testDir, 'exports', 'named');
145
+
146
+ const prefsPath = path.join(testDir, MEMORY_PATHS.LOCAL, 'preferences.json');
147
+ const prefs = JSON.parse(fs.readFileSync(prefsPath, 'utf8'));
148
+
149
+ expect(prefs.codeStyle).toBe('functional');
150
+ expect(prefs.exports).toBe('named');
151
+ });
152
+
153
+ it('overwrites existing preference value', async () => {
154
+ await writePersonalPreference(testDir, 'codeStyle', 'functional');
155
+ await writePersonalPreference(testDir, 'codeStyle', 'object-oriented');
156
+
157
+ const prefsPath = path.join(testDir, MEMORY_PATHS.LOCAL, 'preferences.json');
158
+ const prefs = JSON.parse(fs.readFileSync(prefsPath, 'utf8'));
159
+
160
+ expect(prefs.codeStyle).toBe('object-oriented');
161
+ });
162
+
163
+ it('supports nested preference objects', async () => {
164
+ await writePersonalPreference(testDir, 'testing', {
165
+ framework: 'vitest',
166
+ style: 'describe-it',
167
+ });
168
+
169
+ const prefsPath = path.join(testDir, MEMORY_PATHS.LOCAL, 'preferences.json');
170
+ const prefs = JSON.parse(fs.readFileSync(prefsPath, 'utf8'));
171
+
172
+ expect(prefs.testing.framework).toBe('vitest');
173
+ expect(prefs.testing.style).toBe('describe-it');
174
+ });
175
+ });
176
+
177
+ describe('appendSessionLog', () => {
178
+ it('appends entry to daily session log', async () => {
179
+ await appendSessionLog(testDir, {
180
+ type: 'decision',
181
+ content: 'use JWT for auth',
182
+ classification: 'team',
183
+ });
184
+
185
+ const today = new Date().toISOString().split('T')[0];
186
+ const logPath = path.join(testDir, MEMORY_PATHS.SESSIONS, `${today}.jsonl`);
187
+
188
+ expect(fs.existsSync(logPath)).toBe(true);
189
+
190
+ const lines = fs.readFileSync(logPath, 'utf8').trim().split('\n');
191
+ const entry = JSON.parse(lines[0]);
192
+
193
+ expect(entry.type).toBe('decision');
194
+ expect(entry.content).toBe('use JWT for auth');
195
+ });
196
+
197
+ it('adds timestamp to each entry', async () => {
198
+ await appendSessionLog(testDir, { type: 'test', content: 'hello' });
199
+
200
+ const today = new Date().toISOString().split('T')[0];
201
+ const logPath = path.join(testDir, MEMORY_PATHS.SESSIONS, `${today}.jsonl`);
202
+ const lines = fs.readFileSync(logPath, 'utf8').trim().split('\n');
203
+ const entry = JSON.parse(lines[0]);
204
+
205
+ expect(entry).toHaveProperty('ts');
206
+ expect(entry.ts).toMatch(/^\d{4}-\d{2}-\d{2}T/);
207
+ });
208
+
209
+ it('appends multiple entries to same file', async () => {
210
+ await appendSessionLog(testDir, { type: 'first', content: 'one' });
211
+ await appendSessionLog(testDir, { type: 'second', content: 'two' });
212
+ await appendSessionLog(testDir, { type: 'third', content: 'three' });
213
+
214
+ const today = new Date().toISOString().split('T')[0];
215
+ const logPath = path.join(testDir, MEMORY_PATHS.SESSIONS, `${today}.jsonl`);
216
+ const lines = fs.readFileSync(logPath, 'utf8').trim().split('\n');
217
+
218
+ expect(lines).toHaveLength(3);
219
+ });
220
+
221
+ it('creates session directory if missing', async () => {
222
+ // Remove sessions directory
223
+ const sessionsDir = path.join(testDir, MEMORY_PATHS.SESSIONS);
224
+ fs.rmSync(sessionsDir, { recursive: true });
225
+
226
+ await appendSessionLog(testDir, { type: 'test', content: 'hello' });
227
+
228
+ expect(fs.existsSync(sessionsDir)).toBe(true);
229
+ });
230
+ });
231
+ });
@@ -3,7 +3,11 @@
3
3
  * Automatic parallel execution when tasks are independent
4
4
  *
5
5
  * This is NOT a command - it's integrated into /tlc:build
6
- * When a plan has independent tasks, they run in parallel automatically
6
+ * When a plan has independent tasks, they run in parallel automatically.
7
+ *
8
+ * Default behavior: Auto-parallelize up to 10 agents based on task dependencies
9
+ * Use --sequential to force one-at-a-time execution
10
+ * Use --agents N to limit parallelism to specific number
7
11
  */
8
12
 
9
13
  const fs = require('fs');
@@ -17,7 +21,7 @@ const path = require('path');
17
21
  function parseOverdriveArgs(args = '') {
18
22
  const options = {
19
23
  phase: null,
20
- agents: 3, // Default parallel agents
24
+ agents: 'auto', // Auto-detect based on independent tasks (up to 10)
21
25
  mode: 'build', // build, test, fix
22
26
  dryRun: false,
23
27
  sequential: false,
@@ -36,8 +40,9 @@ function parseOverdriveArgs(args = '') {
36
40
  options.mode = parts[++i];
37
41
  } else if (part === '--dry-run') {
38
42
  options.dryRun = true;
39
- } else if (part === '--sequential') {
43
+ } else if (part === '--sequential' || part === '-s') {
40
44
  options.sequential = true;
45
+ options.agents = 1;
41
46
  } else if (['build', 'test', 'fix'].includes(part)) {
42
47
  options.mode = part;
43
48
  }
@@ -265,8 +270,27 @@ async function executeOverdriveCommand(args = '', context = {}) {
265
270
  };
266
271
  }
267
272
 
268
- // Distribute tasks
269
- const agentCount = Math.min(options.agents, availableTasks.length);
273
+ // Determine agent count: auto = max parallelism based on independent tasks
274
+ let agentCount;
275
+ if (options.sequential) {
276
+ agentCount = 1;
277
+ } else if (options.agents === 'auto') {
278
+ // Auto-detect: use as many agents as there are independent tasks (up to 10)
279
+ let planContent = '';
280
+ try {
281
+ planContent = fs.readFileSync(phaseInfo.planPath, 'utf-8');
282
+ } catch {
283
+ // Fall back to task count
284
+ }
285
+ const depAnalysis = analyzeDependencies(planContent);
286
+ const parallelAnalysis = canParallelize(availableTasks, depAnalysis);
287
+ agentCount = parallelAnalysis.canParallelize
288
+ ? Math.min(parallelAnalysis.independentTasks.length, 10)
289
+ : 1;
290
+ } else {
291
+ agentCount = Math.min(options.agents, availableTasks.length);
292
+ }
293
+
270
294
  const taskGroups = distributeTasks(availableTasks, agentCount);
271
295
 
272
296
  // Generate prompts
@@ -434,7 +458,7 @@ function canParallelize(tasks, depAnalysis) {
434
458
  canParallelize: true,
435
459
  independentTasks,
436
460
  dependentTasks: tasks.filter(t => dependentTasks.has(t.id)),
437
- recommendedAgents: Math.min(independentTasks.length, 3),
461
+ recommendedAgents: Math.min(independentTasks.length, 10), // Max 10 parallel agents
438
462
  };
439
463
  }
440
464
 
@@ -17,7 +17,7 @@ describe('overdrive-command', () => {
17
17
  const options = parseOverdriveArgs('');
18
18
 
19
19
  expect(options.phase).toBeNull();
20
- expect(options.agents).toBe(3);
20
+ expect(options.agents).toBe('auto'); // Auto-parallelize by default
21
21
  expect(options.mode).toBe('build');
22
22
  expect(options.dryRun).toBe(false);
23
23
  expect(options.sequential).toBe(false);
@@ -56,6 +56,13 @@ describe('overdrive-command', () => {
56
56
  it('parses --sequential flag', () => {
57
57
  const options = parseOverdriveArgs('--sequential');
58
58
  expect(options.sequential).toBe(true);
59
+ expect(options.agents).toBe(1); // Sequential forces single agent
60
+ });
61
+
62
+ it('parses -s shorthand for sequential', () => {
63
+ const options = parseOverdriveArgs('-s');
64
+ expect(options.sequential).toBe(true);
65
+ expect(options.agents).toBe(1);
59
66
  });
60
67
 
61
68
  it('parses multiple flags', () => {
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Pattern Detector - Detect memorable patterns from conversation exchanges
3
+ */
4
+
5
+ const PATTERN_TYPES = {
6
+ DECISION: 'decision',
7
+ PREFERENCE: 'preference',
8
+ GOTCHA: 'gotcha',
9
+ REASONING: 'reasoning',
10
+ };
11
+
12
+ /**
13
+ * Decision pattern matchers
14
+ */
15
+ const DECISION_PATTERNS = [
16
+ {
17
+ // "let's use X instead of Y"
18
+ regex: /let'?s\s+use\s+(.+?)\s+instead\s+of\s+(.+?)(?:\s+because\s+(.+?))?(?:\.|,|$)/i,
19
+ extract: (match) => ({
20
+ choice: match[1].trim(),
21
+ over: match[2].trim(),
22
+ reasoning: match[3]?.trim() || null,
23
+ }),
24
+ },
25
+ {
26
+ // "we decided to use X"
27
+ regex: /we\s+decided\s+to\s+(?:use\s+)?(.+?)(?:\s+because\s+(.+?))?(?:\.|,|$)/i,
28
+ extract: (match) => ({
29
+ choice: match[1].trim(),
30
+ reasoning: match[2]?.trim() || null,
31
+ }),
32
+ },
33
+ {
34
+ // "going with X instead of Y"
35
+ regex: /going\s+with\s+(.+?)\s+instead\s+of\s+(.+?)(?:\.|,|$)/i,
36
+ extract: (match) => ({
37
+ choice: match[1].trim(),
38
+ over: match[2].trim(),
39
+ }),
40
+ },
41
+ {
42
+ // "let's use X because Y"
43
+ regex: /let'?s\s+use\s+(.+?)\s+because\s+(.+?)(?:\.|,|$)/i,
44
+ extract: (match) => ({
45
+ choice: match[1].trim(),
46
+ reasoning: match[2].trim(),
47
+ }),
48
+ },
49
+ ];
50
+
51
+ /**
52
+ * Preference pattern matchers
53
+ */
54
+ const PREFERENCE_PATTERNS = [
55
+ {
56
+ // "I prefer X"
57
+ regex: /i\s+prefer\s+(.+?)(?:\s+over\s+(.+?))?(?:\.|,|$)/i,
58
+ extract: (match) => ({
59
+ preference: match[1].trim(),
60
+ antiPreference: match[2]?.trim() || null,
61
+ }),
62
+ },
63
+ {
64
+ // "no, use X not Y" or "use X not Y"
65
+ regex: /(?:no,?\s+)?use\s+(.+?)\s+not\s+(.+?)(?:\.|,|$)/i,
66
+ extract: (match) => ({
67
+ preference: match[1].trim(),
68
+ antiPreference: match[2].trim(),
69
+ }),
70
+ },
71
+ {
72
+ // "always use X"
73
+ regex: /always\s+use\s+(.+?)(?:\s+for\s+(.+?))?(?:\.|,|$)/i,
74
+ extract: (match) => ({
75
+ preference: match[1].trim(),
76
+ context: match[2]?.trim() || null,
77
+ }),
78
+ },
79
+ {
80
+ // "don't use X, use Y"
81
+ regex: /don'?t\s+use\s+(.+?),?\s+use\s+(.+?)(?:\.|,|$)/i,
82
+ extract: (match) => ({
83
+ antiPreference: match[1].trim(),
84
+ preference: match[2].trim(),
85
+ }),
86
+ },
87
+ {
88
+ // "don't use X"
89
+ regex: /don'?t\s+use\s+(.+?)(?:\.|,|$)/i,
90
+ extract: (match) => ({
91
+ antiPreference: match[1].trim(),
92
+ }),
93
+ },
94
+ ];
95
+
96
+ /**
97
+ * Gotcha pattern matchers
98
+ */
99
+ const GOTCHA_PATTERNS = [
100
+ {
101
+ // "X needs to warm up" or "X needs time to Y"
102
+ regex: /(?:ah\s+)?(?:the\s+)?(.+?)\s+needs\s+(?:time\s+to\s+)?(.+?)(?:\.|,|$)/i,
103
+ extract: (match) => ({
104
+ subject: match[1].trim(),
105
+ issue: `needs ${match[2].trim()}`,
106
+ }),
107
+ },
108
+ {
109
+ // "watch out for X"
110
+ regex: /watch\s+out\s+for\s+(.+?)(?:\s+in\s+(.+?))?(?:\.|,|$)/i,
111
+ extract: (match) => ({
112
+ subject: match[2]?.trim() || 'general',
113
+ issue: match[1].trim(),
114
+ }),
115
+ },
116
+ {
117
+ // "X doesn't work because Y"
118
+ regex: /(.+?)\s+doesn'?t\s+work\s+because\s+(?:of\s+)?(.+?)(?:\.|,|$)/i,
119
+ extract: (match) => ({
120
+ subject: match[1].trim(),
121
+ issue: match[2].trim(),
122
+ }),
123
+ },
124
+ {
125
+ // "remember that X"
126
+ regex: /remember\s+that\s+(.+?)(?:\.|,|$)/i,
127
+ extract: (match) => ({
128
+ subject: 'reminder',
129
+ issue: match[1].trim(),
130
+ }),
131
+ },
132
+ ];
133
+
134
+ /**
135
+ * Reasoning pattern matchers
136
+ */
137
+ const REASONING_PATTERNS = [
138
+ {
139
+ // "because X"
140
+ regex: /^because\s+(.+?)(?:\.|,|$)/i,
141
+ extract: (match) => ({
142
+ content: match[1].trim(),
143
+ }),
144
+ },
145
+ {
146
+ // "the reason is X"
147
+ regex: /the\s+reason\s+is\s+(.+?)(?:\.|,|$)/i,
148
+ extract: (match) => ({
149
+ content: match[1].trim(),
150
+ }),
151
+ },
152
+ {
153
+ // "since we need X"
154
+ regex: /since\s+we\s+need\s+(.+?)(?:\.|,|$)/i,
155
+ extract: (match) => ({
156
+ content: `need ${match[1].trim()}`,
157
+ }),
158
+ },
159
+ ];
160
+
161
+ /**
162
+ * Apply pattern matchers to text
163
+ * @param {string} text - Text to search
164
+ * @param {Array} patterns - Array of pattern objects
165
+ * @param {string} type - Pattern type
166
+ * @returns {Array} Matched patterns
167
+ */
168
+ function applyPatterns(text, patterns, type) {
169
+ if (!text) return [];
170
+
171
+ const results = [];
172
+
173
+ for (const pattern of patterns) {
174
+ const match = text.match(pattern.regex);
175
+ if (match) {
176
+ results.push({
177
+ type,
178
+ ...pattern.extract(match),
179
+ raw: match[0],
180
+ });
181
+ }
182
+ }
183
+
184
+ return results;
185
+ }
186
+
187
+ /**
188
+ * Detect memorable patterns from a conversation exchange
189
+ * @param {Object} exchange - The conversation exchange
190
+ * @param {string} exchange.user - User message
191
+ * @param {string} exchange.assistant - Assistant response
192
+ * @returns {Object} Detected patterns by type
193
+ */
194
+ function detectPatterns(exchange) {
195
+ const userMessage = exchange?.user || '';
196
+ const assistantMessage = exchange?.assistant || '';
197
+
198
+ // Combine for context but primarily analyze user message
199
+ const text = userMessage;
200
+
201
+ return {
202
+ decisions: applyPatterns(text, DECISION_PATTERNS, PATTERN_TYPES.DECISION),
203
+ preferences: applyPatterns(text, PREFERENCE_PATTERNS, PATTERN_TYPES.PREFERENCE),
204
+ gotchas: applyPatterns(text, GOTCHA_PATTERNS, PATTERN_TYPES.GOTCHA),
205
+ reasoning: applyPatterns(text, REASONING_PATTERNS, PATTERN_TYPES.REASONING),
206
+ };
207
+ }
208
+
209
+ module.exports = {
210
+ detectPatterns,
211
+ PATTERN_TYPES,
212
+ DECISION_PATTERNS,
213
+ PREFERENCE_PATTERNS,
214
+ GOTCHA_PATTERNS,
215
+ REASONING_PATTERNS,
216
+ };