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.
- package/.claude/rules/mandatory-delegation.md +1 -1
- package/.codex/delegation-matrix.json +4 -3
- package/.codex/delegation-parity.json +4 -3
- package/.codex/instructions.md +2 -2
- package/.sinapse-ai/constitution.md +2 -2
- package/.sinapse-ai/core/doctor/checks/git-hooks.js +76 -10
- package/.sinapse-ai/core/execution/subagent-dispatcher.js +1 -1
- package/.sinapse-ai/core/synapse/engine.js +15 -0
- package/.sinapse-ai/data/entity-registry.yaml +13 -13
- package/.sinapse-ai/development/agents/snps-orqx.md +4 -4
- package/.sinapse-ai/git-hooks/lib/secret-scanner-core.js +76 -4
- package/.sinapse-ai/git-hooks/pre-push +7 -1
- package/.sinapse-ai/install-manifest.yaml +9 -9
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +1247 -0
- package/bin/commands/uninstall.js +2 -2
- package/bin/utils/secret-scanner-core.js +76 -4
- package/docs/agent-reference-guide.md +1 -1
- package/docs/framework/architecture-overview.md +4 -4
- package/docs/framework/guiding-principles.md +9 -9
- package/docs/getting-started.md +1 -1
- package/docs/guides/agent-reference.md +1 -1
- package/docs/guides/codex-config.md +4 -5
- package/docs/pt/architecture/sub-orqx-pattern.md +20 -18
- package/package.json +8 -2
- package/packages/installer/src/installer/git-hooks-installer.js +3 -1
- package/packages/installer/src/wizard/ide-config-generator.js +9 -1
- package/packages/installer/src/wizard/index.js +3 -4
- package/scripts/regenerate-orqx-stubs.ps1 +0 -1
- package/scripts/sync-counts.js +10 -2
- package/scripts/sync-squad-yaml-components.js +108 -6
- package/scripts/validate-squad-orqx.js +19 -9
- package/sinapse/agents/sinapse-orqx.md +4 -4
- package/sinapse/agents/snps-orqx.md +4 -4
- package/sinapse/knowledge-base/routing-catalog.md +1 -1
- package/sinapse/tasks/diagnose-and-route.md +1 -1
- package/sinapse/tasks/squad-status-report.md +1 -1
- package/squads/claude-code-mastery/agents/claude-mastery-chief.md +1 -1
- package/squads/claude-code-mastery/agents/hooks-architect.md +60 -68
- package/squads/claude-code-mastery/knowledge-base/swarm-orchestration-patterns.md +1 -1
- package/squads/claude-code-mastery/tasks/audit-setup.md +1 -1
- package/squads/claude-code-mastery/workflows/optimization-cycle.yaml +4 -4
- package/squads/claude-code-mastery/workflows/project-setup-cycle.yaml +4 -4
- package/squads/squad-animations/README.md +1 -1
- package/squads/squad-cloning/README.md +1 -1
- package/squads/squad-commercial/README.md +1 -1
- package/squads/squad-content/README.md +1 -1
- package/squads/squad-copy/README.md +1 -1
- package/squads/squad-council/README.md +1 -1
- package/squads/squad-courses/README.md +1 -1
- package/squads/squad-cybersecurity/README.md +1 -1
- package/squads/squad-design/README.md +1 -1
- package/squads/squad-finance/README.md +1 -1
- package/squads/squad-growth/README.md +1 -1
- package/squads/squad-paidmedia/README.md +1 -1
- package/squads/squad-product/README.md +1 -1
- package/squads/squad-research/README.md +1 -1
- package/squads/squad-storytelling/README.md +1 -1
- package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +0 -265
- package/.sinapse-ai/core/permissions/__tests__/permission-mode.test.js +0 -293
- package/.sinapse-ai/infrastructure/tests/project-status-loader.test.js +0 -569
- package/.sinapse-ai/infrastructure/tests/regression-suite-v2.md +0 -622
- package/.sinapse-ai/infrastructure/tests/validate-module.js +0 -98
- package/.sinapse-ai/infrastructure/tests/worktree-manager.test.js +0 -620
- package/.sinapse-ai/workflow-intelligence/__tests__/confidence-scorer.test.js +0 -335
- package/.sinapse-ai/workflow-intelligence/__tests__/integration.test.js +0 -340
- package/.sinapse-ai/workflow-intelligence/__tests__/suggestion-engine.test.js +0 -438
- package/.sinapse-ai/workflow-intelligence/__tests__/wave-analyzer.test.js +0 -448
- package/.sinapse-ai/workflow-intelligence/__tests__/workflow-registry.test.js +0 -303
- package/packages/installer/src/__tests__/performance-benchmark.js +0 -383
- package/packages/installer/tests/integration/environment-configuration.test.js +0 -332
- package/packages/installer/tests/integration/wizard-detection.test.js +0 -352
- package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +0 -402
- package/packages/installer/tests/unit/claude-md-template-v5/claude-md-template-v5.test.js +0 -193
- package/packages/installer/tests/unit/config-validator.test.js +0 -315
- package/packages/installer/tests/unit/detection/detect-project-type.test.js +0 -539
- package/packages/installer/tests/unit/doctor/doctor-checks.test.js +0 -675
- package/packages/installer/tests/unit/doctor/doctor-orchestrator.test.js +0 -192
- package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +0 -192
- package/packages/installer/tests/unit/env-template.test.js +0 -187
- package/packages/installer/tests/unit/generate-settings-json/generate-settings-json.test.js +0 -310
- package/packages/installer/tests/unit/git-hooks-installer.test.js +0 -262
- package/packages/installer/tests/unit/ide-sync-integration/ide-sync-integration.test.js +0 -231
- package/packages/installer/tests/unit/merger/env-merger.test.js +0 -191
- package/packages/installer/tests/unit/merger/markdown-merger.test.js +0 -262
- package/packages/installer/tests/unit/merger/strategies.test.js +0 -154
- package/packages/installer/tests/unit/merger/yaml-merger.test.js +0 -328
- 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
|
-
|