@rigour-labs/core 1.7.0 → 2.1.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 (62) hide show
  1. package/dist/context.test.d.ts +1 -0
  2. package/dist/context.test.js +61 -0
  3. package/dist/discovery.js +12 -19
  4. package/dist/environment.test.d.ts +1 -0
  5. package/dist/environment.test.js +97 -0
  6. package/dist/gates/ast-handlers/base.d.ts +12 -0
  7. package/dist/gates/ast-handlers/base.js +6 -0
  8. package/dist/gates/ast-handlers/python.d.ts +6 -0
  9. package/dist/gates/ast-handlers/python.js +64 -0
  10. package/dist/gates/ast-handlers/typescript.d.ts +9 -0
  11. package/dist/gates/ast-handlers/typescript.js +110 -0
  12. package/dist/gates/ast-handlers/universal.d.ts +8 -0
  13. package/dist/gates/ast-handlers/universal.js +156 -0
  14. package/dist/gates/ast.d.ts +1 -3
  15. package/dist/gates/ast.js +34 -110
  16. package/dist/gates/base.d.ts +4 -0
  17. package/dist/gates/base.js +1 -5
  18. package/dist/gates/content.js +9 -9
  19. package/dist/gates/context.d.ts +8 -0
  20. package/dist/gates/context.js +43 -0
  21. package/dist/gates/coverage.d.ts +8 -0
  22. package/dist/gates/coverage.js +62 -0
  23. package/dist/gates/dependency.js +7 -14
  24. package/dist/gates/environment.d.ts +8 -0
  25. package/dist/gates/environment.js +73 -0
  26. package/dist/gates/file.js +9 -9
  27. package/dist/gates/runner.d.ts +1 -1
  28. package/dist/gates/runner.js +41 -24
  29. package/dist/gates/safety.js +4 -8
  30. package/dist/gates/structure.js +6 -13
  31. package/dist/index.js +8 -26
  32. package/dist/services/context-engine.d.ts +22 -0
  33. package/dist/services/context-engine.js +78 -0
  34. package/dist/services/fix-packet-service.js +3 -7
  35. package/dist/services/state-service.js +9 -16
  36. package/dist/smoke.test.js +6 -8
  37. package/dist/templates/index.js +16 -6
  38. package/dist/types/fix-packet.js +22 -25
  39. package/dist/types/index.d.ts +151 -4
  40. package/dist/types/index.js +67 -56
  41. package/dist/utils/logger.js +8 -15
  42. package/dist/utils/scanner.js +13 -16
  43. package/package.json +6 -2
  44. package/src/context.test.ts +73 -0
  45. package/src/environment.test.ts +115 -0
  46. package/src/gates/ast-handlers/base.ts +13 -0
  47. package/src/gates/ast-handlers/python.ts +71 -0
  48. package/src/gates/ast-handlers/python_parser.py +60 -0
  49. package/src/gates/ast-handlers/typescript.ts +125 -0
  50. package/src/gates/ast-handlers/universal.ts +184 -0
  51. package/src/gates/ast.ts +32 -128
  52. package/src/gates/base.ts +4 -0
  53. package/src/gates/content.ts +5 -1
  54. package/src/gates/context.ts +55 -0
  55. package/src/gates/coverage.ts +70 -0
  56. package/src/gates/environment.ts +94 -0
  57. package/src/gates/file.ts +5 -1
  58. package/src/gates/runner.ts +27 -2
  59. package/src/services/context-engine.ts +104 -0
  60. package/src/templates/index.ts +13 -0
  61. package/src/types/index.ts +18 -0
  62. package/src/utils/scanner.ts +9 -4
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,61 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import { GateRunner } from '../src/gates/runner.js';
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const TEST_CWD = path.join(__dirname, '../temp-test-context');
8
+ describe('Context Awareness Engine', () => {
9
+ beforeAll(async () => {
10
+ await fs.ensureDir(TEST_CWD);
11
+ });
12
+ afterAll(async () => {
13
+ await fs.remove(TEST_CWD);
14
+ });
15
+ it('should detect context drift for redundant env suffixes (Golden Example)', async () => {
16
+ // Setup: Define standard GCP_PROJECT_ID
17
+ await fs.writeFile(path.join(TEST_CWD, '.env.example'), 'GCP_PROJECT_ID=my-project\n');
18
+ // Setup: Use drifted GCP_PROJECT_ID_PRODUCTION
19
+ await fs.writeFile(path.join(TEST_CWD, 'feature.js'), `
20
+ const id = process.env.GCP_PROJECT_ID_PRODUCTION;
21
+ console.log(id);
22
+ `);
23
+ const config = {
24
+ version: 1,
25
+ commands: {},
26
+ gates: {
27
+ context: {
28
+ enabled: true,
29
+ sensitivity: 0.8,
30
+ mining_depth: 10,
31
+ },
32
+ },
33
+ output: { report_path: 'rigour-report.json' }
34
+ };
35
+ const runner = new GateRunner(config);
36
+ const report = await runner.run(TEST_CWD);
37
+ const driftFailures = report.failures.filter(f => f.id === 'context-drift');
38
+ expect(driftFailures.length).toBeGreaterThan(0);
39
+ expect(driftFailures[0].details).toContain('GCP_PROJECT_ID_PRODUCTION');
40
+ expect(driftFailures[0].hint).toContain('GCP_PROJECT_ID');
41
+ });
42
+ it('should not flag valid environment variables', async () => {
43
+ await fs.writeFile(path.join(TEST_CWD, 'valid.js'), `
44
+ const id = process.env.GCP_PROJECT_ID;
45
+ `);
46
+ const config = {
47
+ version: 1,
48
+ commands: {},
49
+ gates: {
50
+ context: { enabled: true },
51
+ },
52
+ output: { report_path: 'rigour-report.json' }
53
+ };
54
+ const runner = new GateRunner(config);
55
+ const report = await runner.run(TEST_CWD);
56
+ const driftFailures = report.failures.filter(f => f.id === 'context-drift');
57
+ // Filter out failures from other files if they still exist in TEST_CWD
58
+ const specificFailures = driftFailures.filter(f => f.files?.includes('valid.js'));
59
+ expect(specificFailures.length).toBe(0);
60
+ });
61
+ });
package/dist/discovery.js CHANGED
@@ -1,18 +1,12 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DiscoveryService = void 0;
7
- const fs_extra_1 = __importDefault(require("fs-extra"));
8
- const path_1 = __importDefault(require("path"));
9
- const index_js_1 = require("./templates/index.js");
10
- class DiscoveryService {
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { TEMPLATES, PARADIGM_TEMPLATES, UNIVERSAL_CONFIG } from './templates/index.js';
4
+ export class DiscoveryService {
11
5
  async discover(cwd) {
12
- let config = { ...index_js_1.UNIVERSAL_CONFIG };
6
+ let config = { ...UNIVERSAL_CONFIG };
13
7
  const matches = {};
14
8
  // 1. Detect Role (ui, api, infra, data)
15
- for (const template of index_js_1.TEMPLATES) {
9
+ for (const template of TEMPLATES) {
16
10
  const marker = await this.findFirstMarker(cwd, template.markers, true); // Search content for roles too
17
11
  if (marker) {
18
12
  config = this.mergeConfig(config, template.config);
@@ -21,7 +15,7 @@ class DiscoveryService {
21
15
  }
22
16
  }
23
17
  // 2. Detect Paradigm (oop, functional)
24
- for (const template of index_js_1.PARADIGM_TEMPLATES) {
18
+ for (const template of PARADIGM_TEMPLATES) {
25
19
  const marker = await this.findFirstMarker(cwd, template.markers, true); // Search content
26
20
  if (marker) {
27
21
  config = this.mergeConfig(config, template.config);
@@ -42,9 +36,9 @@ class DiscoveryService {
42
36
  }
43
37
  async findFirstMarker(cwd, markers, searchContent = false) {
44
38
  for (const marker of markers) {
45
- const fullPath = path_1.default.join(cwd, marker);
39
+ const fullPath = path.join(cwd, marker);
46
40
  // File/Directory existence check
47
- if (await fs_extra_1.default.pathExists(fullPath)) {
41
+ if (await fs.pathExists(fullPath)) {
48
42
  return marker;
49
43
  }
50
44
  // Deep content check for paradigms
@@ -60,7 +54,7 @@ class DiscoveryService {
60
54
  // Simple heuristic: search in top 5 source files
61
55
  const files = await this.findSourceFiles(cwd);
62
56
  for (const file of files) {
63
- const content = await fs_extra_1.default.readFile(file, 'utf-8');
57
+ const content = await fs.readFile(file, 'utf-8');
64
58
  if (content.includes(pattern))
65
59
  return true;
66
60
  }
@@ -70,10 +64,10 @@ class DiscoveryService {
70
64
  // Find a few files to sample
71
65
  const extensions = ['.ts', '.js', '.py', '.go', '.java', '.tf', 'package.json'];
72
66
  const samples = [];
73
- const files = await fs_extra_1.default.readdir(cwd);
67
+ const files = await fs.readdir(cwd);
74
68
  for (const file of files) {
75
69
  if (extensions.some(ext => file.endsWith(ext))) {
76
- samples.push(path_1.default.join(cwd, file));
70
+ samples.push(path.join(cwd, file));
77
71
  if (samples.length >= 5)
78
72
  break;
79
73
  }
@@ -81,4 +75,3 @@ class DiscoveryService {
81
75
  return samples;
82
76
  }
83
77
  }
84
- exports.DiscoveryService = DiscoveryService;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,97 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { GateRunner } from './gates/runner.js';
3
+ import { ConfigSchema } from './types/index.js';
4
+ import fs from 'fs-extra';
5
+ import path from 'path';
6
+ describe('Environment Alignment Gate', () => {
7
+ const testDir = path.join(process.cwd(), 'temp-test-env');
8
+ beforeEach(async () => {
9
+ await fs.ensureDir(testDir);
10
+ });
11
+ afterEach(async () => {
12
+ await fs.remove(testDir);
13
+ });
14
+ it('should detect tool version mismatch (Explicit)', async () => {
15
+ const rawConfig = {
16
+ version: 1,
17
+ gates: {
18
+ environment: {
19
+ enabled: true,
20
+ enforce_contracts: false,
21
+ tools: {
22
+ node: ">=99.0.0" // Guaranteed to fail
23
+ }
24
+ }
25
+ }
26
+ };
27
+ const config = ConfigSchema.parse(rawConfig);
28
+ const runner = new GateRunner(config);
29
+ const report = await runner.run(testDir);
30
+ expect(report.status).toBe('FAIL');
31
+ const envFailure = report.failures.find(f => f.id === 'environment-alignment');
32
+ expect(envFailure).toBeDefined();
33
+ expect(envFailure?.details).toContain('node');
34
+ expect(envFailure?.details).toContain('version mismatch');
35
+ });
36
+ it('should detect missing environment variables', async () => {
37
+ const rawConfig = {
38
+ version: 1,
39
+ gates: {
40
+ environment: {
41
+ enabled: true,
42
+ required_env: ["RIGOUR_TEST_VAR_MISSING"]
43
+ }
44
+ }
45
+ };
46
+ const config = ConfigSchema.parse(rawConfig);
47
+ const runner = new GateRunner(config);
48
+ const report = await runner.run(testDir);
49
+ expect(report.status).toBe('FAIL');
50
+ expect(report.failures[0].details).toContain('RIGOUR_TEST_VAR_MISSING');
51
+ });
52
+ it('should discover contracts from pyproject.toml', async () => {
53
+ // Create mock pyproject.toml with a version that will surely fail
54
+ await fs.writeFile(path.join(testDir, 'pyproject.toml'), `
55
+ [tool.ruff]
56
+ ruff = ">=99.14.0"
57
+ `);
58
+ const rawConfig = {
59
+ version: 1,
60
+ gates: {
61
+ environment: {
62
+ enabled: true,
63
+ enforce_contracts: true,
64
+ tools: {} // Should discover ruff from file
65
+ }
66
+ }
67
+ };
68
+ const config = ConfigSchema.parse(rawConfig);
69
+ const runner = new GateRunner(config);
70
+ const report = await runner.run(testDir);
71
+ // This might pass or fail depending on the local ruff version,
72
+ // but we want to check if the gate attempted to check ruff.
73
+ // If ruff is missing, it will fail with "is missing".
74
+ const ruffFailure = report.failures.find(f => f.details.includes('ruff'));
75
+ expect(ruffFailure).toBeDefined();
76
+ });
77
+ it('should prioritize environment gate and run it first', async () => {
78
+ const rawConfig = {
79
+ version: 1,
80
+ gates: {
81
+ max_file_lines: 1,
82
+ environment: {
83
+ enabled: true,
84
+ required_env: ["MANDATORY_SECRET_MISSING"]
85
+ }
86
+ }
87
+ };
88
+ const config = ConfigSchema.parse(rawConfig);
89
+ // Create a file that would fail max_file_lines
90
+ await fs.writeFile(path.join(testDir, 'large.js'), 'line1\nline2\nline3');
91
+ const runner = new GateRunner(config);
92
+ const report = await runner.run(testDir);
93
+ // In a real priority system, we might want to stop after environment failure.
94
+ // Currently GateRunner runs all, but environment-alignment has been unshifted.
95
+ expect(Object.keys(report.summary)[0]).toBe('environment-alignment');
96
+ });
97
+ });
@@ -0,0 +1,12 @@
1
+ import { Failure, Gates } from '../../types/index.js';
2
+ export interface ASTHandlerContext {
3
+ cwd: string;
4
+ file: string;
5
+ content: string;
6
+ }
7
+ export declare abstract class ASTHandler {
8
+ protected config: Gates;
9
+ constructor(config: Gates);
10
+ abstract supports(file: string): boolean;
11
+ abstract run(context: ASTHandlerContext): Promise<Failure[]>;
12
+ }
@@ -0,0 +1,6 @@
1
+ export class ASTHandler {
2
+ config;
3
+ constructor(config) {
4
+ this.config = config;
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ import { ASTHandler, ASTHandlerContext } from './base.js';
2
+ import { Failure } from '../../types/index.js';
3
+ export declare class PythonHandler extends ASTHandler {
4
+ supports(file: string): boolean;
5
+ run(context: ASTHandlerContext): Promise<Failure[]>;
6
+ }
@@ -0,0 +1,64 @@
1
+ import { execa } from 'execa';
2
+ import { ASTHandler } from './base.js';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ export class PythonHandler extends ASTHandler {
7
+ supports(file) {
8
+ return /\.py$/.test(file);
9
+ }
10
+ async run(context) {
11
+ const failures = [];
12
+ const scriptPath = path.join(__dirname, 'python_parser.py');
13
+ try {
14
+ const { stdout } = await execa('python3', [scriptPath], {
15
+ input: context.content,
16
+ cwd: context.cwd
17
+ });
18
+ const metrics = JSON.parse(stdout);
19
+ if (metrics.error)
20
+ return [];
21
+ const astConfig = this.config.ast || {};
22
+ const maxComplexity = astConfig.complexity || 10;
23
+ const maxParams = astConfig.max_params || 5;
24
+ const maxMethods = astConfig.max_methods || 10;
25
+ for (const item of metrics) {
26
+ if (item.type === 'function') {
27
+ if (item.parameters > maxParams) {
28
+ failures.push({
29
+ id: 'AST_MAX_PARAMS',
30
+ title: `Function '${item.name}' has ${item.parameters} parameters (max: ${maxParams})`,
31
+ details: `High parameter count detected in ${context.file} at line ${item.lineno}`,
32
+ files: [context.file],
33
+ hint: `Reduce number of parameters or use an options object.`
34
+ });
35
+ }
36
+ if (item.complexity > maxComplexity) {
37
+ failures.push({
38
+ id: 'AST_COMPLEXITY',
39
+ title: `Function '${item.name}' has complexity of ${item.complexity} (max: ${maxComplexity})`,
40
+ details: `High complexity detected in ${context.file} at line ${item.lineno}`,
41
+ files: [context.file],
42
+ hint: `Refactor '${item.name}' into smaller, more focused functions.`
43
+ });
44
+ }
45
+ }
46
+ else if (item.type === 'class') {
47
+ if (item.methods > maxMethods) {
48
+ failures.push({
49
+ id: 'AST_MAX_METHODS',
50
+ title: `Class '${item.name}' has ${item.methods} methods (max: ${maxMethods})`,
51
+ details: `God Object pattern detected in ${context.file} at line ${item.lineno}`,
52
+ files: [context.file],
53
+ hint: `Split class '${item.name}' into smaller services.`
54
+ });
55
+ }
56
+ }
57
+ }
58
+ }
59
+ catch (e) {
60
+ // If python3 is missing, we skip AST but other gates still run
61
+ }
62
+ return failures;
63
+ }
64
+ }
@@ -0,0 +1,9 @@
1
+ import { ASTHandler, ASTHandlerContext } from './base.js';
2
+ import { Failure } from '../../types/index.js';
3
+ export declare class TypeScriptHandler extends ASTHandler {
4
+ supports(file: string): boolean;
5
+ run(context: ASTHandlerContext): Promise<Failure[]>;
6
+ private analyzeSourceFile;
7
+ private checkBoundary;
8
+ private getNodeName;
9
+ }
@@ -0,0 +1,110 @@
1
+ import ts from 'typescript';
2
+ import { ASTHandler } from './base.js';
3
+ import micromatch from 'micromatch';
4
+ import path from 'path';
5
+ export class TypeScriptHandler extends ASTHandler {
6
+ supports(file) {
7
+ return /\.(ts|js|tsx|jsx)$/.test(file);
8
+ }
9
+ async run(context) {
10
+ const failures = [];
11
+ const sourceFile = ts.createSourceFile(context.file, context.content, ts.ScriptTarget.Latest, true);
12
+ this.analyzeSourceFile(sourceFile, context.file, failures);
13
+ return failures;
14
+ }
15
+ analyzeSourceFile(sourceFile, relativePath, failures) {
16
+ const astConfig = this.config.ast || {};
17
+ const maxComplexity = astConfig.complexity || 10;
18
+ const maxMethods = astConfig.max_methods || 10;
19
+ const maxParams = astConfig.max_params || 5;
20
+ const visit = (node) => {
21
+ if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isArrowFunction(node)) {
22
+ const name = this.getNodeName(node);
23
+ if (node.parameters.length > maxParams) {
24
+ failures.push({
25
+ id: 'AST_MAX_PARAMS',
26
+ title: `Function '${name}' has ${node.parameters.length} parameters (max: ${maxParams})`,
27
+ details: `High parameter count detected in ${relativePath}`,
28
+ files: [relativePath],
29
+ hint: `Reduce number of parameters or use an options object.`
30
+ });
31
+ }
32
+ let complexity = 1;
33
+ const countComplexity = (n) => {
34
+ if (ts.isIfStatement(n) || ts.isCaseClause(n) || ts.isDefaultClause(n) ||
35
+ ts.isForStatement(n) || ts.isForInStatement(n) || ts.isForOfStatement(n) ||
36
+ ts.isWhileStatement(n) || ts.isDoStatement(n) || ts.isConditionalExpression(n)) {
37
+ complexity++;
38
+ }
39
+ if (ts.isBinaryExpression(n)) {
40
+ if (n.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
41
+ n.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
42
+ complexity++;
43
+ }
44
+ }
45
+ ts.forEachChild(n, countComplexity);
46
+ };
47
+ ts.forEachChild(node, countComplexity);
48
+ if (complexity > maxComplexity) {
49
+ failures.push({
50
+ id: 'AST_COMPLEXITY',
51
+ title: `Function '${name}' has cyclomatic complexity of ${complexity} (max: ${maxComplexity})`,
52
+ details: `High complexity detected in ${relativePath}`,
53
+ files: [relativePath],
54
+ hint: `Refactor '${name}' into smaller, more focused functions.`
55
+ });
56
+ }
57
+ }
58
+ if (ts.isClassDeclaration(node)) {
59
+ const name = node.name?.text || 'Anonymous Class';
60
+ const methods = node.members.filter(ts.isMethodDeclaration);
61
+ if (methods.length > maxMethods) {
62
+ failures.push({
63
+ id: 'AST_MAX_METHODS',
64
+ title: `Class '${name}' has ${methods.length} methods (max: ${maxMethods})`,
65
+ details: `God Object pattern detected in ${relativePath}`,
66
+ files: [relativePath],
67
+ hint: `Class '${name}' is becoming too large. Split it into smaller services.`
68
+ });
69
+ }
70
+ }
71
+ if (ts.isImportDeclaration(node)) {
72
+ const importPath = node.moduleSpecifier.text;
73
+ this.checkBoundary(importPath, relativePath, failures);
74
+ }
75
+ ts.forEachChild(node, visit);
76
+ };
77
+ ts.forEachChild(sourceFile, visit);
78
+ }
79
+ checkBoundary(importPath, relativePath, failures) {
80
+ const boundaries = this.config.architecture?.boundaries || [];
81
+ for (const rule of boundaries) {
82
+ if (micromatch.isMatch(relativePath, rule.from)) {
83
+ const resolved = importPath.startsWith('.')
84
+ ? path.join(path.dirname(relativePath), importPath)
85
+ : importPath;
86
+ if (rule.mode === 'deny' && micromatch.isMatch(resolved, rule.to)) {
87
+ failures.push({
88
+ id: 'ARCH_BOUNDARY',
89
+ title: `Architectural Violation`,
90
+ details: `'${relativePath}' is forbidden from importing '${importPath}' (denied by boundary rule).`,
91
+ files: [relativePath],
92
+ hint: `Remove this import to maintain architectural layering.`
93
+ });
94
+ }
95
+ }
96
+ }
97
+ }
98
+ getNodeName(node) {
99
+ if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
100
+ return node.name?.getText() || 'anonymous';
101
+ }
102
+ if (ts.isArrowFunction(node)) {
103
+ const parent = node.parent;
104
+ if (ts.isVariableDeclaration(parent))
105
+ return parent.name.getText();
106
+ return 'anonymous arrow';
107
+ }
108
+ return 'unknown';
109
+ }
110
+ }
@@ -0,0 +1,8 @@
1
+ import { ASTHandler, ASTHandlerContext } from './base.js';
2
+ import { Failure } from '../../types/index.js';
3
+ export declare class UniversalASTHandler extends ASTHandler {
4
+ private parser?;
5
+ private languages;
6
+ supports(file: string): boolean;
7
+ run(context: ASTHandlerContext): Promise<Failure[]>;
8
+ }
@@ -0,0 +1,156 @@
1
+ import * as _Parser from 'web-tree-sitter';
2
+ const Parser = _Parser.default || _Parser;
3
+ import { ASTHandler } from './base.js';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ export class UniversalASTHandler extends ASTHandler {
8
+ parser;
9
+ languages = {
10
+ '.go': {
11
+ grammarPath: '../../vendor/grammars/tree-sitter-go.wasm',
12
+ extensions: ['.go'],
13
+ queries: {
14
+ complexity: '(if_statement) (for_statement) (select_statement) (case_clause)',
15
+ nesting: '(if_statement (block . (if_statement))) (for_statement (block . (for_statement)))',
16
+ parameters: '(parameter_list (parameter_declaration) @param)',
17
+ methods: '(method_declaration) @method (function_declaration) @method',
18
+ securitySinks: '(call_expression function: (selector_expression field: (field_identifier) @id (#match? @id "^(Command|exec|System)$")))',
19
+ ecosystemBlunders: '(if_statement !condition: (binary_expression left: (identifier) @err (#eq? @err "err")))' // Missing err check
20
+ }
21
+ },
22
+ '.py': {
23
+ grammarPath: '../../vendor/grammars/tree-sitter-python.wasm',
24
+ extensions: ['.py'],
25
+ queries: {
26
+ complexity: '(if_statement) (for_statement) (while_statement) (with_statement)',
27
+ nesting: '(if_statement (block (if_statement)))',
28
+ parameters: '(parameters (identifier) @param)',
29
+ methods: '(function_definition) @method',
30
+ securitySinks: '(call_expression function: (identifier) @func (#match? @func "^(eval|exec|os\\.system)$"))',
31
+ ecosystemBlunders: '(parameters (default_parameter value: (list) @mutable))' // Mutable default
32
+ }
33
+ },
34
+ '.java': {
35
+ grammarPath: '../../vendor/grammars/tree-sitter-java.wasm',
36
+ extensions: ['.java'],
37
+ queries: {
38
+ complexity: '(if_statement) (for_statement) (while_statement) (switch_label)',
39
+ nesting: '(if_statement (block (if_statement))) (for_statement (block (for_statement)))',
40
+ parameters: '(formal_parameters (formal_parameter) @param)',
41
+ methods: '(method_declaration) @method',
42
+ securitySinks: '(method_declaration (modifiers (native))) @native (method_invocation name: (identifier) @name (#match? @name "^(exec|System\\.load)$"))',
43
+ ecosystemBlunders: '(catch_clause body: (block . ))' // Empty catch
44
+ }
45
+ },
46
+ '.rs': {
47
+ grammarPath: '../../vendor/grammars/tree-sitter-rust.wasm',
48
+ extensions: ['.rs'],
49
+ queries: {
50
+ complexity: '(if_expression) (for_expression) (while_expression) (loop_expression) (match_arm)',
51
+ nesting: '(if_expression (block (if_expression))) (for_expression (block (for_expression)))',
52
+ parameters: '(parameters (parameter) @param)',
53
+ methods: '(impl_item (function_item)) @method (function_item) @method',
54
+ securitySinks: '(unsafe_block) @unsafe',
55
+ ecosystemBlunders: '(call_expression function: (field_expression field: (field_identifier) @id (#eq? @id "unwrap")))' // .unwrap()
56
+ }
57
+ },
58
+ '.cs': {
59
+ grammarPath: '../../vendor/grammars/tree-sitter-c_sharp.wasm',
60
+ extensions: ['.cs'],
61
+ queries: {
62
+ complexity: '(if_statement) (for_statement) (foreach_statement) (while_statement) (switch_section)',
63
+ nesting: '(if_statement (block (if_statement))) (for_statement (block (for_statement)))',
64
+ parameters: '(parameter_list (parameter) @param)',
65
+ methods: '(method_declaration) @method',
66
+ securitySinks: '(attribute name: (identifier) @attr (#eq? @attr "DllImport")) @violation'
67
+ }
68
+ },
69
+ '.cpp': {
70
+ grammarPath: '../../vendor/grammars/tree-sitter-cpp.wasm',
71
+ extensions: ['.cpp', '.cc', '.cxx', '.h', '.hpp'],
72
+ queries: {
73
+ complexity: '(if_statement) (for_statement) (while_statement) (case_statement)',
74
+ nesting: '(if_statement (compound_statement (if_statement)))',
75
+ parameters: '(parameter_list (parameter_declaration) @param)',
76
+ methods: '(function_definition) @method',
77
+ securitySinks: '(call_expression function: (identifier) @name (#match? @name "^(malloc|free|system|popen)$"))'
78
+ }
79
+ }
80
+ };
81
+ supports(file) {
82
+ const ext = path.extname(file).toLowerCase();
83
+ return ext in this.languages;
84
+ }
85
+ async run(context) {
86
+ const failures = [];
87
+ const ext = path.extname(context.file).toLowerCase();
88
+ const config = this.languages[ext];
89
+ if (!config)
90
+ return [];
91
+ if (!this.parser) {
92
+ await Parser.init();
93
+ this.parser = new Parser();
94
+ }
95
+ try {
96
+ const Lang = await Parser.Language.load(path.resolve(__dirname, config.grammarPath));
97
+ this.parser.setLanguage(Lang);
98
+ const tree = this.parser.parse(context.content);
99
+ const astConfig = this.config.ast || {};
100
+ // 1. Structural Methods Audit
101
+ const methodQuery = Lang.query(config.queries.methods);
102
+ const methodMatches = methodQuery.matches(tree.rootNode);
103
+ for (const match of methodMatches) {
104
+ for (const capture of match.captures) {
105
+ const node = capture.node;
106
+ const name = node.childForFieldName('name')?.text || 'anonymous';
107
+ // SME: Cognitive Complexity (Nesting depth + Cyclomatic)
108
+ const nesting = Lang.query(config.queries.nesting).captures(node).length;
109
+ const cyclomatic = Lang.query(config.queries.complexity).captures(node).length + 1;
110
+ const cognitive = cyclomatic + (nesting * 2);
111
+ if (cognitive > (astConfig.complexity || 10)) {
112
+ failures.push({
113
+ id: 'SME_COGNITIVE_LOAD',
114
+ title: `Method '${name}' has high cognitive load (${cognitive})`,
115
+ details: `Deeply nested or complex logic detected in ${context.file}.`,
116
+ files: [context.file],
117
+ hint: `Flatten logical branches and extract nested loops.`
118
+ });
119
+ }
120
+ }
121
+ }
122
+ // 2. Security Sinks
123
+ if (config.queries.securitySinks) {
124
+ const securityQuery = Lang.query(config.queries.securitySinks);
125
+ const sinks = securityQuery.captures(tree.rootNode);
126
+ for (const capture of sinks) {
127
+ failures.push({
128
+ id: 'SME_SECURITY_SINK',
129
+ title: `Unsafe function call detected: ${capture.node.text}`,
130
+ details: `Potentially dangerous execution in ${context.file}.`,
131
+ files: [context.file],
132
+ hint: `Avoid using shell execution or eval. Use safe alternatives.`
133
+ });
134
+ }
135
+ }
136
+ // 3. Ecosystem Blunders
137
+ if (config.queries.ecosystemBlunders) {
138
+ const blunderQuery = Lang.query(config.queries.ecosystemBlunders);
139
+ const blunders = blunderQuery.captures(tree.rootNode);
140
+ for (const capture of blunders) {
141
+ failures.push({
142
+ id: 'SME_BEST_PRACTICE',
143
+ title: `Ecosystem anti-pattern detected`,
144
+ details: `Violation of ${ext} best practices in ${context.file}.`,
145
+ files: [context.file],
146
+ hint: `Review language-specific best practices (e.g., error handling or mutable defaults).`
147
+ });
148
+ }
149
+ }
150
+ }
151
+ catch (e) {
152
+ // Parser skip
153
+ }
154
+ return failures;
155
+ }
156
+ }
@@ -2,9 +2,7 @@ import { Gate, GateContext } from './base.js';
2
2
  import { Failure, Gates } from '../types/index.js';
3
3
  export declare class ASTGate extends Gate {
4
4
  private config;
5
+ private handlers;
5
6
  constructor(config: Gates);
6
7
  run(context: GateContext): Promise<Failure[]>;
7
- private analyzeSourceFile;
8
- private checkBoundary;
9
- private getNodeName;
10
8
  }