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.
- package/README.md +9 -4
- package/dashboard/dist/components/ActivityFeed.d.ts +17 -0
- package/dashboard/dist/components/ActivityFeed.js +42 -0
- package/dashboard/dist/components/ActivityFeed.test.d.ts +1 -0
- package/dashboard/dist/components/ActivityFeed.test.js +162 -0
- package/dashboard/dist/components/BranchSelector.d.ts +16 -0
- package/dashboard/dist/components/BranchSelector.js +49 -0
- package/dashboard/dist/components/BranchSelector.test.d.ts +1 -0
- package/dashboard/dist/components/BranchSelector.test.js +166 -0
- package/dashboard/dist/components/CommandPalette.d.ts +17 -0
- package/dashboard/dist/components/CommandPalette.js +118 -0
- package/dashboard/dist/components/CommandPalette.test.d.ts +1 -0
- package/dashboard/dist/components/CommandPalette.test.js +181 -0
- package/dashboard/dist/components/ConnectionStatus.d.ts +16 -0
- package/dashboard/dist/components/ConnectionStatus.js +27 -0
- package/dashboard/dist/components/ConnectionStatus.test.d.ts +1 -0
- package/dashboard/dist/components/ConnectionStatus.test.js +121 -0
- package/dashboard/dist/components/DeviceFrame.d.ts +19 -0
- package/dashboard/dist/components/DeviceFrame.js +52 -0
- package/dashboard/dist/components/DeviceFrame.test.d.ts +1 -0
- package/dashboard/dist/components/DeviceFrame.test.js +118 -0
- package/dashboard/dist/components/EnvironmentBadge.d.ts +11 -0
- package/dashboard/dist/components/EnvironmentBadge.js +16 -0
- package/dashboard/dist/components/EnvironmentBadge.test.d.ts +1 -0
- package/dashboard/dist/components/EnvironmentBadge.test.js +102 -0
- package/dashboard/dist/components/FocusIndicator.d.ts +19 -0
- package/dashboard/dist/components/FocusIndicator.js +47 -0
- package/dashboard/dist/components/FocusIndicator.test.d.ts +1 -0
- package/dashboard/dist/components/FocusIndicator.test.js +117 -0
- package/dashboard/dist/components/KeyboardHelp.d.ts +15 -0
- package/dashboard/dist/components/KeyboardHelp.js +61 -0
- package/dashboard/dist/components/KeyboardHelp.test.d.ts +1 -0
- package/dashboard/dist/components/KeyboardHelp.test.js +131 -0
- package/dashboard/dist/components/LogSearch.d.ts +13 -0
- package/dashboard/dist/components/LogSearch.js +43 -0
- package/dashboard/dist/components/LogSearch.test.d.ts +1 -0
- package/dashboard/dist/components/LogSearch.test.js +100 -0
- package/dashboard/dist/components/LogStream.d.ts +21 -0
- package/dashboard/dist/components/LogStream.js +123 -0
- package/dashboard/dist/components/LogStream.test.d.ts +1 -0
- package/dashboard/dist/components/LogStream.test.js +159 -0
- package/dashboard/dist/components/PreviewPanel.d.ts +18 -0
- package/dashboard/dist/components/PreviewPanel.js +73 -0
- package/dashboard/dist/components/PreviewPanel.test.d.ts +1 -0
- package/dashboard/dist/components/PreviewPanel.test.js +124 -0
- package/dashboard/dist/components/ProjectCard.d.ts +18 -0
- package/dashboard/dist/components/ProjectCard.js +19 -0
- package/dashboard/dist/components/ProjectCard.test.d.ts +1 -0
- package/dashboard/dist/components/ProjectCard.test.js +53 -0
- package/dashboard/dist/components/ProjectDetail.d.ts +44 -0
- package/dashboard/dist/components/ProjectDetail.js +65 -0
- package/dashboard/dist/components/ProjectDetail.test.d.ts +1 -0
- package/dashboard/dist/components/ProjectDetail.test.js +196 -0
- package/dashboard/dist/components/ProjectList.d.ts +11 -0
- package/dashboard/dist/components/ProjectList.js +62 -0
- package/dashboard/dist/components/ProjectList.test.d.ts +1 -0
- package/dashboard/dist/components/ProjectList.test.js +93 -0
- package/dashboard/dist/components/SettingsPanel.d.ts +32 -0
- package/dashboard/dist/components/SettingsPanel.js +154 -0
- package/dashboard/dist/components/SettingsPanel.test.d.ts +1 -0
- package/dashboard/dist/components/SettingsPanel.test.js +196 -0
- package/dashboard/dist/components/StatusBar.d.ts +16 -0
- package/dashboard/dist/components/StatusBar.js +47 -0
- package/dashboard/dist/components/StatusBar.test.d.ts +1 -0
- package/dashboard/dist/components/StatusBar.test.js +123 -0
- package/dashboard/dist/components/TaskBoard.d.ts +22 -0
- package/dashboard/dist/components/TaskBoard.js +102 -0
- package/dashboard/dist/components/TaskBoard.test.d.ts +1 -0
- package/dashboard/dist/components/TaskBoard.test.js +113 -0
- package/dashboard/dist/components/TaskCard.d.ts +17 -0
- package/dashboard/dist/components/TaskCard.js +29 -0
- package/dashboard/dist/components/TaskCard.test.d.ts +1 -0
- package/dashboard/dist/components/TaskCard.test.js +109 -0
- package/dashboard/dist/components/TaskDetail.d.ts +36 -0
- package/dashboard/dist/components/TaskDetail.js +41 -0
- package/dashboard/dist/components/TaskDetail.test.d.ts +1 -0
- package/dashboard/dist/components/TaskDetail.test.js +164 -0
- package/dashboard/dist/components/TaskFilter.d.ts +12 -0
- package/dashboard/dist/components/TaskFilter.js +138 -0
- package/dashboard/dist/components/TaskFilter.test.d.ts +1 -0
- package/dashboard/dist/components/TaskFilter.test.js +109 -0
- package/dashboard/dist/components/TeamPanel.d.ts +15 -0
- package/dashboard/dist/components/TeamPanel.js +24 -0
- package/dashboard/dist/components/TeamPanel.test.d.ts +1 -0
- package/dashboard/dist/components/TeamPanel.test.js +109 -0
- package/dashboard/dist/components/TeamPresence.d.ts +14 -0
- package/dashboard/dist/components/TeamPresence.js +31 -0
- package/dashboard/dist/components/TeamPresence.test.d.ts +1 -0
- package/dashboard/dist/components/TeamPresence.test.js +144 -0
- package/dashboard/dist/components/layout/Header.d.ts +9 -0
- package/dashboard/dist/components/layout/Header.js +11 -0
- package/dashboard/dist/components/layout/Header.test.d.ts +1 -0
- package/dashboard/dist/components/layout/Header.test.js +35 -0
- package/dashboard/dist/components/layout/Shell.d.ts +10 -0
- package/dashboard/dist/components/layout/Shell.js +5 -0
- package/dashboard/dist/components/layout/Shell.test.d.ts +1 -0
- package/dashboard/dist/components/layout/Shell.test.js +34 -0
- package/dashboard/dist/components/layout/Sidebar.d.ts +14 -0
- package/dashboard/dist/components/layout/Sidebar.js +8 -0
- package/dashboard/dist/components/layout/Sidebar.test.d.ts +1 -0
- package/dashboard/dist/components/layout/Sidebar.test.js +40 -0
- package/dashboard/dist/components/ui/Badge.d.ts +9 -0
- package/dashboard/dist/components/ui/Badge.js +13 -0
- package/dashboard/dist/components/ui/Badge.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Badge.test.js +69 -0
- package/dashboard/dist/components/ui/Button.d.ts +12 -0
- package/dashboard/dist/components/ui/Button.js +14 -0
- package/dashboard/dist/components/ui/Button.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Button.test.js +81 -0
- package/dashboard/dist/components/ui/Card.d.ts +21 -0
- package/dashboard/dist/components/ui/Card.js +20 -0
- package/dashboard/dist/components/ui/Card.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Card.test.js +82 -0
- package/dashboard/dist/components/ui/Input.d.ts +13 -0
- package/dashboard/dist/components/ui/Input.js +8 -0
- package/dashboard/dist/components/ui/Input.test.d.ts +1 -0
- package/dashboard/dist/components/ui/Input.test.js +68 -0
- package/dashboard/dist/styles/tokens.d.ts +150 -0
- package/dashboard/dist/styles/tokens.js +184 -0
- package/dashboard/dist/styles/tokens.test.d.ts +1 -0
- package/dashboard/dist/styles/tokens.test.js +95 -0
- package/dashboard/dist/test/setup.d.ts +1 -0
- package/dashboard/dist/test/setup.js +1 -0
- package/dashboard/package.json +3 -0
- package/package.json +15 -4
- package/scripts/capture-screenshots.js +170 -0
- package/scripts/docs-update.js +253 -0
- package/scripts/generate-screenshots.js +321 -0
- package/scripts/project-docs.js +377 -0
- package/scripts/vps-setup.sh +477 -0
- package/server/lib/adapters/base-adapter.js +114 -0
- package/server/lib/adapters/base-adapter.test.js +90 -0
- package/server/lib/adapters/claude-adapter.js +141 -0
- package/server/lib/adapters/claude-adapter.test.js +180 -0
- package/server/lib/adapters/deepseek-adapter.js +153 -0
- package/server/lib/adapters/deepseek-adapter.test.js +193 -0
- package/server/lib/adapters/openai-adapter.js +190 -0
- package/server/lib/adapters/openai-adapter.test.js +231 -0
- package/server/lib/budget-tracker.js +169 -0
- package/server/lib/budget-tracker.test.js +165 -0
- package/server/lib/claude-injector.js +85 -0
- package/server/lib/claude-injector.test.js +161 -0
- package/server/lib/consensus-engine.js +135 -0
- package/server/lib/consensus-engine.test.js +152 -0
- package/server/lib/context-builder.js +112 -0
- package/server/lib/context-builder.test.js +120 -0
- package/server/lib/file-collector.js +322 -0
- package/server/lib/file-collector.test.js +307 -0
- package/server/lib/memory-classifier.js +175 -0
- package/server/lib/memory-classifier.test.js +169 -0
- package/server/lib/memory-committer.js +138 -0
- package/server/lib/memory-committer.test.js +136 -0
- package/server/lib/memory-hooks.js +127 -0
- package/server/lib/memory-hooks.test.js +136 -0
- package/server/lib/memory-init.js +104 -0
- package/server/lib/memory-init.test.js +119 -0
- package/server/lib/memory-observer.js +149 -0
- package/server/lib/memory-observer.test.js +158 -0
- package/server/lib/memory-reader.js +243 -0
- package/server/lib/memory-reader.test.js +216 -0
- package/server/lib/memory-storage.js +120 -0
- package/server/lib/memory-storage.test.js +136 -0
- package/server/lib/memory-writer.js +176 -0
- package/server/lib/memory-writer.test.js +231 -0
- package/server/lib/overdrive-command.js +30 -6
- package/server/lib/overdrive-command.test.js +8 -1
- package/server/lib/pattern-detector.js +216 -0
- package/server/lib/pattern-detector.test.js +241 -0
- package/server/lib/relevance-scorer.js +175 -0
- package/server/lib/relevance-scorer.test.js +107 -0
- package/server/lib/review-command.js +238 -0
- package/server/lib/review-command.test.js +245 -0
- package/server/lib/review-orchestrator.js +273 -0
- package/server/lib/review-orchestrator.test.js +300 -0
- package/server/lib/review-reporter.js +288 -0
- package/server/lib/review-reporter.test.js +240 -0
- package/server/lib/session-summary.js +90 -0
- package/server/lib/session-summary.test.js +156 -0
- 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:
|
|
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
|
-
//
|
|
269
|
-
|
|
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,
|
|
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(
|
|
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
|
+
};
|