@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.
- package/dist/__tests__/diff-parser.test.d.ts +2 -0
- package/dist/__tests__/diff-parser.test.d.ts.map +1 -0
- package/dist/__tests__/diff-parser.test.js +89 -0
- package/dist/__tests__/diff-parser.test.js.map +1 -0
- package/dist/__tests__/fixy-commands.test.d.ts +2 -0
- package/dist/__tests__/fixy-commands.test.d.ts.map +1 -0
- package/dist/__tests__/fixy-commands.test.js +176 -0
- package/dist/__tests__/fixy-commands.test.js.map +1 -0
- package/dist/__tests__/registry.test.d.ts +2 -0
- package/dist/__tests__/registry.test.d.ts.map +1 -0
- package/dist/__tests__/registry.test.js +66 -0
- package/dist/__tests__/registry.test.js.map +1 -0
- package/dist/__tests__/router.test.d.ts +2 -0
- package/dist/__tests__/router.test.d.ts.map +1 -0
- package/dist/__tests__/router.test.js +77 -0
- package/dist/__tests__/router.test.js.map +1 -0
- package/dist/__tests__/smoke.test.d.ts +2 -0
- package/dist/__tests__/smoke.test.d.ts.map +1 -0
- package/dist/__tests__/smoke.test.js +7 -0
- package/dist/__tests__/smoke.test.js.map +1 -0
- package/dist/__tests__/store.test.d.ts +2 -0
- package/dist/__tests__/store.test.d.ts.map +1 -0
- package/dist/__tests__/store.test.js +121 -0
- package/dist/__tests__/store.test.js.map +1 -0
- package/dist/__tests__/turn.test.d.ts +2 -0
- package/dist/__tests__/turn.test.d.ts.map +1 -0
- package/dist/__tests__/turn.test.js +194 -0
- package/dist/__tests__/turn.test.js.map +1 -0
- package/dist/__tests__/worktree.test.d.ts +2 -0
- package/dist/__tests__/worktree.test.d.ts.map +1 -0
- package/dist/__tests__/worktree.test.js +119 -0
- package/dist/__tests__/worktree.test.js.map +1 -0
- package/dist/adapter.d.ts +75 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +3 -0
- package/dist/adapter.js.map +1 -0
- package/dist/diff-parser.d.ts +3 -0
- package/dist/diff-parser.d.ts.map +1 -0
- package/dist/diff-parser.js +38 -0
- package/dist/diff-parser.js.map +1 -0
- package/dist/fixy-commands.d.ts +25 -0
- package/dist/fixy-commands.d.ts.map +1 -0
- package/dist/fixy-commands.js +154 -0
- package/dist/fixy-commands.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/paths.d.ts +17 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +36 -0
- package/dist/paths.js.map +1 -0
- package/dist/registry.d.ts +12 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +32 -0
- package/dist/registry.js.map +1 -0
- package/dist/router.d.ts +21 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +36 -0
- package/dist/router.js.map +1 -0
- package/dist/store.d.ts +36 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +127 -0
- package/dist/store.js.map +1 -0
- package/dist/thread.d.ts +47 -0
- package/dist/thread.d.ts.map +1 -0
- package/dist/thread.js +3 -0
- package/dist/thread.js.map +1 -0
- package/dist/turn.d.ts +20 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +130 -0
- package/dist/turn.js.map +1 -0
- package/dist/worktree.d.ts +36 -0
- package/dist/worktree.d.ts.map +1 -0
- package/dist/worktree.js +91 -0
- package/dist/worktree.js.map +1 -0
- package/package.json +21 -0
- package/src/__tests__/diff-parser.test.ts +99 -0
- package/src/__tests__/fixy-commands.test.ts +231 -0
- package/src/__tests__/registry.test.ts +79 -0
- package/src/__tests__/router.test.ts +91 -0
- package/src/__tests__/smoke.test.ts +7 -0
- package/src/__tests__/store.test.ts +151 -0
- package/src/__tests__/turn.test.ts +266 -0
- package/src/__tests__/worktree.test.ts +155 -0
- package/src/adapter.ts +84 -0
- package/src/diff-parser.ts +46 -0
- package/src/fixy-commands.ts +201 -0
- package/src/index.ts +40 -0
- package/src/paths.ts +43 -0
- package/src/registry.ts +40 -0
- package/src/router.ts +49 -0
- package/src/store.ts +164 -0
- package/src/thread.ts +50 -0
- package/src/turn.ts +165 -0
- package/src/worktree.ts +119 -0
- package/tsconfig.json +9 -0
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/smoke.test.ts"],"names":[],"mappings":""}
|
|
@@ -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 @@
|
|
|
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
|