@fixy/core 0.0.0

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 (97) hide show
  1. package/dist/__tests__/diff-parser.test.d.ts +2 -0
  2. package/dist/__tests__/diff-parser.test.d.ts.map +1 -0
  3. package/dist/__tests__/diff-parser.test.js +89 -0
  4. package/dist/__tests__/diff-parser.test.js.map +1 -0
  5. package/dist/__tests__/fixy-commands.test.d.ts +2 -0
  6. package/dist/__tests__/fixy-commands.test.d.ts.map +1 -0
  7. package/dist/__tests__/fixy-commands.test.js +176 -0
  8. package/dist/__tests__/fixy-commands.test.js.map +1 -0
  9. package/dist/__tests__/registry.test.d.ts +2 -0
  10. package/dist/__tests__/registry.test.d.ts.map +1 -0
  11. package/dist/__tests__/registry.test.js +66 -0
  12. package/dist/__tests__/registry.test.js.map +1 -0
  13. package/dist/__tests__/router.test.d.ts +2 -0
  14. package/dist/__tests__/router.test.d.ts.map +1 -0
  15. package/dist/__tests__/router.test.js +77 -0
  16. package/dist/__tests__/router.test.js.map +1 -0
  17. package/dist/__tests__/smoke.test.d.ts +2 -0
  18. package/dist/__tests__/smoke.test.d.ts.map +1 -0
  19. package/dist/__tests__/smoke.test.js +7 -0
  20. package/dist/__tests__/smoke.test.js.map +1 -0
  21. package/dist/__tests__/store.test.d.ts +2 -0
  22. package/dist/__tests__/store.test.d.ts.map +1 -0
  23. package/dist/__tests__/store.test.js +121 -0
  24. package/dist/__tests__/store.test.js.map +1 -0
  25. package/dist/__tests__/turn.test.d.ts +2 -0
  26. package/dist/__tests__/turn.test.d.ts.map +1 -0
  27. package/dist/__tests__/turn.test.js +194 -0
  28. package/dist/__tests__/turn.test.js.map +1 -0
  29. package/dist/__tests__/worktree.test.d.ts +2 -0
  30. package/dist/__tests__/worktree.test.d.ts.map +1 -0
  31. package/dist/__tests__/worktree.test.js +119 -0
  32. package/dist/__tests__/worktree.test.js.map +1 -0
  33. package/dist/adapter.d.ts +75 -0
  34. package/dist/adapter.d.ts.map +1 -0
  35. package/dist/adapter.js +3 -0
  36. package/dist/adapter.js.map +1 -0
  37. package/dist/diff-parser.d.ts +3 -0
  38. package/dist/diff-parser.d.ts.map +1 -0
  39. package/dist/diff-parser.js +38 -0
  40. package/dist/diff-parser.js.map +1 -0
  41. package/dist/fixy-commands.d.ts +25 -0
  42. package/dist/fixy-commands.d.ts.map +1 -0
  43. package/dist/fixy-commands.js +154 -0
  44. package/dist/fixy-commands.js.map +1 -0
  45. package/dist/index.d.ts +15 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +9 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/paths.d.ts +17 -0
  50. package/dist/paths.d.ts.map +1 -0
  51. package/dist/paths.js +36 -0
  52. package/dist/paths.js.map +1 -0
  53. package/dist/registry.d.ts +12 -0
  54. package/dist/registry.d.ts.map +1 -0
  55. package/dist/registry.js +32 -0
  56. package/dist/registry.js.map +1 -0
  57. package/dist/router.d.ts +21 -0
  58. package/dist/router.d.ts.map +1 -0
  59. package/dist/router.js +36 -0
  60. package/dist/router.js.map +1 -0
  61. package/dist/store.d.ts +36 -0
  62. package/dist/store.d.ts.map +1 -0
  63. package/dist/store.js +127 -0
  64. package/dist/store.js.map +1 -0
  65. package/dist/thread.d.ts +47 -0
  66. package/dist/thread.d.ts.map +1 -0
  67. package/dist/thread.js +3 -0
  68. package/dist/thread.js.map +1 -0
  69. package/dist/turn.d.ts +20 -0
  70. package/dist/turn.d.ts.map +1 -0
  71. package/dist/turn.js +130 -0
  72. package/dist/turn.js.map +1 -0
  73. package/dist/worktree.d.ts +36 -0
  74. package/dist/worktree.d.ts.map +1 -0
  75. package/dist/worktree.js +91 -0
  76. package/dist/worktree.js.map +1 -0
  77. package/package.json +21 -0
  78. package/src/__tests__/diff-parser.test.ts +99 -0
  79. package/src/__tests__/fixy-commands.test.ts +231 -0
  80. package/src/__tests__/registry.test.ts +79 -0
  81. package/src/__tests__/router.test.ts +91 -0
  82. package/src/__tests__/smoke.test.ts +7 -0
  83. package/src/__tests__/store.test.ts +151 -0
  84. package/src/__tests__/turn.test.ts +266 -0
  85. package/src/__tests__/worktree.test.ts +155 -0
  86. package/src/adapter.ts +84 -0
  87. package/src/diff-parser.ts +46 -0
  88. package/src/fixy-commands.ts +201 -0
  89. package/src/index.ts +40 -0
  90. package/src/paths.ts +43 -0
  91. package/src/registry.ts +40 -0
  92. package/src/router.ts +49 -0
  93. package/src/store.ts +164 -0
  94. package/src/thread.ts +50 -0
  95. package/src/turn.ts +165 -0
  96. package/src/worktree.ts +119 -0
  97. package/tsconfig.json +9 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=diff-parser.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-parser.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/diff-parser.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,89 @@
1
+ // packages/core/src/__tests__/diff-parser.test.ts
2
+ import { describe, it, expect } from 'vitest';
3
+ import { parseUnifiedDiff } from '../diff-parser.js';
4
+ describe('parseUnifiedDiff', () => {
5
+ it('parses a single file diff correctly', () => {
6
+ const diff = `diff --git a/src/index.ts b/src/index.ts
7
+ index abc1234..def5678 100644
8
+ --- a/src/index.ts
9
+ +++ b/src/index.ts
10
+ @@ -1,3 +1,4 @@
11
+ import { foo } from './foo';
12
+ +import { bar } from './bar';
13
+
14
+ export function main() {
15
+ - return foo();
16
+ + return bar(foo());
17
+ }
18
+ `;
19
+ const patches = parseUnifiedDiff(diff, '/tmp/worktree');
20
+ expect(patches).toHaveLength(1);
21
+ expect(patches[0].relativePath).toBe('src/index.ts');
22
+ expect(patches[0].filePath).toBe('/tmp/worktree/src/index.ts');
23
+ expect(patches[0].stats.additions).toBe(2);
24
+ expect(patches[0].stats.deletions).toBe(1);
25
+ });
26
+ it('parses a multi-file diff correctly', () => {
27
+ const diff = `diff --git a/file1.ts b/file1.ts
28
+ index abc..def 100644
29
+ --- a/file1.ts
30
+ +++ b/file1.ts
31
+ @@ -1,2 +1,3 @@
32
+ const a = 1;
33
+ +const b = 2;
34
+ export { a };
35
+ diff --git a/src/file2.ts b/src/file2.ts
36
+ index 111..222 100644
37
+ --- a/src/file2.ts
38
+ +++ b/src/file2.ts
39
+ @@ -1,3 +1,2 @@
40
+ const x = 1;
41
+ -const y = 2;
42
+ export { x };
43
+ `;
44
+ const patches = parseUnifiedDiff(diff, '/tmp/worktree');
45
+ expect(patches).toHaveLength(2);
46
+ expect(patches[0].relativePath).toBe('file1.ts');
47
+ expect(patches[0].stats.additions).toBe(1);
48
+ expect(patches[0].stats.deletions).toBe(0);
49
+ expect(patches[1].relativePath).toBe('src/file2.ts');
50
+ expect(patches[1].stats.additions).toBe(0);
51
+ expect(patches[1].stats.deletions).toBe(1);
52
+ });
53
+ it('returns an empty array for an empty diff', () => {
54
+ expect(parseUnifiedDiff('', '/tmp/worktree')).toEqual([]);
55
+ expect(parseUnifiedDiff(' \n \n', '/tmp/worktree')).toEqual([]);
56
+ });
57
+ it('parses a new file diff with only additions', () => {
58
+ const diff = `diff --git a/new-file.ts b/new-file.ts
59
+ new file mode 100644
60
+ index 0000000..abc1234
61
+ --- /dev/null
62
+ +++ b/new-file.ts
63
+ @@ -0,0 +1,3 @@
64
+ +const a = 1;
65
+ +const b = 2;
66
+ +export { a, b };
67
+ `;
68
+ const patches = parseUnifiedDiff(diff, '/tmp/worktree');
69
+ expect(patches).toHaveLength(1);
70
+ expect(patches[0].stats.additions).toBe(3);
71
+ expect(patches[0].stats.deletions).toBe(0);
72
+ });
73
+ it('parses a deleted file diff with only deletions', () => {
74
+ const diff = `diff --git a/removed.ts b/removed.ts
75
+ deleted file mode 100644
76
+ index abc1234..0000000
77
+ --- a/removed.ts
78
+ +++ /dev/null
79
+ @@ -1,2 +0,0 @@
80
+ -const old = true;
81
+ -export { old };
82
+ `;
83
+ const patches = parseUnifiedDiff(diff, '/tmp/worktree');
84
+ expect(patches).toHaveLength(1);
85
+ expect(patches[0].stats.additions).toBe(0);
86
+ expect(patches[0].stats.deletions).toBe(2);
87
+ });
88
+ });
89
+ //# sourceMappingURL=diff-parser.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-parser.test.js","sourceRoot":"","sources":["../../src/__tests__/diff-parser.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fixy-commands.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixy-commands.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/fixy-commands.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,176 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { FixyCommandRunner } from '../fixy-commands.js';
6
+ import { AdapterRegistry } from '../registry.js';
7
+ import { LocalThreadStore } from '../store.js';
8
+ import { getThreadFile } from '../paths.js';
9
+ // ---------------------------------------------------------------------------
10
+ // Helpers
11
+ // ---------------------------------------------------------------------------
12
+ function createStubAdapter(id, name, executeFn) {
13
+ return {
14
+ id,
15
+ name,
16
+ probe: async () => ({
17
+ available: true,
18
+ version: '1.0.0',
19
+ authStatus: 'ok',
20
+ detail: null,
21
+ }),
22
+ execute: executeFn ??
23
+ (async () => ({
24
+ exitCode: 0,
25
+ signal: null,
26
+ timedOut: false,
27
+ summary: `response from ${id}`,
28
+ session: null,
29
+ patches: [],
30
+ warnings: [],
31
+ errorMessage: null,
32
+ })),
33
+ };
34
+ }
35
+ const stubWorktreeManager = {
36
+ ensure: vi.fn(async () => ({ path: '', branch: '', agentId: '', threadId: '' })),
37
+ collectPatches: vi.fn(async () => []),
38
+ reset: vi.fn(async () => { }),
39
+ remove: vi.fn(async () => { }),
40
+ list: vi.fn(async () => []),
41
+ };
42
+ // ---------------------------------------------------------------------------
43
+ // Suite
44
+ // ---------------------------------------------------------------------------
45
+ describe('FixyCommandRunner', () => {
46
+ let tmpDir;
47
+ let store;
48
+ let thread;
49
+ let registry;
50
+ let runner;
51
+ beforeEach(async () => {
52
+ tmpDir = await mkdtemp(join(tmpdir(), 'fixy-cmd-test-'));
53
+ process.env['FIXY_HOME'] = tmpDir;
54
+ store = new LocalThreadStore();
55
+ await store.init();
56
+ thread = await store.createThread('/tmp/fake-project');
57
+ registry = new AdapterRegistry();
58
+ runner = new FixyCommandRunner();
59
+ // Reset all vi.fn() call history between tests
60
+ vi.clearAllMocks();
61
+ });
62
+ afterEach(async () => {
63
+ delete process.env['FIXY_HOME'];
64
+ await rm(tmpDir, { recursive: true, force: true });
65
+ });
66
+ function makeCtx(overrides) {
67
+ return {
68
+ thread,
69
+ rest: '',
70
+ store,
71
+ registry,
72
+ worktreeManager: stubWorktreeManager,
73
+ onLog: () => { },
74
+ signal: AbortSignal.timeout(5000),
75
+ ...overrides,
76
+ };
77
+ }
78
+ // -------------------------------------------------------------------------
79
+ // Test 1: /worker <id> — persists workerModel change
80
+ // -------------------------------------------------------------------------
81
+ it('/worker codex — persists workerModel change and appends system message', async () => {
82
+ registry.register(createStubAdapter('claude', 'Claude'));
83
+ registry.register(createStubAdapter('codex', 'Codex'));
84
+ expect(thread.workerModel).toBe('claude');
85
+ await runner.run(makeCtx({ rest: '/worker codex' }));
86
+ const fresh = await store.getThread(thread.id, thread.projectRoot);
87
+ expect(fresh.workerModel).toBe('codex');
88
+ const sysMsg = fresh.messages.find((m) => m.role === 'system' && m.content === 'worker set to codex');
89
+ expect(sysMsg).toBeDefined();
90
+ });
91
+ // -------------------------------------------------------------------------
92
+ // Test 2: /worker unknown — throws for unknown adapter
93
+ // -------------------------------------------------------------------------
94
+ it('/worker unknown — throws for unknown adapter', async () => {
95
+ registry.register(createStubAdapter('claude', 'Claude'));
96
+ await expect(runner.run(makeCtx({ rest: '/worker unknown' }))).rejects.toThrow(/Unknown adapter/);
97
+ });
98
+ // -------------------------------------------------------------------------
99
+ // Test 3: /all build something — returns stub message
100
+ // -------------------------------------------------------------------------
101
+ it('/all build something — returns stub collaboration message', async () => {
102
+ await runner.run(makeCtx({ rest: '/all build something' }));
103
+ const fresh = await store.getThread(thread.id, thread.projectRoot);
104
+ const sysMsg = fresh.messages.find((m) => m.role === 'system' && m.content.includes('collaboration engine not yet implemented'));
105
+ expect(sysMsg).toBeDefined();
106
+ });
107
+ // -------------------------------------------------------------------------
108
+ // Test 4: /settings — returns stub message
109
+ // -------------------------------------------------------------------------
110
+ it('/settings — returns stub message', async () => {
111
+ await runner.run(makeCtx({ rest: '/settings' }));
112
+ const fresh = await store.getThread(thread.id, thread.projectRoot);
113
+ const sysMsg = fresh.messages.find((m) => m.role === 'system' && m.content === 'settings command not yet implemented');
114
+ expect(sysMsg).toBeDefined();
115
+ });
116
+ // -------------------------------------------------------------------------
117
+ // Test 5: /reset — clears agentSessions
118
+ // -------------------------------------------------------------------------
119
+ it('/reset — clears agentSessions and appends confirmation message', async () => {
120
+ // Mutate the in-memory thread to have an active session.
121
+ thread.agentSessions = { claude: { sessionId: 'sess-1', params: {} } };
122
+ thread.worktrees = { claude: '/tmp/worktree-claude' };
123
+ // Persist the modified thread to disk so store.getThread returns it.
124
+ const threadFilePath = getThreadFile(thread.projectRoot, thread.id);
125
+ await writeFile(threadFilePath, JSON.stringify(thread, null, 2), 'utf8');
126
+ await runner.run(makeCtx({ rest: '/reset' }));
127
+ const fresh = await store.getThread(thread.id, thread.projectRoot);
128
+ expect(fresh.agentSessions).toEqual({});
129
+ const sysMsg = fresh.messages.find((m) => m.role === 'system' && m.content.includes('thread reset'));
130
+ expect(sysMsg).toBeDefined();
131
+ // Verify worktreeManager.reset was called for the claude worktree entry.
132
+ expect(stubWorktreeManager.reset).toHaveBeenCalledOnce();
133
+ });
134
+ // -------------------------------------------------------------------------
135
+ // Test 6: /status — shows adapter probe results
136
+ // -------------------------------------------------------------------------
137
+ it('/status — lists adapter probe results including current workerModel', async () => {
138
+ registry.register(createStubAdapter('claude', 'Claude'));
139
+ registry.register(createStubAdapter('codex', 'Codex'));
140
+ await runner.run(makeCtx({ rest: '/status' }));
141
+ const fresh = await store.getThread(thread.id, thread.projectRoot);
142
+ expect(fresh.messages.length).toBeGreaterThan(0);
143
+ const sysMsg = fresh.messages[fresh.messages.length - 1];
144
+ expect(sysMsg?.role).toBe('system');
145
+ const content = sysMsg?.content ?? '';
146
+ expect(content).toContain('claude');
147
+ expect(content).toContain('codex');
148
+ expect(content).toContain('yes'); // available: yes
149
+ expect(content).toContain('1.0.0'); // version
150
+ expect(content).toContain(thread.workerModel); // current workerModel line
151
+ });
152
+ // -------------------------------------------------------------------------
153
+ // Test 7: Bare @fixy prompt — routes through worker adapter
154
+ // -------------------------------------------------------------------------
155
+ it('bare prompt — routes through worker adapter and appends agent message with agentId=fixy', async () => {
156
+ const customExecute = async (_ctx) => ({
157
+ exitCode: 0,
158
+ signal: null,
159
+ timedOut: false,
160
+ summary: 'worker response here',
161
+ session: { sessionId: 'new-sess', params: {} },
162
+ patches: [],
163
+ warnings: [],
164
+ errorMessage: null,
165
+ });
166
+ registry.register(createStubAdapter('claude', 'Claude', customExecute));
167
+ thread.workerModel = 'claude';
168
+ await runner.run(makeCtx({ rest: 'explain this' }));
169
+ const fresh = await store.getThread(thread.id, thread.projectRoot);
170
+ const agentMsg = fresh.messages.find((m) => m.role === 'agent');
171
+ expect(agentMsg).toBeDefined();
172
+ expect(agentMsg?.agentId).toBe('fixy');
173
+ expect(agentMsg?.content).toBe('worker response here');
174
+ });
175
+ });
176
+ //# sourceMappingURL=fixy-commands.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixy-commands.test.js","sourceRoot":"","sources":["../../src/__tests__/fixy-commands.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=registry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/registry.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,66 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { AdapterRegistry } from '../registry.js';
3
+ function createStubAdapter(id, name) {
4
+ return {
5
+ id,
6
+ name,
7
+ probe: async () => ({
8
+ available: true,
9
+ version: '1.0.0',
10
+ authStatus: 'ok',
11
+ detail: null,
12
+ }),
13
+ execute: async () => ({
14
+ exitCode: 0,
15
+ signal: null,
16
+ timedOut: false,
17
+ summary: '',
18
+ session: null,
19
+ patches: [],
20
+ warnings: [],
21
+ errorMessage: null,
22
+ }),
23
+ };
24
+ }
25
+ describe('AdapterRegistry', () => {
26
+ let registry;
27
+ beforeEach(() => {
28
+ registry = new AdapterRegistry();
29
+ });
30
+ it('register() adds adapters and list() returns them', () => {
31
+ registry.register(createStubAdapter('claude', 'Claude'));
32
+ registry.register(createStubAdapter('codex', 'Codex'));
33
+ expect(registry.list()).toHaveLength(2);
34
+ });
35
+ it('require() returns the correct adapter', () => {
36
+ registry.register(createStubAdapter('claude', 'Claude'));
37
+ expect(registry.require('claude').name).toBe('Claude');
38
+ });
39
+ it('require() throws for unknown id', () => {
40
+ expect(() => registry.require('unknown')).toThrow('Unknown adapter: unknown');
41
+ });
42
+ it('register() throws on duplicate id', () => {
43
+ registry.register(createStubAdapter('claude', 'Claude'));
44
+ expect(() => registry.register(createStubAdapter('claude', 'Claude 2'))).toThrow('Adapter already registered: claude');
45
+ });
46
+ it('unregister() removes the adapter', () => {
47
+ registry.register(createStubAdapter('claude', 'Claude'));
48
+ registry.unregister('claude');
49
+ expect(registry.has('claude')).toBe(false);
50
+ });
51
+ it('unregister() does not throw for unknown id', () => {
52
+ expect(() => registry.unregister('nonexistent')).not.toThrow();
53
+ });
54
+ it('get() returns adapter or undefined', () => {
55
+ registry.register(createStubAdapter('claude', 'Claude'));
56
+ expect(registry.get('claude')?.id).toBe('claude');
57
+ expect(registry.get('unknown')).toBeUndefined();
58
+ });
59
+ it('clear() removes all adapters', () => {
60
+ registry.register(createStubAdapter('claude', 'Claude'));
61
+ registry.register(createStubAdapter('codex', 'Codex'));
62
+ registry.clear();
63
+ expect(registry.list()).toHaveLength(0);
64
+ });
65
+ });
66
+ //# sourceMappingURL=registry.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.test.js","sourceRoot":"","sources":["../../src/__tests__/registry.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=router.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/router.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,77 @@
1
+ // packages/core/src/__tests__/router.test.ts
2
+ import { describe, it, expect, beforeEach } from 'vitest';
3
+ import { Router } from '../router.js';
4
+ import { AdapterRegistry } from '../registry.js';
5
+ function createStubAdapter(id, name) {
6
+ return {
7
+ id,
8
+ name,
9
+ probe: async () => ({
10
+ available: true,
11
+ version: '1.0.0',
12
+ authStatus: 'ok',
13
+ detail: null,
14
+ }),
15
+ execute: async () => ({
16
+ exitCode: 0,
17
+ signal: null,
18
+ timedOut: false,
19
+ summary: '',
20
+ session: null,
21
+ patches: [],
22
+ warnings: [],
23
+ errorMessage: null,
24
+ }),
25
+ };
26
+ }
27
+ describe('Router', () => {
28
+ let registry;
29
+ let router;
30
+ beforeEach(() => {
31
+ registry = new AdapterRegistry();
32
+ registry.register(createStubAdapter('claude', 'Claude'));
33
+ registry.register(createStubAdapter('codex', 'Codex'));
34
+ router = new Router(registry);
35
+ });
36
+ it('rule 1: single mention dispatches to that adapter', () => {
37
+ const result = router.parse('@claude do something');
38
+ expect(result).toEqual({ kind: 'mention', agentIds: ['claude'], body: 'do something' });
39
+ });
40
+ it('rule 2: multi mention dispatches to all in order', () => {
41
+ const result = router.parse('@claude @codex brainstorm');
42
+ expect(result).toEqual({ kind: 'mention', agentIds: ['claude', 'codex'], body: 'brainstorm' });
43
+ });
44
+ it('rule 3: @fixy routes to fixy command handler', () => {
45
+ const result = router.parse('@fixy /status');
46
+ expect(result).toEqual({ kind: 'fixy', rest: '/status' });
47
+ });
48
+ it('rule 3: @fixy /worker with args', () => {
49
+ const result = router.parse('@fixy /worker claude');
50
+ expect(result).toEqual({ kind: 'fixy', rest: '/worker claude' });
51
+ });
52
+ it('rule 4: no mention falls to bare', () => {
53
+ const result = router.parse('just do it');
54
+ expect(result).toEqual({ kind: 'bare', body: 'just do it' });
55
+ });
56
+ it('rule 5: unknown mention returns error', () => {
57
+ const result = router.parse('@unknown do something');
58
+ expect(result).toEqual({ kind: 'error', reason: 'unknown agent: @unknown' });
59
+ });
60
+ it('empty string returns bare', () => {
61
+ const result = router.parse('');
62
+ expect(result).toEqual({ kind: 'bare', body: '' });
63
+ });
64
+ it('@fixy alone with no rest', () => {
65
+ const result = router.parse('@fixy');
66
+ expect(result).toEqual({ kind: 'fixy', rest: '' });
67
+ });
68
+ it('mixed known and unknown mentions returns error for unknown', () => {
69
+ const result = router.parse('@claude @unknown brainstorm');
70
+ expect(result).toEqual({ kind: 'error', reason: 'unknown agent: @unknown' });
71
+ });
72
+ it('@fixy takes priority even with other mentions after', () => {
73
+ const result = router.parse('@fixy @claude do something');
74
+ expect(result).toEqual({ kind: 'fixy', rest: '@claude do something' });
75
+ });
76
+ });
77
+ //# sourceMappingURL=router.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.test.js","sourceRoot":"","sources":["../../src/__tests__/router.test.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAE7C,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGjD,SAAS,iBAAiB,CAAC,EAAU,EAAE,IAAY;IACjD,OAAO;QACL,EAAE;QACF,IAAI;QACJ,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;YAClB,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,OAAO;YAChB,UAAU,EAAE,IAAa;YACzB,MAAM,EAAE,IAAI;SACb,CAAC;QACF,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;YACpB,QAAQ,EAAE,CAAC;YACX,MAAM,EAAE,IAAI;YACZ,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,EAAE;YACX,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,EAAE;YACX,QAAQ,EAAE,EAAE;YACZ,YAAY,EAAE,IAAI;SACnB,CAAC;KACH,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;IACtB,IAAI,QAAyB,CAAC;IAC9B,IAAI,MAAc,CAAC;IAEnB,UAAU,CAAC,GAAG,EAAE;QACd,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;QACjC,QAAQ,CAAC,QAAQ,CAAC,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;QACzD,QAAQ,CAAC,QAAQ,CAAC,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QACvD,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACpD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;QACzD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;IACjG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACpD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACrD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAChC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;QAC3D,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAC1D,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,EAAE,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=smoke.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smoke.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/smoke.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ describe('smoke test', () => {
3
+ it('should pass a basic assertion', () => {
4
+ expect(1 + 1).toBe(2);
5
+ });
6
+ });
7
+ //# sourceMappingURL=smoke.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smoke.test.js","sourceRoot":"","sources":["../../src/__tests__/smoke.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=store.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/store.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,121 @@
1
+ // packages/core/src/__tests__/store.test.ts
2
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
3
+ import { mkdtemp, rm, readdir } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { randomUUID } from 'node:crypto';
7
+ import { LocalThreadStore } from '../store.js';
8
+ // ---------------------------------------------------------------------------
9
+ // Helper
10
+ // ---------------------------------------------------------------------------
11
+ function makeMessage(content, role = 'user') {
12
+ return {
13
+ id: randomUUID(),
14
+ createdAt: new Date().toISOString(),
15
+ role,
16
+ agentId: role === 'agent' ? 'claude' : null,
17
+ content,
18
+ runId: role === 'agent' ? randomUUID() : null,
19
+ dispatchedTo: role === 'user' ? ['claude'] : [],
20
+ patches: [],
21
+ warnings: [],
22
+ };
23
+ }
24
+ // ---------------------------------------------------------------------------
25
+ // Setup / teardown
26
+ // ---------------------------------------------------------------------------
27
+ let tempDir;
28
+ let store;
29
+ const PROJECT_ROOT = '/tmp/fake-project';
30
+ beforeEach(async () => {
31
+ tempDir = await mkdtemp(join(tmpdir(), 'fixy-test-'));
32
+ process.env['FIXY_HOME'] = tempDir;
33
+ store = new LocalThreadStore();
34
+ await store.init();
35
+ });
36
+ afterEach(async () => {
37
+ delete process.env['FIXY_HOME'];
38
+ await rm(tempDir, { recursive: true, force: true });
39
+ });
40
+ // ---------------------------------------------------------------------------
41
+ // Tests
42
+ // ---------------------------------------------------------------------------
43
+ describe('LocalThreadStore', () => {
44
+ describe('createThread', () => {
45
+ it('creates a thread with correct projectRoot, status=active, and an id', async () => {
46
+ const thread = await store.createThread(PROJECT_ROOT);
47
+ expect(thread.id).toBeTruthy();
48
+ expect(typeof thread.id).toBe('string');
49
+ expect(thread.projectRoot).toBe(PROJECT_ROOT);
50
+ expect(thread.status).toBe('active');
51
+ // Verify the thread is also persisted on disk
52
+ const loaded = await store.getThread(thread.id, PROJECT_ROOT);
53
+ expect(loaded.id).toBe(thread.id);
54
+ expect(loaded.projectRoot).toBe(PROJECT_ROOT);
55
+ expect(loaded.status).toBe('active');
56
+ });
57
+ });
58
+ describe('appendMessage', () => {
59
+ it('appends multiple messages in order and persists them to disk', async () => {
60
+ const thread = await store.createThread(PROJECT_ROOT);
61
+ const msg1 = makeMessage('first message', 'user');
62
+ const msg2 = makeMessage('second message', 'agent');
63
+ const msg3 = makeMessage('third message', 'user');
64
+ await store.appendMessage(thread.id, PROJECT_ROOT, msg1);
65
+ await store.appendMessage(thread.id, PROJECT_ROOT, msg2);
66
+ await store.appendMessage(thread.id, PROJECT_ROOT, msg3);
67
+ // Reload from disk to confirm persistence
68
+ const loaded = await store.getThread(thread.id, PROJECT_ROOT);
69
+ expect(loaded.messages).toHaveLength(3);
70
+ expect(loaded.messages[0]).toHaveProperty('content', 'first message');
71
+ expect(loaded.messages[1]).toHaveProperty('content', 'second message');
72
+ expect(loaded.messages[2]).toHaveProperty('content', 'third message');
73
+ });
74
+ });
75
+ describe('getThread / listThreads', () => {
76
+ it('lists both threads and returns the correct one by id', async () => {
77
+ const threadA = await store.createThread(PROJECT_ROOT);
78
+ const threadB = await store.createThread(PROJECT_ROOT);
79
+ const threads = await store.listThreads(PROJECT_ROOT);
80
+ const ids = threads.map((t) => t.id);
81
+ expect(ids).toContain(threadA.id);
82
+ expect(ids).toContain(threadB.id);
83
+ expect(threads).toHaveLength(2);
84
+ const loadedA = await store.getThread(threadA.id, PROJECT_ROOT);
85
+ expect(loadedA.id).toBe(threadA.id);
86
+ const loadedB = await store.getThread(threadB.id, PROJECT_ROOT);
87
+ expect(loadedB.id).toBe(threadB.id);
88
+ });
89
+ });
90
+ describe('archiveThread', () => {
91
+ it('sets status to "archived" and persists the change', async () => {
92
+ const thread = await store.createThread(PROJECT_ROOT);
93
+ expect(thread.status).toBe('active');
94
+ const archived = await store.archiveThread(thread.id, PROJECT_ROOT);
95
+ expect(archived.status).toBe('archived');
96
+ // Reload from disk to confirm persistence
97
+ const loaded = await store.getThread(thread.id, PROJECT_ROOT);
98
+ expect(loaded.status).toBe('archived');
99
+ });
100
+ });
101
+ describe('atomic write safety', () => {
102
+ it('leaves no .tmp files in the threads directory after writes', async () => {
103
+ const thread = await store.createThread(PROJECT_ROOT);
104
+ await store.appendMessage(thread.id, PROJECT_ROOT, makeMessage('hello'));
105
+ // Derive the threads directory using the same path logic the store uses
106
+ const { getThreadsDir } = await import('../paths.js');
107
+ const threadsDir = getThreadsDir(PROJECT_ROOT);
108
+ const entries = await readdir(threadsDir);
109
+ const tmpFiles = entries.filter((name) => name.endsWith('.tmp'));
110
+ expect(tmpFiles).toHaveLength(0);
111
+ });
112
+ });
113
+ describe('appendMessage on non-existent thread', () => {
114
+ it('throws an error containing "Thread not found"', async () => {
115
+ const fakeId = randomUUID();
116
+ const msg = makeMessage('should fail');
117
+ await expect(store.appendMessage(fakeId, PROJECT_ROOT, msg)).rejects.toThrow('Thread not found');
118
+ });
119
+ });
120
+ });
121
+ //# sourceMappingURL=store.test.js.map