@rigour-labs/mcp 2.21.2 → 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/dist/index.js CHANGED
@@ -1,1299 +1,136 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * Rigour MCP Server — Entry Point
4
+ *
5
+ * Slim orchestration layer that wires tool definitions to handlers.
6
+ * All business logic lives in focused modules under tools/ and utils/.
7
+ *
8
+ * @since v2.17.0 — refactored from 1,487-line monolith
9
+ */
2
10
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
11
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
12
  import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
- import fs from "fs-extra";
6
- import path from "path";
7
- import yaml from "yaml";
8
13
  import { randomUUID } from "crypto";
9
- import { GateRunner, ConfigSchema, } from "@rigour-labs/core";
10
- import { PatternMatcher, loadPatternIndex, getDefaultIndexPath, StalenessDetector, SecurityDetector } from "@rigour-labs/core/pattern-index";
11
- const server = new Server({
12
- name: "rigour-mcp",
13
- version: "1.0.0",
14
- }, {
15
- capabilities: {
16
- tools: {},
17
- },
18
- });
19
- async function loadConfig(cwd) {
20
- const configPath = path.join(cwd, "rigour.yml");
21
- if (!(await fs.pathExists(configPath))) {
22
- // Auto-initialize Rigour if config doesn't exist
23
- console.error(`[RIGOUR] rigour.yml not found in ${cwd}, auto-initializing...`);
24
- const { execa } = await import("execa");
25
- try {
26
- await execa("npx", ["rigour", "init"], { cwd, shell: true });
27
- console.error(`[RIGOUR] Auto-initialization complete.`);
28
- }
29
- catch (initError) {
30
- throw new Error(`Rigour auto-initialization failed: ${initError.message}. Please run 'npx rigour init' manually.`);
31
- }
32
- }
33
- const configContent = await fs.readFile(configPath, "utf-8");
34
- return ConfigSchema.parse(yaml.parse(configContent));
35
- }
36
- async function getMemoryPath(cwd) {
37
- const rigourDir = path.join(cwd, ".rigour");
38
- await fs.ensureDir(rigourDir);
39
- return path.join(rigourDir, "memory.json");
40
- }
41
- async function loadMemory(cwd) {
42
- const memPath = await getMemoryPath(cwd);
43
- if (await fs.pathExists(memPath)) {
44
- const content = await fs.readFile(memPath, "utf-8");
45
- return JSON.parse(content);
46
- }
47
- return { memories: {} };
48
- }
49
- async function saveMemory(cwd, store) {
50
- const memPath = await getMemoryPath(cwd);
51
- await fs.writeFile(memPath, JSON.stringify(store, null, 2));
52
- }
53
- // Helper to log events for Rigour Studio
54
- async function logStudioEvent(cwd, event) {
55
- try {
56
- const rigourDir = path.join(cwd, ".rigour");
57
- await fs.ensureDir(rigourDir);
58
- const eventsPath = path.join(rigourDir, "events.jsonl");
59
- const logEntry = JSON.stringify({
60
- id: randomUUID(),
61
- timestamp: new Date().toISOString(),
62
- ...event
63
- }) + "\n";
64
- await fs.appendFile(eventsPath, logEntry);
65
- }
66
- catch {
67
- // Silent fail - Studio logging is non-blocking and zero-telemetry
68
- }
69
- }
70
- // Helper to parse diff and get modified lines per file
71
- function parseDiff(diff) {
72
- const lines = diff.split('\n');
73
- const mapping = {};
74
- let currentFile = "";
75
- let currentLine = 0;
76
- for (const line of lines) {
77
- if (line.startsWith('+++ b/')) {
78
- currentFile = line.slice(6);
79
- mapping[currentFile] = new Set();
80
- }
81
- else if (line.startsWith('@@')) {
82
- const match = line.match(/\+(\d+)/);
83
- if (match) {
84
- currentLine = parseInt(match[1], 10);
85
- }
86
- }
87
- else if (line.startsWith('+') && !line.startsWith('+++')) {
88
- if (currentFile) {
89
- mapping[currentFile].add(currentLine);
90
- }
91
- currentLine++;
92
- }
93
- else if (!line.startsWith('-')) {
94
- currentLine++;
95
- }
96
- }
97
- return mapping;
98
- }
99
- server.setRequestHandler(ListToolsRequestSchema, async () => {
100
- return {
101
- tools: [
102
- {
103
- name: "rigour_check",
104
- description: "Run quality gate checks on the project. Matches the CLI 'check' command.",
105
- inputSchema: {
106
- type: "object",
107
- properties: {
108
- cwd: {
109
- type: "string",
110
- description: "Absolute path to the project root.",
111
- },
112
- },
113
- required: ["cwd"],
114
- },
115
- },
116
- {
117
- name: "rigour_explain",
118
- description: "Explain the last quality gate failures with actionable bullets. Matches the CLI 'explain' command.",
119
- inputSchema: {
120
- type: "object",
121
- properties: {
122
- cwd: {
123
- type: "string",
124
- description: "Absolute path to the project root.",
125
- },
126
- },
127
- required: ["cwd"],
128
- },
129
- },
130
- {
131
- name: "rigour_status",
132
- description: "Quick PASS/FAIL check with JSON-friendly output for polling current project state.",
133
- inputSchema: {
134
- type: "object",
135
- properties: {
136
- cwd: {
137
- type: "string",
138
- description: "Absolute path to the project root.",
139
- },
140
- },
141
- required: ["cwd"],
142
- },
143
- },
144
- {
145
- name: "rigour_get_fix_packet",
146
- description: "Retrieves a prioritized 'Fix Packet' (v2 schema) containing detailed machine-readable diagnostic data.",
147
- inputSchema: {
148
- type: "object",
149
- properties: {
150
- cwd: {
151
- type: "string",
152
- description: "Absolute path to the project root.",
153
- },
154
- },
155
- required: ["cwd"],
156
- },
157
- },
158
- {
159
- name: "rigour_list_gates",
160
- description: "Lists all configured quality gates and their thresholds for the current project.",
161
- inputSchema: {
162
- type: "object",
163
- properties: {
164
- cwd: {
165
- type: "string",
166
- description: "Absolute path to the project root.",
167
- },
168
- },
169
- required: ["cwd"],
170
- },
171
- },
172
- {
173
- name: "rigour_get_config",
174
- description: "Returns the current Rigour configuration (rigour.yml) for agent reasoning.",
175
- inputSchema: {
176
- type: "object",
177
- properties: {
178
- cwd: {
179
- type: "string",
180
- description: "Absolute path to the project root.",
181
- },
182
- },
183
- required: ["cwd"],
184
- },
185
- },
186
- {
187
- name: "rigour_remember",
188
- description: "Store a persistent instruction or context that the AI should remember across sessions. Use this to persist user preferences, project conventions, or critical instructions.",
189
- inputSchema: {
190
- type: "object",
191
- properties: {
192
- cwd: {
193
- type: "string",
194
- description: "Absolute path to the project root.",
195
- },
196
- key: {
197
- type: "string",
198
- description: "A unique key for this memory (e.g., 'user_preferences', 'coding_style').",
199
- },
200
- value: {
201
- type: "string",
202
- description: "The instruction or context to remember.",
203
- },
204
- },
205
- required: ["cwd", "key", "value"],
206
- },
207
- },
208
- {
209
- name: "rigour_recall",
210
- description: "Retrieve stored instructions or context. Call this at the start of each session to restore memory. Returns all stored memories if no key specified.",
211
- inputSchema: {
212
- type: "object",
213
- properties: {
214
- cwd: {
215
- type: "string",
216
- description: "Absolute path to the project root.",
217
- },
218
- key: {
219
- type: "string",
220
- description: "Optional. Key of specific memory to retrieve.",
221
- },
222
- },
223
- required: ["cwd"],
224
- },
225
- },
226
- {
227
- name: "rigour_forget",
228
- description: "Remove a stored memory by key.",
229
- inputSchema: {
230
- type: "object",
231
- properties: {
232
- cwd: {
233
- type: "string",
234
- description: "Absolute path to the project root.",
235
- },
236
- key: {
237
- type: "string",
238
- description: "Key of the memory to remove.",
239
- },
240
- },
241
- required: ["cwd", "key"],
242
- },
243
- },
244
- {
245
- name: "rigour_check_pattern",
246
- description: "Checks if a proposed code pattern (function, component, etc.) already exists, is stale, or has security vulnerabilities (CVEs). CALL THIS BEFORE CREATING NEW CODE.",
247
- inputSchema: {
248
- type: "object",
249
- properties: {
250
- cwd: {
251
- type: "string",
252
- description: "Absolute path to the project root.",
253
- },
254
- name: {
255
- type: "string",
256
- description: "The name of the function, class, or component you want to create.",
257
- },
258
- type: {
259
- type: "string",
260
- description: "The type of pattern (e.g., 'function', 'component', 'hook', 'type').",
261
- },
262
- intent: {
263
- type: "string",
264
- description: "What the code is for (e.g., 'format dates', 'user authentication').",
265
- },
266
- },
267
- required: ["cwd", "name"],
268
- },
269
- },
270
- {
271
- name: "rigour_security_audit",
272
- description: "Runs a live security audit (CVE check) on the project dependencies.",
273
- inputSchema: {
274
- type: "object",
275
- properties: {
276
- cwd: {
277
- type: "string",
278
- description: "Absolute path to the project root.",
279
- },
280
- },
281
- required: ["cwd"],
282
- },
283
- },
284
- {
285
- name: "rigour_run",
286
- description: "Execute a command under Rigour supervision. This tool can be INTERCEPTED and ARBITRATED by the Governance Studio.",
287
- inputSchema: {
288
- type: "object",
289
- properties: {
290
- cwd: {
291
- type: "string",
292
- description: "Absolute path to the project root.",
293
- },
294
- command: {
295
- type: "string",
296
- description: "The command to run (e.g., 'npm test', 'pytest').",
297
- },
298
- silent: {
299
- type: "boolean",
300
- description: "If true, hides the command output from the agent.",
301
- }
302
- },
303
- required: ["cwd", "command"],
304
- },
305
- },
306
- {
307
- name: "rigour_run_supervised",
308
- description: "Run a command under FULL Supervisor Mode. Iteratively executes the command, checks quality gates, and returns fix packets until PASS or max retries reached. Use this for self-healing agent loops.",
309
- inputSchema: {
310
- type: "object",
311
- properties: {
312
- cwd: {
313
- type: "string",
314
- description: "Absolute path to the project root.",
315
- },
316
- command: {
317
- type: "string",
318
- description: "The agent command to run (e.g., 'claude \"fix the bug\"', 'aider --message \"refactor auth\"').",
319
- },
320
- maxRetries: {
321
- type: "number",
322
- description: "Maximum retry iterations (default: 3).",
323
- },
324
- dryRun: {
325
- type: "boolean",
326
- description: "If true, simulates the loop without executing the command. Useful for testing gate checks.",
327
- },
328
- },
329
- required: ["cwd", "command"],
330
- },
331
- },
332
- // === FRONTIER MODEL TOOLS (v2.14+) ===
333
- // For Opus 4.6, GPT-5.3-Codex multi-agent and long-running sessions
334
- {
335
- name: "rigour_agent_register",
336
- description: "Register an agent in a multi-agent session. Use this at the START of agent execution to claim task scope and enable cross-agent conflict detection. Required for Agent Team Governance.",
337
- inputSchema: {
338
- type: "object",
339
- properties: {
340
- cwd: {
341
- type: "string",
342
- description: "Absolute path to the project root.",
343
- },
344
- agentId: {
345
- type: "string",
346
- description: "Unique identifier for this agent (e.g., 'agent-a', 'opus-frontend').",
347
- },
348
- taskScope: {
349
- type: "array",
350
- items: { type: "string" },
351
- description: "Glob patterns defining the files/directories this agent will work on (e.g., ['src/api/**', 'tests/api/**']).",
352
- },
353
- },
354
- required: ["cwd", "agentId", "taskScope"],
355
- },
356
- },
357
- {
358
- name: "rigour_checkpoint",
359
- description: "Record a quality checkpoint during long-running agent execution. Use periodically (every 15-30 min) to enable drift detection and quality monitoring. Essential for GPT-5.3 coworking mode.",
360
- inputSchema: {
361
- type: "object",
362
- properties: {
363
- cwd: {
364
- type: "string",
365
- description: "Absolute path to the project root.",
366
- },
367
- progressPct: {
368
- type: "number",
369
- description: "Estimated progress percentage (0-100).",
370
- },
371
- filesChanged: {
372
- type: "array",
373
- items: { type: "string" },
374
- description: "List of files modified since last checkpoint.",
375
- },
376
- summary: {
377
- type: "string",
378
- description: "Brief description of work done since last checkpoint.",
379
- },
380
- qualityScore: {
381
- type: "number",
382
- description: "Self-assessed quality score (0-100). Be honest - artificially high scores trigger drift detection.",
383
- },
384
- },
385
- required: ["cwd", "progressPct", "summary", "qualityScore"],
386
- },
387
- },
388
- {
389
- name: "rigour_handoff",
390
- description: "Handoff task to another agent in a multi-agent workflow. Use when delegating a subtask or completing your scope. Enables verified handoff governance.",
391
- inputSchema: {
392
- type: "object",
393
- properties: {
394
- cwd: {
395
- type: "string",
396
- description: "Absolute path to the project root.",
397
- },
398
- fromAgentId: {
399
- type: "string",
400
- description: "ID of the agent initiating the handoff.",
401
- },
402
- toAgentId: {
403
- type: "string",
404
- description: "ID of the agent receiving the handoff.",
405
- },
406
- taskDescription: {
407
- type: "string",
408
- description: "Description of the task being handed off.",
409
- },
410
- filesInScope: {
411
- type: "array",
412
- items: { type: "string" },
413
- description: "Files relevant to the handoff.",
414
- },
415
- context: {
416
- type: "string",
417
- description: "Additional context for the receiving agent.",
418
- },
419
- },
420
- required: ["cwd", "fromAgentId", "toAgentId", "taskDescription"],
421
- },
422
- },
423
- {
424
- name: "rigour_agent_deregister",
425
- description: "Deregister an agent from the multi-agent session. Use when an agent completes its work or needs to release its scope for another agent.",
426
- inputSchema: {
427
- type: "object",
428
- properties: {
429
- cwd: {
430
- type: "string",
431
- description: "Absolute path to the project root.",
432
- },
433
- agentId: {
434
- type: "string",
435
- description: "ID of the agent to deregister.",
436
- },
437
- },
438
- required: ["cwd", "agentId"],
439
- },
440
- },
441
- {
442
- name: "rigour_handoff_accept",
443
- description: "Accept a pending handoff from another agent. Use to formally acknowledge receipt of a task and verify you are the intended recipient.",
444
- inputSchema: {
445
- type: "object",
446
- properties: {
447
- cwd: {
448
- type: "string",
449
- description: "Absolute path to the project root.",
450
- },
451
- handoffId: {
452
- type: "string",
453
- description: "ID of the handoff to accept.",
454
- },
455
- agentId: {
456
- type: "string",
457
- description: "ID of the accepting agent (must match toAgentId in the handoff).",
458
- },
459
- },
460
- required: ["cwd", "handoffId", "agentId"],
461
- },
462
- },
463
- {
464
- name: "rigour_review",
465
- description: "Perform a high-fidelity code review on a pull request diff. Analyzes changed files using all active quality gates.",
466
- inputSchema: {
467
- type: "object",
468
- properties: {
469
- cwd: {
470
- type: "string",
471
- description: "Absolute path to the project root.",
472
- },
473
- repository: {
474
- type: "string",
475
- description: "Full repository name (e.g., 'owner/repo').",
476
- },
477
- branch: {
478
- type: "string",
479
- description: "The branch containing the changes.",
480
- },
481
- diff: {
482
- type: "string",
483
- description: "The git diff content to analyze.",
484
- },
485
- files: {
486
- type: "array",
487
- items: { type: "string" },
488
- description: "List of filenames that were changed.",
489
- },
490
- },
491
- required: ["cwd", "diff"],
492
- },
493
- }
494
- ],
495
- };
496
- });
14
+ import { GateRunner } from "@rigour-labs/core";
15
+ // Utils
16
+ import { loadConfig, logStudioEvent } from './utils/config.js';
17
+ // Tool definitions
18
+ import { TOOL_DEFINITIONS } from './tools/definitions.js';
19
+ // Tool handlers
20
+ import { handleCheck, handleExplain, handleStatus, handleGetFixPacket, handleListGates, handleGetConfig } from './tools/quality-handlers.js';
21
+ import { handleRemember, handleRecall, handleForget } from './tools/memory-handlers.js';
22
+ import { handleCheckPattern, handleSecurityAudit } from './tools/pattern-handlers.js';
23
+ import { handleRun, handleRunSupervised } from './tools/execution-handlers.js';
24
+ import { handleAgentRegister, handleCheckpoint, handleHandoff, handleAgentDeregister, handleHandoffAccept } from './tools/agent-handlers.js';
25
+ import { handleReview } from './tools/review-handler.js';
26
+ // ─── Server Setup ─────────────────────────────────────────────────
27
+ const server = new Server({ name: "rigour-mcp", version: "1.0.0" }, { capabilities: { tools: {} } });
28
+ // ─── Tool Listing ─────────────────────────────────────────────────
29
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
30
+ tools: TOOL_DEFINITIONS,
31
+ }));
32
+ // ─── Tool Dispatch ────────────────────────────────────────────────
497
33
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
498
34
  const { name, arguments: args } = request.params;
499
35
  const cwd = args?.cwd || process.cwd();
500
36
  const requestId = randomUUID();
501
37
  try {
502
- await logStudioEvent(cwd, {
503
- type: "tool_call",
504
- requestId,
505
- tool: name,
506
- arguments: args
507
- });
38
+ await logStudioEvent(cwd, { type: "tool_call", requestId, tool: name, arguments: args });
508
39
  const config = await loadConfig(cwd);
509
40
  const runner = new GateRunner(config);
510
41
  let result;
511
42
  switch (name) {
512
- case "rigour_check": {
513
- const report = await runner.run(cwd);
514
- result = {
515
- content: [
516
- {
517
- type: "text",
518
- text: `RIGOUR AUDIT RESULT: ${report.status}\n\nSummary:\n${Object.entries(report.summary).map(([k, v]) => `- ${k}: ${v}`).join("\n")}`,
519
- },
520
- ],
521
- };
522
- // Add the report to the tool_response log for high-fidelity Studio visualization
523
- result._rigour_report = report;
43
+ // Quality gates
44
+ case "rigour_check":
45
+ result = await handleCheck(runner, cwd);
524
46
  break;
525
- }
526
- case "rigour_explain": {
527
- const report = await runner.run(cwd);
528
- if (report.status === "PASS") {
529
- result = {
530
- content: [
531
- {
532
- type: "text",
533
- text: "ALL QUALITY GATES PASSED. No failures to explain.",
534
- },
535
- ],
536
- };
537
- }
538
- else {
539
- const bullets = report.failures.map((f, i) => {
540
- return `${i + 1}. [${f.id.toUpperCase()}] ${f.title}: ${f.details}${f.hint ? ` (Hint: ${f.hint})` : ''}`;
541
- }).join("\n");
542
- result = {
543
- content: [
544
- {
545
- type: "text",
546
- text: `RIGOUR EXPLAIN:\n\n${bullets}`,
547
- },
548
- ],
549
- };
550
- }
47
+ case "rigour_explain":
48
+ result = await handleExplain(runner, cwd);
551
49
  break;
552
- }
553
- case "rigour_status": {
554
- const report = await runner.run(cwd);
555
- result = {
556
- content: [
557
- {
558
- type: "text",
559
- text: JSON.stringify({
560
- status: report.status,
561
- summary: report.summary,
562
- failureCount: report.failures.length,
563
- durationMs: report.stats.duration_ms
564
- }, null, 2),
565
- },
566
- ],
567
- };
50
+ case "rigour_status":
51
+ result = await handleStatus(runner, cwd);
568
52
  break;
569
- }
570
- case "rigour_get_fix_packet": {
571
- const report = await runner.run(cwd);
572
- if (report.status === "PASS") {
573
- result = {
574
- content: [
575
- {
576
- type: "text",
577
- text: "ALL QUALITY GATES PASSED. The current state meets the required engineering standards.",
578
- },
579
- ],
580
- };
581
- }
582
- else {
583
- const packet = report.failures.map((f, i) => {
584
- let text = `FIX TASK ${i + 1}: [${f.id.toUpperCase()}] ${f.title}\n`;
585
- text += ` - CONTEXT: ${f.details}\n`;
586
- if (f.files && f.files.length > 0) {
587
- text += ` - TARGET FILES: ${f.files.join(", ")}\n`;
588
- }
589
- if (f.hint) {
590
- text += ` - REFACTORING GUIDANCE: ${f.hint}\n`;
591
- }
592
- return text;
593
- }).join("\n---\n");
594
- result = {
595
- content: [
596
- {
597
- type: "text",
598
- text: `ENGINEERING REFINEMENT REQUIRED:\n\nThe project state violated ${report.failures.length} quality gates. You MUST address these failures before declaring the task complete:\n\n${packet}`,
599
- },
600
- ],
601
- };
602
- }
53
+ case "rigour_get_fix_packet":
54
+ result = await handleGetFixPacket(runner, cwd, config);
603
55
  break;
604
- }
605
56
  case "rigour_list_gates":
606
- result = {
607
- content: [
608
- {
609
- type: "text",
610
- text: `ACTIVE QUALITY GATES:\n\n${Object.entries(config.gates).map(([k, v]) => {
611
- if (typeof v === 'object' && v !== null) {
612
- return `- ${k}: ${JSON.stringify(v)}`;
613
- }
614
- return `- ${k}: ${v}`;
615
- }).join("\n")}`,
616
- },
617
- ],
618
- };
57
+ result = handleListGates(config);
619
58
  break;
620
59
  case "rigour_get_config":
621
- result = {
622
- content: [
623
- {
624
- type: "text",
625
- text: JSON.stringify(config, null, 2),
626
- },
627
- ],
628
- };
60
+ result = handleGetConfig(config);
629
61
  break;
630
- case "rigour_remember": {
631
- const { key, value } = args;
632
- const store = await loadMemory(cwd);
633
- store.memories[key] = {
634
- value,
635
- timestamp: new Date().toISOString(),
636
- };
637
- await saveMemory(cwd, store);
638
- result = {
639
- content: [
640
- {
641
- type: "text",
642
- text: `MEMORY STORED: "${key}" has been saved. This instruction will persist across sessions.\n\nStored value: ${value}`,
643
- },
644
- ],
645
- };
62
+ // Memory
63
+ case "rigour_remember":
64
+ result = await handleRemember(cwd, args.key, args.value);
646
65
  break;
647
- }
648
- case "rigour_recall": {
649
- const { key } = args;
650
- const store = await loadMemory(cwd);
651
- if (key) {
652
- const memory = store.memories[key];
653
- if (!memory) {
654
- result = {
655
- content: [
656
- {
657
- type: "text",
658
- text: `NO MEMORY FOUND for key "${key}". Use rigour_remember to store instructions.`,
659
- },
660
- ],
661
- };
662
- }
663
- else {
664
- result = {
665
- content: [
666
- {
667
- type: "text",
668
- text: `RECALLED MEMORY [${key}]:\n${memory.value}\n\n(Stored: ${memory.timestamp})`,
669
- },
670
- ],
671
- };
672
- }
673
- }
674
- else {
675
- const keys = Object.keys(store.memories);
676
- if (keys.length === 0) {
677
- result = {
678
- content: [
679
- {
680
- type: "text",
681
- text: "NO MEMORIES STORED. Use rigour_remember to persist important instructions.",
682
- },
683
- ],
684
- };
685
- }
686
- else {
687
- const allMemories = keys.map(k => {
688
- const mem = store.memories[k];
689
- return `## ${k}\n${mem.value}\n(Stored: ${mem.timestamp})`;
690
- }).join("\n\n---\n\n");
691
- result = {
692
- content: [
693
- {
694
- type: "text",
695
- text: `RECALLED ALL MEMORIES (${keys.length} items):\n\n${allMemories}\n\n---\nIMPORTANT: Follow these stored instructions throughout this session.`,
696
- },
697
- ],
698
- };
699
- }
700
- }
66
+ case "rigour_recall":
67
+ result = await handleRecall(cwd, args.key);
701
68
  break;
702
- }
703
- case "rigour_forget": {
704
- const { key } = args;
705
- const store = await loadMemory(cwd);
706
- if (!store.memories[key]) {
707
- result = {
708
- content: [
709
- {
710
- type: "text",
711
- text: `NO MEMORY FOUND for key "${key}". Nothing to forget.`,
712
- },
713
- ],
714
- };
715
- }
716
- else {
717
- delete store.memories[key];
718
- await saveMemory(cwd, store);
719
- result = {
720
- content: [
721
- {
722
- type: "text",
723
- text: `MEMORY DELETED: "${key}" has been removed.`,
724
- },
725
- ],
726
- };
727
- }
69
+ case "rigour_forget":
70
+ result = await handleForget(cwd, args.key);
728
71
  break;
729
- }
730
- case "rigour_check_pattern": {
731
- const { name: patternName, type, intent } = args;
732
- const indexPath = getDefaultIndexPath(cwd);
733
- const index = await loadPatternIndex(indexPath);
734
- let resultText = "";
735
- // 1. Check for Reinvention
736
- if (index) {
737
- const matcher = new PatternMatcher(index);
738
- const matchResult = await matcher.match({ name: patternName, type, intent });
739
- if (matchResult.status === "FOUND_SIMILAR") {
740
- resultText += `🚨 PATTERN REINVENTION DETECTED\n`;
741
- resultText += `Similar pattern already exists: "${matchResult.matches[0].pattern.name}" in ${matchResult.matches[0].pattern.file}\n`;
742
- resultText += `SUGGESTION: ${matchResult.suggestion}\n\n`;
743
- }
744
- }
745
- else {
746
- resultText += `⚠️ Pattern index not found. Run 'rigour index' to enable reinvention detection.\n\n`;
747
- }
748
- // 2. Check for Staleness/Best Practices
749
- const detector = new StalenessDetector(cwd);
750
- const staleness = await detector.checkStaleness(`${type || 'function'} ${patternName} {}`);
751
- if (staleness.status !== "FRESH") {
752
- resultText += `⚠️ STALENESS/ANTI-PATTERN WARNING\n`;
753
- for (const issue of staleness.issues) {
754
- resultText += `- ${issue.reason}\n REPLACEMENT: ${issue.replacement}\n`;
755
- }
756
- resultText += `\n`;
757
- }
758
- // 3. Check Security for this library (if it's an import)
759
- if (intent && intent.includes('import')) {
760
- const security = new SecurityDetector(cwd);
761
- const audit = await security.runAudit();
762
- const relatedVulns = audit.vulnerabilities.filter(v => patternName.toLowerCase().includes(v.packageName.toLowerCase()) ||
763
- intent.toLowerCase().includes(v.packageName.toLowerCase()));
764
- if (relatedVulns.length > 0) {
765
- resultText += `🛡️ SECURITY/CVE WARNING\n`;
766
- for (const v of relatedVulns) {
767
- resultText += `- [${v.severity.toUpperCase()}] ${v.packageName}: ${v.title} (${v.url})\n`;
768
- }
769
- resultText += `\n`;
770
- }
771
- }
772
- if (!resultText) {
773
- resultText = `✅ Pattern "${patternName}" is fresh, secure, and unique to the codebase.\n\nRECOMMENDED ACTION: Proceed with implementation.`;
774
- }
775
- else {
776
- let recommendation = "Proceed with caution, addressing the warnings above.";
777
- if (resultText.includes("🚨 PATTERN REINVENTION")) {
778
- recommendation = "STOP and REUSE the existing pattern mentioned above. Do not create a duplicate.";
779
- }
780
- else if (resultText.includes("🛡️ SECURITY/CVE WARNING")) {
781
- recommendation = "STOP and update your dependencies or find an alternative library. Do not proceed with vulnerable code.";
782
- }
783
- else if (resultText.includes("⚠️ STALENESS")) {
784
- recommendation = "Follow the replacement suggestion to ensure best practices.";
785
- }
786
- resultText += `\nRECOMMENDED ACTION: ${recommendation}`;
787
- }
788
- result = {
789
- content: [
790
- {
791
- type: "text",
792
- text: resultText,
793
- },
794
- ],
795
- };
72
+ // Pattern intelligence
73
+ case "rigour_check_pattern":
74
+ result = await handleCheckPattern(cwd, args.name, args.type, args.intent);
796
75
  break;
797
- }
798
- case "rigour_security_audit": {
799
- const security = new SecurityDetector(cwd);
800
- const summary = await security.getSecuritySummary();
801
- result = {
802
- content: [
803
- {
804
- type: "text",
805
- text: summary,
806
- },
807
- ],
808
- };
76
+ case "rigour_security_audit":
77
+ result = await handleSecurityAudit(cwd);
809
78
  break;
810
- }
811
- case "rigour_run": {
812
- const { command } = args;
813
- // 1. Log Interceptable Event
814
- await logStudioEvent(cwd, {
815
- type: "interception_requested",
816
- requestId: requestId,
817
- tool: "rigour_run",
818
- command
819
- });
820
- // 2. Poll for Human Arbitration (Max 60s wait for this demo/test)
821
- // In production, this would be a blocking call wait
822
- console.error(`[RIGOUR] Waiting for human arbitration for command: ${command}`);
823
- const pollArbitration = async (rid, timeout) => {
824
- const start = Date.now();
825
- const eventsPath = path.join(cwd, '.rigour/events.jsonl');
826
- while (Date.now() - start < timeout) {
827
- if (await fs.pathExists(eventsPath)) {
828
- const content = await fs.readFile(eventsPath, 'utf-8');
829
- const lines = content.split('\n').filter(l => l.trim());
830
- for (const line of lines.reverse()) {
831
- const event = JSON.parse(line);
832
- if (event.tool === 'human_arbitration' && event.requestId === rid) {
833
- return event.decision;
834
- }
835
- }
836
- }
837
- await new Promise(r => setTimeout(r, 1000));
838
- }
839
- return "approve"; // Default to auto-approve if no human response (for non-blocking feel)
840
- };
841
- const decision = await pollArbitration(requestId, 60000);
842
- if (decision === 'reject') {
843
- result = {
844
- content: [
845
- {
846
- type: "text",
847
- text: `❌ COMMAND REJECTED BY GOVERNOR: The execution of "${command}" was blocked by a human operator in the Governance Studio.`,
848
- },
849
- ],
850
- isError: true
851
- };
852
- }
853
- else {
854
- // Execute
855
- const { execa } = await import("execa");
856
- try {
857
- const { stdout, stderr } = await execa(command, { shell: true, cwd });
858
- result = {
859
- content: [
860
- {
861
- type: "text",
862
- text: `✅ COMMAND EXECUTED (Approved by Governor):\n\nSTDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`,
863
- },
864
- ],
865
- };
866
- }
867
- catch (e) {
868
- result = {
869
- content: [
870
- {
871
- type: "text",
872
- text: `❌ COMMAND FAILED:\n\n${e.message}`,
873
- },
874
- ],
875
- isError: true
876
- };
877
- }
878
- }
79
+ // Execution
80
+ case "rigour_run":
81
+ result = await handleRun(cwd, args.command, requestId);
879
82
  break;
880
- }
881
83
  case "rigour_run_supervised": {
882
84
  const { command, maxRetries = 3, dryRun = false } = args;
883
- const { execa } = await import("execa");
884
- let iteration = 0;
885
- let lastReport = null;
886
- const iterations = [];
887
- await logStudioEvent(cwd, {
888
- type: "supervisor_started",
889
- requestId,
890
- command,
891
- maxRetries,
892
- dryRun
893
- });
894
- while (iteration < maxRetries) {
895
- iteration++;
896
- // 1. Execute the agent command (skip in dryRun mode)
897
- if (!dryRun) {
898
- try {
899
- await execa(command, { shell: true, cwd });
900
- }
901
- catch (e) {
902
- // Command failure is OK - agent might have partial progress
903
- console.error(`[RIGOUR] Iteration ${iteration} command error: ${e.message}`);
904
- }
905
- }
906
- else {
907
- console.error(`[RIGOUR] Iteration ${iteration} (DRY RUN - skipping command execution)`);
908
- }
909
- // 2. Check quality gates
910
- lastReport = await runner.run(cwd);
911
- iterations.push({
912
- iteration,
913
- status: lastReport.status,
914
- failures: lastReport.failures.length
915
- });
916
- await logStudioEvent(cwd, {
917
- type: "supervisor_iteration",
918
- requestId,
919
- iteration,
920
- status: lastReport.status,
921
- failures: lastReport.failures.length
922
- });
923
- // 3. If PASS, we're done
924
- if (lastReport.status === "PASS") {
925
- result = {
926
- content: [
927
- {
928
- type: "text",
929
- text: `✅ SUPERVISOR MODE: PASSED on iteration ${iteration}/${maxRetries}\n\nIterations:\n${iterations.map(i => ` ${i.iteration}. ${i.status} (${i.failures} failures)`).join("\n")}\n\nAll quality gates have been satisfied.`,
930
- },
931
- ],
932
- };
933
- break;
934
- }
935
- // 4. If not at max retries, continue the loop (agent will use fix packet next iteration)
936
- if (iteration >= maxRetries) {
937
- // Final failure - return fix packet
938
- const fixPacket = lastReport.failures.map((f, i) => {
939
- let text = `FIX TASK ${i + 1}: [${f.id.toUpperCase()}] ${f.title}\n`;
940
- text += ` - CONTEXT: ${f.details}\n`;
941
- if (f.files && f.files.length > 0) {
942
- text += ` - TARGET FILES: ${f.files.join(", ")}\n`;
943
- }
944
- if (f.hint) {
945
- text += ` - REFACTORING GUIDANCE: ${f.hint}\n`;
946
- }
947
- return text;
948
- }).join("\n---\n");
949
- result = {
950
- content: [
951
- {
952
- type: "text",
953
- text: `❌ SUPERVISOR MODE: FAILED after ${iteration} iterations\n\nIterations:\n${iterations.map(i => ` ${i.iteration}. ${i.status} (${i.failures} failures)`).join("\n")}\n\nFINAL FIX PACKET:\n${fixPacket}`,
954
- },
955
- ],
956
- isError: true
957
- };
958
- }
959
- }
960
- await logStudioEvent(cwd, {
961
- type: "supervisor_completed",
962
- requestId,
963
- finalStatus: lastReport?.status || "UNKNOWN",
964
- totalIterations: iteration
965
- });
85
+ result = await handleRunSupervised(runner, cwd, command, maxRetries, dryRun, requestId);
966
86
  break;
967
87
  }
968
- // === FRONTIER MODEL TOOL HANDLERS (v2.14+) ===
969
- case "rigour_agent_register": {
970
- const { agentId, taskScope } = args;
971
- // Load or create agent session
972
- const sessionPath = path.join(cwd, '.rigour', 'agent-session.json');
973
- let session = { agents: [], startedAt: new Date().toISOString() };
974
- if (await fs.pathExists(sessionPath)) {
975
- session = JSON.parse(await fs.readFile(sessionPath, 'utf-8'));
976
- }
977
- // Check for existing agent
978
- const existingIdx = session.agents.findIndex((a) => a.agentId === agentId);
979
- if (existingIdx >= 0) {
980
- session.agents[existingIdx] = {
981
- agentId,
982
- taskScope,
983
- registeredAt: session.agents[existingIdx].registeredAt,
984
- lastCheckpoint: new Date().toISOString(),
985
- };
986
- }
987
- else {
988
- session.agents.push({
989
- agentId,
990
- taskScope,
991
- registeredAt: new Date().toISOString(),
992
- lastCheckpoint: new Date().toISOString(),
993
- });
994
- }
995
- // Check for scope conflicts
996
- const conflicts = [];
997
- for (const agent of session.agents) {
998
- if (agent.agentId !== agentId) {
999
- for (const scope of taskScope) {
1000
- if (agent.taskScope.includes(scope)) {
1001
- conflicts.push(`${agent.agentId} also claims "${scope}"`);
1002
- }
1003
- }
1004
- }
1005
- }
1006
- await fs.ensureDir(path.join(cwd, '.rigour'));
1007
- await fs.writeFile(sessionPath, JSON.stringify(session, null, 2));
1008
- await logStudioEvent(cwd, {
1009
- type: "agent_registered",
1010
- requestId,
1011
- agentId,
1012
- taskScope,
1013
- conflicts,
1014
- });
1015
- let responseText = `✅ AGENT REGISTERED: "${agentId}" claimed scope: ${taskScope.join(', ')}\n\n`;
1016
- responseText += `Active agents in session: ${session.agents.length}\n`;
1017
- if (conflicts.length > 0) {
1018
- responseText += `\n⚠️ SCOPE CONFLICTS DETECTED:\n${conflicts.map(c => ` - ${c}`).join('\n')}\n`;
1019
- responseText += `\nConsider coordinating with other agents or narrowing your scope.`;
1020
- }
1021
- result = {
1022
- content: [{ type: "text", text: responseText }],
1023
- };
88
+ // Multi-agent governance
89
+ case "rigour_agent_register":
90
+ result = await handleAgentRegister(cwd, args.agentId, args.taskScope, requestId);
1024
91
  break;
1025
- }
1026
92
  case "rigour_checkpoint": {
1027
93
  const { progressPct, filesChanged = [], summary, qualityScore } = args;
1028
- // Load checkpoint session
1029
- const checkpointPath = path.join(cwd, '.rigour', 'checkpoint-session.json');
1030
- let session = {
1031
- sessionId: `chk-session-${Date.now()}`,
1032
- startedAt: new Date().toISOString(),
1033
- checkpoints: [],
1034
- status: 'active'
1035
- };
1036
- if (await fs.pathExists(checkpointPath)) {
1037
- session = JSON.parse(await fs.readFile(checkpointPath, 'utf-8'));
1038
- }
1039
- const checkpointId = `cp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1040
- const warnings = [];
1041
- // Quality threshold check
1042
- if (qualityScore < 80) {
1043
- warnings.push(`Quality score ${qualityScore}% is below threshold 80%`);
1044
- }
1045
- // Drift detection (quality degrading over time)
1046
- if (session.checkpoints.length >= 2) {
1047
- const recentScores = session.checkpoints.slice(-3).map((cp) => cp.qualityScore);
1048
- const avgRecent = recentScores.reduce((a, b) => a + b, 0) / recentScores.length;
1049
- if (qualityScore < avgRecent - 10) {
1050
- warnings.push(`Drift detected: quality dropped from avg ${avgRecent.toFixed(0)}% to ${qualityScore}%`);
1051
- }
1052
- }
1053
- const checkpoint = {
1054
- checkpointId,
1055
- timestamp: new Date().toISOString(),
1056
- progressPct,
1057
- filesChanged,
1058
- summary,
1059
- qualityScore,
1060
- warnings,
1061
- };
1062
- session.checkpoints.push(checkpoint);
1063
- await fs.ensureDir(path.join(cwd, '.rigour'));
1064
- await fs.writeFile(checkpointPath, JSON.stringify(session, null, 2));
1065
- await logStudioEvent(cwd, {
1066
- type: "checkpoint_recorded",
1067
- requestId,
1068
- checkpointId,
1069
- progressPct,
1070
- qualityScore,
1071
- warnings,
1072
- });
1073
- let responseText = `📍 CHECKPOINT RECORDED: ${checkpointId}\n\n`;
1074
- responseText += `Progress: ${progressPct}% | Quality: ${qualityScore}%\n`;
1075
- responseText += `Summary: ${summary}\n`;
1076
- responseText += `Total checkpoints: ${session.checkpoints.length}\n`;
1077
- if (warnings.length > 0) {
1078
- responseText += `\n⚠️ WARNINGS:\n${warnings.map(w => ` - ${w}`).join('\n')}\n`;
1079
- if (qualityScore < 80) {
1080
- responseText += `\n⛔ QUALITY BELOW THRESHOLD: Consider pausing and reviewing recent work.`;
1081
- }
1082
- }
1083
- const shouldContinue = qualityScore >= 80;
1084
- result._shouldContinue = shouldContinue;
1085
- result = {
1086
- content: [{ type: "text", text: responseText }],
1087
- };
94
+ result = await handleCheckpoint(cwd, progressPct, filesChanged, summary, qualityScore, requestId);
1088
95
  break;
1089
96
  }
1090
97
  case "rigour_handoff": {
1091
98
  const { fromAgentId, toAgentId, taskDescription, filesInScope = [], context = '' } = args;
1092
- const handoffId = `handoff-${Date.now()}`;
1093
- const handoffPath = path.join(cwd, '.rigour', 'handoffs.jsonl');
1094
- const handoff = {
1095
- handoffId,
1096
- timestamp: new Date().toISOString(),
1097
- fromAgentId,
1098
- toAgentId,
1099
- taskDescription,
1100
- filesInScope,
1101
- context,
1102
- status: 'pending',
1103
- };
1104
- await fs.ensureDir(path.join(cwd, '.rigour'));
1105
- await fs.appendFile(handoffPath, JSON.stringify(handoff) + '\n');
1106
- await logStudioEvent(cwd, {
1107
- type: "handoff_initiated",
1108
- requestId,
1109
- handoffId,
1110
- fromAgentId,
1111
- toAgentId,
1112
- taskDescription,
1113
- });
1114
- let responseText = `🤝 HANDOFF INITIATED: ${handoffId}\n\n`;
1115
- responseText += `From: ${fromAgentId} → To: ${toAgentId}\n`;
1116
- responseText += `Task: ${taskDescription}\n`;
1117
- if (filesInScope.length > 0) {
1118
- responseText += `Files in scope: ${filesInScope.join(', ')}\n`;
1119
- }
1120
- if (context) {
1121
- responseText += `Context: ${context}\n`;
1122
- }
1123
- responseText += `\nThe receiving agent should call rigour_agent_register to claim this scope.`;
1124
- result = {
1125
- content: [{ type: "text", text: responseText }],
1126
- };
99
+ result = await handleHandoff(cwd, fromAgentId, toAgentId, taskDescription, filesInScope, context, requestId);
1127
100
  break;
1128
101
  }
1129
- case "rigour_agent_deregister": {
1130
- const { agentId } = args;
1131
- const sessionPath = path.join(cwd, '.rigour', 'agent-session.json');
1132
- if (!await fs.pathExists(sessionPath)) {
1133
- result = {
1134
- content: [{ type: "text", text: `❌ No active agent session found.` }],
1135
- };
1136
- break;
1137
- }
1138
- const session = JSON.parse(await fs.readFile(sessionPath, 'utf-8'));
1139
- const initialCount = session.agents.length;
1140
- session.agents = session.agents.filter((a) => a.agentId !== agentId);
1141
- if (session.agents.length === initialCount) {
1142
- result = {
1143
- content: [{ type: "text", text: `❌ Agent "${agentId}" not found in session.` }],
1144
- };
1145
- break;
1146
- }
1147
- await fs.writeFile(sessionPath, JSON.stringify(session, null, 2));
1148
- await logStudioEvent(cwd, {
1149
- type: "agent_deregistered",
1150
- requestId,
1151
- agentId,
1152
- remainingAgents: session.agents.length,
1153
- });
1154
- let responseText = `✅ AGENT DEREGISTERED: "${agentId}" has been removed from the session.\n\n`;
1155
- responseText += `Remaining agents: ${session.agents.length}\n`;
1156
- if (session.agents.length > 0) {
1157
- responseText += `Active: ${session.agents.map((a) => a.agentId).join(', ')}`;
1158
- }
1159
- result = {
1160
- content: [{ type: "text", text: responseText }],
1161
- };
102
+ case "rigour_agent_deregister":
103
+ result = await handleAgentDeregister(cwd, args.agentId, requestId);
1162
104
  break;
1163
- }
1164
- case "rigour_handoff_accept": {
1165
- const { handoffId, agentId } = args;
1166
- const handoffPath = path.join(cwd, '.rigour', 'handoffs.jsonl');
1167
- if (!await fs.pathExists(handoffPath)) {
1168
- result = {
1169
- content: [{ type: "text", text: `❌ No handoffs found.` }],
1170
- };
1171
- break;
1172
- }
1173
- const content = await fs.readFile(handoffPath, 'utf-8');
1174
- const handoffs = content.trim().split('\n').filter(l => l).map(line => JSON.parse(line));
1175
- const handoff = handoffs.find((h) => h.handoffId === handoffId);
1176
- if (!handoff) {
1177
- result = {
1178
- content: [{ type: "text", text: `❌ Handoff "${handoffId}" not found.` }],
1179
- };
1180
- break;
1181
- }
1182
- if (handoff.toAgentId !== agentId) {
1183
- result = {
1184
- content: [{
1185
- type: "text",
1186
- text: `❌ Agent "${agentId}" is not the intended recipient.\nHandoff is for: ${handoff.toAgentId}`
1187
- }],
1188
- isError: true
1189
- };
1190
- break;
1191
- }
1192
- handoff.status = 'accepted';
1193
- handoff.acceptedAt = new Date().toISOString();
1194
- handoff.acceptedBy = agentId;
1195
- // Rewrite the file with updated handoff
1196
- const updatedContent = handoffs.map((h) => JSON.stringify(h)).join('\n') + '\n';
1197
- await fs.writeFile(handoffPath, updatedContent);
1198
- await logStudioEvent(cwd, {
1199
- type: "handoff_accepted",
1200
- requestId,
1201
- handoffId,
1202
- acceptedBy: agentId,
1203
- fromAgentId: handoff.fromAgentId,
1204
- });
1205
- let responseText = `✅ HANDOFF ACCEPTED: ${handoffId}\n\n`;
1206
- responseText += `From: ${handoff.fromAgentId}\n`;
1207
- responseText += `Task: ${handoff.taskDescription}\n`;
1208
- if (handoff.filesInScope?.length > 0) {
1209
- responseText += `Files in scope: ${handoff.filesInScope.join(', ')}\n`;
1210
- }
1211
- responseText += `\nYou should now call rigour_agent_register to formally claim the scope.`;
1212
- result = {
1213
- content: [{ type: "text", text: responseText }],
1214
- };
105
+ case "rigour_handoff_accept":
106
+ result = await handleHandoffAccept(cwd, args.handoffId, args.agentId, requestId);
1215
107
  break;
1216
- }
1217
- case "rigour_review": {
1218
- const { diff, files: changedFiles } = args;
1219
- // 1. Map diff to line numbers for filtering
1220
- const diffMapping = parseDiff(diff);
1221
- const targetFiles = changedFiles || Object.keys(diffMapping);
1222
- // 2. Run high-fidelity analysis on changed files
1223
- const report = await runner.run(cwd, targetFiles);
1224
- // 3. Filter failures to only those on changed lines (or global gate failures)
1225
- const filteredFailures = report.failures.filter(failure => {
1226
- // Global failures (no file associated) are always reported
1227
- if (!failure.files || failure.files.length === 0)
1228
- return true;
1229
- return failure.files.some(file => {
1230
- const fileModifiedLines = diffMapping[file];
1231
- // If we can't find the file in the diff, assume it's a side-effect or skip
1232
- if (!fileModifiedLines)
1233
- return false;
1234
- // If failure has line info, check if it's in modifiedLines
1235
- if (failure.line !== undefined) {
1236
- return fileModifiedLines.has(failure.line);
1237
- }
1238
- // Fallback: if file is changed and no specific line is given, report it
1239
- return true;
1240
- });
1241
- });
1242
- result = {
1243
- content: [
1244
- {
1245
- type: "text",
1246
- text: JSON.stringify({
1247
- status: filteredFailures.length > 0 ? "FAIL" : "PASS",
1248
- failures: filteredFailures.map(f => ({
1249
- id: f.id,
1250
- gate: f.title,
1251
- severity: "FAIL", // Rigour failures are FAIL by default
1252
- message: f.details,
1253
- file: f.files?.[0] || "",
1254
- line: f.line || 1,
1255
- suggestion: f.hint
1256
- }))
1257
- }),
1258
- },
1259
- ],
1260
- };
108
+ // Code review
109
+ case "rigour_review":
110
+ result = await handleReview(runner, cwd, args.diff, args.files);
1261
111
  break;
1262
- }
1263
112
  default:
1264
113
  throw new Error(`Unknown tool: ${name}`);
1265
114
  }
1266
115
  await logStudioEvent(cwd, {
1267
- type: "tool_response",
1268
- requestId,
1269
- tool: name,
1270
- status: "success",
1271
- content: result.content,
1272
- _rigour_report: result._rigour_report
116
+ type: "tool_response", requestId, tool: name, status: "success",
117
+ content: result.content, _rigour_report: result._rigour_report,
1273
118
  });
1274
119
  return result;
1275
120
  }
1276
121
  catch (error) {
1277
122
  const errorResponse = {
1278
- content: [
1279
- {
1280
- type: "text",
1281
- text: `RIGOUR ERROR: ${error.message}`,
1282
- },
1283
- ],
123
+ content: [{ type: "text", text: `RIGOUR ERROR: ${error.message}` }],
1284
124
  isError: true,
1285
125
  };
1286
126
  await logStudioEvent(cwd, {
1287
- type: "tool_response",
1288
- requestId,
1289
- tool: name,
1290
- status: "error",
1291
- error: error.message,
1292
- content: errorResponse.content
127
+ type: "tool_response", requestId, tool: name,
128
+ status: "error", error: error.message, content: errorResponse.content,
1293
129
  });
1294
130
  return errorResponse;
1295
131
  }
1296
132
  });
133
+ // ─── Start ────────────────────────────────────────────────────────
1297
134
  async function main() {
1298
135
  const transport = new StdioServerTransport();
1299
136
  await server.connect(transport);