@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
package/dist/gates/ast.js CHANGED
@@ -1,124 +1,48 @@
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}'], {
22
- cwd: context.cwd,
23
- ignore: ['node_modules/**', 'dist/**', 'build/**', '**/*.test.*', '**/*.spec.*'],
20
+ const patterns = (context.patterns || ['**/*.{ts,js,tsx,jsx,py,go,rs,cs,java,rb,c,cpp,php,swift,kt}']).map(p => p.replace(/\\/g, '/'));
21
+ const ignore = (context.ignore || ['node_modules/**', 'dist/**', 'build/**', '**/*.test.*', '**/*.spec.*', '**/__pycache__/**']).map(p => p.replace(/\\/g, '/'));
22
+ const normalizedCwd = context.cwd.replace(/\\/g, '/');
23
+ // Find all supported files
24
+ const files = await globby(patterns, {
25
+ cwd: normalizedCwd,
26
+ ignore: ignore,
24
27
  });
25
28
  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
- }
29
+ const handler = this.handlers.find(h => h.supports(file));
30
+ if (!handler)
31
+ continue;
32
+ const fullPath = path.join(context.cwd, file);
33
+ try {
34
+ const content = await fs.readFile(fullPath, 'utf-8');
35
+ const gateFailures = await handler.run({
36
+ cwd: context.cwd,
37
+ file: file,
38
+ content
39
+ });
40
+ failures.push(...gateFailures);
69
41
  }
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
- }
42
+ catch (error) {
43
+ // Individual file read failures shouldn't crash the whole run
107
44
  }
108
45
  }
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';
46
+ return failures;
122
47
  }
123
48
  }
124
- exports.ASTGate = ASTGate;
@@ -1,6 +1,10 @@
1
+ import { GoldenRecord } from '../services/context-engine.js';
1
2
  import { Failure } from '../types/index.js';
2
3
  export interface GateContext {
3
4
  cwd: string;
5
+ record?: GoldenRecord;
6
+ ignore?: string[];
7
+ patterns?: string[];
4
8
  }
5
9
  export declare abstract class Gate {
6
10
  readonly id: string;
@@ -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;
@@ -1,9 +1,6 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ContentGate = void 0;
4
- const base_js_1 = require("./base.js");
5
- const scanner_js_1 = require("../utils/scanner.js");
6
- class ContentGate extends base_js_1.Gate {
1
+ import { Gate } from './base.js';
2
+ import { FileScanner } from '../utils/scanner.js';
3
+ export class ContentGate extends Gate {
7
4
  config;
8
5
  constructor(config) {
9
6
  super('content-check', 'Forbidden Content');
@@ -17,8 +14,12 @@ class ContentGate extends base_js_1.Gate {
17
14
  patterns.push(/FIXME/i);
18
15
  if (patterns.length === 0)
19
16
  return [];
20
- const files = await scanner_js_1.FileScanner.findFiles({ cwd: context.cwd });
21
- const contents = await scanner_js_1.FileScanner.readFiles(context.cwd, files);
17
+ const files = await FileScanner.findFiles({
18
+ cwd: context.cwd,
19
+ ignore: context.ignore,
20
+ patterns: context.patterns
21
+ });
22
+ const contents = await FileScanner.readFiles(context.cwd, files);
22
23
  const violations = [];
23
24
  for (const [file, content] of contents) {
24
25
  for (const pattern of patterns) {
@@ -36,4 +37,3 @@ class ContentGate extends base_js_1.Gate {
36
37
  return [];
37
38
  }
38
39
  }
39
- exports.ContentGate = ContentGate;
@@ -0,0 +1,8 @@
1
+ import { Gate, GateContext } from './base.js';
2
+ import { Failure, Gates } from '../types/index.js';
3
+ export declare class ContextGate extends Gate {
4
+ private config;
5
+ constructor(config: Gates);
6
+ run(context: GateContext): Promise<Failure[]>;
7
+ private checkEnvDrift;
8
+ }
@@ -0,0 +1,43 @@
1
+ import { Gate } from './base.js';
2
+ import { FileScanner } from '../utils/scanner.js';
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+ export class ContextGate extends Gate {
6
+ config;
7
+ constructor(config) {
8
+ super('context-drift', 'Context Awareness & Drift Detection');
9
+ this.config = config;
10
+ }
11
+ async run(context) {
12
+ const failures = [];
13
+ const record = context.record;
14
+ if (!record || !this.config.context?.enabled)
15
+ return [];
16
+ const files = await FileScanner.findFiles({ cwd: context.cwd });
17
+ const envAnchors = record.anchors.filter(a => a.type === 'env' && a.confidence >= 1);
18
+ for (const file of files) {
19
+ try {
20
+ const content = await fs.readFile(path.join(context.cwd, file), 'utf-8');
21
+ // 1. Detect Redundant Suffixes (The Golden Example)
22
+ this.checkEnvDrift(content, file, envAnchors, failures);
23
+ }
24
+ catch (e) { }
25
+ }
26
+ return failures;
27
+ }
28
+ checkEnvDrift(content, file, anchors, failures) {
29
+ // Find all environment variable accesses in the content
30
+ const matches = content.matchAll(/process\.env(?:\.([A-Z0-9_]+)|\[['"]([A-Z0-9_]+)['"]\])/g);
31
+ for (const match of matches) {
32
+ const accessedVar = match[1] || match[2];
33
+ for (const anchor of anchors) {
34
+ // If the accessed variable contains the anchor but is not equal to it,
35
+ // it's a potential "invented" redundancy (e.g. CORE_URL vs CORE_URL_PROD)
36
+ if (accessedVar !== anchor.id && accessedVar.includes(anchor.id)) {
37
+ const deviation = accessedVar.replace(anchor.id, '').replace(/^_|_$/, '');
38
+ failures.push(this.createFailure(`Context Drift: Redundant variation '${accessedVar}' detected in ${file}.`, [file], `The project already uses '${anchor.id}' as a standard anchor. Avoid inventing variations like '${deviation}'. Reuse the existing anchor or align with established project patterns.`));
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,8 @@
1
+ import { Gate, GateContext } from './base.js';
2
+ import { Failure, Gates } from '../types/index.js';
3
+ export declare class CoverageGate extends Gate {
4
+ private config;
5
+ constructor(config: Gates);
6
+ run(context: GateContext): Promise<Failure[]>;
7
+ private parseLcov;
8
+ }
@@ -0,0 +1,62 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { Gate } from './base.js';
4
+ import { globby } from 'globby';
5
+ export class CoverageGate extends Gate {
6
+ config;
7
+ constructor(config) {
8
+ super('coverage-guard', 'Dynamic Coverage Guard');
9
+ this.config = config;
10
+ }
11
+ async run(context) {
12
+ const failures = [];
13
+ // 1. Locate coverage report (lcov.info is standard)
14
+ const reports = await globby(['**/lcov.info', '**/coverage-final.json'], {
15
+ cwd: context.cwd,
16
+ ignore: ['node_modules/**']
17
+ });
18
+ if (reports.length === 0) {
19
+ // If no reports found, and coverage is required, we could flag it.
20
+ // But for now, we'll just skip silently if not configured.
21
+ return [];
22
+ }
23
+ // 2. Parse coverage (Simplified LCOV parser for demonstration)
24
+ const coverageData = await this.parseLcov(path.join(context.cwd, reports[0]));
25
+ // 3. Quality Handshake: SME SME LOGIC
26
+ // We look for files that have high complexity but low coverage.
27
+ // In a real implementation, we would share data between ASTGate and CoverageGate.
28
+ // For this demo, we'll implement a standalone check.
29
+ for (const [file, stats] of Object.entries(coverageData)) {
30
+ const coverage = (stats.hit / stats.found) * 100;
31
+ const threshold = stats.isComplex ? 80 : 50; // SME logic: Complex files need higher coverage
32
+ if (coverage < threshold) {
33
+ failures.push({
34
+ id: 'DYNAMIC_COVERAGE_LOW',
35
+ title: `Low coverage for high-risk file: ${file}`,
36
+ details: `Current coverage: ${coverage.toFixed(2)}%. Required: ${threshold}% due to structural risk.`,
37
+ files: [file],
38
+ hint: `Add dynamic tests to cover complex logical branches in this file.`
39
+ });
40
+ }
41
+ }
42
+ return failures;
43
+ }
44
+ async parseLcov(reportPath) {
45
+ const content = await fs.readFile(reportPath, 'utf-8');
46
+ const results = {};
47
+ let currentFile = '';
48
+ for (const line of content.split('\n')) {
49
+ if (line.startsWith('SF:')) {
50
+ currentFile = line.substring(3);
51
+ results[currentFile] = { found: 0, hit: 0, isComplex: false };
52
+ }
53
+ else if (line.startsWith('LF:')) {
54
+ results[currentFile].found = parseInt(line.substring(3));
55
+ }
56
+ else if (line.startsWith('LH:')) {
57
+ results[currentFile].hit = parseInt(line.substring(3));
58
+ }
59
+ }
60
+ return results;
61
+ }
62
+ }
@@ -1,13 +1,7 @@
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.DependencyGate = void 0;
7
- const fs_extra_1 = __importDefault(require("fs-extra"));
8
- const path_1 = __importDefault(require("path"));
9
- const base_js_1 = require("./base.js");
10
- class DependencyGate extends base_js_1.Gate {
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { Gate } from './base.js';
4
+ export class DependencyGate extends Gate {
11
5
  config;
12
6
  constructor(config) {
13
7
  super('dependency-guardian', 'Dependency Guardian');
@@ -20,10 +14,10 @@ class DependencyGate extends base_js_1.Gate {
20
14
  return [];
21
15
  const { cwd } = context;
22
16
  // 1. Scan Node.js (package.json)
23
- const pkgPath = path_1.default.join(cwd, 'package.json');
24
- if (await fs_extra_1.default.pathExists(pkgPath)) {
17
+ const pkgPath = path.join(cwd, 'package.json');
18
+ if (await fs.pathExists(pkgPath)) {
25
19
  try {
26
- const pkg = await fs_extra_1.default.readJson(pkgPath);
20
+ const pkg = await fs.readJson(pkgPath);
27
21
  const allDeps = {
28
22
  ...(pkg.dependencies || {}),
29
23
  ...(pkg.devDependencies || {}),
@@ -40,4 +34,3 @@ class DependencyGate extends base_js_1.Gate {
40
34
  return failures;
41
35
  }
42
36
  }
43
- exports.DependencyGate = DependencyGate;
@@ -0,0 +1,8 @@
1
+ import { Gate, GateContext } from './base.js';
2
+ import { Failure, Gates } from '../types/index.js';
3
+ export declare class EnvironmentGate extends Gate {
4
+ private config;
5
+ constructor(config: Gates);
6
+ run(context: GateContext): Promise<Failure[]>;
7
+ private discoverContracts;
8
+ }
@@ -0,0 +1,73 @@
1
+ import { Gate } from './base.js';
2
+ import { execa } from 'execa';
3
+ import semver from 'semver';
4
+ import fs from 'fs-extra';
5
+ import path from 'path';
6
+ export class EnvironmentGate extends Gate {
7
+ config;
8
+ constructor(config) {
9
+ super('environment-alignment', 'Environment & Tooling Alignment');
10
+ this.config = config;
11
+ }
12
+ async run(context) {
13
+ const failures = [];
14
+ const envConfig = this.config.environment;
15
+ if (!envConfig || !envConfig.enabled)
16
+ return [];
17
+ const contracts = envConfig.enforce_contracts ? await this.discoverContracts(context.cwd) : {};
18
+ const toolsToCheck = { ...contracts, ...(envConfig.tools || {}) };
19
+ // 1. Verify Tool Versions
20
+ for (const [tool, range] of Object.entries(toolsToCheck)) {
21
+ // Ensure range is a string
22
+ const semverRange = String(range);
23
+ try {
24
+ const { stdout } = await execa(tool, ['--version'], { shell: true });
25
+ const versionMatch = stdout.match(/(\d+\.\d+\.\d+)/);
26
+ if (versionMatch) {
27
+ const version = versionMatch[1];
28
+ if (!semver.satisfies(version, semverRange)) {
29
+ failures.push(this.createFailure(`Environment Alignment: Tool '${tool}' version mismatch.`, [], `Project requires '${tool} ${semverRange}' (discovered from contract), but found version '${version}'. Please align your local environment to prevent drift.`));
30
+ }
31
+ }
32
+ else {
33
+ failures.push(this.createFailure(`Environment Alignment: Could not determine version for '${tool}'.`, [], `Ensure '${tool} --version' returns a standard SemVer string.`));
34
+ }
35
+ }
36
+ catch (e) {
37
+ failures.push(this.createFailure(`Environment Alignment: Required tool '${tool}' is missing.`, [], `Install '${tool}' and ensure it is in your $PATH.`));
38
+ }
39
+ }
40
+ // 2. Verify Required Env Vars
41
+ const requiredEnv = envConfig.required_env || [];
42
+ for (const envVar of requiredEnv) {
43
+ if (!process.env[envVar]) {
44
+ failures.push(this.createFailure(`Environment Alignment: Missing required environment variable '${envVar}'.`, [], `Ensure '${envVar}' is defined in your environment or .env file.`));
45
+ }
46
+ }
47
+ return failures;
48
+ }
49
+ async discoverContracts(cwd) {
50
+ const contracts = {};
51
+ // 1. Scan pyproject.toml (for ruff, mypy)
52
+ const pyprojectPath = path.join(cwd, 'pyproject.toml');
53
+ if (await fs.pathExists(pyprojectPath)) {
54
+ const content = await fs.readFile(pyprojectPath, 'utf-8');
55
+ // SME Logic: Look for ruff and mypy version constraints
56
+ // Handle both ruff = "^0.14.0" and ruff = { version = "^0.14.0" }
57
+ const ruffMatch = content.match(/ruff\s*=\s*(?:['"]([^'"]+)['"]|\{\s*version\s*=\s*['"]([^'"]+)['"]\s*\})/);
58
+ if (ruffMatch)
59
+ contracts['ruff'] = ruffMatch[1] || ruffMatch[2];
60
+ const mypyMatch = content.match(/mypy\s*=\s*(?:['"]([^'"]+)['"]|\{\s*version\s*=\s*['"]([^'"]+)['"]\s*\})/);
61
+ if (mypyMatch)
62
+ contracts['mypy'] = mypyMatch[1] || mypyMatch[2];
63
+ }
64
+ // 2. Scan package.json (for node/npm/pnpm)
65
+ const pkgPath = path.join(cwd, 'package.json');
66
+ if (await fs.pathExists(pkgPath)) {
67
+ const pkg = await fs.readJson(pkgPath);
68
+ if (pkg.engines?.node)
69
+ contracts['node'] = pkg.engines.node;
70
+ }
71
+ return contracts;
72
+ }
73
+ }
@@ -1,17 +1,18 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FileGate = void 0;
4
- const base_js_1 = require("./base.js");
5
- const scanner_js_1 = require("../utils/scanner.js");
6
- class FileGate extends base_js_1.Gate {
1
+ import { Gate } from './base.js';
2
+ import { FileScanner } from '../utils/scanner.js';
3
+ export class FileGate extends Gate {
7
4
  config;
8
5
  constructor(config) {
9
6
  super('file-size', 'File Size Limit');
10
7
  this.config = config;
11
8
  }
12
9
  async run(context) {
13
- const files = await scanner_js_1.FileScanner.findFiles({ cwd: context.cwd });
14
- const contents = await scanner_js_1.FileScanner.readFiles(context.cwd, files);
10
+ const files = await FileScanner.findFiles({
11
+ cwd: context.cwd,
12
+ ignore: context.ignore,
13
+ patterns: context.patterns
14
+ });
15
+ const contents = await FileScanner.readFiles(context.cwd, files);
15
16
  const violations = [];
16
17
  for (const [file, content] of contents) {
17
18
  const lines = content.split('\n').length;
@@ -27,4 +28,3 @@ class FileGate extends base_js_1.Gate {
27
28
  return [];
28
29
  }
29
30
  }
30
- exports.FileGate = FileGate;
@@ -9,5 +9,5 @@ export declare class GateRunner {
9
9
  * Allows adding custom gates dynamically (SOLID - Open/Closed Principle)
10
10
  */
11
11
  addGate(gate: Gate): void;
12
- run(cwd: string): Promise<Report>;
12
+ run(cwd: string, patterns?: string[]): Promise<Report>;
13
13
  }
@@ -1,15 +1,16 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GateRunner = void 0;
4
- const file_js_1 = require("./file.js");
5
- const content_js_1 = require("./content.js");
6
- const structure_js_1 = require("./structure.js");
7
- const ast_js_1 = require("./ast.js");
8
- const safety_js_1 = require("./safety.js");
9
- const dependency_js_1 = require("./dependency.js");
10
- const execa_1 = require("execa");
11
- const logger_js_1 = require("../utils/logger.js");
12
- class GateRunner {
1
+ import { FileGate } from './file.js';
2
+ import { ContentGate } from './content.js';
3
+ import { StructureGate } from './structure.js';
4
+ import { ASTGate } from './ast.js';
5
+ import { SafetyGate } from './safety.js';
6
+ import { DependencyGate } from './dependency.js';
7
+ import { CoverageGate } from './coverage.js';
8
+ import { ContextGate } from './context.js';
9
+ import { ContextEngine } from '../services/context-engine.js';
10
+ import { EnvironmentGate } from './environment.js'; // [NEW]
11
+ import { execa } from 'execa';
12
+ import { Logger } from '../utils/logger.js';
13
+ export class GateRunner {
13
14
  config;
14
15
  gates = [];
15
16
  constructor(config) {
@@ -18,18 +19,26 @@ class GateRunner {
18
19
  }
19
20
  initializeGates() {
20
21
  if (this.config.gates.max_file_lines) {
21
- this.gates.push(new file_js_1.FileGate({ maxLines: this.config.gates.max_file_lines }));
22
+ this.gates.push(new FileGate({ maxLines: this.config.gates.max_file_lines }));
22
23
  }
23
- this.gates.push(new content_js_1.ContentGate({
24
+ this.gates.push(new ContentGate({
24
25
  forbidTodos: !!this.config.gates.forbid_todos,
25
26
  forbidFixme: !!this.config.gates.forbid_fixme,
26
27
  }));
27
28
  if (this.config.gates.required_files) {
28
- this.gates.push(new structure_js_1.StructureGate({ requiredFiles: this.config.gates.required_files }));
29
+ this.gates.push(new StructureGate({ requiredFiles: this.config.gates.required_files }));
30
+ }
31
+ this.gates.push(new ASTGate(this.config.gates));
32
+ this.gates.push(new DependencyGate(this.config));
33
+ this.gates.push(new SafetyGate(this.config.gates));
34
+ this.gates.push(new CoverageGate(this.config.gates));
35
+ if (this.config.gates.context?.enabled) {
36
+ this.gates.push(new ContextGate(this.config.gates));
37
+ }
38
+ // Environment Alignment Gate (Should be prioritized)
39
+ if (this.config.gates.environment?.enabled) {
40
+ this.gates.unshift(new EnvironmentGate(this.config.gates));
29
41
  }
30
- this.gates.push(new ast_js_1.ASTGate(this.config.gates));
31
- this.gates.push(new dependency_js_1.DependencyGate(this.config));
32
- this.gates.push(new safety_js_1.SafetyGate(this.config.gates));
33
42
  }
34
43
  /**
35
44
  * Allows adding custom gates dynamically (SOLID - Open/Closed Principle)
@@ -37,14 +46,21 @@ class GateRunner {
37
46
  addGate(gate) {
38
47
  this.gates.push(gate);
39
48
  }
40
- async run(cwd) {
49
+ async run(cwd, patterns) {
41
50
  const start = Date.now();
42
51
  const failures = [];
43
52
  const summary = {};
53
+ const ignore = this.config.ignore;
54
+ // 0. Run Context Discovery
55
+ let record;
56
+ if (this.config.gates.context?.enabled) {
57
+ const engine = new ContextEngine(this.config);
58
+ record = await engine.discover(cwd);
59
+ }
44
60
  // 1. Run internal gates
45
61
  for (const gate of this.gates) {
46
62
  try {
47
- const gateFailures = await gate.run({ cwd });
63
+ const gateFailures = await gate.run({ cwd, record, ignore, patterns });
48
64
  if (gateFailures.length > 0) {
49
65
  failures.push(...gateFailures);
50
66
  summary[gate.id] = 'FAIL';
@@ -54,7 +70,7 @@ class GateRunner {
54
70
  }
55
71
  }
56
72
  catch (error) {
57
- logger_js_1.Logger.error(`Gate ${gate.id} failed with error: ${error.message}`);
73
+ Logger.error(`Gate ${gate.id} failed with error: ${error.message}`);
58
74
  summary[gate.id] = 'ERROR';
59
75
  failures.push({
60
76
  id: gate.id,
@@ -73,8 +89,8 @@ class GateRunner {
73
89
  continue;
74
90
  }
75
91
  try {
76
- logger_js_1.Logger.info(`Running command gate: ${key} (${cmd})`);
77
- await (0, execa_1.execa)(cmd, { shell: true, cwd });
92
+ Logger.info(`Running command gate: ${key} (${cmd})`);
93
+ await execa(cmd, { shell: true, cwd });
78
94
  summary[key] = 'PASS';
79
95
  }
80
96
  catch (error) {
@@ -89,14 +105,15 @@ class GateRunner {
89
105
  }
90
106
  }
91
107
  const status = failures.length > 0 ? 'FAIL' : 'PASS';
108
+ const score = Math.max(0, 100 - (failures.length * 5)); // Basic SME scoring logic
92
109
  return {
93
110
  status,
94
111
  summary,
95
112
  failures,
96
113
  stats: {
97
114
  duration_ms: Date.now() - start,
115
+ score,
98
116
  },
99
117
  };
100
118
  }
101
119
  }
102
- exports.GateRunner = GateRunner;