@rigour-labs/mcp 2.22.0 → 3.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/README.md +11 -1
- package/dist/index.js +73 -1236
- package/dist/tools/agent-handlers.d.ts +14 -0
- package/dist/tools/agent-handlers.js +176 -0
- package/dist/tools/definitions.d.ts +293 -0
- package/dist/tools/definitions.js +249 -0
- package/dist/tools/execution-handlers.d.ts +11 -0
- package/dist/tools/execution-handlers.js +134 -0
- package/dist/tools/memory-handlers.d.ts +10 -0
- package/dist/tools/memory-handlers.js +52 -0
- package/dist/tools/pattern-handlers.d.ts +9 -0
- package/dist/tools/pattern-handlers.js +72 -0
- package/dist/tools/quality-handlers.d.ts +25 -0
- package/dist/tools/quality-handlers.js +116 -0
- package/dist/tools/review-handler.d.ts +16 -0
- package/dist/tools/review-handler.js +42 -0
- package/dist/utils/config.d.ts +146 -0
- package/dist/utils/config.js +93 -0
- package/package.json +21 -3
- package/.claude-plugin/SKILL.md +0 -51
- package/.claude-plugin/marketplace.json +0 -21
- package/.claude-plugin/plugin.json +0 -17
- package/server.json +0 -21
- package/src/index.test.ts +0 -333
- package/src/index.ts +0 -1432
- package/src/smoke.test.ts +0 -7
- package/src/supervisor.test.ts +0 -158
- package/tsconfig.json +0 -10
package/src/index.test.ts
DELETED
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import * as fs from 'fs-extra';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import * as os from 'os';
|
|
5
|
-
|
|
6
|
-
// Mock the MCP tool handlers for testing
|
|
7
|
-
// In a real scenario, we'd refactor index.ts to export testable functions
|
|
8
|
-
|
|
9
|
-
describe('MCP Frontier Tools', () => {
|
|
10
|
-
let testDir: string;
|
|
11
|
-
let rigourDir: string;
|
|
12
|
-
|
|
13
|
-
beforeEach(async () => {
|
|
14
|
-
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-test-'));
|
|
15
|
-
rigourDir = path.join(testDir, '.rigour');
|
|
16
|
-
await fs.ensureDir(rigourDir);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(async () => {
|
|
20
|
-
await fs.remove(testDir);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe('rigour_agent_register', () => {
|
|
24
|
-
const sessionPath = () => path.join(rigourDir, 'agent-session.json');
|
|
25
|
-
|
|
26
|
-
async function registerAgent(agentId: string, taskScope: string[]) {
|
|
27
|
-
let session = { agents: [] as any[], startedAt: new Date().toISOString() };
|
|
28
|
-
|
|
29
|
-
if (await fs.pathExists(sessionPath())) {
|
|
30
|
-
session = JSON.parse(await fs.readFile(sessionPath(), 'utf-8'));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const existingIdx = session.agents.findIndex((a: any) => a.agentId === agentId);
|
|
34
|
-
if (existingIdx >= 0) {
|
|
35
|
-
session.agents[existingIdx] = {
|
|
36
|
-
agentId,
|
|
37
|
-
taskScope,
|
|
38
|
-
registeredAt: session.agents[existingIdx].registeredAt,
|
|
39
|
-
lastCheckpoint: new Date().toISOString(),
|
|
40
|
-
};
|
|
41
|
-
} else {
|
|
42
|
-
session.agents.push({
|
|
43
|
-
agentId,
|
|
44
|
-
taskScope,
|
|
45
|
-
registeredAt: new Date().toISOString(),
|
|
46
|
-
lastCheckpoint: new Date().toISOString(),
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Check for scope conflicts
|
|
51
|
-
const conflicts: string[] = [];
|
|
52
|
-
for (const agent of session.agents) {
|
|
53
|
-
if (agent.agentId !== agentId) {
|
|
54
|
-
for (const scope of taskScope) {
|
|
55
|
-
if (agent.taskScope.includes(scope)) {
|
|
56
|
-
conflicts.push(`${agent.agentId} also claims "${scope}"`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
await fs.writeFile(sessionPath(), JSON.stringify(session, null, 2));
|
|
63
|
-
return { session, conflicts };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
it('should register a new agent', async () => {
|
|
67
|
-
const { session, conflicts } = await registerAgent('agent-a', ['src/api/**']);
|
|
68
|
-
|
|
69
|
-
expect(session.agents).toHaveLength(1);
|
|
70
|
-
expect(session.agents[0].agentId).toBe('agent-a');
|
|
71
|
-
expect(conflicts).toHaveLength(0);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should detect scope conflicts', async () => {
|
|
75
|
-
await registerAgent('agent-a', ['src/api/**', 'src/utils/**']);
|
|
76
|
-
const { conflicts } = await registerAgent('agent-b', ['src/api/**']);
|
|
77
|
-
|
|
78
|
-
expect(conflicts).toHaveLength(1);
|
|
79
|
-
expect(conflicts[0]).toContain('agent-a');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('should update existing agent registration', async () => {
|
|
83
|
-
await registerAgent('agent-a', ['src/api/**']);
|
|
84
|
-
const { session } = await registerAgent('agent-a', ['src/api/**', 'tests/**']);
|
|
85
|
-
|
|
86
|
-
expect(session.agents).toHaveLength(1);
|
|
87
|
-
expect(session.agents[0].taskScope).toContain('tests/**');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('should support multiple agents', async () => {
|
|
91
|
-
await registerAgent('agent-a', ['src/frontend/**']);
|
|
92
|
-
await registerAgent('agent-b', ['src/backend/**']);
|
|
93
|
-
const { session } = await registerAgent('agent-c', ['src/shared/**']);
|
|
94
|
-
|
|
95
|
-
expect(session.agents).toHaveLength(3);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('rigour_checkpoint', () => {
|
|
100
|
-
const checkpointPath = () => path.join(rigourDir, 'checkpoint-session.json');
|
|
101
|
-
|
|
102
|
-
async function recordCheckpoint(progressPct: number, qualityScore: number, summary = 'Test') {
|
|
103
|
-
let session = {
|
|
104
|
-
sessionId: `chk-session-${Date.now()}`,
|
|
105
|
-
startedAt: new Date().toISOString(),
|
|
106
|
-
checkpoints: [] as any[],
|
|
107
|
-
status: 'active'
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
if (await fs.pathExists(checkpointPath())) {
|
|
111
|
-
session = JSON.parse(await fs.readFile(checkpointPath(), 'utf-8'));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const warnings: string[] = [];
|
|
115
|
-
|
|
116
|
-
if (qualityScore < 80) {
|
|
117
|
-
warnings.push(`Quality score ${qualityScore}% is below threshold 80%`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Drift detection
|
|
121
|
-
if (session.checkpoints.length >= 2) {
|
|
122
|
-
const recentScores = session.checkpoints.slice(-3).map((cp: any) => cp.qualityScore);
|
|
123
|
-
const avgRecent = recentScores.reduce((a: number, b: number) => a + b, 0) / recentScores.length;
|
|
124
|
-
if (qualityScore < avgRecent - 10) {
|
|
125
|
-
warnings.push(`Drift detected: quality dropped from avg ${avgRecent.toFixed(0)}% to ${qualityScore}%`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const checkpoint = {
|
|
130
|
-
checkpointId: `cp-${Date.now()}`,
|
|
131
|
-
timestamp: new Date().toISOString(),
|
|
132
|
-
progressPct,
|
|
133
|
-
summary,
|
|
134
|
-
qualityScore,
|
|
135
|
-
warnings,
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
session.checkpoints.push(checkpoint);
|
|
139
|
-
await fs.writeFile(checkpointPath(), JSON.stringify(session, null, 2));
|
|
140
|
-
|
|
141
|
-
return { checkpoint, warnings, session };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
it('should record a checkpoint', async () => {
|
|
145
|
-
const { checkpoint, session } = await recordCheckpoint(25, 85, 'Initial work');
|
|
146
|
-
|
|
147
|
-
expect(checkpoint.progressPct).toBe(25);
|
|
148
|
-
expect(checkpoint.qualityScore).toBe(85);
|
|
149
|
-
expect(session.checkpoints).toHaveLength(1);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('should warn on low quality score', async () => {
|
|
153
|
-
const { warnings } = await recordCheckpoint(50, 65, 'Struggling');
|
|
154
|
-
|
|
155
|
-
expect(warnings.some(w => w.includes('below threshold'))).toBe(true);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('should detect quality drift', async () => {
|
|
159
|
-
await recordCheckpoint(20, 90);
|
|
160
|
-
await recordCheckpoint(40, 88);
|
|
161
|
-
await recordCheckpoint(60, 85);
|
|
162
|
-
const { warnings } = await recordCheckpoint(80, 70);
|
|
163
|
-
|
|
164
|
-
expect(warnings.some(w => w.includes('Drift detected'))).toBe(true);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('should track multiple checkpoints', async () => {
|
|
168
|
-
await recordCheckpoint(25, 90);
|
|
169
|
-
await recordCheckpoint(50, 88);
|
|
170
|
-
await recordCheckpoint(75, 92);
|
|
171
|
-
const { session } = await recordCheckpoint(100, 95);
|
|
172
|
-
|
|
173
|
-
expect(session.checkpoints).toHaveLength(4);
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
describe('rigour_handoff', () => {
|
|
178
|
-
const handoffPath = () => path.join(rigourDir, 'handoffs.jsonl');
|
|
179
|
-
|
|
180
|
-
async function createHandoff(
|
|
181
|
-
fromAgentId: string,
|
|
182
|
-
toAgentId: string,
|
|
183
|
-
taskDescription: string,
|
|
184
|
-
filesInScope: string[] = []
|
|
185
|
-
) {
|
|
186
|
-
const handoff = {
|
|
187
|
-
handoffId: `handoff-${Date.now()}`,
|
|
188
|
-
timestamp: new Date().toISOString(),
|
|
189
|
-
fromAgentId,
|
|
190
|
-
toAgentId,
|
|
191
|
-
taskDescription,
|
|
192
|
-
filesInScope,
|
|
193
|
-
status: 'pending',
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
await fs.appendFile(handoffPath(), JSON.stringify(handoff) + '\n');
|
|
197
|
-
return handoff;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
it('should create a handoff record', async () => {
|
|
201
|
-
const handoff = await createHandoff('agent-a', 'agent-b', 'Complete API integration');
|
|
202
|
-
|
|
203
|
-
expect(handoff.fromAgentId).toBe('agent-a');
|
|
204
|
-
expect(handoff.toAgentId).toBe('agent-b');
|
|
205
|
-
expect(handoff.status).toBe('pending');
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it('should include files in scope', async () => {
|
|
209
|
-
const handoff = await createHandoff(
|
|
210
|
-
'agent-a',
|
|
211
|
-
'agent-b',
|
|
212
|
-
'Fix tests',
|
|
213
|
-
['tests/api.test.ts', 'tests/utils.test.ts']
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
expect(handoff.filesInScope).toHaveLength(2);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it('should append multiple handoffs', async () => {
|
|
220
|
-
await createHandoff('agent-a', 'agent-b', 'Task 1');
|
|
221
|
-
await createHandoff('agent-b', 'agent-c', 'Task 2');
|
|
222
|
-
|
|
223
|
-
const content = await fs.readFile(handoffPath(), 'utf-8');
|
|
224
|
-
const lines = content.trim().split('\n');
|
|
225
|
-
|
|
226
|
-
expect(lines).toHaveLength(2);
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
describe('rigour_agent_deregister', () => {
|
|
231
|
-
const sessionPath = () => path.join(rigourDir, 'agent-session.json');
|
|
232
|
-
|
|
233
|
-
async function deregisterAgent(agentId: string) {
|
|
234
|
-
if (!await fs.pathExists(sessionPath())) {
|
|
235
|
-
return { success: false, message: 'No active session' };
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const session = JSON.parse(await fs.readFile(sessionPath(), 'utf-8'));
|
|
239
|
-
const initialCount = session.agents.length;
|
|
240
|
-
session.agents = session.agents.filter((a: any) => a.agentId !== agentId);
|
|
241
|
-
|
|
242
|
-
await fs.writeFile(sessionPath(), JSON.stringify(session, null, 2));
|
|
243
|
-
|
|
244
|
-
return {
|
|
245
|
-
success: session.agents.length < initialCount,
|
|
246
|
-
remainingAgents: session.agents.length,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
it('should remove an agent from session', async () => {
|
|
251
|
-
// First register
|
|
252
|
-
const session = { agents: [{ agentId: 'agent-a', taskScope: [] }], startedAt: new Date().toISOString() };
|
|
253
|
-
await fs.writeFile(sessionPath(), JSON.stringify(session));
|
|
254
|
-
|
|
255
|
-
const result = await deregisterAgent('agent-a');
|
|
256
|
-
|
|
257
|
-
expect(result.success).toBe(true);
|
|
258
|
-
expect(result.remainingAgents).toBe(0);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it('should handle non-existent agent', async () => {
|
|
262
|
-
const session = { agents: [{ agentId: 'agent-a', taskScope: [] }], startedAt: new Date().toISOString() };
|
|
263
|
-
await fs.writeFile(sessionPath(), JSON.stringify(session));
|
|
264
|
-
|
|
265
|
-
const result = await deregisterAgent('agent-b');
|
|
266
|
-
|
|
267
|
-
expect(result.success).toBe(false);
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
describe('rigour_handoff_accept', () => {
|
|
272
|
-
const handoffPath = () => path.join(rigourDir, 'handoffs.jsonl');
|
|
273
|
-
|
|
274
|
-
async function acceptHandoff(handoffId: string, agentId: string) {
|
|
275
|
-
if (!await fs.pathExists(handoffPath())) {
|
|
276
|
-
return { success: false, message: 'No handoffs found' };
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const content = await fs.readFile(handoffPath(), 'utf-8');
|
|
280
|
-
const handoffs = content.trim().split('\n').map(line => JSON.parse(line));
|
|
281
|
-
|
|
282
|
-
const handoff = handoffs.find(h => h.handoffId === handoffId);
|
|
283
|
-
if (!handoff) {
|
|
284
|
-
return { success: false, message: 'Handoff not found' };
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (handoff.toAgentId !== agentId) {
|
|
288
|
-
return { success: false, message: 'Agent not the intended recipient' };
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
handoff.status = 'accepted';
|
|
292
|
-
handoff.acceptedAt = new Date().toISOString();
|
|
293
|
-
|
|
294
|
-
// Rewrite the file with updated handoff
|
|
295
|
-
const updatedContent = handoffs.map(h => JSON.stringify(h)).join('\n') + '\n';
|
|
296
|
-
await fs.writeFile(handoffPath(), updatedContent);
|
|
297
|
-
|
|
298
|
-
return { success: true, handoff };
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
it('should accept a pending handoff', async () => {
|
|
302
|
-
const handoff = {
|
|
303
|
-
handoffId: 'handoff-123',
|
|
304
|
-
fromAgentId: 'agent-a',
|
|
305
|
-
toAgentId: 'agent-b',
|
|
306
|
-
taskDescription: 'Test task',
|
|
307
|
-
status: 'pending',
|
|
308
|
-
};
|
|
309
|
-
await fs.writeFile(handoffPath(), JSON.stringify(handoff) + '\n');
|
|
310
|
-
|
|
311
|
-
const result = await acceptHandoff('handoff-123', 'agent-b');
|
|
312
|
-
|
|
313
|
-
expect(result.success).toBe(true);
|
|
314
|
-
expect(result.handoff?.status).toBe('accepted');
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it('should reject if agent is not recipient', async () => {
|
|
318
|
-
const handoff = {
|
|
319
|
-
handoffId: 'handoff-123',
|
|
320
|
-
fromAgentId: 'agent-a',
|
|
321
|
-
toAgentId: 'agent-b',
|
|
322
|
-
taskDescription: 'Test task',
|
|
323
|
-
status: 'pending',
|
|
324
|
-
};
|
|
325
|
-
await fs.writeFile(handoffPath(), JSON.stringify(handoff) + '\n');
|
|
326
|
-
|
|
327
|
-
const result = await acceptHandoff('handoff-123', 'agent-c');
|
|
328
|
-
|
|
329
|
-
expect(result.success).toBe(false);
|
|
330
|
-
expect(result.message).toContain('not the intended recipient');
|
|
331
|
-
});
|
|
332
|
-
});
|
|
333
|
-
});
|