tlc-claude-code 1.3.0 → 1.4.1
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/dashboard/dist/components/AuditPane.d.ts +30 -0
- package/dashboard/dist/components/AuditPane.js +127 -0
- package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
- package/dashboard/dist/components/AuditPane.test.js +339 -0
- package/dashboard/dist/components/CompliancePane.d.ts +39 -0
- package/dashboard/dist/components/CompliancePane.js +96 -0
- package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
- package/dashboard/dist/components/CompliancePane.test.js +183 -0
- package/dashboard/dist/components/SSOPane.d.ts +36 -0
- package/dashboard/dist/components/SSOPane.js +71 -0
- package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
- package/dashboard/dist/components/SSOPane.test.js +155 -0
- package/dashboard/dist/components/WorkspaceDocsPane.js +0 -16
- package/dashboard/dist/components/WorkspacePane.d.ts +1 -1
- package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
- package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
- package/package.json +1 -1
- package/server/lib/access-control-doc.js +541 -0
- package/server/lib/access-control-doc.test.js +672 -0
- package/server/lib/adr-generator.js +423 -0
- package/server/lib/adr-generator.test.js +586 -0
- package/server/lib/agent-progress-monitor.js +223 -0
- package/server/lib/agent-progress-monitor.test.js +202 -0
- package/server/lib/audit-attribution.js +191 -0
- package/server/lib/audit-attribution.test.js +359 -0
- package/server/lib/audit-classifier.js +202 -0
- package/server/lib/audit-classifier.test.js +209 -0
- package/server/lib/audit-command.js +275 -0
- package/server/lib/audit-command.test.js +325 -0
- package/server/lib/audit-exporter.js +380 -0
- package/server/lib/audit-exporter.test.js +464 -0
- package/server/lib/audit-logger.js +236 -0
- package/server/lib/audit-logger.test.js +364 -0
- package/server/lib/audit-query.js +257 -0
- package/server/lib/audit-query.test.js +352 -0
- package/server/lib/audit-storage.js +269 -0
- package/server/lib/audit-storage.test.js +272 -0
- package/server/lib/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -0
- package/server/lib/compliance-checklist.js +866 -0
- package/server/lib/compliance-checklist.test.js +476 -0
- package/server/lib/compliance-command.js +616 -0
- package/server/lib/compliance-command.test.js +551 -0
- package/server/lib/compliance-reporter.js +692 -0
- package/server/lib/compliance-reporter.test.js +707 -0
- package/server/lib/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -0
- package/server/lib/ephemeral-storage.js +249 -0
- package/server/lib/ephemeral-storage.test.js +254 -0
- package/server/lib/evidence-collector.js +627 -0
- package/server/lib/evidence-collector.test.js +901 -0
- package/server/lib/flow-diagram-generator.js +474 -0
- package/server/lib/flow-diagram-generator.test.js +446 -0
- package/server/lib/idp-manager.js +626 -0
- package/server/lib/idp-manager.test.js +587 -0
- package/server/lib/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -0
- package/server/lib/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -0
- package/server/lib/oauth-flow.js +375 -0
- package/server/lib/oauth-flow.test.js +487 -0
- package/server/lib/oauth-registry.js +190 -0
- package/server/lib/oauth-registry.test.js +306 -0
- package/server/lib/readme-generator.js +490 -0
- package/server/lib/readme-generator.test.js +493 -0
- package/server/lib/repo-dependency-tracker.js +261 -0
- package/server/lib/repo-dependency-tracker.test.js +350 -0
- package/server/lib/retention-policy.js +281 -0
- package/server/lib/retention-policy.test.js +486 -0
- package/server/lib/role-mapper.js +236 -0
- package/server/lib/role-mapper.test.js +395 -0
- package/server/lib/saml-provider.js +765 -0
- package/server/lib/saml-provider.test.js +643 -0
- package/server/lib/security-policy-generator.js +682 -0
- package/server/lib/security-policy-generator.test.js +544 -0
- package/server/lib/sensitive-detector.js +112 -0
- package/server/lib/sensitive-detector.test.js +209 -0
- package/server/lib/service-interaction-diagram.js +700 -0
- package/server/lib/service-interaction-diagram.test.js +638 -0
- package/server/lib/service-summary.js +553 -0
- package/server/lib/service-summary.test.js +619 -0
- package/server/lib/session-purge.js +460 -0
- package/server/lib/session-purge.test.js +312 -0
- package/server/lib/sso-command.js +544 -0
- package/server/lib/sso-command.test.js +552 -0
- package/server/lib/sso-session.js +492 -0
- package/server/lib/sso-session.test.js +670 -0
- package/server/lib/workspace-command.js +249 -0
- package/server/lib/workspace-command.test.js +264 -0
- package/server/lib/workspace-config.js +270 -0
- package/server/lib/workspace-config.test.js +312 -0
- package/server/lib/workspace-docs-command.js +547 -0
- package/server/lib/workspace-docs-command.test.js +692 -0
- package/server/lib/workspace-memory.js +451 -0
- package/server/lib/workspace-memory.test.js +403 -0
- package/server/lib/workspace-scanner.js +452 -0
- package/server/lib/workspace-scanner.test.js +677 -0
- package/server/lib/workspace-test-runner.js +315 -0
- package/server/lib/workspace-test-runner.test.js +294 -0
- package/server/lib/zero-retention-command.js +439 -0
- package/server/lib/zero-retention-command.test.js +448 -0
- package/server/lib/zero-retention.js +322 -0
- package/server/lib/zero-retention.test.js +258 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
|
|
6
|
+
const { WorkspaceMemory } = await import('./workspace-memory.js');
|
|
7
|
+
|
|
8
|
+
describe('WorkspaceMemory', () => {
|
|
9
|
+
let tempDir;
|
|
10
|
+
let workspaceRoot;
|
|
11
|
+
let repoA;
|
|
12
|
+
let repoB;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
// Create temp workspace with two repos
|
|
16
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'workspace-memory-test-'));
|
|
17
|
+
workspaceRoot = tempDir;
|
|
18
|
+
|
|
19
|
+
// Create repo-a
|
|
20
|
+
repoA = path.join(tempDir, 'repo-a');
|
|
21
|
+
fs.mkdirSync(repoA);
|
|
22
|
+
fs.writeFileSync(path.join(repoA, 'package.json'), JSON.stringify({ name: 'repo-a' }));
|
|
23
|
+
|
|
24
|
+
// Create repo-b
|
|
25
|
+
repoB = path.join(tempDir, 'repo-b');
|
|
26
|
+
fs.mkdirSync(repoB);
|
|
27
|
+
fs.writeFileSync(path.join(repoB, 'package.json'), JSON.stringify({ name: 'repo-b' }));
|
|
28
|
+
|
|
29
|
+
// Create workspace config
|
|
30
|
+
fs.writeFileSync(
|
|
31
|
+
path.join(tempDir, '.tlc-workspace.json'),
|
|
32
|
+
JSON.stringify({
|
|
33
|
+
root: tempDir,
|
|
34
|
+
repos: ['repo-a', 'repo-b'],
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('initialization', () => {
|
|
44
|
+
it('creates workspace memory directory', () => {
|
|
45
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
46
|
+
memory.init();
|
|
47
|
+
|
|
48
|
+
const memoryDir = path.join(workspaceRoot, '.tlc-workspace', 'memory');
|
|
49
|
+
expect(fs.existsSync(memoryDir)).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('creates decisions subdirectory in workspace memory', () => {
|
|
53
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
54
|
+
memory.init();
|
|
55
|
+
|
|
56
|
+
const decisionsDir = path.join(workspaceRoot, '.tlc-workspace', 'memory', 'decisions');
|
|
57
|
+
expect(fs.existsSync(decisionsDir)).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('creates gotchas subdirectory in workspace memory', () => {
|
|
61
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
62
|
+
memory.init();
|
|
63
|
+
|
|
64
|
+
const gotchasDir = path.join(workspaceRoot, '.tlc-workspace', 'memory', 'gotchas');
|
|
65
|
+
expect(fs.existsSync(gotchasDir)).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('workspace-level memory', () => {
|
|
70
|
+
it('writes to workspace-level memory', async () => {
|
|
71
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
72
|
+
memory.init();
|
|
73
|
+
|
|
74
|
+
await memory.writeDecision({
|
|
75
|
+
title: 'Use ESM modules across all repos',
|
|
76
|
+
reasoning: 'Consistent module system',
|
|
77
|
+
level: 'workspace',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const decisionsDir = path.join(workspaceRoot, '.tlc-workspace', 'memory', 'decisions');
|
|
81
|
+
const files = fs.readdirSync(decisionsDir);
|
|
82
|
+
expect(files.length).toBe(1);
|
|
83
|
+
expect(files[0]).toMatch(/use-esm-modules-across-all-repos\.md$/);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('reads from workspace-level memory', async () => {
|
|
87
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
88
|
+
memory.init();
|
|
89
|
+
|
|
90
|
+
await memory.writeDecision({
|
|
91
|
+
title: 'Use TypeScript everywhere',
|
|
92
|
+
reasoning: 'Type safety',
|
|
93
|
+
level: 'workspace',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const decisions = await memory.getDecisions('workspace');
|
|
97
|
+
expect(decisions.length).toBe(1);
|
|
98
|
+
expect(decisions[0].title).toBe('Use TypeScript everywhere');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('workspace memory visible to all repos', async () => {
|
|
102
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
103
|
+
memory.init();
|
|
104
|
+
|
|
105
|
+
// Write workspace-level decision
|
|
106
|
+
await memory.writeDecision({
|
|
107
|
+
title: 'Shared API conventions',
|
|
108
|
+
reasoning: 'Consistency across services',
|
|
109
|
+
level: 'workspace',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Read from repo-a context
|
|
113
|
+
const decisionsA = await memory.getDecisions('all', repoA);
|
|
114
|
+
expect(decisionsA.some(d => d.title === 'Shared API conventions')).toBe(true);
|
|
115
|
+
|
|
116
|
+
// Read from repo-b context
|
|
117
|
+
const decisionsB = await memory.getDecisions('all', repoB);
|
|
118
|
+
expect(decisionsB.some(d => d.title === 'Shared API conventions')).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('repo-level memory', () => {
|
|
123
|
+
it('writes to repo-level memory', async () => {
|
|
124
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
125
|
+
memory.init();
|
|
126
|
+
|
|
127
|
+
await memory.writeDecision({
|
|
128
|
+
title: 'Use Jest for testing',
|
|
129
|
+
reasoning: 'Already configured',
|
|
130
|
+
level: 'repo',
|
|
131
|
+
repoPath: repoA,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const decisionsDir = path.join(repoA, '.tlc', 'memory', 'team', 'decisions');
|
|
135
|
+
expect(fs.existsSync(decisionsDir)).toBe(true);
|
|
136
|
+
const files = fs.readdirSync(decisionsDir);
|
|
137
|
+
expect(files.length).toBe(1);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('reads from repo-level memory', async () => {
|
|
141
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
142
|
+
memory.init();
|
|
143
|
+
|
|
144
|
+
await memory.writeDecision({
|
|
145
|
+
title: 'Custom logging format',
|
|
146
|
+
reasoning: 'Repo-specific needs',
|
|
147
|
+
level: 'repo',
|
|
148
|
+
repoPath: repoA,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const decisions = await memory.getDecisions('repo', repoA);
|
|
152
|
+
expect(decisions.length).toBe(1);
|
|
153
|
+
expect(decisions[0].title).toBe('Custom logging format');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('repo memory only visible to that repo', async () => {
|
|
157
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
158
|
+
memory.init();
|
|
159
|
+
|
|
160
|
+
// Write repo-a specific decision
|
|
161
|
+
await memory.writeDecision({
|
|
162
|
+
title: 'RepoA-only config',
|
|
163
|
+
reasoning: 'Only for repo-a',
|
|
164
|
+
level: 'repo',
|
|
165
|
+
repoPath: repoA,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Read from repo-a - should see it
|
|
169
|
+
const decisionsA = await memory.getDecisions('repo', repoA);
|
|
170
|
+
expect(decisionsA.some(d => d.title === 'RepoA-only config')).toBe(true);
|
|
171
|
+
|
|
172
|
+
// Read from repo-b - should NOT see it
|
|
173
|
+
const decisionsB = await memory.getDecisions('repo', repoB);
|
|
174
|
+
expect(decisionsB.some(d => d.title === 'RepoA-only config')).toBe(false);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('reads from both levels', () => {
|
|
179
|
+
it('returns both workspace and repo decisions with getDecisions("all")', async () => {
|
|
180
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
181
|
+
memory.init();
|
|
182
|
+
|
|
183
|
+
// Write workspace decision
|
|
184
|
+
await memory.writeDecision({
|
|
185
|
+
title: 'Workspace-wide rule',
|
|
186
|
+
reasoning: 'Applies everywhere',
|
|
187
|
+
level: 'workspace',
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Write repo decision
|
|
191
|
+
await memory.writeDecision({
|
|
192
|
+
title: 'Repo-specific rule',
|
|
193
|
+
reasoning: 'Only repo-a',
|
|
194
|
+
level: 'repo',
|
|
195
|
+
repoPath: repoA,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Read all from repo-a context
|
|
199
|
+
const decisions = await memory.getDecisions('all', repoA);
|
|
200
|
+
expect(decisions.length).toBe(2);
|
|
201
|
+
expect(decisions.some(d => d.title === 'Workspace-wide rule')).toBe(true);
|
|
202
|
+
expect(decisions.some(d => d.title === 'Repo-specific rule')).toBe(true);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('conflict resolution', () => {
|
|
207
|
+
it('repo overrides workspace for same key', async () => {
|
|
208
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
209
|
+
memory.init();
|
|
210
|
+
|
|
211
|
+
// Write workspace decision
|
|
212
|
+
await memory.writeDecision({
|
|
213
|
+
title: 'Testing framework',
|
|
214
|
+
reasoning: 'Use Vitest',
|
|
215
|
+
level: 'workspace',
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Write repo decision with same title (override)
|
|
219
|
+
await memory.writeDecision({
|
|
220
|
+
title: 'Testing framework',
|
|
221
|
+
reasoning: 'Use Jest instead',
|
|
222
|
+
level: 'repo',
|
|
223
|
+
repoPath: repoA,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Get resolved decisions - repo should win
|
|
227
|
+
const resolved = await memory.getResolvedDecisions(repoA);
|
|
228
|
+
const testingDecision = resolved.find(d => d.title === 'Testing framework');
|
|
229
|
+
expect(testingDecision).toBeDefined();
|
|
230
|
+
expect(testingDecision.reasoning).toContain('Jest');
|
|
231
|
+
expect(testingDecision.level).toBe('repo');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('workspace applies when no repo override', async () => {
|
|
235
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
236
|
+
memory.init();
|
|
237
|
+
|
|
238
|
+
// Write workspace decision
|
|
239
|
+
await memory.writeDecision({
|
|
240
|
+
title: 'Code style',
|
|
241
|
+
reasoning: 'Use Prettier',
|
|
242
|
+
level: 'workspace',
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Get resolved decisions from repo-a (no override)
|
|
246
|
+
const resolved = await memory.getResolvedDecisions(repoA);
|
|
247
|
+
const styleDecision = resolved.find(d => d.title === 'Code style');
|
|
248
|
+
expect(styleDecision).toBeDefined();
|
|
249
|
+
expect(styleDecision.reasoning).toContain('Prettier');
|
|
250
|
+
expect(styleDecision.level).toBe('workspace');
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('memory search', () => {
|
|
255
|
+
it('memory search spans workspace', async () => {
|
|
256
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
257
|
+
memory.init();
|
|
258
|
+
|
|
259
|
+
// Write workspace decision
|
|
260
|
+
await memory.writeDecision({
|
|
261
|
+
title: 'API versioning strategy',
|
|
262
|
+
reasoning: 'Use URL path versioning /v1/, /v2/',
|
|
263
|
+
level: 'workspace',
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Write repo-a decision
|
|
267
|
+
await memory.writeDecision({
|
|
268
|
+
title: 'Database connection pooling',
|
|
269
|
+
reasoning: 'Pool size of 10',
|
|
270
|
+
level: 'repo',
|
|
271
|
+
repoPath: repoA,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Write repo-b decision
|
|
275
|
+
await memory.writeDecision({
|
|
276
|
+
title: 'Cache expiration',
|
|
277
|
+
reasoning: 'TTL of 5 minutes',
|
|
278
|
+
level: 'repo',
|
|
279
|
+
repoPath: repoB,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Search across all
|
|
283
|
+
const results = await memory.search('versioning');
|
|
284
|
+
expect(results.length).toBe(1);
|
|
285
|
+
expect(results[0].title).toBe('API versioning strategy');
|
|
286
|
+
|
|
287
|
+
// Search finds repo-specific content
|
|
288
|
+
const poolResults = await memory.search('pooling');
|
|
289
|
+
expect(poolResults.length).toBe(1);
|
|
290
|
+
expect(poolResults[0].title).toBe('Database connection pooling');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('search returns results from all repos', async () => {
|
|
294
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
295
|
+
memory.init();
|
|
296
|
+
|
|
297
|
+
// Write decisions with common term
|
|
298
|
+
await memory.writeDecision({
|
|
299
|
+
title: 'Config for repo-a',
|
|
300
|
+
reasoning: 'Configuration pattern',
|
|
301
|
+
level: 'repo',
|
|
302
|
+
repoPath: repoA,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
await memory.writeDecision({
|
|
306
|
+
title: 'Config for repo-b',
|
|
307
|
+
reasoning: 'Configuration pattern',
|
|
308
|
+
level: 'repo',
|
|
309
|
+
repoPath: repoB,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const results = await memory.search('config');
|
|
313
|
+
expect(results.length).toBe(2);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe('gotchas', () => {
|
|
318
|
+
it('writes workspace-level gotcha', async () => {
|
|
319
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
320
|
+
memory.init();
|
|
321
|
+
|
|
322
|
+
await memory.writeGotcha({
|
|
323
|
+
title: 'Cross-repo import issue',
|
|
324
|
+
issue: 'Circular dependencies between repos',
|
|
325
|
+
level: 'workspace',
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const gotchasDir = path.join(workspaceRoot, '.tlc-workspace', 'memory', 'gotchas');
|
|
329
|
+
const files = fs.readdirSync(gotchasDir);
|
|
330
|
+
expect(files.length).toBe(1);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('writes repo-level gotcha', async () => {
|
|
334
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
335
|
+
memory.init();
|
|
336
|
+
|
|
337
|
+
await memory.writeGotcha({
|
|
338
|
+
title: 'Node version mismatch',
|
|
339
|
+
issue: 'Must use Node 18+',
|
|
340
|
+
level: 'repo',
|
|
341
|
+
repoPath: repoA,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const gotchasDir = path.join(repoA, '.tlc', 'memory', 'team', 'gotchas');
|
|
345
|
+
expect(fs.existsSync(gotchasDir)).toBe(true);
|
|
346
|
+
const files = fs.readdirSync(gotchasDir);
|
|
347
|
+
expect(files.length).toBe(1);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('reads gotchas from both levels', async () => {
|
|
351
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
352
|
+
memory.init();
|
|
353
|
+
|
|
354
|
+
await memory.writeGotcha({
|
|
355
|
+
title: 'Workspace gotcha',
|
|
356
|
+
issue: 'Shared issue',
|
|
357
|
+
level: 'workspace',
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
await memory.writeGotcha({
|
|
361
|
+
title: 'Repo gotcha',
|
|
362
|
+
issue: 'Repo-specific issue',
|
|
363
|
+
level: 'repo',
|
|
364
|
+
repoPath: repoA,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
const gotchas = await memory.getGotchas('all', repoA);
|
|
368
|
+
expect(gotchas.length).toBe(2);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe('sync decisions', () => {
|
|
373
|
+
it('syncs decisions from workspace to repo', async () => {
|
|
374
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
375
|
+
memory.init();
|
|
376
|
+
|
|
377
|
+
// Write workspace decision
|
|
378
|
+
await memory.writeDecision({
|
|
379
|
+
title: 'Sync test decision',
|
|
380
|
+
reasoning: 'Testing sync',
|
|
381
|
+
level: 'workspace',
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Sync to repo-a
|
|
385
|
+
await memory.syncToRepo(repoA);
|
|
386
|
+
|
|
387
|
+
// Repo-a should be able to read workspace decisions in its context
|
|
388
|
+
const decisions = await memory.getDecisions('all', repoA);
|
|
389
|
+
expect(decisions.some(d => d.title === 'Sync test decision')).toBe(true);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
describe('getWorkspaceConfig', () => {
|
|
394
|
+
it('loads workspace config', () => {
|
|
395
|
+
const memory = new WorkspaceMemory(workspaceRoot);
|
|
396
|
+
|
|
397
|
+
const config = memory.getWorkspaceConfig();
|
|
398
|
+
expect(config).toBeDefined();
|
|
399
|
+
expect(config.repos).toContain('repo-a');
|
|
400
|
+
expect(config.repos).toContain('repo-b');
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
});
|