@rigour-labs/core 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.
Files changed (43) hide show
  1. package/dist/context.test.js +15 -1
  2. package/dist/discovery.js +14 -1
  3. package/dist/discovery.test.js +23 -0
  4. package/dist/gates/agent-team.d.ts +50 -0
  5. package/dist/gates/agent-team.js +159 -0
  6. package/dist/gates/agent-team.test.d.ts +1 -0
  7. package/dist/gates/agent-team.test.js +113 -0
  8. package/dist/gates/checkpoint.d.ts +72 -0
  9. package/dist/gates/checkpoint.js +231 -0
  10. package/dist/gates/checkpoint.test.d.ts +1 -0
  11. package/dist/gates/checkpoint.test.js +102 -0
  12. package/dist/gates/context.d.ts +35 -0
  13. package/dist/gates/context.js +151 -2
  14. package/dist/gates/runner.js +15 -0
  15. package/dist/gates/security-patterns.d.ts +48 -0
  16. package/dist/gates/security-patterns.js +236 -0
  17. package/dist/gates/security-patterns.test.d.ts +1 -0
  18. package/dist/gates/security-patterns.test.js +133 -0
  19. package/dist/services/adaptive-thresholds.d.ts +63 -0
  20. package/dist/services/adaptive-thresholds.js +204 -0
  21. package/dist/services/adaptive-thresholds.test.d.ts +1 -0
  22. package/dist/services/adaptive-thresholds.test.js +129 -0
  23. package/dist/templates/index.d.ts +1 -0
  24. package/dist/templates/index.js +81 -0
  25. package/dist/types/fix-packet.d.ts +4 -4
  26. package/dist/types/index.d.ts +404 -0
  27. package/dist/types/index.js +36 -0
  28. package/package.json +1 -1
  29. package/src/context.test.ts +15 -1
  30. package/src/discovery.test.ts +27 -0
  31. package/src/discovery.ts +15 -2
  32. package/src/gates/agent-team.test.ts +134 -0
  33. package/src/gates/agent-team.ts +210 -0
  34. package/src/gates/checkpoint.test.ts +135 -0
  35. package/src/gates/checkpoint.ts +311 -0
  36. package/src/gates/context.ts +200 -2
  37. package/src/gates/runner.ts +18 -0
  38. package/src/gates/security-patterns.test.ts +162 -0
  39. package/src/gates/security-patterns.ts +303 -0
  40. package/src/services/adaptive-thresholds.test.ts +189 -0
  41. package/src/services/adaptive-thresholds.ts +275 -0
  42. package/src/templates/index.ts +82 -0
  43. package/src/types/index.ts +36 -0
@@ -28,6 +28,11 @@ describe('Context Awareness Engine', () => {
28
28
  enabled: true,
29
29
  sensitivity: 0.8,
30
30
  mining_depth: 10,
31
+ ignored_patterns: [],
32
+ cross_file_patterns: true,
33
+ naming_consistency: true,
34
+ import_relationships: true,
35
+ max_cross_file_depth: 50,
31
36
  },
32
37
  },
33
38
  output: { report_path: 'rigour-report.json' }
@@ -47,7 +52,16 @@ describe('Context Awareness Engine', () => {
47
52
  version: 1,
48
53
  commands: {},
49
54
  gates: {
50
- context: { enabled: true },
55
+ context: {
56
+ enabled: true,
57
+ sensitivity: 0.8,
58
+ mining_depth: 100,
59
+ ignored_patterns: [],
60
+ cross_file_patterns: true,
61
+ naming_consistency: true,
62
+ import_relationships: true,
63
+ max_cross_file_depth: 50,
64
+ },
51
65
  },
52
66
  output: { report_path: 'rigour-report.json' }
53
67
  };
package/dist/discovery.js CHANGED
@@ -26,12 +26,25 @@ export class DiscoveryService {
26
26
  return { config, matches };
27
27
  }
28
28
  mergeConfig(base, extension) {
29
+ // Deep merge for gates to preserve defaults when overrides are partial
30
+ const mergedGates = { ...base.gates };
31
+ if (extension.gates) {
32
+ for (const [key, value] of Object.entries(extension.gates)) {
33
+ if (typeof value === 'object' && value !== null && !Array.isArray(value) && mergedGates[key]) {
34
+ mergedGates[key] = { ...mergedGates[key], ...value };
35
+ }
36
+ else {
37
+ mergedGates[key] = value;
38
+ }
39
+ }
40
+ }
29
41
  return {
30
42
  ...base,
31
43
  preset: extension.preset || base.preset,
32
44
  paradigm: extension.paradigm || base.paradigm,
33
45
  commands: { ...base.commands, ...extension.commands },
34
- gates: { ...base.gates, ...extension.gates },
46
+ gates: mergedGates,
47
+ ignore: [...new Set([...(base.ignore || []), ...(extension.ignore || [])])],
35
48
  };
36
49
  }
37
50
  async findFirstMarker(cwd, markers, searchContent = false) {
@@ -54,4 +54,27 @@ describe('DiscoveryService', () => {
54
54
  const result = await service.discover('/test');
55
55
  expect(result.matches.paradigm?.name).toBe('oop');
56
56
  });
57
+ it('should include project-type-aware ignore patterns for API preset', async () => {
58
+ const service = new DiscoveryService();
59
+ // Mock finding requirements.txt (Python API marker)
60
+ vi.mocked(fs.pathExists).mockImplementation(async (p) => p.includes('requirements.txt'));
61
+ vi.mocked(fs.readdir).mockResolvedValue(['requirements.txt']);
62
+ vi.mocked(fs.readFile).mockResolvedValue('flask==2.0.0');
63
+ const result = await service.discover('/test');
64
+ expect(result.matches.preset?.name).toBe('api');
65
+ expect(result.config.ignore).toContain('venv/**');
66
+ expect(result.config.ignore).toContain('__pycache__/**');
67
+ expect(result.config.ignore).toContain('*.pyc');
68
+ });
69
+ it('should include project-type-aware ignore patterns for UI preset', async () => {
70
+ const service = new DiscoveryService();
71
+ // Mock finding next.config.js (UI marker)
72
+ vi.mocked(fs.pathExists).mockImplementation(async (p) => p.includes('next.config.js'));
73
+ vi.mocked(fs.readdir).mockResolvedValue(['next.config.js']);
74
+ vi.mocked(fs.readFile).mockResolvedValue('module.exports = {}');
75
+ const result = await service.discover('/test');
76
+ expect(result.matches.preset?.name).toBe('ui');
77
+ expect(result.config.ignore).toContain('node_modules/**');
78
+ expect(result.config.ignore).toContain('.next/**');
79
+ });
57
80
  });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Agent Team Governance Gate
3
+ *
4
+ * Supervises multi-agent coordination for frontier models like
5
+ * Opus 4.6 (agent teams) and GPT-5.3-Codex (coworking mode).
6
+ *
7
+ * Detects:
8
+ * - Cross-agent pattern conflicts
9
+ * - Task scope violations
10
+ * - Handoff context loss
11
+ *
12
+ * @since v2.14.0
13
+ */
14
+ import { Gate, GateContext } from './base.js';
15
+ import { Failure } from '../types/index.js';
16
+ export interface AgentRegistration {
17
+ agentId: string;
18
+ taskScope: string[];
19
+ registeredAt: Date;
20
+ lastActivity?: Date;
21
+ }
22
+ export interface AgentTeamSession {
23
+ sessionId: string;
24
+ agents: AgentRegistration[];
25
+ startedAt: Date;
26
+ }
27
+ export interface AgentTeamConfig {
28
+ enabled?: boolean;
29
+ max_concurrent_agents?: number;
30
+ cross_agent_pattern_check?: boolean;
31
+ handoff_verification?: boolean;
32
+ task_ownership?: 'strict' | 'collaborative';
33
+ }
34
+ /**
35
+ * Register an agent in the current session
36
+ */
37
+ export declare function registerAgent(cwd: string, agentId: string, taskScope: string[]): AgentTeamSession;
38
+ /**
39
+ * Get current session status
40
+ */
41
+ export declare function getSession(cwd: string): AgentTeamSession | null;
42
+ /**
43
+ * Clear current session
44
+ */
45
+ export declare function clearSession(cwd: string): void;
46
+ export declare class AgentTeamGate extends Gate {
47
+ private config;
48
+ constructor(config?: AgentTeamConfig);
49
+ run(context: GateContext): Promise<Failure[]>;
50
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Agent Team Governance Gate
3
+ *
4
+ * Supervises multi-agent coordination for frontier models like
5
+ * Opus 4.6 (agent teams) and GPT-5.3-Codex (coworking mode).
6
+ *
7
+ * Detects:
8
+ * - Cross-agent pattern conflicts
9
+ * - Task scope violations
10
+ * - Handoff context loss
11
+ *
12
+ * @since v2.14.0
13
+ */
14
+ import { Gate } from './base.js';
15
+ import { Logger } from '../utils/logger.js';
16
+ import * as fs from 'fs';
17
+ import * as path from 'path';
18
+ // In-memory session store (persisted to .rigour/agent-session.json)
19
+ let currentSession = null;
20
+ /**
21
+ * Register an agent in the current session
22
+ */
23
+ export function registerAgent(cwd, agentId, taskScope) {
24
+ if (!currentSession) {
25
+ currentSession = {
26
+ sessionId: `session-${Date.now()}`,
27
+ agents: [],
28
+ startedAt: new Date(),
29
+ };
30
+ }
31
+ // Check if agent already registered
32
+ const existing = currentSession.agents.find(a => a.agentId === agentId);
33
+ if (existing) {
34
+ existing.taskScope = taskScope;
35
+ existing.lastActivity = new Date();
36
+ }
37
+ else {
38
+ currentSession.agents.push({
39
+ agentId,
40
+ taskScope,
41
+ registeredAt: new Date(),
42
+ });
43
+ }
44
+ // Persist session
45
+ persistSession(cwd);
46
+ return currentSession;
47
+ }
48
+ /**
49
+ * Get current session status
50
+ */
51
+ export function getSession(cwd) {
52
+ if (!currentSession) {
53
+ loadSession(cwd);
54
+ }
55
+ return currentSession;
56
+ }
57
+ /**
58
+ * Clear current session
59
+ */
60
+ export function clearSession(cwd) {
61
+ currentSession = null;
62
+ const sessionPath = path.join(cwd, '.rigour', 'agent-session.json');
63
+ if (fs.existsSync(sessionPath)) {
64
+ fs.unlinkSync(sessionPath);
65
+ }
66
+ }
67
+ function persistSession(cwd) {
68
+ const rigourDir = path.join(cwd, '.rigour');
69
+ if (!fs.existsSync(rigourDir)) {
70
+ fs.mkdirSync(rigourDir, { recursive: true });
71
+ }
72
+ const sessionPath = path.join(rigourDir, 'agent-session.json');
73
+ fs.writeFileSync(sessionPath, JSON.stringify(currentSession, null, 2));
74
+ }
75
+ function loadSession(cwd) {
76
+ const sessionPath = path.join(cwd, '.rigour', 'agent-session.json');
77
+ if (fs.existsSync(sessionPath)) {
78
+ try {
79
+ const data = JSON.parse(fs.readFileSync(sessionPath, 'utf-8'));
80
+ currentSession = {
81
+ ...data,
82
+ startedAt: new Date(data.startedAt),
83
+ agents: data.agents.map((a) => ({
84
+ ...a,
85
+ registeredAt: new Date(a.registeredAt),
86
+ lastActivity: a.lastActivity ? new Date(a.lastActivity) : undefined,
87
+ })),
88
+ };
89
+ }
90
+ catch (err) {
91
+ Logger.warn('Failed to load agent session, starting fresh');
92
+ currentSession = null;
93
+ }
94
+ }
95
+ }
96
+ /**
97
+ * Check if two glob patterns might overlap
98
+ */
99
+ function scopesOverlap(scope1, scope2) {
100
+ const overlapping = [];
101
+ for (const s1 of scope1) {
102
+ for (const s2 of scope2) {
103
+ // Simple overlap detection - same path or one is prefix of other
104
+ if (s1 === s2 || s1.startsWith(s2.replace('**', '')) || s2.startsWith(s1.replace('**', ''))) {
105
+ overlapping.push(`${s1} ↔ ${s2}`);
106
+ }
107
+ }
108
+ }
109
+ return overlapping;
110
+ }
111
+ export class AgentTeamGate extends Gate {
112
+ config;
113
+ constructor(config = {}) {
114
+ super('agent-team', 'Agent Team Governance');
115
+ this.config = {
116
+ enabled: config.enabled ?? false,
117
+ max_concurrent_agents: config.max_concurrent_agents ?? 3,
118
+ cross_agent_pattern_check: config.cross_agent_pattern_check ?? true,
119
+ handoff_verification: config.handoff_verification ?? true,
120
+ task_ownership: config.task_ownership ?? 'strict',
121
+ };
122
+ }
123
+ async run(context) {
124
+ if (!this.config.enabled) {
125
+ return [];
126
+ }
127
+ const failures = [];
128
+ const session = getSession(context.cwd);
129
+ if (!session || session.agents.length === 0) {
130
+ // No multi-agent session active, skip
131
+ return [];
132
+ }
133
+ Logger.info(`Agent Team Gate: ${session.agents.length} agents in session`);
134
+ // Check 1: Max concurrent agents
135
+ if (session.agents.length > (this.config.max_concurrent_agents ?? 3)) {
136
+ failures.push(this.createFailure(`Too many concurrent agents: ${session.agents.length} (max: ${this.config.max_concurrent_agents})`, undefined, 'Reduce the number of concurrent agents or increase max_concurrent_agents in rigour.yml', 'Too Many Concurrent Agents'));
137
+ }
138
+ // Check 2: Task scope conflicts (strict mode only)
139
+ if (this.config.task_ownership === 'strict') {
140
+ for (let i = 0; i < session.agents.length; i++) {
141
+ for (let j = i + 1; j < session.agents.length; j++) {
142
+ const agent1 = session.agents[i];
143
+ const agent2 = session.agents[j];
144
+ const overlaps = scopesOverlap(agent1.taskScope, agent2.taskScope);
145
+ if (overlaps.length > 0) {
146
+ failures.push(this.createFailure(`Task scope conflict between ${agent1.agentId} and ${agent2.agentId}: ${overlaps.join(', ')}`, undefined, 'In strict mode, each agent must have exclusive task scope. Either adjust scopes or set task_ownership: collaborative', 'Task Scope Conflict'));
147
+ }
148
+ }
149
+ }
150
+ }
151
+ // Check 3: Cross-agent pattern detection (if enabled)
152
+ if (this.config.cross_agent_pattern_check && context.record) {
153
+ // This would integrate with the Pattern Index to detect conflicting patterns
154
+ // For now, we log that we would do this check
155
+ Logger.debug('Cross-agent pattern check: would analyze patterns across agent scopes');
156
+ }
157
+ return failures;
158
+ }
159
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,113 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { AgentTeamGate, registerAgent, getSession, clearSession } from './agent-team.js';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import * as os from 'os';
6
+ describe('AgentTeamGate', () => {
7
+ let testDir;
8
+ beforeEach(() => {
9
+ testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-team-test-'));
10
+ });
11
+ afterEach(() => {
12
+ clearSession(testDir);
13
+ fs.rmSync(testDir, { recursive: true, force: true });
14
+ });
15
+ describe('gate initialization', () => {
16
+ it('should create gate with default config', () => {
17
+ const gate = new AgentTeamGate();
18
+ expect(gate.id).toBe('agent-team');
19
+ expect(gate.title).toBe('Agent Team Governance');
20
+ });
21
+ it('should skip when not enabled', async () => {
22
+ const gate = new AgentTeamGate({ enabled: false });
23
+ const failures = await gate.run({ cwd: testDir });
24
+ expect(failures).toEqual([]);
25
+ });
26
+ });
27
+ describe('session management', () => {
28
+ it('should register an agent', () => {
29
+ const session = registerAgent(testDir, 'agent-a', ['src/api/**']);
30
+ expect(session.agents).toHaveLength(1);
31
+ expect(session.agents[0].agentId).toBe('agent-a');
32
+ expect(session.agents[0].taskScope).toEqual(['src/api/**']);
33
+ });
34
+ it('should update existing agent registration', () => {
35
+ registerAgent(testDir, 'agent-a', ['src/api/**']);
36
+ const session = registerAgent(testDir, 'agent-a', ['src/api/**', 'src/utils/**']);
37
+ expect(session.agents).toHaveLength(1);
38
+ expect(session.agents[0].taskScope).toEqual(['src/api/**', 'src/utils/**']);
39
+ });
40
+ it('should persist session to disk', () => {
41
+ registerAgent(testDir, 'agent-a', ['src/**']);
42
+ const sessionPath = path.join(testDir, '.rigour', 'agent-session.json');
43
+ expect(fs.existsSync(sessionPath)).toBe(true);
44
+ });
45
+ it('should load session from disk', () => {
46
+ registerAgent(testDir, 'agent-a', ['src/**']);
47
+ // Clear in-memory cache
48
+ clearSession(testDir);
49
+ // Re-register to re-create session file
50
+ registerAgent(testDir, 'agent-b', ['tests/**']);
51
+ const session = getSession(testDir);
52
+ expect(session).not.toBeNull();
53
+ expect(session.agents).toHaveLength(1); // Only agent-b after clearSession
54
+ });
55
+ it('should clear session', () => {
56
+ registerAgent(testDir, 'agent-a', ['src/**']);
57
+ clearSession(testDir);
58
+ const session = getSession(testDir);
59
+ expect(session).toBeNull();
60
+ });
61
+ });
62
+ describe('max concurrent agents check', () => {
63
+ it('should pass when under limit', async () => {
64
+ const gate = new AgentTeamGate({ enabled: true, max_concurrent_agents: 3 });
65
+ registerAgent(testDir, 'agent-a', ['src/a/**']);
66
+ registerAgent(testDir, 'agent-b', ['src/b/**']);
67
+ const failures = await gate.run({ cwd: testDir });
68
+ expect(failures).toEqual([]);
69
+ });
70
+ it('should fail when over limit', async () => {
71
+ const gate = new AgentTeamGate({ enabled: true, max_concurrent_agents: 2 });
72
+ registerAgent(testDir, 'agent-a', ['src/a/**']);
73
+ registerAgent(testDir, 'agent-b', ['src/b/**']);
74
+ registerAgent(testDir, 'agent-c', ['src/c/**']);
75
+ const failures = await gate.run({ cwd: testDir });
76
+ expect(failures).toHaveLength(1);
77
+ expect(failures[0].title).toBe('Too Many Concurrent Agents');
78
+ });
79
+ });
80
+ describe('task scope conflicts (strict mode)', () => {
81
+ it('should pass when scopes are disjoint', async () => {
82
+ const gate = new AgentTeamGate({
83
+ enabled: true,
84
+ task_ownership: 'strict'
85
+ });
86
+ registerAgent(testDir, 'agent-a', ['src/api/**']);
87
+ registerAgent(testDir, 'agent-b', ['src/ui/**']);
88
+ const failures = await gate.run({ cwd: testDir });
89
+ expect(failures).toEqual([]);
90
+ });
91
+ it('should fail when scopes overlap', async () => {
92
+ const gate = new AgentTeamGate({
93
+ enabled: true,
94
+ task_ownership: 'strict'
95
+ });
96
+ registerAgent(testDir, 'agent-a', ['src/api/**']);
97
+ registerAgent(testDir, 'agent-b', ['src/api/**']); // Same scope!
98
+ const failures = await gate.run({ cwd: testDir });
99
+ expect(failures).toHaveLength(1);
100
+ expect(failures[0].title).toBe('Task Scope Conflict');
101
+ });
102
+ it('should allow overlapping scopes in collaborative mode', async () => {
103
+ const gate = new AgentTeamGate({
104
+ enabled: true,
105
+ task_ownership: 'collaborative'
106
+ });
107
+ registerAgent(testDir, 'agent-a', ['src/api/**']);
108
+ registerAgent(testDir, 'agent-b', ['src/api/**']); // Same scope - OK in collaborative
109
+ const failures = await gate.run({ cwd: testDir });
110
+ expect(failures).toEqual([]);
111
+ });
112
+ });
113
+ });
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Checkpoint Supervision Gate
3
+ *
4
+ * Monitors agent quality during extended execution for frontier models
5
+ * like GPT-5.3-Codex "coworking mode" that run autonomously for long periods.
6
+ *
7
+ * Features:
8
+ * - Time-based checkpoint triggers
9
+ * - Quality score tracking
10
+ * - Drift detection (quality degradation over time)
11
+ * - Auto-save on failure
12
+ *
13
+ * @since v2.14.0
14
+ */
15
+ import { Gate, GateContext } from './base.js';
16
+ import { Failure } from '../types/index.js';
17
+ export interface CheckpointEntry {
18
+ checkpointId: string;
19
+ timestamp: Date;
20
+ progressPct: number;
21
+ filesChanged: string[];
22
+ summary: string;
23
+ qualityScore: number;
24
+ warnings: string[];
25
+ }
26
+ export interface CheckpointSession {
27
+ sessionId: string;
28
+ startedAt: Date;
29
+ lastCheckpoint?: Date;
30
+ checkpoints: CheckpointEntry[];
31
+ status: 'active' | 'completed' | 'aborted';
32
+ }
33
+ export interface CheckpointConfig {
34
+ enabled?: boolean;
35
+ interval_minutes?: number;
36
+ quality_threshold?: number;
37
+ drift_detection?: boolean;
38
+ auto_save_on_failure?: boolean;
39
+ }
40
+ /**
41
+ * Get or create checkpoint session
42
+ */
43
+ export declare function getOrCreateCheckpointSession(cwd: string): CheckpointSession;
44
+ /**
45
+ * Record a checkpoint with quality evaluation
46
+ */
47
+ export declare function recordCheckpoint(cwd: string, progressPct: number, filesChanged: string[], summary: string, qualityScore: number): {
48
+ continue: boolean;
49
+ warnings: string[];
50
+ checkpoint: CheckpointEntry;
51
+ };
52
+ /**
53
+ * Get current checkpoint session
54
+ */
55
+ export declare function getCheckpointSession(cwd: string): CheckpointSession | null;
56
+ /**
57
+ * Complete checkpoint session
58
+ */
59
+ export declare function completeCheckpointSession(cwd: string): void;
60
+ /**
61
+ * Abort checkpoint session (quality too low)
62
+ */
63
+ export declare function abortCheckpointSession(cwd: string, reason: string): void;
64
+ /**
65
+ * Clear checkpoint session
66
+ */
67
+ export declare function clearCheckpointSession(cwd: string): void;
68
+ export declare class CheckpointGate extends Gate {
69
+ private config;
70
+ constructor(config?: CheckpointConfig);
71
+ run(context: GateContext): Promise<Failure[]>;
72
+ }