@rigour-labs/mcp 2.17.2 → 2.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,6 +24,8 @@ Rigour moves code quality enforcement from the "Post-Commit" phase to the "In-Pr
24
24
 
25
25
  ## šŸ› ļø Available Tools
26
26
 
27
+ ### Core Tools
28
+
27
29
  | Tool | Description |
28
30
  |:---|:---|
29
31
  | `rigour_check` | Runs all configured quality gates on the current workspace. |
@@ -31,6 +33,19 @@ Rigour moves code quality enforcement from the "Post-Commit" phase to the "In-Pr
31
33
  | `rigour_check_pattern` | Checks if a proposed code pattern already exists in the codebase. |
32
34
  | `rigour_remember` | Stores project-specific context or rules in Rigour's persistent memory. |
33
35
  | `rigour_recall` | Retrieves stored context to guide AI generation. |
36
+ | `rigour_security_audit` | Runs a live CVE check on project dependencies. |
37
+
38
+ ### Frontier Model Tools (v2.14+)
39
+
40
+ For next-gen multi-agent workflows (Opus 4.6, GPT-5.3-Codex):
41
+
42
+ | Tool | Description |
43
+ |:---|:---|
44
+ | `rigour_agent_register` | Register agent in session with scope conflict detection. |
45
+ | `rigour_agent_deregister` | Remove agent from session when work is complete. |
46
+ | `rigour_checkpoint` | Record quality checkpoint with drift detection. |
47
+ | `rigour_handoff` | Initiate task handoff to another agent. |
48
+ | `rigour_handoff_accept` | Accept a pending handoff from another agent. |
34
49
 
35
50
  ---
36
51
 
package/dist/index.js CHANGED
@@ -273,6 +273,163 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
273
273
  },
274
274
  required: ["cwd", "command"],
275
275
  },
276
+ },
277
+ {
278
+ name: "rigour_run_supervised",
279
+ 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.",
280
+ inputSchema: {
281
+ type: "object",
282
+ properties: {
283
+ cwd: {
284
+ type: "string",
285
+ description: "Absolute path to the project root.",
286
+ },
287
+ command: {
288
+ type: "string",
289
+ description: "The agent command to run (e.g., 'claude \"fix the bug\"', 'aider --message \"refactor auth\"').",
290
+ },
291
+ maxRetries: {
292
+ type: "number",
293
+ description: "Maximum retry iterations (default: 3).",
294
+ },
295
+ dryRun: {
296
+ type: "boolean",
297
+ description: "If true, simulates the loop without executing the command. Useful for testing gate checks.",
298
+ },
299
+ },
300
+ required: ["cwd", "command"],
301
+ },
302
+ },
303
+ // === FRONTIER MODEL TOOLS (v2.14+) ===
304
+ // For Opus 4.6, GPT-5.3-Codex multi-agent and long-running sessions
305
+ {
306
+ name: "rigour_agent_register",
307
+ 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.",
308
+ inputSchema: {
309
+ type: "object",
310
+ properties: {
311
+ cwd: {
312
+ type: "string",
313
+ description: "Absolute path to the project root.",
314
+ },
315
+ agentId: {
316
+ type: "string",
317
+ description: "Unique identifier for this agent (e.g., 'agent-a', 'opus-frontend').",
318
+ },
319
+ taskScope: {
320
+ type: "array",
321
+ items: { type: "string" },
322
+ description: "Glob patterns defining the files/directories this agent will work on (e.g., ['src/api/**', 'tests/api/**']).",
323
+ },
324
+ },
325
+ required: ["cwd", "agentId", "taskScope"],
326
+ },
327
+ },
328
+ {
329
+ name: "rigour_checkpoint",
330
+ 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.",
331
+ inputSchema: {
332
+ type: "object",
333
+ properties: {
334
+ cwd: {
335
+ type: "string",
336
+ description: "Absolute path to the project root.",
337
+ },
338
+ progressPct: {
339
+ type: "number",
340
+ description: "Estimated progress percentage (0-100).",
341
+ },
342
+ filesChanged: {
343
+ type: "array",
344
+ items: { type: "string" },
345
+ description: "List of files modified since last checkpoint.",
346
+ },
347
+ summary: {
348
+ type: "string",
349
+ description: "Brief description of work done since last checkpoint.",
350
+ },
351
+ qualityScore: {
352
+ type: "number",
353
+ description: "Self-assessed quality score (0-100). Be honest - artificially high scores trigger drift detection.",
354
+ },
355
+ },
356
+ required: ["cwd", "progressPct", "summary", "qualityScore"],
357
+ },
358
+ },
359
+ {
360
+ name: "rigour_handoff",
361
+ description: "Handoff task to another agent in a multi-agent workflow. Use when delegating a subtask or completing your scope. Enables verified handoff governance.",
362
+ inputSchema: {
363
+ type: "object",
364
+ properties: {
365
+ cwd: {
366
+ type: "string",
367
+ description: "Absolute path to the project root.",
368
+ },
369
+ fromAgentId: {
370
+ type: "string",
371
+ description: "ID of the agent initiating the handoff.",
372
+ },
373
+ toAgentId: {
374
+ type: "string",
375
+ description: "ID of the agent receiving the handoff.",
376
+ },
377
+ taskDescription: {
378
+ type: "string",
379
+ description: "Description of the task being handed off.",
380
+ },
381
+ filesInScope: {
382
+ type: "array",
383
+ items: { type: "string" },
384
+ description: "Files relevant to the handoff.",
385
+ },
386
+ context: {
387
+ type: "string",
388
+ description: "Additional context for the receiving agent.",
389
+ },
390
+ },
391
+ required: ["cwd", "fromAgentId", "toAgentId", "taskDescription"],
392
+ },
393
+ },
394
+ {
395
+ name: "rigour_agent_deregister",
396
+ 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.",
397
+ inputSchema: {
398
+ type: "object",
399
+ properties: {
400
+ cwd: {
401
+ type: "string",
402
+ description: "Absolute path to the project root.",
403
+ },
404
+ agentId: {
405
+ type: "string",
406
+ description: "ID of the agent to deregister.",
407
+ },
408
+ },
409
+ required: ["cwd", "agentId"],
410
+ },
411
+ },
412
+ {
413
+ name: "rigour_handoff_accept",
414
+ description: "Accept a pending handoff from another agent. Use to formally acknowledge receipt of a task and verify you are the intended recipient.",
415
+ inputSchema: {
416
+ type: "object",
417
+ properties: {
418
+ cwd: {
419
+ type: "string",
420
+ description: "Absolute path to the project root.",
421
+ },
422
+ handoffId: {
423
+ type: "string",
424
+ description: "ID of the handoff to accept.",
425
+ },
426
+ agentId: {
427
+ type: "string",
428
+ description: "ID of the accepting agent (must match toAgentId in the handoff).",
429
+ },
430
+ },
431
+ required: ["cwd", "handoffId", "agentId"],
432
+ },
276
433
  }
277
434
  ],
278
435
  };
@@ -661,6 +818,342 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
661
818
  }
662
819
  break;
663
820
  }
821
+ case "rigour_run_supervised": {
822
+ const { command, maxRetries = 3, dryRun = false } = args;
823
+ const { execa } = await import("execa");
824
+ let iteration = 0;
825
+ let lastReport = null;
826
+ const iterations = [];
827
+ await logStudioEvent(cwd, {
828
+ type: "supervisor_started",
829
+ requestId,
830
+ command,
831
+ maxRetries,
832
+ dryRun
833
+ });
834
+ while (iteration < maxRetries) {
835
+ iteration++;
836
+ // 1. Execute the agent command (skip in dryRun mode)
837
+ if (!dryRun) {
838
+ try {
839
+ await execa(command, { shell: true, cwd });
840
+ }
841
+ catch (e) {
842
+ // Command failure is OK - agent might have partial progress
843
+ console.error(`[RIGOUR] Iteration ${iteration} command error: ${e.message}`);
844
+ }
845
+ }
846
+ else {
847
+ console.error(`[RIGOUR] Iteration ${iteration} (DRY RUN - skipping command execution)`);
848
+ }
849
+ // 2. Check quality gates
850
+ lastReport = await runner.run(cwd);
851
+ iterations.push({
852
+ iteration,
853
+ status: lastReport.status,
854
+ failures: lastReport.failures.length
855
+ });
856
+ await logStudioEvent(cwd, {
857
+ type: "supervisor_iteration",
858
+ requestId,
859
+ iteration,
860
+ status: lastReport.status,
861
+ failures: lastReport.failures.length
862
+ });
863
+ // 3. If PASS, we're done
864
+ if (lastReport.status === "PASS") {
865
+ result = {
866
+ content: [
867
+ {
868
+ type: "text",
869
+ 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.`,
870
+ },
871
+ ],
872
+ };
873
+ break;
874
+ }
875
+ // 4. If not at max retries, continue the loop (agent will use fix packet next iteration)
876
+ if (iteration >= maxRetries) {
877
+ // Final failure - return fix packet
878
+ const fixPacket = lastReport.failures.map((f, i) => {
879
+ let text = `FIX TASK ${i + 1}: [${f.id.toUpperCase()}] ${f.title}\n`;
880
+ text += ` - CONTEXT: ${f.details}\n`;
881
+ if (f.files && f.files.length > 0) {
882
+ text += ` - TARGET FILES: ${f.files.join(", ")}\n`;
883
+ }
884
+ if (f.hint) {
885
+ text += ` - REFACTORING GUIDANCE: ${f.hint}\n`;
886
+ }
887
+ return text;
888
+ }).join("\n---\n");
889
+ result = {
890
+ content: [
891
+ {
892
+ type: "text",
893
+ 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}`,
894
+ },
895
+ ],
896
+ isError: true
897
+ };
898
+ }
899
+ }
900
+ await logStudioEvent(cwd, {
901
+ type: "supervisor_completed",
902
+ requestId,
903
+ finalStatus: lastReport?.status || "UNKNOWN",
904
+ totalIterations: iteration
905
+ });
906
+ break;
907
+ }
908
+ // === FRONTIER MODEL TOOL HANDLERS (v2.14+) ===
909
+ case "rigour_agent_register": {
910
+ const { agentId, taskScope } = args;
911
+ // Load or create agent session
912
+ const sessionPath = path.join(cwd, '.rigour', 'agent-session.json');
913
+ let session = { agents: [], startedAt: new Date().toISOString() };
914
+ if (await fs.pathExists(sessionPath)) {
915
+ session = JSON.parse(await fs.readFile(sessionPath, 'utf-8'));
916
+ }
917
+ // Check for existing agent
918
+ const existingIdx = session.agents.findIndex((a) => a.agentId === agentId);
919
+ if (existingIdx >= 0) {
920
+ session.agents[existingIdx] = {
921
+ agentId,
922
+ taskScope,
923
+ registeredAt: session.agents[existingIdx].registeredAt,
924
+ lastCheckpoint: new Date().toISOString(),
925
+ };
926
+ }
927
+ else {
928
+ session.agents.push({
929
+ agentId,
930
+ taskScope,
931
+ registeredAt: new Date().toISOString(),
932
+ lastCheckpoint: new Date().toISOString(),
933
+ });
934
+ }
935
+ // Check for scope conflicts
936
+ const conflicts = [];
937
+ for (const agent of session.agents) {
938
+ if (agent.agentId !== agentId) {
939
+ for (const scope of taskScope) {
940
+ if (agent.taskScope.includes(scope)) {
941
+ conflicts.push(`${agent.agentId} also claims "${scope}"`);
942
+ }
943
+ }
944
+ }
945
+ }
946
+ await fs.ensureDir(path.join(cwd, '.rigour'));
947
+ await fs.writeFile(sessionPath, JSON.stringify(session, null, 2));
948
+ await logStudioEvent(cwd, {
949
+ type: "agent_registered",
950
+ requestId,
951
+ agentId,
952
+ taskScope,
953
+ conflicts,
954
+ });
955
+ let responseText = `āœ… AGENT REGISTERED: "${agentId}" claimed scope: ${taskScope.join(', ')}\n\n`;
956
+ responseText += `Active agents in session: ${session.agents.length}\n`;
957
+ if (conflicts.length > 0) {
958
+ responseText += `\nāš ļø SCOPE CONFLICTS DETECTED:\n${conflicts.map(c => ` - ${c}`).join('\n')}\n`;
959
+ responseText += `\nConsider coordinating with other agents or narrowing your scope.`;
960
+ }
961
+ result = {
962
+ content: [{ type: "text", text: responseText }],
963
+ };
964
+ break;
965
+ }
966
+ case "rigour_checkpoint": {
967
+ const { progressPct, filesChanged = [], summary, qualityScore } = args;
968
+ // Load checkpoint session
969
+ const checkpointPath = path.join(cwd, '.rigour', 'checkpoint-session.json');
970
+ let session = {
971
+ sessionId: `chk-session-${Date.now()}`,
972
+ startedAt: new Date().toISOString(),
973
+ checkpoints: [],
974
+ status: 'active'
975
+ };
976
+ if (await fs.pathExists(checkpointPath)) {
977
+ session = JSON.parse(await fs.readFile(checkpointPath, 'utf-8'));
978
+ }
979
+ const checkpointId = `cp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
980
+ const warnings = [];
981
+ // Quality threshold check
982
+ if (qualityScore < 80) {
983
+ warnings.push(`Quality score ${qualityScore}% is below threshold 80%`);
984
+ }
985
+ // Drift detection (quality degrading over time)
986
+ if (session.checkpoints.length >= 2) {
987
+ const recentScores = session.checkpoints.slice(-3).map((cp) => cp.qualityScore);
988
+ const avgRecent = recentScores.reduce((a, b) => a + b, 0) / recentScores.length;
989
+ if (qualityScore < avgRecent - 10) {
990
+ warnings.push(`Drift detected: quality dropped from avg ${avgRecent.toFixed(0)}% to ${qualityScore}%`);
991
+ }
992
+ }
993
+ const checkpoint = {
994
+ checkpointId,
995
+ timestamp: new Date().toISOString(),
996
+ progressPct,
997
+ filesChanged,
998
+ summary,
999
+ qualityScore,
1000
+ warnings,
1001
+ };
1002
+ session.checkpoints.push(checkpoint);
1003
+ await fs.ensureDir(path.join(cwd, '.rigour'));
1004
+ await fs.writeFile(checkpointPath, JSON.stringify(session, null, 2));
1005
+ await logStudioEvent(cwd, {
1006
+ type: "checkpoint_recorded",
1007
+ requestId,
1008
+ checkpointId,
1009
+ progressPct,
1010
+ qualityScore,
1011
+ warnings,
1012
+ });
1013
+ let responseText = `šŸ“ CHECKPOINT RECORDED: ${checkpointId}\n\n`;
1014
+ responseText += `Progress: ${progressPct}% | Quality: ${qualityScore}%\n`;
1015
+ responseText += `Summary: ${summary}\n`;
1016
+ responseText += `Total checkpoints: ${session.checkpoints.length}\n`;
1017
+ if (warnings.length > 0) {
1018
+ responseText += `\nāš ļø WARNINGS:\n${warnings.map(w => ` - ${w}`).join('\n')}\n`;
1019
+ if (qualityScore < 80) {
1020
+ responseText += `\nā›” QUALITY BELOW THRESHOLD: Consider pausing and reviewing recent work.`;
1021
+ }
1022
+ }
1023
+ const shouldContinue = qualityScore >= 80;
1024
+ result._shouldContinue = shouldContinue;
1025
+ result = {
1026
+ content: [{ type: "text", text: responseText }],
1027
+ };
1028
+ break;
1029
+ }
1030
+ case "rigour_handoff": {
1031
+ const { fromAgentId, toAgentId, taskDescription, filesInScope = [], context = '' } = args;
1032
+ const handoffId = `handoff-${Date.now()}`;
1033
+ const handoffPath = path.join(cwd, '.rigour', 'handoffs.jsonl');
1034
+ const handoff = {
1035
+ handoffId,
1036
+ timestamp: new Date().toISOString(),
1037
+ fromAgentId,
1038
+ toAgentId,
1039
+ taskDescription,
1040
+ filesInScope,
1041
+ context,
1042
+ status: 'pending',
1043
+ };
1044
+ await fs.ensureDir(path.join(cwd, '.rigour'));
1045
+ await fs.appendFile(handoffPath, JSON.stringify(handoff) + '\n');
1046
+ await logStudioEvent(cwd, {
1047
+ type: "handoff_initiated",
1048
+ requestId,
1049
+ handoffId,
1050
+ fromAgentId,
1051
+ toAgentId,
1052
+ taskDescription,
1053
+ });
1054
+ let responseText = `šŸ¤ HANDOFF INITIATED: ${handoffId}\n\n`;
1055
+ responseText += `From: ${fromAgentId} → To: ${toAgentId}\n`;
1056
+ responseText += `Task: ${taskDescription}\n`;
1057
+ if (filesInScope.length > 0) {
1058
+ responseText += `Files in scope: ${filesInScope.join(', ')}\n`;
1059
+ }
1060
+ if (context) {
1061
+ responseText += `Context: ${context}\n`;
1062
+ }
1063
+ responseText += `\nThe receiving agent should call rigour_agent_register to claim this scope.`;
1064
+ result = {
1065
+ content: [{ type: "text", text: responseText }],
1066
+ };
1067
+ break;
1068
+ }
1069
+ case "rigour_agent_deregister": {
1070
+ const { agentId } = args;
1071
+ const sessionPath = path.join(cwd, '.rigour', 'agent-session.json');
1072
+ if (!await fs.pathExists(sessionPath)) {
1073
+ result = {
1074
+ content: [{ type: "text", text: `āŒ No active agent session found.` }],
1075
+ };
1076
+ break;
1077
+ }
1078
+ const session = JSON.parse(await fs.readFile(sessionPath, 'utf-8'));
1079
+ const initialCount = session.agents.length;
1080
+ session.agents = session.agents.filter((a) => a.agentId !== agentId);
1081
+ if (session.agents.length === initialCount) {
1082
+ result = {
1083
+ content: [{ type: "text", text: `āŒ Agent "${agentId}" not found in session.` }],
1084
+ };
1085
+ break;
1086
+ }
1087
+ await fs.writeFile(sessionPath, JSON.stringify(session, null, 2));
1088
+ await logStudioEvent(cwd, {
1089
+ type: "agent_deregistered",
1090
+ requestId,
1091
+ agentId,
1092
+ remainingAgents: session.agents.length,
1093
+ });
1094
+ let responseText = `āœ… AGENT DEREGISTERED: "${agentId}" has been removed from the session.\n\n`;
1095
+ responseText += `Remaining agents: ${session.agents.length}\n`;
1096
+ if (session.agents.length > 0) {
1097
+ responseText += `Active: ${session.agents.map((a) => a.agentId).join(', ')}`;
1098
+ }
1099
+ result = {
1100
+ content: [{ type: "text", text: responseText }],
1101
+ };
1102
+ break;
1103
+ }
1104
+ case "rigour_handoff_accept": {
1105
+ const { handoffId, agentId } = args;
1106
+ const handoffPath = path.join(cwd, '.rigour', 'handoffs.jsonl');
1107
+ if (!await fs.pathExists(handoffPath)) {
1108
+ result = {
1109
+ content: [{ type: "text", text: `āŒ No handoffs found.` }],
1110
+ };
1111
+ break;
1112
+ }
1113
+ const content = await fs.readFile(handoffPath, 'utf-8');
1114
+ const handoffs = content.trim().split('\n').filter(l => l).map(line => JSON.parse(line));
1115
+ const handoff = handoffs.find((h) => h.handoffId === handoffId);
1116
+ if (!handoff) {
1117
+ result = {
1118
+ content: [{ type: "text", text: `āŒ Handoff "${handoffId}" not found.` }],
1119
+ };
1120
+ break;
1121
+ }
1122
+ if (handoff.toAgentId !== agentId) {
1123
+ result = {
1124
+ content: [{
1125
+ type: "text",
1126
+ text: `āŒ Agent "${agentId}" is not the intended recipient.\nHandoff is for: ${handoff.toAgentId}`
1127
+ }],
1128
+ isError: true
1129
+ };
1130
+ break;
1131
+ }
1132
+ handoff.status = 'accepted';
1133
+ handoff.acceptedAt = new Date().toISOString();
1134
+ handoff.acceptedBy = agentId;
1135
+ // Rewrite the file with updated handoff
1136
+ const updatedContent = handoffs.map((h) => JSON.stringify(h)).join('\n') + '\n';
1137
+ await fs.writeFile(handoffPath, updatedContent);
1138
+ await logStudioEvent(cwd, {
1139
+ type: "handoff_accepted",
1140
+ requestId,
1141
+ handoffId,
1142
+ acceptedBy: agentId,
1143
+ fromAgentId: handoff.fromAgentId,
1144
+ });
1145
+ let responseText = `āœ… HANDOFF ACCEPTED: ${handoffId}\n\n`;
1146
+ responseText += `From: ${handoff.fromAgentId}\n`;
1147
+ responseText += `Task: ${handoff.taskDescription}\n`;
1148
+ if (handoff.filesInScope?.length > 0) {
1149
+ responseText += `Files in scope: ${handoff.filesInScope.join(', ')}\n`;
1150
+ }
1151
+ responseText += `\nYou should now call rigour_agent_register to formally claim the scope.`;
1152
+ result = {
1153
+ content: [{ type: "text", text: responseText }],
1154
+ };
1155
+ break;
1156
+ }
664
1157
  default:
665
1158
  throw new Error(`Unknown tool: ${name}`);
666
1159
  }
@@ -0,0 +1 @@
1
+ export {};