@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.
- package/dist/context.test.js +15 -1
- package/dist/discovery.js +14 -1
- package/dist/discovery.test.js +23 -0
- package/dist/gates/agent-team.d.ts +50 -0
- package/dist/gates/agent-team.js +159 -0
- package/dist/gates/agent-team.test.d.ts +1 -0
- package/dist/gates/agent-team.test.js +113 -0
- package/dist/gates/checkpoint.d.ts +72 -0
- package/dist/gates/checkpoint.js +231 -0
- package/dist/gates/checkpoint.test.d.ts +1 -0
- package/dist/gates/checkpoint.test.js +102 -0
- package/dist/gates/context.d.ts +35 -0
- package/dist/gates/context.js +151 -2
- package/dist/gates/runner.js +15 -0
- package/dist/gates/security-patterns.d.ts +48 -0
- package/dist/gates/security-patterns.js +236 -0
- package/dist/gates/security-patterns.test.d.ts +1 -0
- package/dist/gates/security-patterns.test.js +133 -0
- package/dist/services/adaptive-thresholds.d.ts +63 -0
- package/dist/services/adaptive-thresholds.js +204 -0
- package/dist/services/adaptive-thresholds.test.d.ts +1 -0
- package/dist/services/adaptive-thresholds.test.js +129 -0
- package/dist/templates/index.d.ts +1 -0
- package/dist/templates/index.js +81 -0
- package/dist/types/fix-packet.d.ts +4 -4
- package/dist/types/index.d.ts +404 -0
- package/dist/types/index.js +36 -0
- package/package.json +1 -1
- package/src/context.test.ts +15 -1
- package/src/discovery.test.ts +27 -0
- package/src/discovery.ts +15 -2
- package/src/gates/agent-team.test.ts +134 -0
- package/src/gates/agent-team.ts +210 -0
- package/src/gates/checkpoint.test.ts +135 -0
- package/src/gates/checkpoint.ts +311 -0
- package/src/gates/context.ts +200 -2
- package/src/gates/runner.ts +18 -0
- package/src/gates/security-patterns.test.ts +162 -0
- package/src/gates/security-patterns.ts +303 -0
- package/src/services/adaptive-thresholds.test.ts +189 -0
- package/src/services/adaptive-thresholds.ts +275 -0
- package/src/templates/index.ts +82 -0
- package/src/types/index.ts +36 -0
package/dist/context.test.js
CHANGED
|
@@ -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: {
|
|
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:
|
|
46
|
+
gates: mergedGates,
|
|
47
|
+
ignore: [...new Set([...(base.ignore || []), ...(extension.ignore || [])])],
|
|
35
48
|
};
|
|
36
49
|
}
|
|
37
50
|
async findFirstMarker(cwd, markers, searchContent = false) {
|
package/dist/discovery.test.js
CHANGED
|
@@ -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
|
+
}
|