@rigour-labs/core 1.7.0 → 2.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 (40) hide show
  1. package/dist/discovery.js +12 -19
  2. package/dist/gates/ast-handlers/base.d.ts +12 -0
  3. package/dist/gates/ast-handlers/base.js +6 -0
  4. package/dist/gates/ast-handlers/python.d.ts +6 -0
  5. package/dist/gates/ast-handlers/python.js +64 -0
  6. package/dist/gates/ast-handlers/typescript.d.ts +9 -0
  7. package/dist/gates/ast-handlers/typescript.js +110 -0
  8. package/dist/gates/ast-handlers/universal.d.ts +8 -0
  9. package/dist/gates/ast-handlers/universal.js +156 -0
  10. package/dist/gates/ast.d.ts +1 -3
  11. package/dist/gates/ast.js +30 -109
  12. package/dist/gates/base.js +1 -5
  13. package/dist/gates/content.js +5 -9
  14. package/dist/gates/coverage.d.ts +8 -0
  15. package/dist/gates/coverage.js +62 -0
  16. package/dist/gates/dependency.js +7 -14
  17. package/dist/gates/file.js +5 -9
  18. package/dist/gates/runner.js +22 -22
  19. package/dist/gates/safety.js +4 -8
  20. package/dist/gates/structure.js +6 -13
  21. package/dist/index.js +8 -26
  22. package/dist/services/fix-packet-service.js +3 -7
  23. package/dist/services/state-service.js +9 -16
  24. package/dist/smoke.test.js +6 -8
  25. package/dist/templates/index.js +3 -6
  26. package/dist/types/fix-packet.js +22 -25
  27. package/dist/types/index.d.ts +5 -0
  28. package/dist/types/index.js +54 -56
  29. package/dist/utils/logger.js +8 -15
  30. package/dist/utils/scanner.js +7 -14
  31. package/package.json +3 -1
  32. package/src/gates/ast-handlers/base.ts +13 -0
  33. package/src/gates/ast-handlers/python.ts +71 -0
  34. package/src/gates/ast-handlers/python_parser.py +60 -0
  35. package/src/gates/ast-handlers/typescript.ts +125 -0
  36. package/src/gates/ast-handlers/universal.ts +184 -0
  37. package/src/gates/ast.ts +27 -127
  38. package/src/gates/coverage.ts +70 -0
  39. package/src/gates/runner.ts +4 -0
  40. package/src/types/index.ts +1 -0
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,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
  }
package/dist/gates/ast.js CHANGED
@@ -1,124 +1,45 @@
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.ASTGate = void 0;
7
- const typescript_1 = __importDefault(require("typescript"));
8
- const fs_extra_1 = __importDefault(require("fs-extra"));
9
- const path_1 = __importDefault(require("path"));
10
- const globby_1 = require("globby");
11
- const base_js_1 = require("./base.js");
12
- const micromatch_1 = __importDefault(require("micromatch"));
13
- class ASTGate extends base_js_1.Gate {
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { globby } from 'globby';
4
+ import { Gate } from './base.js';
5
+ import { TypeScriptHandler } from './ast-handlers/typescript.js';
6
+ import { PythonHandler } from './ast-handlers/python.js';
7
+ import { UniversalASTHandler } from './ast-handlers/universal.js';
8
+ export class ASTGate extends Gate {
14
9
  config;
10
+ handlers = [];
15
11
  constructor(config) {
16
12
  super('ast-analysis', 'AST Structural Analysis');
17
13
  this.config = config;
14
+ this.handlers.push(new TypeScriptHandler(config));
15
+ this.handlers.push(new PythonHandler(config));
16
+ this.handlers.push(new UniversalASTHandler(config));
18
17
  }
19
18
  async run(context) {
20
19
  const failures = [];
21
- const files = await (0, globby_1.globby)(['**/*.{ts,js,tsx,jsx}'], {
20
+ // Find all supported files
21
+ const files = await globby(['**/*.{ts,js,tsx,jsx,py,go,rs,cs,java,rb,c,cpp,php,swift,kt}'], {
22
22
  cwd: context.cwd,
23
- ignore: ['node_modules/**', 'dist/**', 'build/**', '**/*.test.*', '**/*.spec.*'],
23
+ ignore: ['node_modules/**', 'dist/**', 'build/**', '**/*.test.*', '**/*.spec.*', '**/__pycache__/**'],
24
24
  });
25
25
  for (const file of files) {
26
- const fullPath = path_1.default.join(context.cwd, file);
27
- const content = await fs_extra_1.default.readFile(fullPath, 'utf-8');
28
- const sourceFile = typescript_1.default.createSourceFile(file, content, typescript_1.default.ScriptTarget.Latest, true);
29
- this.analyzeSourceFile(sourceFile, file, failures);
30
- }
31
- return failures;
32
- }
33
- analyzeSourceFile(sourceFile, relativePath, failures) {
34
- const astConfig = this.config.ast || {};
35
- const maxComplexity = astConfig.complexity || 10;
36
- const maxMethods = astConfig.max_methods || 10;
37
- const maxParams = astConfig.max_params || 5;
38
- const visit = (node) => {
39
- // 1. Complexity & Params for functions
40
- if (typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isMethodDeclaration(node) || typescript_1.default.isArrowFunction(node)) {
41
- const name = this.getNodeName(node);
42
- // Parameter count
43
- if (node.parameters.length > maxParams) {
44
- failures.push(this.createFailure(`Function '${name}' has ${node.parameters.length} parameters (max: ${maxParams})`, [relativePath], `Reduce number of parameters or use an options object.`));
45
- // Update: Failures in Runner will be mapped to FixPacket
46
- failures[failures.length - 1].metrics = { count: node.parameters.length, max: maxParams };
47
- }
48
- // Cyclomatic Complexity (Simplified: nodes that cause branching)
49
- let complexity = 1;
50
- const countComplexity = (n) => {
51
- if (typescript_1.default.isIfStatement(n) || typescript_1.default.isCaseClause(n) || typescript_1.default.isDefaultClause(n) ||
52
- typescript_1.default.isForStatement(n) || typescript_1.default.isForInStatement(n) || typescript_1.default.isForOfStatement(n) ||
53
- typescript_1.default.isWhileStatement(n) || typescript_1.default.isDoStatement(n) || typescript_1.default.isConditionalExpression(n)) {
54
- complexity++;
55
- }
56
- if (typescript_1.default.isBinaryExpression(n)) {
57
- if (n.operatorToken.kind === typescript_1.default.SyntaxKind.AmpersandAmpersandToken ||
58
- n.operatorToken.kind === typescript_1.default.SyntaxKind.BarBarToken) {
59
- complexity++;
60
- }
61
- }
62
- typescript_1.default.forEachChild(n, countComplexity);
63
- };
64
- typescript_1.default.forEachChild(node, countComplexity);
65
- if (complexity > maxComplexity) {
66
- failures.push(this.createFailure(`Function '${name}' has cyclomatic complexity of ${complexity} (max: ${maxComplexity})`, [relativePath], `Refactor '${name}' into smaller, more focused functions.`));
67
- failures[failures.length - 1].metrics = { complexity, max: maxComplexity };
68
- }
26
+ const handler = this.handlers.find(h => h.supports(file));
27
+ if (!handler)
28
+ continue;
29
+ const fullPath = path.join(context.cwd, file);
30
+ try {
31
+ const content = await fs.readFile(fullPath, 'utf-8');
32
+ const gateFailures = await handler.run({
33
+ cwd: context.cwd,
34
+ file: file,
35
+ content
36
+ });
37
+ failures.push(...gateFailures);
69
38
  }
70
- // 2. Class metrics
71
- if (typescript_1.default.isClassDeclaration(node)) {
72
- const name = node.name?.text || 'Anonymous Class';
73
- const methods = node.members.filter(typescript_1.default.isMethodDeclaration);
74
- if (methods.length > maxMethods) {
75
- failures.push(this.createFailure(`Class '${name}' has ${methods.length} methods (max: ${maxMethods})`, [relativePath], `Class '${name}' is becoming a 'God Object'. Split it into smaller services.`));
76
- failures[failures.length - 1].metrics = { methodCount: methods.length, max: maxMethods };
77
- }
78
- }
79
- // 3. Import check for Layer Boundaries
80
- if (typescript_1.default.isImportDeclaration(node)) {
81
- const importPath = node.moduleSpecifier.text;
82
- this.checkBoundary(importPath, relativePath, failures);
83
- }
84
- typescript_1.default.forEachChild(node, visit);
85
- };
86
- typescript_1.default.forEachChild(sourceFile, visit);
87
- }
88
- checkBoundary(importPath, relativePath, failures) {
89
- const boundaries = this.config.architecture?.boundaries || [];
90
- if (boundaries.length === 0)
91
- return;
92
- for (const rule of boundaries) {
93
- const isFromMatch = micromatch_1.default.isMatch(relativePath, rule.from);
94
- if (isFromMatch) {
95
- // Approximate resolution (simplified for now)
96
- // Real implementation would need to handle alias and absolute path resolution
97
- const resolved = importPath.startsWith('.')
98
- ? path_1.default.join(path_1.default.dirname(relativePath), importPath)
99
- : importPath;
100
- const isToMatch = micromatch_1.default.isMatch(resolved, rule.to);
101
- if (rule.mode === 'deny' && isToMatch) {
102
- failures.push(this.createFailure(`Architectural Violation: '${relativePath}' is forbidden from importing '${importPath}' (denied by boundary rule).`, [relativePath], `Remove this import to maintain architectural layering.`));
103
- }
104
- else if (rule.mode === 'allow' && !isToMatch && importPath.startsWith('.')) {
105
- // Complexity: Allow rules are trickier to implement strictly without full resolution
106
- }
39
+ catch (error) {
40
+ // Individual file read failures shouldn't crash the whole run
107
41
  }
108
42
  }
109
- }
110
- getNodeName(node) {
111
- if (typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isMethodDeclaration(node)) {
112
- return node.name?.getText() || 'anonymous';
113
- }
114
- if (typescript_1.default.isArrowFunction(node)) {
115
- const parent = node.parent;
116
- if (typescript_1.default.isVariableDeclaration(parent)) {
117
- return parent.name.getText();
118
- }
119
- return 'anonymous arrow';
120
- }
121
- return 'unknown';
43
+ return failures;
122
44
  }
123
45
  }
124
- exports.ASTGate = ASTGate;
@@ -1,7 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Gate = void 0;
4
- class Gate {
1
+ export class Gate {
5
2
  id;
6
3
  title;
7
4
  constructor(id, title) {
@@ -18,4 +15,3 @@ class Gate {
18
15
  };
19
16
  }
20
17
  }
21
- exports.Gate = Gate;