@rigour-labs/core 2.22.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/context.test.js +2 -3
- package/dist/environment.test.js +2 -1
- package/dist/gates/agent-team.d.ts +2 -1
- package/dist/gates/agent-team.js +1 -0
- package/dist/gates/base.d.ts +3 -1
- package/dist/gates/base.js +3 -0
- package/dist/gates/checkpoint.d.ts +2 -1
- package/dist/gates/checkpoint.js +3 -2
- package/dist/gates/context-window-artifacts.d.ts +2 -1
- package/dist/gates/context-window-artifacts.js +6 -3
- package/dist/gates/context.d.ts +2 -1
- package/dist/gates/context.js +1 -0
- package/dist/gates/coverage.js +3 -1
- package/dist/gates/dependency.js +5 -5
- package/dist/gates/duplication-drift.d.ts +2 -1
- package/dist/gates/duplication-drift.js +4 -1
- package/dist/gates/environment.js +4 -4
- package/dist/gates/hallucinated-imports.d.ts +21 -2
- package/dist/gates/hallucinated-imports.js +116 -2
- package/dist/gates/inconsistent-error-handling.d.ts +2 -1
- package/dist/gates/inconsistent-error-handling.js +21 -7
- package/dist/gates/promise-safety.d.ts +68 -0
- package/dist/gates/promise-safety.js +509 -0
- package/dist/gates/retry-loop-breaker.d.ts +2 -1
- package/dist/gates/retry-loop-breaker.js +2 -1
- package/dist/gates/runner.js +34 -1
- package/dist/gates/safety.d.ts +2 -1
- package/dist/gates/safety.js +2 -1
- package/dist/gates/security-patterns.d.ts +2 -1
- package/dist/gates/security-patterns.js +1 -0
- package/dist/gates/structure.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/services/fix-packet-service.d.ts +0 -1
- package/dist/services/fix-packet-service.js +9 -14
- package/dist/services/score-history.d.ts +54 -0
- package/dist/services/score-history.js +122 -0
- package/dist/templates/index.js +169 -0
- package/dist/types/fix-packet.d.ts +5 -5
- package/dist/types/fix-packet.js +1 -1
- package/dist/types/index.d.ts +153 -0
- package/dist/types/index.js +19 -0
- package/package.json +21 -1
- package/src/context.test.ts +0 -256
- package/src/discovery.test.ts +0 -88
- package/src/discovery.ts +0 -112
- package/src/environment.test.ts +0 -115
- package/src/gates/agent-team.test.ts +0 -134
- package/src/gates/agent-team.ts +0 -210
- package/src/gates/ast-handlers/base.ts +0 -13
- package/src/gates/ast-handlers/python.ts +0 -145
- package/src/gates/ast-handlers/python_parser.py +0 -181
- package/src/gates/ast-handlers/typescript.ts +0 -264
- package/src/gates/ast-handlers/universal.ts +0 -184
- package/src/gates/ast.ts +0 -54
- package/src/gates/base.ts +0 -28
- package/src/gates/checkpoint.test.ts +0 -135
- package/src/gates/checkpoint.ts +0 -311
- package/src/gates/content.ts +0 -51
- package/src/gates/context-window-artifacts.ts +0 -277
- package/src/gates/context.ts +0 -270
- package/src/gates/coverage.ts +0 -74
- package/src/gates/dependency.ts +0 -108
- package/src/gates/duplication-drift.ts +0 -231
- package/src/gates/environment.ts +0 -94
- package/src/gates/file.ts +0 -46
- package/src/gates/hallucinated-imports.ts +0 -361
- package/src/gates/inconsistent-error-handling.ts +0 -254
- package/src/gates/retry-loop-breaker.ts +0 -151
- package/src/gates/runner.ts +0 -188
- package/src/gates/safety.ts +0 -56
- package/src/gates/security-patterns.test.ts +0 -162
- package/src/gates/security-patterns.ts +0 -306
- package/src/gates/structure.ts +0 -36
- package/src/index.ts +0 -13
- package/src/pattern-index/embeddings.ts +0 -84
- package/src/pattern-index/index.ts +0 -59
- package/src/pattern-index/indexer.test.ts +0 -276
- package/src/pattern-index/indexer.ts +0 -1023
- package/src/pattern-index/matcher.test.ts +0 -293
- package/src/pattern-index/matcher.ts +0 -493
- package/src/pattern-index/overrides.ts +0 -235
- package/src/pattern-index/security.ts +0 -151
- package/src/pattern-index/staleness.test.ts +0 -313
- package/src/pattern-index/staleness.ts +0 -568
- package/src/pattern-index/types.ts +0 -339
- package/src/safety.test.ts +0 -53
- package/src/services/adaptive-thresholds.test.ts +0 -189
- package/src/services/adaptive-thresholds.ts +0 -275
- package/src/services/context-engine.ts +0 -104
- package/src/services/fix-packet-service.ts +0 -42
- package/src/services/state-service.ts +0 -138
- package/src/smoke.test.ts +0 -18
- package/src/templates/index.ts +0 -338
- package/src/types/fix-packet.ts +0 -32
- package/src/types/index.ts +0 -200
- package/src/utils/logger.ts +0 -43
- package/src/utils/scanner.test.ts +0 -37
- package/src/utils/scanner.ts +0 -43
- package/tsconfig.json +0 -10
- package/vitest.config.ts +0 -7
- package/vitest.setup.ts +0 -30
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import * as _Parser from 'web-tree-sitter';
|
|
2
|
-
const Parser = (_Parser as any).default || _Parser;
|
|
3
|
-
import { ASTHandler, ASTHandlerContext } from './base.js';
|
|
4
|
-
import { Failure } from '../../types/index.js';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import { fileURLToPath } from 'url';
|
|
7
|
-
|
|
8
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
|
|
10
|
-
interface LanguageConfig {
|
|
11
|
-
grammarPath: string;
|
|
12
|
-
extensions: string[];
|
|
13
|
-
queries: {
|
|
14
|
-
methods: string;
|
|
15
|
-
parameters: string;
|
|
16
|
-
complexity: string; // Cyclomatic
|
|
17
|
-
nesting: string; // For Cognitive Complexity
|
|
18
|
-
securitySinks?: string;
|
|
19
|
-
resourceLeaks?: string;
|
|
20
|
-
nPlusOne?: string;
|
|
21
|
-
ecosystemBlunders?: string;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class UniversalASTHandler extends ASTHandler {
|
|
26
|
-
private parser?: any;
|
|
27
|
-
private languages: Record<string, LanguageConfig> = {
|
|
28
|
-
'.go': {
|
|
29
|
-
grammarPath: '../../vendor/grammars/tree-sitter-go.wasm',
|
|
30
|
-
extensions: ['.go'],
|
|
31
|
-
queries: {
|
|
32
|
-
complexity: '(if_statement) (for_statement) (select_statement) (case_clause)',
|
|
33
|
-
nesting: '(if_statement (block . (if_statement))) (for_statement (block . (for_statement)))',
|
|
34
|
-
parameters: '(parameter_list (parameter_declaration) @param)',
|
|
35
|
-
methods: '(method_declaration) @method (function_declaration) @method',
|
|
36
|
-
securitySinks: '(call_expression function: (selector_expression field: (field_identifier) @id (#match? @id "^(Command|exec|System)$")))',
|
|
37
|
-
ecosystemBlunders: '(if_statement !condition: (binary_expression left: (identifier) @err (#eq? @err "err")))' // Missing err check
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
'.py': {
|
|
41
|
-
grammarPath: '../../vendor/grammars/tree-sitter-python.wasm',
|
|
42
|
-
extensions: ['.py'],
|
|
43
|
-
queries: {
|
|
44
|
-
complexity: '(if_statement) (for_statement) (while_statement) (with_statement)',
|
|
45
|
-
nesting: '(if_statement (block (if_statement)))',
|
|
46
|
-
parameters: '(parameters (identifier) @param)',
|
|
47
|
-
methods: '(function_definition) @method',
|
|
48
|
-
securitySinks: '(call_expression function: (identifier) @func (#match? @func "^(eval|exec|os\\.system)$"))',
|
|
49
|
-
ecosystemBlunders: '(parameters (default_parameter value: (list) @mutable))' // Mutable default
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
'.java': {
|
|
53
|
-
grammarPath: '../../vendor/grammars/tree-sitter-java.wasm',
|
|
54
|
-
extensions: ['.java'],
|
|
55
|
-
queries: {
|
|
56
|
-
complexity: '(if_statement) (for_statement) (while_statement) (switch_label)',
|
|
57
|
-
nesting: '(if_statement (block (if_statement))) (for_statement (block (for_statement)))',
|
|
58
|
-
parameters: '(formal_parameters (formal_parameter) @param)',
|
|
59
|
-
methods: '(method_declaration) @method',
|
|
60
|
-
securitySinks: '(method_declaration (modifiers (native))) @native (method_invocation name: (identifier) @name (#match? @name "^(exec|System\\.load)$"))',
|
|
61
|
-
ecosystemBlunders: '(catch_clause body: (block . ))' // Empty catch
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
'.rs': {
|
|
65
|
-
grammarPath: '../../vendor/grammars/tree-sitter-rust.wasm',
|
|
66
|
-
extensions: ['.rs'],
|
|
67
|
-
queries: {
|
|
68
|
-
complexity: '(if_expression) (for_expression) (while_expression) (loop_expression) (match_arm)',
|
|
69
|
-
nesting: '(if_expression (block (if_expression))) (for_expression (block (for_expression)))',
|
|
70
|
-
parameters: '(parameters (parameter) @param)',
|
|
71
|
-
methods: '(impl_item (function_item)) @method (function_item) @method',
|
|
72
|
-
securitySinks: '(unsafe_block) @unsafe',
|
|
73
|
-
ecosystemBlunders: '(call_expression function: (field_expression field: (field_identifier) @id (#eq? @id "unwrap")))' // .unwrap()
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
'.cs': {
|
|
77
|
-
grammarPath: '../../vendor/grammars/tree-sitter-c_sharp.wasm',
|
|
78
|
-
extensions: ['.cs'],
|
|
79
|
-
queries: {
|
|
80
|
-
complexity: '(if_statement) (for_statement) (foreach_statement) (while_statement) (switch_section)',
|
|
81
|
-
nesting: '(if_statement (block (if_statement))) (for_statement (block (for_statement)))',
|
|
82
|
-
parameters: '(parameter_list (parameter) @param)',
|
|
83
|
-
methods: '(method_declaration) @method',
|
|
84
|
-
securitySinks: '(attribute name: (identifier) @attr (#eq? @attr "DllImport")) @violation'
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
'.cpp': {
|
|
88
|
-
grammarPath: '../../vendor/grammars/tree-sitter-cpp.wasm',
|
|
89
|
-
extensions: ['.cpp', '.cc', '.cxx', '.h', '.hpp'],
|
|
90
|
-
queries: {
|
|
91
|
-
complexity: '(if_statement) (for_statement) (while_statement) (case_statement)',
|
|
92
|
-
nesting: '(if_statement (compound_statement (if_statement)))',
|
|
93
|
-
parameters: '(parameter_list (parameter_declaration) @param)',
|
|
94
|
-
methods: '(function_definition) @method',
|
|
95
|
-
securitySinks: '(call_expression function: (identifier) @name (#match? @name "^(malloc|free|system|popen)$"))'
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
supports(file: string): boolean {
|
|
101
|
-
const ext = path.extname(file).toLowerCase();
|
|
102
|
-
return ext in this.languages;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async run(context: ASTHandlerContext): Promise<Failure[]> {
|
|
106
|
-
const failures: Failure[] = [];
|
|
107
|
-
const ext = path.extname(context.file).toLowerCase();
|
|
108
|
-
const config = this.languages[ext];
|
|
109
|
-
if (!config) return [];
|
|
110
|
-
|
|
111
|
-
if (!this.parser) {
|
|
112
|
-
await (Parser as any).init();
|
|
113
|
-
this.parser = new (Parser as any)();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
const Lang = await Parser.Language.load(path.resolve(__dirname, config.grammarPath));
|
|
118
|
-
this.parser.setLanguage(Lang);
|
|
119
|
-
const tree = this.parser.parse(context.content);
|
|
120
|
-
const astConfig = this.config.ast || {};
|
|
121
|
-
|
|
122
|
-
// 1. Structural Methods Audit
|
|
123
|
-
const methodQuery = (Lang as any).query(config.queries.methods);
|
|
124
|
-
const methodMatches = methodQuery.matches(tree.rootNode);
|
|
125
|
-
|
|
126
|
-
for (const match of methodMatches) {
|
|
127
|
-
for (const capture of match.captures) {
|
|
128
|
-
const node = capture.node;
|
|
129
|
-
const name = node.childForFieldName('name')?.text || 'anonymous';
|
|
130
|
-
|
|
131
|
-
// SME: Cognitive Complexity (Nesting depth + Cyclomatic)
|
|
132
|
-
const nesting = (Lang as any).query(config.queries.nesting).captures(node).length;
|
|
133
|
-
const cyclomatic = (Lang as any).query(config.queries.complexity).captures(node).length + 1;
|
|
134
|
-
const cognitive = cyclomatic + (nesting * 2);
|
|
135
|
-
|
|
136
|
-
if (cognitive > (astConfig.complexity || 10)) {
|
|
137
|
-
failures.push({
|
|
138
|
-
id: 'SME_COGNITIVE_LOAD',
|
|
139
|
-
title: `Method '${name}' has high cognitive load (${cognitive})`,
|
|
140
|
-
details: `Deeply nested or complex logic detected in ${context.file}.`,
|
|
141
|
-
files: [context.file],
|
|
142
|
-
hint: `Flatten logical branches and extract nested loops.`
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// 2. Security Sinks
|
|
149
|
-
if (config.queries.securitySinks) {
|
|
150
|
-
const securityQuery = (Lang as any).query(config.queries.securitySinks);
|
|
151
|
-
const sinks = securityQuery.captures(tree.rootNode);
|
|
152
|
-
for (const capture of sinks) {
|
|
153
|
-
failures.push({
|
|
154
|
-
id: 'SME_SECURITY_SINK',
|
|
155
|
-
title: `Unsafe function call detected: ${capture.node.text}`,
|
|
156
|
-
details: `Potentially dangerous execution in ${context.file}.`,
|
|
157
|
-
files: [context.file],
|
|
158
|
-
hint: `Avoid using shell execution or eval. Use safe alternatives.`
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// 3. Ecosystem Blunders
|
|
164
|
-
if (config.queries.ecosystemBlunders) {
|
|
165
|
-
const blunderQuery = (Lang as any).query(config.queries.ecosystemBlunders);
|
|
166
|
-
const blunders = blunderQuery.captures(tree.rootNode);
|
|
167
|
-
for (const capture of blunders) {
|
|
168
|
-
failures.push({
|
|
169
|
-
id: 'SME_BEST_PRACTICE',
|
|
170
|
-
title: `Ecosystem anti-pattern detected`,
|
|
171
|
-
details: `Violation of ${ext} best practices in ${context.file}.`,
|
|
172
|
-
files: [context.file],
|
|
173
|
-
hint: `Review language-specific best practices (e.g., error handling or mutable defaults).`
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
} catch (e) {
|
|
179
|
-
// Parser skip
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return failures;
|
|
183
|
-
}
|
|
184
|
-
}
|
package/src/gates/ast.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { globby } from 'globby';
|
|
4
|
-
import { Gate, GateContext } from './base.js';
|
|
5
|
-
import { Failure, Gates } from '../types/index.js';
|
|
6
|
-
import { ASTHandler } from './ast-handlers/base.js';
|
|
7
|
-
import { TypeScriptHandler } from './ast-handlers/typescript.js';
|
|
8
|
-
import { PythonHandler } from './ast-handlers/python.js';
|
|
9
|
-
import { UniversalASTHandler } from './ast-handlers/universal.js';
|
|
10
|
-
|
|
11
|
-
export class ASTGate extends Gate {
|
|
12
|
-
private handlers: ASTHandler[] = [];
|
|
13
|
-
|
|
14
|
-
constructor(private config: Gates) {
|
|
15
|
-
super('ast-analysis', 'AST Structural Analysis');
|
|
16
|
-
this.handlers.push(new TypeScriptHandler(config));
|
|
17
|
-
this.handlers.push(new PythonHandler(config));
|
|
18
|
-
this.handlers.push(new UniversalASTHandler(config));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async run(context: GateContext): Promise<Failure[]> {
|
|
22
|
-
const failures: Failure[] = [];
|
|
23
|
-
|
|
24
|
-
const patterns = (context.patterns || ['**/*.{ts,js,tsx,jsx,py,go,rs,cs,java,rb,c,cpp,php,swift,kt}']).map(p => p.replace(/\\/g, '/'));
|
|
25
|
-
const ignore = (context.ignore || ['**/node_modules/**', '**/dist/**', '**/build/**', '**/*.test.*', '**/*.spec.*', '**/__pycache__/**']).map(p => p.replace(/\\/g, '/'));
|
|
26
|
-
const normalizedCwd = context.cwd.replace(/\\/g, '/');
|
|
27
|
-
|
|
28
|
-
// Find all supported files
|
|
29
|
-
const files = await globby(patterns, {
|
|
30
|
-
cwd: normalizedCwd,
|
|
31
|
-
ignore: ignore,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
for (const file of files) {
|
|
35
|
-
const handler = this.handlers.find(h => h.supports(file));
|
|
36
|
-
if (!handler) continue;
|
|
37
|
-
|
|
38
|
-
const fullPath = path.join(context.cwd, file);
|
|
39
|
-
try {
|
|
40
|
-
const content = await fs.readFile(fullPath, 'utf-8');
|
|
41
|
-
const gateFailures = await handler.run({
|
|
42
|
-
cwd: context.cwd,
|
|
43
|
-
file: file,
|
|
44
|
-
content
|
|
45
|
-
});
|
|
46
|
-
failures.push(...gateFailures);
|
|
47
|
-
} catch (error: any) {
|
|
48
|
-
// Individual file read failures shouldn't crash the whole run
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return failures;
|
|
53
|
-
}
|
|
54
|
-
}
|
package/src/gates/base.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { GoldenRecord } from '../services/context-engine.js';
|
|
2
|
-
import { Failure, Severity } from '../types/index.js';
|
|
3
|
-
|
|
4
|
-
export interface GateContext {
|
|
5
|
-
cwd: string;
|
|
6
|
-
record?: GoldenRecord;
|
|
7
|
-
ignore?: string[];
|
|
8
|
-
patterns?: string[];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export abstract class Gate {
|
|
12
|
-
constructor(public readonly id: string, public readonly title: string) { }
|
|
13
|
-
|
|
14
|
-
abstract run(context: GateContext): Promise<Failure[]>;
|
|
15
|
-
|
|
16
|
-
protected createFailure(details: string, files?: string[], hint?: string, title?: string, line?: number, endLine?: number, severity?: Severity): Failure {
|
|
17
|
-
return {
|
|
18
|
-
id: this.id,
|
|
19
|
-
title: title || this.title,
|
|
20
|
-
details,
|
|
21
|
-
severity: severity || 'medium',
|
|
22
|
-
files,
|
|
23
|
-
line,
|
|
24
|
-
endLine,
|
|
25
|
-
hint,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
CheckpointGate,
|
|
4
|
-
recordCheckpoint,
|
|
5
|
-
getCheckpointSession,
|
|
6
|
-
clearCheckpointSession,
|
|
7
|
-
getOrCreateCheckpointSession,
|
|
8
|
-
completeCheckpointSession,
|
|
9
|
-
abortCheckpointSession
|
|
10
|
-
} from './checkpoint.js';
|
|
11
|
-
import * as fs from 'fs';
|
|
12
|
-
import * as path from 'path';
|
|
13
|
-
import * as os from 'os';
|
|
14
|
-
|
|
15
|
-
describe('CheckpointGate', () => {
|
|
16
|
-
let testDir: string;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'checkpoint-test-'));
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
clearCheckpointSession(testDir);
|
|
24
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('gate initialization', () => {
|
|
28
|
-
it('should create gate with default config', () => {
|
|
29
|
-
const gate = new CheckpointGate();
|
|
30
|
-
expect(gate.id).toBe('checkpoint');
|
|
31
|
-
expect(gate.title).toBe('Checkpoint Supervision');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('should skip when not enabled', async () => {
|
|
35
|
-
const gate = new CheckpointGate({ enabled: false });
|
|
36
|
-
const failures = await gate.run({ cwd: testDir });
|
|
37
|
-
expect(failures).toEqual([]);
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe('session management', () => {
|
|
42
|
-
it('should create a new session', () => {
|
|
43
|
-
const session = getOrCreateCheckpointSession(testDir);
|
|
44
|
-
expect(session.sessionId).toMatch(/^chk-session-/);
|
|
45
|
-
expect(session.status).toBe('active');
|
|
46
|
-
expect(session.checkpoints).toHaveLength(0);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should record a checkpoint', () => {
|
|
50
|
-
const result = recordCheckpoint(
|
|
51
|
-
testDir,
|
|
52
|
-
25, // progressPct
|
|
53
|
-
['src/api/users.ts'],
|
|
54
|
-
'Implemented user API',
|
|
55
|
-
85 // qualityScore
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
expect(result.continue).toBe(true);
|
|
59
|
-
expect(result.checkpoint.progressPct).toBe(25);
|
|
60
|
-
expect(result.checkpoint.qualityScore).toBe(85);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should persist session to disk', () => {
|
|
64
|
-
recordCheckpoint(testDir, 50, [], 'Test', 90);
|
|
65
|
-
const sessionPath = path.join(testDir, '.rigour', 'checkpoint-session.json');
|
|
66
|
-
expect(fs.existsSync(sessionPath)).toBe(true);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should complete session', () => {
|
|
70
|
-
getOrCreateCheckpointSession(testDir);
|
|
71
|
-
completeCheckpointSession(testDir);
|
|
72
|
-
const session = getCheckpointSession(testDir);
|
|
73
|
-
expect(session?.status).toBe('completed');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('should abort session with reason', () => {
|
|
77
|
-
getOrCreateCheckpointSession(testDir);
|
|
78
|
-
abortCheckpointSession(testDir, 'Quality too low');
|
|
79
|
-
const session = getCheckpointSession(testDir);
|
|
80
|
-
expect(session?.status).toBe('aborted');
|
|
81
|
-
expect(session?.checkpoints[0].summary).toContain('Quality too low');
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe('quality threshold', () => {
|
|
86
|
-
it('should continue when quality above threshold', () => {
|
|
87
|
-
const result = recordCheckpoint(testDir, 50, [], 'Good work', 85);
|
|
88
|
-
expect(result.continue).toBe(true);
|
|
89
|
-
expect(result.warnings).toHaveLength(0);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should stop when quality below threshold', () => {
|
|
93
|
-
const result = recordCheckpoint(testDir, 50, [], 'Poor work', 70);
|
|
94
|
-
expect(result.continue).toBe(false);
|
|
95
|
-
expect(result.warnings).toContain('Quality score 70% is below threshold 80%');
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('drift detection', () => {
|
|
100
|
-
it('should detect quality degradation', () => {
|
|
101
|
-
// Record several checkpoints with declining quality
|
|
102
|
-
recordCheckpoint(testDir, 20, [], 'Start', 95);
|
|
103
|
-
recordCheckpoint(testDir, 40, [], 'Middle', 90);
|
|
104
|
-
const result = recordCheckpoint(testDir, 60, [], 'Decline', 75);
|
|
105
|
-
|
|
106
|
-
expect(result.warnings.some(w => w.includes('Drift detected'))).toBe(true);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should not flag stable quality', () => {
|
|
110
|
-
recordCheckpoint(testDir, 20, [], 'Start', 85);
|
|
111
|
-
recordCheckpoint(testDir, 40, [], 'Middle', 85);
|
|
112
|
-
const result = recordCheckpoint(testDir, 60, [], 'Stable', 85);
|
|
113
|
-
|
|
114
|
-
expect(result.warnings.filter(w => w.includes('Drift'))).toHaveLength(0);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe('gate run', () => {
|
|
119
|
-
it('should pass with healthy checkpoints', async () => {
|
|
120
|
-
const gate = new CheckpointGate({ enabled: true, quality_threshold: 80 });
|
|
121
|
-
recordCheckpoint(testDir, 50, [], 'Good work', 90);
|
|
122
|
-
|
|
123
|
-
const failures = await gate.run({ cwd: testDir });
|
|
124
|
-
expect(failures).toHaveLength(0);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('should fail when quality below threshold', async () => {
|
|
128
|
-
const gate = new CheckpointGate({ enabled: true, quality_threshold: 80 });
|
|
129
|
-
recordCheckpoint(testDir, 50, [], 'Poor work', 70);
|
|
130
|
-
|
|
131
|
-
const failures = await gate.run({ cwd: testDir });
|
|
132
|
-
expect(failures.some(f => f.title === 'Quality Below Threshold')).toBe(true);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
});
|