sinapse-ai 1.9.0 → 1.9.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.
Files changed (88) hide show
  1. package/.claude/rules/mandatory-delegation.md +1 -1
  2. package/.codex/delegation-matrix.json +4 -3
  3. package/.codex/delegation-parity.json +4 -3
  4. package/.codex/instructions.md +2 -2
  5. package/.sinapse-ai/constitution.md +2 -2
  6. package/.sinapse-ai/core/doctor/checks/git-hooks.js +76 -10
  7. package/.sinapse-ai/core/execution/subagent-dispatcher.js +1 -1
  8. package/.sinapse-ai/core/synapse/engine.js +15 -0
  9. package/.sinapse-ai/data/entity-registry.yaml +13 -13
  10. package/.sinapse-ai/development/agents/snps-orqx.md +4 -4
  11. package/.sinapse-ai/git-hooks/lib/secret-scanner-core.js +76 -4
  12. package/.sinapse-ai/git-hooks/pre-push +7 -1
  13. package/.sinapse-ai/install-manifest.yaml +9 -9
  14. package/AGENTS.md +2 -2
  15. package/CHANGELOG.md +1247 -0
  16. package/bin/commands/uninstall.js +2 -2
  17. package/bin/utils/secret-scanner-core.js +76 -4
  18. package/docs/agent-reference-guide.md +1 -1
  19. package/docs/framework/architecture-overview.md +4 -4
  20. package/docs/framework/guiding-principles.md +9 -9
  21. package/docs/getting-started.md +1 -1
  22. package/docs/guides/agent-reference.md +1 -1
  23. package/docs/guides/codex-config.md +4 -5
  24. package/docs/pt/architecture/sub-orqx-pattern.md +20 -18
  25. package/package.json +8 -2
  26. package/packages/installer/src/installer/git-hooks-installer.js +3 -1
  27. package/packages/installer/src/wizard/ide-config-generator.js +9 -1
  28. package/packages/installer/src/wizard/index.js +3 -4
  29. package/scripts/regenerate-orqx-stubs.ps1 +0 -1
  30. package/scripts/sync-counts.js +10 -2
  31. package/scripts/sync-squad-yaml-components.js +108 -6
  32. package/scripts/validate-squad-orqx.js +19 -9
  33. package/sinapse/agents/sinapse-orqx.md +4 -4
  34. package/sinapse/agents/snps-orqx.md +4 -4
  35. package/sinapse/knowledge-base/routing-catalog.md +1 -1
  36. package/sinapse/tasks/diagnose-and-route.md +1 -1
  37. package/sinapse/tasks/squad-status-report.md +1 -1
  38. package/squads/claude-code-mastery/agents/claude-mastery-chief.md +1 -1
  39. package/squads/claude-code-mastery/agents/hooks-architect.md +60 -68
  40. package/squads/claude-code-mastery/knowledge-base/swarm-orchestration-patterns.md +1 -1
  41. package/squads/claude-code-mastery/tasks/audit-setup.md +1 -1
  42. package/squads/claude-code-mastery/workflows/optimization-cycle.yaml +4 -4
  43. package/squads/claude-code-mastery/workflows/project-setup-cycle.yaml +4 -4
  44. package/squads/squad-animations/README.md +1 -1
  45. package/squads/squad-cloning/README.md +1 -1
  46. package/squads/squad-commercial/README.md +1 -1
  47. package/squads/squad-content/README.md +1 -1
  48. package/squads/squad-copy/README.md +1 -1
  49. package/squads/squad-council/README.md +1 -1
  50. package/squads/squad-courses/README.md +1 -1
  51. package/squads/squad-cybersecurity/README.md +1 -1
  52. package/squads/squad-design/README.md +1 -1
  53. package/squads/squad-finance/README.md +1 -1
  54. package/squads/squad-growth/README.md +1 -1
  55. package/squads/squad-paidmedia/README.md +1 -1
  56. package/squads/squad-product/README.md +1 -1
  57. package/squads/squad-research/README.md +1 -1
  58. package/squads/squad-storytelling/README.md +1 -1
  59. package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +0 -265
  60. package/.sinapse-ai/core/permissions/__tests__/permission-mode.test.js +0 -293
  61. package/.sinapse-ai/infrastructure/tests/project-status-loader.test.js +0 -569
  62. package/.sinapse-ai/infrastructure/tests/regression-suite-v2.md +0 -622
  63. package/.sinapse-ai/infrastructure/tests/validate-module.js +0 -98
  64. package/.sinapse-ai/infrastructure/tests/worktree-manager.test.js +0 -620
  65. package/.sinapse-ai/workflow-intelligence/__tests__/confidence-scorer.test.js +0 -335
  66. package/.sinapse-ai/workflow-intelligence/__tests__/integration.test.js +0 -340
  67. package/.sinapse-ai/workflow-intelligence/__tests__/suggestion-engine.test.js +0 -438
  68. package/.sinapse-ai/workflow-intelligence/__tests__/wave-analyzer.test.js +0 -448
  69. package/.sinapse-ai/workflow-intelligence/__tests__/workflow-registry.test.js +0 -303
  70. package/packages/installer/src/__tests__/performance-benchmark.js +0 -383
  71. package/packages/installer/tests/integration/environment-configuration.test.js +0 -332
  72. package/packages/installer/tests/integration/wizard-detection.test.js +0 -352
  73. package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +0 -402
  74. package/packages/installer/tests/unit/claude-md-template-v5/claude-md-template-v5.test.js +0 -193
  75. package/packages/installer/tests/unit/config-validator.test.js +0 -315
  76. package/packages/installer/tests/unit/detection/detect-project-type.test.js +0 -539
  77. package/packages/installer/tests/unit/doctor/doctor-checks.test.js +0 -675
  78. package/packages/installer/tests/unit/doctor/doctor-orchestrator.test.js +0 -192
  79. package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +0 -192
  80. package/packages/installer/tests/unit/env-template.test.js +0 -187
  81. package/packages/installer/tests/unit/generate-settings-json/generate-settings-json.test.js +0 -310
  82. package/packages/installer/tests/unit/git-hooks-installer.test.js +0 -262
  83. package/packages/installer/tests/unit/ide-sync-integration/ide-sync-integration.test.js +0 -231
  84. package/packages/installer/tests/unit/merger/env-merger.test.js +0 -191
  85. package/packages/installer/tests/unit/merger/markdown-merger.test.js +0 -262
  86. package/packages/installer/tests/unit/merger/strategies.test.js +0 -154
  87. package/packages/installer/tests/unit/merger/yaml-merger.test.js +0 -328
  88. package/packages/sinapse-install/tests/unit/chrome-brain.smoke.test.js +0 -66
@@ -1,569 +0,0 @@
1
- /**
2
- * @jest-environment node
3
- */
4
-
5
- const fs = require('fs').promises;
6
- const path = require('path');
7
- const os = require('os');
8
- const yaml = require('js-yaml');
9
- const { ProjectStatusLoader } = require('../scripts/project-status-loader');
10
-
11
- describe('ProjectStatusLoader', () => {
12
- let loader;
13
- let testRoot;
14
- let cacheFile;
15
-
16
- beforeEach(async () => {
17
- // Use OS temp directory to ensure complete isolation from parent git repo
18
- testRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'sinapse-test-'));
19
- loader = new ProjectStatusLoader(testRoot);
20
- cacheFile = path.join(testRoot, '.sinapse', 'project-status.yaml');
21
- });
22
-
23
- afterEach(async () => {
24
- // Cleanup test directory
25
- try {
26
- await fs.rm(testRoot, { recursive: true, force: true });
27
- } catch (error) {
28
- // Ignore cleanup errors
29
- }
30
- });
31
-
32
- describe('isGitRepository', () => {
33
- it('should return false for non-git directory', async () => {
34
- // testRoot already created by mkdtemp in beforeEach
35
- const isGit = await loader.isGitRepository();
36
- expect(isGit).toBe(false);
37
- });
38
-
39
- it('should return true for git repository', async () => {
40
- // This test requires actual git - skip in CI if git not available
41
- // testRoot already created by mkdtemp in beforeEach
42
-
43
- // Try to initialize git
44
- try {
45
- const { execa } = require('execa');
46
- await execa('git', ['init'], { cwd: testRoot });
47
- const isGit = await loader.isGitRepository();
48
- expect(isGit).toBe(true);
49
- } catch (error) {
50
- console.warn('Git not available, skipping test');
51
- expect(true).toBe(true); // Skip test gracefully
52
- }
53
- });
54
- });
55
-
56
- describe('getGitBranch', () => {
57
- it('should return unknown on error', async () => {
58
- // testRoot already created by mkdtemp in beforeEach
59
- const branch = await loader.getGitBranch();
60
- expect(branch).toBe('unknown');
61
- });
62
-
63
- it('should detect git branch', async () => {
64
- // testRoot already created by mkdtemp in beforeEach
65
-
66
- try {
67
- const { execa } = require('execa');
68
- await execa('git', ['init'], { cwd: testRoot });
69
-
70
- // Create initial commit
71
- await fs.writeFile(path.join(testRoot, 'test.txt'), 'test');
72
- await execa('git', ['config', 'user.email', 'test@test.com'], { cwd: testRoot });
73
- await execa('git', ['config', 'user.name', 'Test User'], { cwd: testRoot });
74
- await execa('git', ['add', '.'], { cwd: testRoot });
75
- await execa('git', ['commit', '-m', 'Initial commit'], { cwd: testRoot });
76
-
77
- const branch = await loader.getGitBranch();
78
- expect(['main', 'master']).toContain(branch);
79
- } catch (error) {
80
- console.warn('Git not available, skipping test');
81
- expect(true).toBe(true);
82
- }
83
- });
84
- });
85
-
86
- describe('getModifiedFiles', () => {
87
- it('should return empty result for non-git repo', async () => {
88
- // testRoot already created by mkdtemp in beforeEach
89
- const result = await loader.getModifiedFiles();
90
- // Implementation returns { files: [], totalCount: 0 } for non-git repos
91
- expect(result.files || result).toEqual([]);
92
- });
93
-
94
- it('should detect modified files', async () => {
95
- // testRoot already created by mkdtemp in beforeEach
96
-
97
- try {
98
- const { execa } = require('execa');
99
- await execa('git', ['init'], { cwd: testRoot });
100
- await execa('git', ['config', 'user.email', 'test@test.com'], { cwd: testRoot });
101
- await execa('git', ['config', 'user.name', 'Test User'], { cwd: testRoot });
102
-
103
- // Create and commit initial file
104
- await fs.writeFile(path.join(testRoot, 'existing.txt'), 'existing');
105
- await execa('git', ['add', '.'], { cwd: testRoot });
106
- await execa('git', ['commit', '-m', 'Initial'], { cwd: testRoot });
107
-
108
- // Modify file
109
- await fs.writeFile(path.join(testRoot, 'existing.txt'), 'modified');
110
-
111
- const files = await loader.getModifiedFiles();
112
- expect(files).toContain('existing.txt');
113
- } catch (error) {
114
- console.warn('Git not available, skipping test');
115
- expect(true).toBe(true);
116
- }
117
- });
118
-
119
- it('should limit to 5 files maximum', async () => {
120
- // testRoot already created by mkdtemp in beforeEach
121
-
122
- try {
123
- const { execa } = require('execa');
124
- await execa('git', ['init'], { cwd: testRoot });
125
- await execa('git', ['config', 'user.email', 'test@test.com'], { cwd: testRoot });
126
- await execa('git', ['config', 'user.name', 'Test User'], { cwd: testRoot });
127
-
128
- // Create 7 files
129
- for (let i = 0; i < 7; i++) {
130
- await fs.writeFile(path.join(testRoot, `file${i}.txt`), 'content');
131
- }
132
-
133
- const files = await loader.getModifiedFiles();
134
- expect(files.length).toBeLessThanOrEqual(5);
135
- } catch (error) {
136
- console.warn('Git not available, skipping test');
137
- expect(true).toBe(true);
138
- }
139
- });
140
- });
141
-
142
- describe('getRecentCommits', () => {
143
- it('should return empty array for non-git repo', async () => {
144
- // testRoot already created by mkdtemp in beforeEach
145
- const commits = await loader.getRecentCommits();
146
- expect(commits).toEqual([]);
147
- });
148
-
149
- it('should return empty array for repo with no commits', async () => {
150
- // testRoot already created by mkdtemp in beforeEach
151
-
152
- try {
153
- const { execa } = require('execa');
154
- await execa('git', ['init'], { cwd: testRoot });
155
-
156
- const commits = await loader.getRecentCommits();
157
- expect(commits).toEqual([]);
158
- } catch (error) {
159
- console.warn('Git not available, skipping test');
160
- expect(true).toBe(true);
161
- }
162
- });
163
-
164
- it('should limit to 2 commits', async () => {
165
- // testRoot already created by mkdtemp in beforeEach
166
-
167
- try {
168
- const { execa } = require('execa');
169
- await execa('git', ['init'], { cwd: testRoot });
170
- await execa('git', ['config', 'user.email', 'test@test.com'], { cwd: testRoot });
171
- await execa('git', ['config', 'user.name', 'Test User'], { cwd: testRoot });
172
-
173
- // Create 3 commits
174
- for (let i = 0; i < 3; i++) {
175
- await fs.writeFile(path.join(testRoot, `file${i}.txt`), 'content');
176
- await execa('git', ['add', '.'], { cwd: testRoot });
177
- await execa('git', ['commit', '-m', `Commit ${i}`], { cwd: testRoot });
178
- }
179
-
180
- const commits = await loader.getRecentCommits();
181
- expect(commits.length).toBeLessThanOrEqual(2);
182
- } catch (error) {
183
- console.warn('Git not available, skipping test');
184
- expect(true).toBe(true);
185
- }
186
- });
187
- });
188
-
189
- describe('getCurrentStoryInfo', () => {
190
- it('should return null when stories directory missing', async () => {
191
- // testRoot already created by mkdtemp in beforeEach
192
- const info = await loader.getCurrentStoryInfo();
193
- expect(info).toEqual({ story: null, epic: null });
194
- });
195
-
196
- it('should detect InProgress story', async () => {
197
- await fs.mkdir(path.join(testRoot, 'docs', 'stories'), { recursive: true });
198
-
199
- const storyContent = `# Story 6.1.2.4
200
-
201
- **Story ID:** STORY-6.1.2.4
202
- **Epic:** Epic 6.1 - Agent Identity
203
- **Status:** InProgress
204
-
205
- ## Description
206
- Test story
207
- `;
208
-
209
- await fs.writeFile(path.join(testRoot, 'docs', 'stories', 'story-6.1.2.4.md'), storyContent);
210
-
211
- const info = await loader.getCurrentStoryInfo();
212
- expect(info.story).toBe('STORY-6.1.2.4');
213
- expect(info.epic).toContain('Epic 6.1');
214
- });
215
-
216
- it('should handle multiple story files correctly', async () => {
217
- await fs.mkdir(path.join(testRoot, 'docs', 'stories'), { recursive: true });
218
-
219
- // Create one completed story
220
- await fs.writeFile(
221
- path.join(testRoot, 'docs', 'stories', 'story-1.md'),
222
- '**Status:** Completed',
223
- );
224
-
225
- // Create one in progress story
226
- await fs.writeFile(
227
- path.join(testRoot, 'docs', 'stories', 'story-2.md'),
228
- '**Status:** InProgress\n**Story ID:** STORY-2',
229
- );
230
-
231
- const info = await loader.getCurrentStoryInfo();
232
- expect(info.story).toBe('STORY-2');
233
- });
234
- });
235
-
236
- describe('Cache Management', () => {
237
- it('should create cache file on first load', async () => {
238
- // testRoot already created by mkdtemp in beforeEach
239
-
240
- const status = await loader.loadProjectStatus();
241
-
242
- // Check cache file exists
243
- const cacheExists = await fs
244
- .access(cacheFile)
245
- .then(() => true)
246
- .catch(() => false);
247
-
248
- expect(cacheExists).toBe(true);
249
- });
250
-
251
- it('should return cached status within TTL', async () => {
252
- // testRoot already created by mkdtemp in beforeEach
253
-
254
- // First load
255
- const status1 = await loader.loadProjectStatus();
256
- const time1 = status1.lastUpdate;
257
-
258
- // Immediate second load (within TTL)
259
- const status2 = await loader.loadProjectStatus();
260
- const time2 = status2.lastUpdate;
261
-
262
- // Should return same cached status
263
- expect(time1).toBe(time2);
264
- });
265
-
266
- it('should invalidate cache after TTL expires', async () => {
267
- // testRoot already created by mkdtemp in beforeEach
268
-
269
- // Override TTL to 0 seconds for testing
270
- loader.cacheTTL = 0;
271
-
272
- // First load
273
- const status1 = await loader.loadProjectStatus();
274
- const time1 = status1.lastUpdate;
275
-
276
- // Wait 10ms
277
- await new Promise((resolve) => setTimeout(resolve, 10));
278
-
279
- // Second load (after TTL)
280
- const status2 = await loader.loadProjectStatus();
281
- const time2 = status2.lastUpdate;
282
-
283
- // Should be different timestamps
284
- expect(time1).not.toBe(time2);
285
- });
286
-
287
- it('should clear cache successfully', async () => {
288
- // testRoot already created by mkdtemp in beforeEach
289
-
290
- // Create cache
291
- await loader.loadProjectStatus();
292
-
293
- // Clear cache
294
- const cleared = await loader.clearCache();
295
- expect(cleared).toBe(true);
296
-
297
- // Check cache file deleted
298
- const cacheExists = await fs
299
- .access(cacheFile)
300
- .then(() => true)
301
- .catch(() => false);
302
-
303
- expect(cacheExists).toBe(false);
304
- });
305
- });
306
-
307
- describe('Edge Cases', () => {
308
- it('should handle detached HEAD state', async () => {
309
- // testRoot already created by mkdtemp in beforeEach
310
-
311
- try {
312
- const { execa } = require('execa');
313
- await execa('git', ['init'], { cwd: testRoot });
314
- await execa('git', ['config', 'user.email', 'test@test.com'], { cwd: testRoot });
315
- await execa('git', ['config', 'user.name', 'Test User'], { cwd: testRoot });
316
-
317
- // Create commit
318
- await fs.writeFile(path.join(testRoot, 'test.txt'), 'test');
319
- await execa('git', ['add', '.'], { cwd: testRoot });
320
- await execa('git', ['commit', '-m', 'Initial'], { cwd: testRoot });
321
-
322
- // Get commit hash
323
- const { stdout: hash } = await execa('git', ['rev-parse', 'HEAD'], { cwd: testRoot });
324
-
325
- // Checkout detached HEAD
326
- await execa('git', ['checkout', hash.trim()], { cwd: testRoot });
327
-
328
- const branch = await loader.getGitBranch();
329
- expect(typeof branch).toBe('string');
330
- } catch (error) {
331
- console.warn('Git not available, skipping test');
332
- expect(true).toBe(true);
333
- }
334
- });
335
-
336
- it('should gracefully handle non-git project', async () => {
337
- // testRoot already created by mkdtemp in beforeEach
338
-
339
- const status = await loader.loadProjectStatus();
340
-
341
- expect(status.isGitRepo).toBe(false);
342
- expect(status.branch).toBeNull();
343
- expect(status.modifiedFiles).toEqual([]);
344
- expect(status.recentCommits).toEqual([]);
345
- });
346
- });
347
-
348
- describe('formatStatusDisplay', () => {
349
- it('should format git status correctly', () => {
350
- const status = {
351
- isGitRepo: true,
352
- branch: 'main',
353
- modifiedFiles: ['file1.md', 'file2.js'],
354
- recentCommits: ['chore: cleanup', 'feat: new feature'],
355
- currentStory: 'STORY-123',
356
- lastUpdate: new Date().toISOString(),
357
- };
358
-
359
- const display = loader.formatStatusDisplay(status);
360
-
361
- expect(display).toContain('Branch: main');
362
- expect(display).toContain('Modified: file1.md, file2.js');
363
- expect(display).toContain('Recent: chore: cleanup, feat: new feature');
364
- expect(display).toContain('Story: STORY-123');
365
- });
366
-
367
- it('should handle non-git repo message', () => {
368
- const status = {
369
- isGitRepo: false,
370
- branch: null,
371
- modifiedFiles: [],
372
- recentCommits: [],
373
- currentStory: null,
374
- lastUpdate: new Date().toISOString(),
375
- };
376
-
377
- const display = loader.formatStatusDisplay(status);
378
- expect(display).toContain('Not a git repository');
379
- });
380
-
381
- it('should handle clean repository', () => {
382
- const status = {
383
- isGitRepo: true,
384
- branch: 'main',
385
- modifiedFiles: [],
386
- recentCommits: [],
387
- currentStory: null,
388
- lastUpdate: new Date().toISOString(),
389
- };
390
-
391
- const display = loader.formatStatusDisplay(status);
392
- expect(display).toContain('Branch: main');
393
- });
394
-
395
- // Story 1.5: Worktree Status Integration tests
396
- it('should display worktrees summary in status', () => {
397
- const status = {
398
- isGitRepo: true,
399
- branch: 'main',
400
- modifiedFiles: [],
401
- recentCommits: [],
402
- currentStory: null,
403
- lastUpdate: new Date().toISOString(),
404
- worktrees: {
405
- 'STORY-42': {
406
- path: '.sinapse/worktrees/STORY-42',
407
- branch: 'auto-claude/STORY-42',
408
- createdAt: '2026-01-28T10:00:00Z',
409
- lastActivity: '2026-01-28T12:30:00Z',
410
- uncommittedChanges: 3,
411
- status: 'active',
412
- },
413
- 'STORY-43': {
414
- path: '.sinapse/worktrees/STORY-43',
415
- branch: 'auto-claude/STORY-43',
416
- createdAt: '2026-01-27T10:00:00Z',
417
- lastActivity: '2026-01-27T12:30:00Z',
418
- uncommittedChanges: 0,
419
- status: 'active',
420
- },
421
- },
422
- };
423
-
424
- const display = loader.formatStatusDisplay(status);
425
- expect(display).toContain('Worktrees:');
426
- expect(display).toContain('2/2 active');
427
- expect(display).toContain('1 with changes');
428
- });
429
-
430
- it('should not display worktrees section when empty', () => {
431
- const status = {
432
- isGitRepo: true,
433
- branch: 'main',
434
- modifiedFiles: [],
435
- recentCommits: [],
436
- currentStory: null,
437
- lastUpdate: new Date().toISOString(),
438
- worktrees: {},
439
- };
440
-
441
- const display = loader.formatStatusDisplay(status);
442
- expect(display).not.toContain('Worktrees:');
443
- });
444
-
445
- it('should not display worktrees section when undefined', () => {
446
- const status = {
447
- isGitRepo: true,
448
- branch: 'main',
449
- modifiedFiles: [],
450
- recentCommits: [],
451
- currentStory: null,
452
- lastUpdate: new Date().toISOString(),
453
- };
454
-
455
- const display = loader.formatStatusDisplay(status);
456
- expect(display).not.toContain('Worktrees:');
457
- });
458
- });
459
-
460
- // Story 1.5: Worktree Status Integration
461
- describe('getWorktreesStatus', () => {
462
- it('should return null when no worktrees exist', async () => {
463
- // testRoot already created by mkdtemp in beforeEach
464
-
465
- const worktrees = await loader.getWorktreesStatus();
466
- expect(worktrees).toBeNull();
467
- });
468
-
469
- it('should return worktree info with required fields', async () => {
470
- // testRoot already created by mkdtemp in beforeEach
471
-
472
- try {
473
- const { execa } = require('execa');
474
- await execa('git', ['init'], { cwd: testRoot });
475
- await execa('git', ['config', 'user.email', 'test@test.com'], { cwd: testRoot });
476
- await execa('git', ['config', 'user.name', 'Test User'], { cwd: testRoot });
477
-
478
- // Create initial commit (required for worktree)
479
- await fs.writeFile(path.join(testRoot, 'test.txt'), 'test');
480
- await execa('git', ['add', '.'], { cwd: testRoot });
481
- await execa('git', ['commit', '-m', 'Initial commit'], { cwd: testRoot });
482
-
483
- // Create worktree manually
484
- const worktreePath = path.join(testRoot, '.sinapse', 'worktrees', 'STORY-TEST');
485
- await execa('git', ['worktree', 'add', worktreePath, '-b', 'auto-claude/STORY-TEST'], {
486
- cwd: testRoot,
487
- });
488
-
489
- const worktrees = await loader.getWorktreesStatus();
490
-
491
- expect(worktrees).not.toBeNull();
492
- expect(worktrees['STORY-TEST']).toBeDefined();
493
- expect(worktrees['STORY-TEST'].path).toContain('STORY-TEST');
494
- expect(worktrees['STORY-TEST'].branch).toBe('auto-claude/STORY-TEST');
495
- expect(worktrees['STORY-TEST'].createdAt).toBeDefined();
496
- expect(worktrees['STORY-TEST'].lastActivity).toBeDefined();
497
- expect(typeof worktrees['STORY-TEST'].uncommittedChanges).toBe('number');
498
- expect(['active', 'stale']).toContain(worktrees['STORY-TEST'].status);
499
-
500
- // Cleanup worktree
501
- await execa('git', ['worktree', 'remove', worktreePath, '--force'], { cwd: testRoot });
502
- await execa('git', ['branch', '-D', 'auto-claude/STORY-TEST'], { cwd: testRoot });
503
- } catch (error) {
504
- console.warn('Git not available, skipping test:', error.message);
505
- expect(true).toBe(true);
506
- }
507
- });
508
- });
509
-
510
- describe('generateStatus with worktrees', () => {
511
- it('should include worktrees in generated status', async () => {
512
- // testRoot already created by mkdtemp in beforeEach
513
-
514
- try {
515
- const { execa } = require('execa');
516
- await execa('git', ['init'], { cwd: testRoot });
517
- await execa('git', ['config', 'user.email', 'test@test.com'], { cwd: testRoot });
518
- await execa('git', ['config', 'user.name', 'Test User'], { cwd: testRoot });
519
-
520
- // Create initial commit
521
- await fs.writeFile(path.join(testRoot, 'test.txt'), 'test');
522
- await execa('git', ['add', '.'], { cwd: testRoot });
523
- await execa('git', ['commit', '-m', 'Initial commit'], { cwd: testRoot });
524
-
525
- // Create worktree
526
- const worktreePath = path.join(testRoot, '.sinapse', 'worktrees', 'STORY-GEN');
527
- await execa('git', ['worktree', 'add', worktreePath, '-b', 'auto-claude/STORY-GEN'], {
528
- cwd: testRoot,
529
- });
530
-
531
- const status = await loader.generateStatus();
532
-
533
- expect(status.worktrees).toBeDefined();
534
- expect(status.worktrees['STORY-GEN']).toBeDefined();
535
-
536
- // Cleanup
537
- await execa('git', ['worktree', 'remove', worktreePath, '--force'], { cwd: testRoot });
538
- await execa('git', ['branch', '-D', 'auto-claude/STORY-GEN'], { cwd: testRoot });
539
- } catch (error) {
540
- console.warn('Git not available, skipping test:', error.message);
541
- expect(true).toBe(true);
542
- }
543
- });
544
-
545
- it('should not include worktrees key when none exist', async () => {
546
- // testRoot already created by mkdtemp in beforeEach
547
-
548
- try {
549
- const { execa } = require('execa');
550
- await execa('git', ['init'], { cwd: testRoot });
551
- await execa('git', ['config', 'user.email', 'test@test.com'], { cwd: testRoot });
552
- await execa('git', ['config', 'user.name', 'Test User'], { cwd: testRoot });
553
-
554
- await fs.writeFile(path.join(testRoot, 'test.txt'), 'test');
555
- await execa('git', ['add', '.'], { cwd: testRoot });
556
- await execa('git', ['commit', '-m', 'Initial commit'], { cwd: testRoot });
557
-
558
- const status = await loader.generateStatus();
559
-
560
- // worktrees should be undefined (not included) when none exist
561
- expect(status.worktrees).toBeUndefined();
562
- } catch (error) {
563
- console.warn('Git not available, skipping test:', error.message);
564
- expect(true).toBe(true);
565
- }
566
- });
567
- });
568
- });
569
-