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,620 +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 WorktreeManager = require('../scripts/worktree-manager');
|
|
9
|
-
|
|
10
|
-
describe('WorktreeManager', () => {
|
|
11
|
-
let manager;
|
|
12
|
-
let testRoot;
|
|
13
|
-
|
|
14
|
-
beforeEach(async () => {
|
|
15
|
-
// Use OS temp directory to ensure complete isolation from source tree
|
|
16
|
-
testRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'sinapse-worktree-test-'));
|
|
17
|
-
|
|
18
|
-
// Initialize git repo for tests
|
|
19
|
-
try {
|
|
20
|
-
const execa = require('execa');
|
|
21
|
-
await execa('git', ['init'], { cwd: testRoot });
|
|
22
|
-
await execa('git', ['config', 'user.email', 'test@test.com'], {
|
|
23
|
-
cwd: testRoot,
|
|
24
|
-
});
|
|
25
|
-
await execa('git', ['config', 'user.name', 'Test User'], { cwd: testRoot });
|
|
26
|
-
|
|
27
|
-
// Create initial commit (required for worktrees)
|
|
28
|
-
const testFile = path.join(testRoot, 'README.md');
|
|
29
|
-
await fs.writeFile(testFile, '# Test Repo');
|
|
30
|
-
await execa('git', ['add', '.'], { cwd: testRoot });
|
|
31
|
-
await execa('git', ['commit', '-m', 'Initial commit'], { cwd: testRoot });
|
|
32
|
-
} catch (error) {
|
|
33
|
-
console.warn('Git not available, some tests will be skipped:', error.message);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
manager = new WorktreeManager(testRoot, {
|
|
37
|
-
maxWorktrees: 3,
|
|
38
|
-
staleDays: 30,
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
afterEach(async () => {
|
|
43
|
-
// Cleanup test directory
|
|
44
|
-
try {
|
|
45
|
-
await fs.rm(testRoot, { recursive: true, force: true });
|
|
46
|
-
} catch (error) {
|
|
47
|
-
// Ignore cleanup errors
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe('constructor', () => {
|
|
52
|
-
it('should use default options when none provided', () => {
|
|
53
|
-
const defaultManager = new WorktreeManager('/tmp/test');
|
|
54
|
-
expect(defaultManager.maxWorktrees).toBe(10);
|
|
55
|
-
expect(defaultManager.worktreeDir).toBe('.sinapse/worktrees');
|
|
56
|
-
expect(defaultManager.branchPrefix).toBe('auto-claude/');
|
|
57
|
-
expect(defaultManager.staleDays).toBe(30);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('should accept custom options', () => {
|
|
61
|
-
const customManager = new WorktreeManager('/tmp/test', {
|
|
62
|
-
maxWorktrees: 5,
|
|
63
|
-
worktreeDir: '.custom/worktrees',
|
|
64
|
-
branchPrefix: 'custom/',
|
|
65
|
-
staleDays: 15,
|
|
66
|
-
});
|
|
67
|
-
expect(customManager.maxWorktrees).toBe(5);
|
|
68
|
-
expect(customManager.worktreeDir).toBe('.custom/worktrees');
|
|
69
|
-
expect(customManager.branchPrefix).toBe('custom/');
|
|
70
|
-
expect(customManager.staleDays).toBe(15);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe('getWorktreePath', () => {
|
|
75
|
-
it('should return correct path for story', () => {
|
|
76
|
-
const wtPath = manager.getWorktreePath('STORY-42');
|
|
77
|
-
expect(wtPath).toBe(path.join(testRoot, '.sinapse/worktrees', 'STORY-42'));
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe('getBranchName', () => {
|
|
82
|
-
it('should return correct branch name for story', () => {
|
|
83
|
-
const branch = manager.getBranchName('STORY-42');
|
|
84
|
-
expect(branch).toBe('auto-claude/STORY-42');
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
describe('exists', () => {
|
|
89
|
-
it('should return false for non-existent worktree', async () => {
|
|
90
|
-
const exists = await manager.exists('NONEXISTENT');
|
|
91
|
-
expect(exists).toBe(false);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('create', () => {
|
|
96
|
-
it('should create worktree with correct structure', async () => {
|
|
97
|
-
try {
|
|
98
|
-
const info = await manager.create('STORY-42');
|
|
99
|
-
|
|
100
|
-
expect(info).not.toBeNull();
|
|
101
|
-
expect(info.storyId).toBe('STORY-42');
|
|
102
|
-
expect(info.branch).toBe('auto-claude/STORY-42');
|
|
103
|
-
expect(info.status).toBe('active');
|
|
104
|
-
expect(info.uncommittedChanges).toBe(0);
|
|
105
|
-
|
|
106
|
-
// Verify directory was created
|
|
107
|
-
const exists = await manager.exists('STORY-42');
|
|
108
|
-
expect(exists).toBe(true);
|
|
109
|
-
} catch (error) {
|
|
110
|
-
// Git might not be available in CI
|
|
111
|
-
if (error.message.includes('Git command failed')) {
|
|
112
|
-
console.warn('Git not available, skipping test');
|
|
113
|
-
expect(true).toBe(true);
|
|
114
|
-
} else {
|
|
115
|
-
throw error;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should throw error if worktree already exists', async () => {
|
|
121
|
-
try {
|
|
122
|
-
await manager.create('STORY-42');
|
|
123
|
-
await expect(manager.create('STORY-42')).rejects.toThrow(
|
|
124
|
-
"Worktree for story 'STORY-42' already exists",
|
|
125
|
-
);
|
|
126
|
-
} catch (error) {
|
|
127
|
-
if (error.message.includes('Git command failed')) {
|
|
128
|
-
console.warn('Git not available, skipping test');
|
|
129
|
-
expect(true).toBe(true);
|
|
130
|
-
} else if (!error.message.includes('already exists')) {
|
|
131
|
-
throw error;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('should throw error when max worktrees reached', async () => {
|
|
137
|
-
try {
|
|
138
|
-
// Create max worktrees (3 in test config)
|
|
139
|
-
await manager.create('STORY-1');
|
|
140
|
-
await manager.create('STORY-2');
|
|
141
|
-
await manager.create('STORY-3');
|
|
142
|
-
|
|
143
|
-
// Fourth should fail
|
|
144
|
-
await expect(manager.create('STORY-4')).rejects.toThrow(
|
|
145
|
-
'Maximum worktrees limit (3) reached',
|
|
146
|
-
);
|
|
147
|
-
} catch (error) {
|
|
148
|
-
if (error.message.includes('Git command failed')) {
|
|
149
|
-
console.warn('Git not available, skipping test');
|
|
150
|
-
expect(true).toBe(true);
|
|
151
|
-
} else if (!error.message.includes('Maximum worktrees limit')) {
|
|
152
|
-
throw error;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
describe('list', () => {
|
|
159
|
-
it('should return empty array when no worktrees', async () => {
|
|
160
|
-
try {
|
|
161
|
-
const worktrees = await manager.list();
|
|
162
|
-
expect(worktrees).toEqual([]);
|
|
163
|
-
} catch (error) {
|
|
164
|
-
if (error.message.includes('Git command failed')) {
|
|
165
|
-
console.warn('Git not available, skipping test');
|
|
166
|
-
expect(true).toBe(true);
|
|
167
|
-
} else {
|
|
168
|
-
throw error;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('should return created worktrees', async () => {
|
|
174
|
-
try {
|
|
175
|
-
await manager.create('STORY-1');
|
|
176
|
-
await manager.create('STORY-2');
|
|
177
|
-
|
|
178
|
-
const worktrees = await manager.list();
|
|
179
|
-
expect(worktrees.length).toBe(2);
|
|
180
|
-
expect(worktrees.map((w) => w.storyId).sort()).toEqual(['STORY-1', 'STORY-2']);
|
|
181
|
-
} catch (error) {
|
|
182
|
-
if (error.message.includes('Git command failed')) {
|
|
183
|
-
console.warn('Git not available, skipping test');
|
|
184
|
-
expect(true).toBe(true);
|
|
185
|
-
} else {
|
|
186
|
-
throw error;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
describe('get', () => {
|
|
193
|
-
it('should return null for non-existent worktree', async () => {
|
|
194
|
-
const info = await manager.get('NONEXISTENT');
|
|
195
|
-
expect(info).toBeNull();
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it('should return worktree info for existing worktree', async () => {
|
|
199
|
-
try {
|
|
200
|
-
await manager.create('STORY-42');
|
|
201
|
-
const info = await manager.get('STORY-42');
|
|
202
|
-
|
|
203
|
-
expect(info).not.toBeNull();
|
|
204
|
-
expect(info.storyId).toBe('STORY-42');
|
|
205
|
-
expect(info.branch).toBe('auto-claude/STORY-42');
|
|
206
|
-
} catch (error) {
|
|
207
|
-
if (error.message.includes('Git command failed')) {
|
|
208
|
-
console.warn('Git not available, skipping test');
|
|
209
|
-
expect(true).toBe(true);
|
|
210
|
-
} else {
|
|
211
|
-
throw error;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
describe('remove', () => {
|
|
218
|
-
it('should throw error for non-existent worktree', async () => {
|
|
219
|
-
await expect(manager.remove('NONEXISTENT')).rejects.toThrow(
|
|
220
|
-
"Worktree for story 'NONEXISTENT' does not exist",
|
|
221
|
-
);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it('should remove existing worktree', async () => {
|
|
225
|
-
try {
|
|
226
|
-
await manager.create('STORY-42');
|
|
227
|
-
expect(await manager.exists('STORY-42')).toBe(true);
|
|
228
|
-
|
|
229
|
-
await manager.remove('STORY-42');
|
|
230
|
-
expect(await manager.exists('STORY-42')).toBe(false);
|
|
231
|
-
} catch (error) {
|
|
232
|
-
if (error.message.includes('Git command failed')) {
|
|
233
|
-
console.warn('Git not available, skipping test');
|
|
234
|
-
expect(true).toBe(true);
|
|
235
|
-
} else {
|
|
236
|
-
throw error;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
describe('getCount', () => {
|
|
243
|
-
it('should return correct counts', async () => {
|
|
244
|
-
try {
|
|
245
|
-
const count = await manager.getCount();
|
|
246
|
-
expect(count.total).toBe(0);
|
|
247
|
-
expect(count.active).toBe(0);
|
|
248
|
-
expect(count.stale).toBe(0);
|
|
249
|
-
|
|
250
|
-
await manager.create('STORY-1');
|
|
251
|
-
await manager.create('STORY-2');
|
|
252
|
-
|
|
253
|
-
const count2 = await manager.getCount();
|
|
254
|
-
expect(count2.total).toBe(2);
|
|
255
|
-
expect(count2.active).toBe(2);
|
|
256
|
-
expect(count2.stale).toBe(0);
|
|
257
|
-
} catch (error) {
|
|
258
|
-
if (error.message.includes('Git command failed')) {
|
|
259
|
-
console.warn('Git not available, skipping test');
|
|
260
|
-
expect(true).toBe(true);
|
|
261
|
-
} else {
|
|
262
|
-
throw error;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
describe('formatList', () => {
|
|
269
|
-
it('should return empty message for no worktrees', () => {
|
|
270
|
-
const output = manager.formatList([]);
|
|
271
|
-
expect(output).toContain('No active worktrees');
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it('should format worktrees correctly', () => {
|
|
275
|
-
const worktrees = [
|
|
276
|
-
{
|
|
277
|
-
storyId: 'STORY-42',
|
|
278
|
-
path: '/test/path',
|
|
279
|
-
branch: 'auto-claude/STORY-42',
|
|
280
|
-
createdAt: new Date(),
|
|
281
|
-
uncommittedChanges: 3,
|
|
282
|
-
status: 'active',
|
|
283
|
-
},
|
|
284
|
-
];
|
|
285
|
-
|
|
286
|
-
const output = manager.formatList(worktrees);
|
|
287
|
-
expect(output).toContain('STORY-42');
|
|
288
|
-
expect(output).toContain('auto-claude/STORY-42');
|
|
289
|
-
expect(output).toContain('3 uncommitted');
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
describe('formatAge', () => {
|
|
294
|
-
it('should format just now correctly', () => {
|
|
295
|
-
const age = manager.formatAge(new Date());
|
|
296
|
-
expect(age).toBe('just now');
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
it('should format hours correctly', () => {
|
|
300
|
-
const twoHoursAgo = new Date(Date.now() - 2 * 60 * 60 * 1000);
|
|
301
|
-
const age = manager.formatAge(twoHoursAgo);
|
|
302
|
-
expect(age).toBe('2h ago');
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it('should format days correctly', () => {
|
|
306
|
-
const threeDaysAgo = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000);
|
|
307
|
-
const age = manager.formatAge(threeDaysAgo);
|
|
308
|
-
expect(age).toBe('3d ago');
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
describe('detectConflicts', () => {
|
|
313
|
-
it('should throw error for non-existent worktree', async () => {
|
|
314
|
-
await expect(manager.detectConflicts('NONEXISTENT')).rejects.toThrow(
|
|
315
|
-
"Worktree for story 'NONEXISTENT' does not exist",
|
|
316
|
-
);
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
it('should return empty array when no conflicts', async () => {
|
|
320
|
-
try {
|
|
321
|
-
await manager.create('STORY-42');
|
|
322
|
-
|
|
323
|
-
// Make a change in worktree without conflicting
|
|
324
|
-
const execa = require('execa');
|
|
325
|
-
const worktreePath = manager.getWorktreePath('STORY-42');
|
|
326
|
-
const newFile = path.join(worktreePath, 'new-file.txt');
|
|
327
|
-
await fs.writeFile(newFile, 'New content');
|
|
328
|
-
await execa('git', ['add', '.'], { cwd: worktreePath });
|
|
329
|
-
await execa('git', ['commit', '-m', 'Add new file'], { cwd: worktreePath });
|
|
330
|
-
|
|
331
|
-
const conflicts = await manager.detectConflicts('STORY-42');
|
|
332
|
-
expect(conflicts).toEqual([]);
|
|
333
|
-
} catch (error) {
|
|
334
|
-
if (error.message.includes('Git command failed')) {
|
|
335
|
-
console.warn('Git not available, skipping test');
|
|
336
|
-
expect(true).toBe(true);
|
|
337
|
-
} else {
|
|
338
|
-
throw error;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
it('should detect conflicting files', async () => {
|
|
344
|
-
try {
|
|
345
|
-
await manager.create('STORY-42');
|
|
346
|
-
const execa = require('execa');
|
|
347
|
-
const worktreePath = manager.getWorktreePath('STORY-42');
|
|
348
|
-
|
|
349
|
-
// Modify README.md in worktree
|
|
350
|
-
const wtReadme = path.join(worktreePath, 'README.md');
|
|
351
|
-
await fs.writeFile(wtReadme, '# Modified in worktree');
|
|
352
|
-
await execa('git', ['add', '.'], { cwd: worktreePath });
|
|
353
|
-
await execa('git', ['commit', '-m', 'Modify README in worktree'], { cwd: worktreePath });
|
|
354
|
-
|
|
355
|
-
// Modify README.md in main
|
|
356
|
-
const mainReadme = path.join(testRoot, 'README.md');
|
|
357
|
-
await fs.writeFile(mainReadme, '# Modified in main');
|
|
358
|
-
await execa('git', ['add', '.'], { cwd: testRoot });
|
|
359
|
-
await execa('git', ['commit', '-m', 'Modify README in main'], { cwd: testRoot });
|
|
360
|
-
|
|
361
|
-
const conflicts = await manager.detectConflicts('STORY-42');
|
|
362
|
-
expect(conflicts).toContain('README.md');
|
|
363
|
-
} catch (error) {
|
|
364
|
-
if (error.message.includes('Git command failed')) {
|
|
365
|
-
console.warn('Git not available, skipping test');
|
|
366
|
-
expect(true).toBe(true);
|
|
367
|
-
} else {
|
|
368
|
-
throw error;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
describe('mergeToBase', () => {
|
|
375
|
-
it('should throw error for non-existent worktree', async () => {
|
|
376
|
-
await expect(manager.mergeToBase('NONEXISTENT')).rejects.toThrow(
|
|
377
|
-
"Worktree for story 'NONEXISTENT' does not exist",
|
|
378
|
-
);
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
it('should merge worktree successfully', async () => {
|
|
382
|
-
try {
|
|
383
|
-
await manager.create('STORY-42');
|
|
384
|
-
const execa = require('execa');
|
|
385
|
-
const worktreePath = manager.getWorktreePath('STORY-42');
|
|
386
|
-
|
|
387
|
-
// Make a non-conflicting change
|
|
388
|
-
const newFile = path.join(worktreePath, 'feature.txt');
|
|
389
|
-
await fs.writeFile(newFile, 'New feature content');
|
|
390
|
-
await execa('git', ['add', '.'], { cwd: worktreePath });
|
|
391
|
-
await execa('git', ['commit', '-m', 'Add feature'], { cwd: worktreePath });
|
|
392
|
-
|
|
393
|
-
const result = await manager.mergeToBase('STORY-42');
|
|
394
|
-
|
|
395
|
-
expect(result.success).toBe(true);
|
|
396
|
-
expect(result.storyId).toBe('STORY-42');
|
|
397
|
-
expect(result.sourceBranch).toBe('auto-claude/STORY-42');
|
|
398
|
-
expect(result.conflicts).toEqual([]);
|
|
399
|
-
expect(result.commitHash).toBeDefined();
|
|
400
|
-
expect(result.logPath).toBeDefined();
|
|
401
|
-
|
|
402
|
-
// Verify file exists in main after merge
|
|
403
|
-
const mainFile = path.join(testRoot, 'feature.txt');
|
|
404
|
-
const exists = await fs
|
|
405
|
-
.access(mainFile)
|
|
406
|
-
.then(() => true)
|
|
407
|
-
.catch(() => false);
|
|
408
|
-
expect(exists).toBe(true);
|
|
409
|
-
} catch (error) {
|
|
410
|
-
if (error.message.includes('Git command failed')) {
|
|
411
|
-
console.warn('Git not available, skipping test');
|
|
412
|
-
expect(true).toBe(true);
|
|
413
|
-
} else {
|
|
414
|
-
throw error;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
it('should support staged option (--no-commit)', async () => {
|
|
420
|
-
try {
|
|
421
|
-
await manager.create('STORY-42');
|
|
422
|
-
const execa = require('execa');
|
|
423
|
-
const worktreePath = manager.getWorktreePath('STORY-42');
|
|
424
|
-
|
|
425
|
-
const newFile = path.join(worktreePath, 'staged-feature.txt');
|
|
426
|
-
await fs.writeFile(newFile, 'Staged feature');
|
|
427
|
-
await execa('git', ['add', '.'], { cwd: worktreePath });
|
|
428
|
-
await execa('git', ['commit', '-m', 'Add staged feature'], { cwd: worktreePath });
|
|
429
|
-
|
|
430
|
-
// Get HEAD before merge
|
|
431
|
-
const headBefore = await execa('git', ['rev-parse', 'HEAD'], { cwd: testRoot });
|
|
432
|
-
|
|
433
|
-
const result = await manager.mergeToBase('STORY-42', { staged: true });
|
|
434
|
-
|
|
435
|
-
expect(result.success).toBe(true);
|
|
436
|
-
expect(result.commitHash).toBeUndefined(); // Not committed
|
|
437
|
-
|
|
438
|
-
// Get HEAD after merge - should be same (no commit was made)
|
|
439
|
-
const headAfter = await execa('git', ['rev-parse', 'HEAD'], { cwd: testRoot });
|
|
440
|
-
expect(headAfter.stdout).toBe(headBefore.stdout);
|
|
441
|
-
|
|
442
|
-
// Verify the file was merged to working tree
|
|
443
|
-
const mainFile = path.join(testRoot, 'staged-feature.txt');
|
|
444
|
-
const fileExists = await fs
|
|
445
|
-
.access(mainFile)
|
|
446
|
-
.then(() => true)
|
|
447
|
-
.catch(() => false);
|
|
448
|
-
expect(fileExists).toBe(true);
|
|
449
|
-
|
|
450
|
-
// Clean up merge state for next tests
|
|
451
|
-
await execa('git', ['merge', '--abort'], { cwd: testRoot }).catch(() => {
|
|
452
|
-
return execa('git', ['reset', '--hard', 'HEAD'], { cwd: testRoot });
|
|
453
|
-
});
|
|
454
|
-
} catch (error) {
|
|
455
|
-
if (error.message.includes('Git command failed')) {
|
|
456
|
-
console.warn('Git not available, skipping test');
|
|
457
|
-
expect(true).toBe(true);
|
|
458
|
-
} else {
|
|
459
|
-
throw error;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
it('should support squash option', async () => {
|
|
465
|
-
try {
|
|
466
|
-
await manager.create('STORY-42');
|
|
467
|
-
const execa = require('execa');
|
|
468
|
-
const worktreePath = manager.getWorktreePath('STORY-42');
|
|
469
|
-
|
|
470
|
-
// Make multiple commits
|
|
471
|
-
for (let i = 1; i <= 3; i++) {
|
|
472
|
-
const file = path.join(worktreePath, `file${i}.txt`);
|
|
473
|
-
await fs.writeFile(file, `Content ${i}`);
|
|
474
|
-
await execa('git', ['add', '.'], { cwd: worktreePath });
|
|
475
|
-
await execa('git', ['commit', '-m', `Commit ${i}`], { cwd: worktreePath });
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
const result = await manager.mergeToBase('STORY-42', { squash: true });
|
|
479
|
-
|
|
480
|
-
expect(result.success).toBe(true);
|
|
481
|
-
expect(result.commitHash).toBeDefined();
|
|
482
|
-
} catch (error) {
|
|
483
|
-
if (error.message.includes('Git command failed')) {
|
|
484
|
-
console.warn('Git not available, skipping test');
|
|
485
|
-
expect(true).toBe(true);
|
|
486
|
-
} else {
|
|
487
|
-
throw error;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
it('should fail gracefully on conflicts', async () => {
|
|
493
|
-
try {
|
|
494
|
-
await manager.create('STORY-42');
|
|
495
|
-
const execa = require('execa');
|
|
496
|
-
const worktreePath = manager.getWorktreePath('STORY-42');
|
|
497
|
-
|
|
498
|
-
// Create conflict
|
|
499
|
-
const wtReadme = path.join(worktreePath, 'README.md');
|
|
500
|
-
await fs.writeFile(wtReadme, '# Worktree version');
|
|
501
|
-
await execa('git', ['add', '.'], { cwd: worktreePath });
|
|
502
|
-
await execa('git', ['commit', '-m', 'Modify in worktree'], { cwd: worktreePath });
|
|
503
|
-
|
|
504
|
-
const mainReadme = path.join(testRoot, 'README.md');
|
|
505
|
-
await fs.writeFile(mainReadme, '# Main version');
|
|
506
|
-
await execa('git', ['add', '.'], { cwd: testRoot });
|
|
507
|
-
await execa('git', ['commit', '-m', 'Modify in main'], { cwd: testRoot });
|
|
508
|
-
|
|
509
|
-
const result = await manager.mergeToBase('STORY-42');
|
|
510
|
-
|
|
511
|
-
expect(result.success).toBe(false);
|
|
512
|
-
expect(result.conflicts.length).toBeGreaterThan(0);
|
|
513
|
-
expect(result.error).toContain('conflict');
|
|
514
|
-
} catch (error) {
|
|
515
|
-
if (error.message.includes('Git command failed')) {
|
|
516
|
-
console.warn('Git not available, skipping test');
|
|
517
|
-
expect(true).toBe(true);
|
|
518
|
-
} else {
|
|
519
|
-
throw error;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
it('should cleanup worktree after merge when cleanup option is true', async () => {
|
|
525
|
-
try {
|
|
526
|
-
await manager.create('STORY-42');
|
|
527
|
-
const execa = require('execa');
|
|
528
|
-
const worktreePath = manager.getWorktreePath('STORY-42');
|
|
529
|
-
|
|
530
|
-
const newFile = path.join(worktreePath, 'cleanup-test.txt');
|
|
531
|
-
await fs.writeFile(newFile, 'Cleanup test');
|
|
532
|
-
await execa('git', ['add', '.'], { cwd: worktreePath });
|
|
533
|
-
await execa('git', ['commit', '-m', 'Add cleanup test'], { cwd: worktreePath });
|
|
534
|
-
|
|
535
|
-
const result = await manager.mergeToBase('STORY-42', { cleanup: true });
|
|
536
|
-
|
|
537
|
-
expect(result.success).toBe(true);
|
|
538
|
-
expect(await manager.exists('STORY-42')).toBe(false);
|
|
539
|
-
} catch (error) {
|
|
540
|
-
if (error.message.includes('Git command failed')) {
|
|
541
|
-
console.warn('Git not available, skipping test');
|
|
542
|
-
expect(true).toBe(true);
|
|
543
|
-
} else {
|
|
544
|
-
throw error;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
});
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
describe('getMergeHistory', () => {
|
|
551
|
-
it('should return empty array when no merge history', async () => {
|
|
552
|
-
const history = await manager.getMergeHistory();
|
|
553
|
-
expect(history).toEqual([]);
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
it('should return merge history after merge', async () => {
|
|
557
|
-
try {
|
|
558
|
-
await manager.create('STORY-42');
|
|
559
|
-
const execa = require('execa');
|
|
560
|
-
const worktreePath = manager.getWorktreePath('STORY-42');
|
|
561
|
-
|
|
562
|
-
const newFile = path.join(worktreePath, 'history-test.txt');
|
|
563
|
-
await fs.writeFile(newFile, 'History test');
|
|
564
|
-
await execa('git', ['add', '.'], { cwd: worktreePath });
|
|
565
|
-
await execa('git', ['commit', '-m', 'Add history test'], { cwd: worktreePath });
|
|
566
|
-
|
|
567
|
-
await manager.mergeToBase('STORY-42');
|
|
568
|
-
|
|
569
|
-
const history = await manager.getMergeHistory();
|
|
570
|
-
expect(history.length).toBe(1);
|
|
571
|
-
expect(history[0].storyId).toBe('STORY-42');
|
|
572
|
-
expect(history[0].success).toBe(true);
|
|
573
|
-
} catch (error) {
|
|
574
|
-
if (error.message.includes('Git command failed')) {
|
|
575
|
-
console.warn('Git not available, skipping test');
|
|
576
|
-
expect(true).toBe(true);
|
|
577
|
-
} else {
|
|
578
|
-
throw error;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
it('should filter by storyId', async () => {
|
|
584
|
-
try {
|
|
585
|
-
await manager.create('STORY-42');
|
|
586
|
-
await manager.create('STORY-43');
|
|
587
|
-
const execa = require('execa');
|
|
588
|
-
|
|
589
|
-
// Merge STORY-42
|
|
590
|
-
const wt42 = manager.getWorktreePath('STORY-42');
|
|
591
|
-
await fs.writeFile(path.join(wt42, 'file42.txt'), 'Content 42');
|
|
592
|
-
await execa('git', ['add', '.'], { cwd: wt42 });
|
|
593
|
-
await execa('git', ['commit', '-m', 'Add file 42'], { cwd: wt42 });
|
|
594
|
-
await manager.mergeToBase('STORY-42');
|
|
595
|
-
|
|
596
|
-
// Merge STORY-43
|
|
597
|
-
const wt43 = manager.getWorktreePath('STORY-43');
|
|
598
|
-
await fs.writeFile(path.join(wt43, 'file43.txt'), 'Content 43');
|
|
599
|
-
await execa('git', ['add', '.'], { cwd: wt43 });
|
|
600
|
-
await execa('git', ['commit', '-m', 'Add file 43'], { cwd: wt43 });
|
|
601
|
-
await manager.mergeToBase('STORY-43');
|
|
602
|
-
|
|
603
|
-
const allHistory = await manager.getMergeHistory();
|
|
604
|
-
expect(allHistory.length).toBe(2);
|
|
605
|
-
|
|
606
|
-
const story42History = await manager.getMergeHistory('STORY-42');
|
|
607
|
-
expect(story42History.length).toBe(1);
|
|
608
|
-
expect(story42History[0].storyId).toBe('STORY-42');
|
|
609
|
-
} catch (error) {
|
|
610
|
-
if (error.message.includes('Git command failed')) {
|
|
611
|
-
console.warn('Git not available, skipping test');
|
|
612
|
-
expect(true).toBe(true);
|
|
613
|
-
} else {
|
|
614
|
-
throw error;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
});
|
|
619
|
-
});
|
|
620
|
-
|