@rigour-labs/core 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.
Files changed (101) hide show
  1. package/README.md +58 -0
  2. package/dist/context.test.js +2 -3
  3. package/dist/environment.test.js +2 -1
  4. package/dist/gates/agent-team.d.ts +2 -1
  5. package/dist/gates/agent-team.js +1 -0
  6. package/dist/gates/base.d.ts +4 -2
  7. package/dist/gates/base.js +5 -1
  8. package/dist/gates/checkpoint.d.ts +2 -1
  9. package/dist/gates/checkpoint.js +3 -2
  10. package/dist/gates/content.js +1 -1
  11. package/dist/gates/context-window-artifacts.d.ts +34 -0
  12. package/dist/gates/context-window-artifacts.js +214 -0
  13. package/dist/gates/context.d.ts +2 -1
  14. package/dist/gates/context.js +4 -3
  15. package/dist/gates/coverage.js +3 -1
  16. package/dist/gates/dependency.js +5 -5
  17. package/dist/gates/duplication-drift.d.ts +33 -0
  18. package/dist/gates/duplication-drift.js +190 -0
  19. package/dist/gates/environment.js +4 -4
  20. package/dist/gates/file.js +1 -1
  21. package/dist/gates/hallucinated-imports.d.ts +63 -0
  22. package/dist/gates/hallucinated-imports.js +406 -0
  23. package/dist/gates/inconsistent-error-handling.d.ts +39 -0
  24. package/dist/gates/inconsistent-error-handling.js +236 -0
  25. package/dist/gates/promise-safety.d.ts +68 -0
  26. package/dist/gates/promise-safety.js +509 -0
  27. package/dist/gates/retry-loop-breaker.d.ts +2 -1
  28. package/dist/gates/retry-loop-breaker.js +2 -1
  29. package/dist/gates/runner.js +62 -1
  30. package/dist/gates/safety.d.ts +2 -1
  31. package/dist/gates/safety.js +2 -1
  32. package/dist/gates/security-patterns.d.ts +2 -1
  33. package/dist/gates/security-patterns.js +2 -1
  34. package/dist/gates/structure.js +1 -1
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +1 -0
  37. package/dist/services/fix-packet-service.d.ts +0 -1
  38. package/dist/services/fix-packet-service.js +9 -14
  39. package/dist/services/score-history.d.ts +54 -0
  40. package/dist/services/score-history.js +122 -0
  41. package/dist/templates/index.js +195 -0
  42. package/dist/types/fix-packet.d.ts +5 -5
  43. package/dist/types/fix-packet.js +1 -1
  44. package/dist/types/index.d.ts +430 -0
  45. package/dist/types/index.js +57 -0
  46. package/package.json +21 -1
  47. package/src/context.test.ts +0 -256
  48. package/src/discovery.test.ts +0 -88
  49. package/src/discovery.ts +0 -112
  50. package/src/environment.test.ts +0 -115
  51. package/src/gates/agent-team.test.ts +0 -134
  52. package/src/gates/agent-team.ts +0 -210
  53. package/src/gates/ast-handlers/base.ts +0 -13
  54. package/src/gates/ast-handlers/python.ts +0 -145
  55. package/src/gates/ast-handlers/python_parser.py +0 -181
  56. package/src/gates/ast-handlers/typescript.ts +0 -264
  57. package/src/gates/ast-handlers/universal.ts +0 -184
  58. package/src/gates/ast.ts +0 -54
  59. package/src/gates/base.ts +0 -27
  60. package/src/gates/checkpoint.test.ts +0 -135
  61. package/src/gates/checkpoint.ts +0 -311
  62. package/src/gates/content.ts +0 -50
  63. package/src/gates/context.ts +0 -267
  64. package/src/gates/coverage.ts +0 -74
  65. package/src/gates/dependency.ts +0 -108
  66. package/src/gates/environment.ts +0 -94
  67. package/src/gates/file.ts +0 -42
  68. package/src/gates/retry-loop-breaker.ts +0 -151
  69. package/src/gates/runner.ts +0 -156
  70. package/src/gates/safety.ts +0 -56
  71. package/src/gates/security-patterns.test.ts +0 -162
  72. package/src/gates/security-patterns.ts +0 -305
  73. package/src/gates/structure.ts +0 -36
  74. package/src/index.ts +0 -13
  75. package/src/pattern-index/embeddings.ts +0 -84
  76. package/src/pattern-index/index.ts +0 -59
  77. package/src/pattern-index/indexer.test.ts +0 -276
  78. package/src/pattern-index/indexer.ts +0 -1023
  79. package/src/pattern-index/matcher.test.ts +0 -293
  80. package/src/pattern-index/matcher.ts +0 -493
  81. package/src/pattern-index/overrides.ts +0 -235
  82. package/src/pattern-index/security.ts +0 -151
  83. package/src/pattern-index/staleness.test.ts +0 -313
  84. package/src/pattern-index/staleness.ts +0 -568
  85. package/src/pattern-index/types.ts +0 -339
  86. package/src/safety.test.ts +0 -53
  87. package/src/services/adaptive-thresholds.test.ts +0 -189
  88. package/src/services/adaptive-thresholds.ts +0 -275
  89. package/src/services/context-engine.ts +0 -104
  90. package/src/services/fix-packet-service.ts +0 -42
  91. package/src/services/state-service.ts +0 -138
  92. package/src/smoke.test.ts +0 -18
  93. package/src/templates/index.ts +0 -312
  94. package/src/types/fix-packet.ts +0 -32
  95. package/src/types/index.ts +0 -159
  96. package/src/utils/logger.ts +0 -43
  97. package/src/utils/scanner.test.ts +0 -37
  98. package/src/utils/scanner.ts +0 -43
  99. package/tsconfig.json +0 -10
  100. package/vitest.config.ts +0 -7
  101. package/vitest.setup.ts +0 -30
@@ -1,264 +0,0 @@
1
- import ts from 'typescript';
2
- import { ASTHandler, ASTHandlerContext } from './base.js';
3
- import { Failure } from '../../types/index.js';
4
- import micromatch from 'micromatch';
5
- import path from 'path';
6
-
7
- export class TypeScriptHandler extends ASTHandler {
8
- supports(file: string): boolean {
9
- return /\.(ts|js|tsx|jsx)$/.test(file);
10
- }
11
-
12
- async run(context: ASTHandlerContext): Promise<Failure[]> {
13
- const failures: Failure[] = [];
14
- const sourceFile = ts.createSourceFile(context.file, context.content, ts.ScriptTarget.Latest, true);
15
- this.analyzeSourceFile(sourceFile, context.file, failures);
16
- return failures;
17
- }
18
-
19
- private analyzeSourceFile(sourceFile: ts.SourceFile, relativePath: string, failures: Failure[]) {
20
- const astConfig = this.config.ast || {};
21
- const stalenessConfig = (this.config as any).staleness || {};
22
- const stalenessRules = stalenessConfig.rules || {};
23
- const maxComplexity = astConfig.complexity || 10;
24
- const maxMethods = astConfig.max_methods || 10;
25
- const maxParams = astConfig.max_params || 5;
26
-
27
- // Limit failures per file to avoid output bloat on large files
28
- const MAX_FAILURES_PER_FILE = 50;
29
- const fileFailureCount: Record<string, number> = {};
30
-
31
- const addFailure = (failure: Failure): boolean => {
32
- const ruleId = failure.id;
33
- fileFailureCount[ruleId] = (fileFailureCount[ruleId] || 0) + 1;
34
- if (fileFailureCount[ruleId] <= MAX_FAILURES_PER_FILE) {
35
- failures.push(failure);
36
- return true;
37
- }
38
- // Add summary failure once when limit is reached
39
- if (fileFailureCount[ruleId] === MAX_FAILURES_PER_FILE + 1) {
40
- failures.push({
41
- id: `${ruleId}_LIMIT_EXCEEDED`,
42
- title: `More than ${MAX_FAILURES_PER_FILE} ${ruleId} violations in ${relativePath}`,
43
- details: `Truncated output: showing first ${MAX_FAILURES_PER_FILE} violations. Consider fixing the root cause.`,
44
- files: [relativePath],
45
- hint: `This file has many violations. Fix them systematically or exclude the file if it's legacy code.`
46
- });
47
- }
48
- return false;
49
- };
50
-
51
- // Helper to check if a staleness rule is enabled
52
- const isRuleEnabled = (rule: string): boolean => {
53
- if (!stalenessConfig.enabled) return false;
54
- return stalenessRules[rule] !== false; // Enabled by default if staleness is on
55
- };
56
-
57
- const visit = (node: ts.Node) => {
58
- // === STALENESS CHECKS (Rule-based) ===
59
-
60
- // no-var: Forbid legacy 'var' keyword
61
- if (isRuleEnabled('no-var') && ts.isVariableStatement(node)) {
62
- const declarationList = node.declarationList;
63
- // NodeFlags: Let = 1, Const = 2, None = 0 (var)
64
- if ((declarationList.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) === 0) {
65
- const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
66
- addFailure({
67
- id: 'STALENESS_NO_VAR',
68
- title: `Stale 'var' keyword`,
69
- details: `Use 'const' or 'let' instead of 'var' in ${relativePath}:${line}`,
70
- files: [relativePath],
71
- line,
72
- hint: `Replace 'var' with 'const' (preferred) or 'let' for modern JavaScript.`
73
- });
74
- }
75
- }
76
-
77
- // no-commonjs: Forbid require() in favor of import
78
- if (isRuleEnabled('no-commonjs') && ts.isCallExpression(node)) {
79
- if (ts.isIdentifier(node.expression) && node.expression.text === 'require') {
80
- const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
81
- addFailure({
82
- id: 'STALENESS_NO_COMMONJS',
83
- title: `CommonJS require()`,
84
- details: `Use ES6 'import' instead of 'require()' in ${relativePath}:${line}`,
85
- files: [relativePath],
86
- line,
87
- hint: `Replace require('module') with import module from 'module'.`
88
- });
89
- }
90
- }
91
-
92
- // no-arguments: Forbid 'arguments' object (use rest params)
93
- if (isRuleEnabled('no-arguments') && ts.isIdentifier(node) && node.text === 'arguments') {
94
- // Check if it's actually the arguments keyword and not a variable named arguments
95
- const parent = node.parent;
96
- if (!ts.isVariableDeclaration(parent) && !ts.isPropertyAccessExpression(parent)) {
97
- const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
98
- addFailure({
99
- id: 'STALENESS_NO_ARGUMENTS',
100
- title: `Legacy 'arguments' object`,
101
- details: `Use rest parameters (...args) instead of 'arguments' in ${relativePath}:${line}`,
102
- files: [relativePath],
103
- line,
104
- hint: `Replace 'arguments' with rest parameters: function(...args) { }`
105
- });
106
- }
107
- }
108
-
109
- // === SECURITY CHECKS (Prototype Pollution) ===
110
-
111
- // Check for direct __proto__ access: obj.__proto__
112
- if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === '__proto__') {
113
- const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
114
- addFailure({
115
- id: 'SECURITY_PROTOTYPE_POLLUTION',
116
- title: `Direct __proto__ access`,
117
- details: `Prototype pollution vulnerability in ${relativePath}:${line}`,
118
- files: [relativePath],
119
- line,
120
- hint: `Use Object.getPrototypeOf() or Object.setPrototypeOf() instead of __proto__.`
121
- });
122
- }
123
-
124
- // Check for bracket notation __proto__ access: obj["__proto__"]
125
- if (ts.isElementAccessExpression(node) && ts.isStringLiteral(node.argumentExpression)) {
126
- const accessKey = node.argumentExpression.text;
127
- if (accessKey === '__proto__' || accessKey === 'constructor' || accessKey === 'prototype') {
128
- const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
129
- addFailure({
130
- id: 'SECURITY_PROTOTYPE_POLLUTION',
131
- title: `Unsafe bracket notation access to '${accessKey}'`,
132
- details: `Potential prototype pollution via bracket notation in ${relativePath}:${line}`,
133
- files: [relativePath],
134
- line,
135
- hint: `Block access to '${accessKey}' property when handling user input. Use allowlist for object keys.`
136
- });
137
- }
138
- }
139
-
140
- // Check for Object.assign with user-controllable input (common prototype pollution pattern)
141
- if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
142
- const propAccess = node.expression;
143
- if (ts.isIdentifier(propAccess.expression) && propAccess.expression.text === 'Object' &&
144
- ts.isIdentifier(propAccess.name) && propAccess.name.text === 'assign') {
145
- // This is Object.assign() - warn if first arg is empty object (merge pattern)
146
- if (node.arguments.length >= 2) {
147
- const firstArg = node.arguments[0];
148
- if (ts.isObjectLiteralExpression(firstArg) && firstArg.properties.length === 0) {
149
- const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
150
- addFailure({
151
- id: 'SECURITY_PROTOTYPE_POLLUTION_MERGE',
152
- title: `Object.assign() merge pattern`,
153
- details: `Object.assign({}, ...) can propagate prototype pollution in ${relativePath}:${line}`,
154
- files: [relativePath],
155
- line,
156
- hint: `Validate and sanitize source objects before merging. Block __proto__ and constructor keys.`
157
- });
158
- }
159
- }
160
- }
161
- }
162
-
163
- // === COMPLEXITY CHECKS ===
164
-
165
- if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isArrowFunction(node)) {
166
- const name = this.getNodeName(node);
167
-
168
- if (node.parameters.length > maxParams) {
169
- addFailure({
170
- id: 'AST_MAX_PARAMS',
171
- title: `Function '${name}' has ${node.parameters.length} parameters (max: ${maxParams})`,
172
- details: `High parameter count detected in ${relativePath}`,
173
- files: [relativePath],
174
- hint: `Reduce number of parameters or use an options object.`
175
- });
176
- }
177
-
178
- let complexity = 1;
179
- const countComplexity = (n: ts.Node) => {
180
- if (ts.isIfStatement(n) || ts.isCaseClause(n) || ts.isDefaultClause(n) ||
181
- ts.isForStatement(n) || ts.isForInStatement(n) || ts.isForOfStatement(n) ||
182
- ts.isWhileStatement(n) || ts.isDoStatement(n) || ts.isConditionalExpression(n)) {
183
- complexity++;
184
- }
185
- if (ts.isBinaryExpression(n)) {
186
- if (n.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
187
- n.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
188
- complexity++;
189
- }
190
- }
191
- ts.forEachChild(n, countComplexity);
192
- };
193
- ts.forEachChild(node, countComplexity);
194
-
195
- if (complexity > maxComplexity) {
196
- addFailure({
197
- id: 'AST_COMPLEXITY',
198
- title: `Function '${name}' has cyclomatic complexity of ${complexity} (max: ${maxComplexity})`,
199
- details: `High complexity detected in ${relativePath}`,
200
- files: [relativePath],
201
- hint: `Refactor '${name}' into smaller, more focused functions.`
202
- });
203
- }
204
- }
205
-
206
- if (ts.isClassDeclaration(node)) {
207
- const name = node.name?.text || 'Anonymous Class';
208
- const methods = node.members.filter(ts.isMethodDeclaration);
209
-
210
- if (methods.length > maxMethods) {
211
- addFailure({
212
- id: 'AST_MAX_METHODS',
213
- title: `Class '${name}' has ${methods.length} methods (max: ${maxMethods})`,
214
- details: `God Object pattern detected in ${relativePath}`,
215
- files: [relativePath],
216
- hint: `Class '${name}' is becoming too large. Split it into smaller services.`
217
- });
218
- }
219
- }
220
-
221
- if (ts.isImportDeclaration(node)) {
222
- const importPath = (node.moduleSpecifier as ts.StringLiteral).text;
223
- this.checkBoundary(importPath, relativePath, failures);
224
- }
225
-
226
- ts.forEachChild(node, visit);
227
- };
228
-
229
- ts.forEachChild(sourceFile, visit);
230
- }
231
-
232
- private checkBoundary(importPath: string, relativePath: string, failures: Failure[]) {
233
- const boundaries = (this.config as any).architecture?.boundaries || [];
234
- for (const rule of boundaries) {
235
- if (micromatch.isMatch(relativePath, rule.from)) {
236
- const resolved = importPath.startsWith('.')
237
- ? path.join(path.dirname(relativePath), importPath)
238
- : importPath;
239
-
240
- if (rule.mode === 'deny' && micromatch.isMatch(resolved, rule.to)) {
241
- failures.push({
242
- id: 'ARCH_BOUNDARY',
243
- title: `Architectural Violation`,
244
- details: `'${relativePath}' is forbidden from importing '${importPath}' (denied by boundary rule).`,
245
- files: [relativePath],
246
- hint: `Remove this import to maintain architectural layering.`
247
- });
248
- }
249
- }
250
- }
251
- }
252
-
253
- private getNodeName(node: ts.Node): string {
254
- if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
255
- return node.name?.getText() || 'anonymous';
256
- }
257
- if (ts.isArrowFunction(node)) {
258
- const parent = node.parent;
259
- if (ts.isVariableDeclaration(parent)) return parent.name.getText();
260
- return 'anonymous arrow';
261
- }
262
- return 'unknown';
263
- }
264
- }
@@ -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,27 +0,0 @@
1
- import { GoldenRecord } from '../services/context-engine.js';
2
- import { Failure } 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): Failure {
17
- return {
18
- id: this.id,
19
- title: title || this.title,
20
- details,
21
- files,
22
- line,
23
- endLine,
24
- hint,
25
- };
26
- }
27
- }
@@ -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
- });