@rigour-labs/core 1.0.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Rigour Contributors
3
+ Copyright (c) 2026 Ashutosh (https://github.com/erashu212), Rigour Labs.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,5 +1,21 @@
1
1
  import { Config } from './types/index.js';
2
+ export interface DiscoveryResult {
3
+ config: Config;
4
+ matches: {
5
+ preset?: {
6
+ name: string;
7
+ marker: string;
8
+ };
9
+ paradigm?: {
10
+ name: string;
11
+ marker: string;
12
+ };
13
+ };
14
+ }
2
15
  export declare class DiscoveryService {
3
- discover(cwd: string): Promise<Config>;
4
- private hasAnyMarker;
16
+ discover(cwd: string): Promise<DiscoveryResult>;
17
+ private mergeConfig;
18
+ private findFirstMarker;
19
+ private existsInContent;
20
+ private findSourceFiles;
5
21
  }
package/dist/discovery.js CHANGED
@@ -9,24 +9,76 @@ const path_1 = __importDefault(require("path"));
9
9
  const index_js_1 = require("./templates/index.js");
10
10
  class DiscoveryService {
11
11
  async discover(cwd) {
12
- const config = { ...index_js_1.UNIVERSAL_CONFIG };
12
+ let config = { ...index_js_1.UNIVERSAL_CONFIG };
13
+ const matches = {};
14
+ // 1. Detect Role (ui, api, infra, data)
13
15
  for (const template of index_js_1.TEMPLATES) {
14
- const match = await this.hasAnyMarker(cwd, template.markers);
15
- if (match) {
16
- // Merge template config
17
- config.commands = { ...config.commands, ...template.config.commands };
18
- config.gates = { ...config.gates, ...template.config.gates };
16
+ const marker = await this.findFirstMarker(cwd, template.markers);
17
+ if (marker) {
18
+ config = this.mergeConfig(config, template.config);
19
+ matches.preset = { name: template.name, marker };
20
+ break; // Only one role for now
19
21
  }
20
22
  }
21
- return config;
23
+ // 2. Detect Paradigm (oop, functional)
24
+ for (const template of index_js_1.PARADIGM_TEMPLATES) {
25
+ const marker = await this.findFirstMarker(cwd, template.markers, true); // Search content
26
+ if (marker) {
27
+ config = this.mergeConfig(config, template.config);
28
+ matches.paradigm = { name: template.name, marker };
29
+ break;
30
+ }
31
+ }
32
+ return { config, matches };
33
+ }
34
+ mergeConfig(base, extension) {
35
+ return {
36
+ ...base,
37
+ preset: extension.preset || base.preset,
38
+ paradigm: extension.paradigm || base.paradigm,
39
+ commands: { ...base.commands, ...extension.commands },
40
+ gates: { ...base.gates, ...extension.gates },
41
+ };
22
42
  }
23
- async hasAnyMarker(cwd, markers) {
43
+ async findFirstMarker(cwd, markers, searchContent = false) {
24
44
  for (const marker of markers) {
25
- if (await fs_extra_1.default.pathExists(path_1.default.join(cwd, marker))) {
26
- return true;
45
+ const fullPath = path_1.default.join(cwd, marker);
46
+ // File/Directory existence check
47
+ if (await fs_extra_1.default.pathExists(fullPath)) {
48
+ return marker;
49
+ }
50
+ // Deep content check for paradigms
51
+ if (searchContent) {
52
+ const match = await this.existsInContent(cwd, marker);
53
+ if (match)
54
+ return `content:${marker}`;
27
55
  }
28
56
  }
57
+ return null;
58
+ }
59
+ async existsInContent(cwd, pattern) {
60
+ // Simple heuristic: search in top 5 source files
61
+ const files = await this.findSourceFiles(cwd);
62
+ for (const file of files) {
63
+ const content = await fs_extra_1.default.readFile(file, 'utf-8');
64
+ if (content.includes(pattern))
65
+ return true;
66
+ }
29
67
  return false;
30
68
  }
69
+ async findSourceFiles(cwd) {
70
+ // Find a few files to sample
71
+ const extensions = ['.ts', '.js', '.py', '.go', '.java', '.tf'];
72
+ const samples = [];
73
+ const files = await fs_extra_1.default.readdir(cwd);
74
+ for (const file of files) {
75
+ if (extensions.some(ext => file.endsWith(ext))) {
76
+ samples.push(path_1.default.join(cwd, file));
77
+ if (samples.length >= 5)
78
+ break;
79
+ }
80
+ }
81
+ return samples;
82
+ }
31
83
  }
32
84
  exports.DiscoveryService = DiscoveryService;
@@ -0,0 +1,10 @@
1
+ import { Gate, GateContext } from './base.js';
2
+ import { Failure, Gates } from '../types/index.js';
3
+ export declare class ASTGate extends Gate {
4
+ private config;
5
+ constructor(config: Gates);
6
+ run(context: GateContext): Promise<Failure[]>;
7
+ private analyzeSourceFile;
8
+ private checkBoundary;
9
+ private getNodeName;
10
+ }
@@ -0,0 +1,124 @@
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 {
14
+ config;
15
+ constructor(config) {
16
+ super('ast-analysis', 'AST Structural Analysis');
17
+ this.config = config;
18
+ }
19
+ async run(context) {
20
+ 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.*'],
24
+ });
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
+ }
69
+ }
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
+ }
107
+ }
108
+ }
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';
122
+ }
123
+ }
124
+ exports.ASTGate = ASTGate;
@@ -0,0 +1,7 @@
1
+ import { Failure, Config } from '../types/index.js';
2
+ import { Gate, GateContext } from './base.js';
3
+ export declare class DependencyGate extends Gate {
4
+ private config;
5
+ constructor(config: Config);
6
+ run(context: GateContext): Promise<Failure[]>;
7
+ }
@@ -0,0 +1,43 @@
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 {
11
+ config;
12
+ constructor(config) {
13
+ super('dependency-guardian', 'Dependency Guardian');
14
+ this.config = config;
15
+ }
16
+ async run(context) {
17
+ const failures = [];
18
+ const forbidden = this.config.gates.dependencies?.forbid || [];
19
+ if (forbidden.length === 0)
20
+ return [];
21
+ const { cwd } = context;
22
+ // 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)) {
25
+ try {
26
+ const pkg = await fs_extra_1.default.readJson(pkgPath);
27
+ const allDeps = {
28
+ ...(pkg.dependencies || {}),
29
+ ...(pkg.devDependencies || {}),
30
+ ...(pkg.peerDependencies || {}),
31
+ };
32
+ for (const dep of forbidden) {
33
+ if (allDeps[dep]) {
34
+ failures.push(this.createFailure(`The package '${dep}' is forbidden by project standards.`, ['package.json'], `Remove '${dep}' from package.json and use approved alternatives.`));
35
+ }
36
+ }
37
+ }
38
+ catch (e) { }
39
+ }
40
+ return failures;
41
+ }
42
+ }
43
+ exports.DependencyGate = DependencyGate;
@@ -4,6 +4,9 @@ exports.GateRunner = void 0;
4
4
  const file_js_1 = require("./file.js");
5
5
  const content_js_1 = require("./content.js");
6
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");
7
10
  const execa_1 = require("execa");
8
11
  const logger_js_1 = require("../utils/logger.js");
9
12
  class GateRunner {
@@ -24,6 +27,9 @@ class GateRunner {
24
27
  if (this.config.gates.required_files) {
25
28
  this.gates.push(new structure_js_1.StructureGate({ requiredFiles: this.config.gates.required_files }));
26
29
  }
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));
27
33
  }
28
34
  /**
29
35
  * Allows adding custom gates dynamically (SOLID - Open/Closed Principle)
@@ -0,0 +1,8 @@
1
+ import { Gate, GateContext } from './base.js';
2
+ import { Failure, Gates } from '../types/index.js';
3
+ export declare class SafetyGate extends Gate {
4
+ private config;
5
+ constructor(config: Gates);
6
+ run(context: GateContext): Promise<Failure[]>;
7
+ private isProtected;
8
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SafetyGate = void 0;
4
+ const base_js_1 = require("./base.js");
5
+ const execa_1 = require("execa");
6
+ class SafetyGate extends base_js_1.Gate {
7
+ config;
8
+ constructor(config) {
9
+ super('safety-rail', 'Safety & Protection Rails');
10
+ this.config = config;
11
+ }
12
+ async run(context) {
13
+ const failures = [];
14
+ const safety = this.config.safety || {};
15
+ const protectedPaths = safety.protected_paths || [];
16
+ if (protectedPaths.length === 0)
17
+ return [];
18
+ try {
19
+ // Check for modified files in protected paths using git
20
+ // This is a "Safety Rail" - if an agent touched these, we fail.
21
+ const { stdout } = await (0, execa_1.execa)('git', ['status', '--porcelain'], { cwd: context.cwd });
22
+ const modifiedFiles = stdout.split('\n')
23
+ .filter(line => line.trim().length > 0)
24
+ .map(line => line.slice(3));
25
+ for (const file of modifiedFiles) {
26
+ if (this.isProtected(file, protectedPaths)) {
27
+ failures.push(this.createFailure(`Protected file '${file}' was modified.`, [file], `Agents are forbidden from modifying files in ${protectedPaths.join(', ')}.`));
28
+ }
29
+ }
30
+ }
31
+ catch (error) {
32
+ // If not a git repo, skip safety for now
33
+ }
34
+ return failures;
35
+ }
36
+ isProtected(file, patterns) {
37
+ return patterns.some(p => {
38
+ const cleanP = p.replace('/**', '').replace('/*', '');
39
+ if (file === cleanP)
40
+ return true;
41
+ if (cleanP.endsWith('/'))
42
+ return file.startsWith(cleanP);
43
+ return file.startsWith(cleanP + '/');
44
+ });
45
+ }
46
+ }
47
+ exports.SafetyGate = SafetyGate;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  export * from './types/index.js';
2
2
  export * from './gates/runner.js';
3
3
  export * from './discovery.js';
4
+ export * from './services/fix-packet-service.js';
4
5
  export * from './templates/index.js';
6
+ export * from './types/fix-packet.js';
7
+ export { Gate, GateContext } from './gates/base.js';
5
8
  export * from './utils/logger.js';
package/dist/index.js CHANGED
@@ -14,8 +14,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.Gate = void 0;
17
18
  __exportStar(require("./types/index.js"), exports);
18
19
  __exportStar(require("./gates/runner.js"), exports);
19
20
  __exportStar(require("./discovery.js"), exports);
21
+ __exportStar(require("./services/fix-packet-service.js"), exports);
20
22
  __exportStar(require("./templates/index.js"), exports);
23
+ __exportStar(require("./types/fix-packet.js"), exports);
24
+ var base_js_1 = require("./gates/base.js");
25
+ Object.defineProperty(exports, "Gate", { enumerable: true, get: function () { return base_js_1.Gate; } });
21
26
  __exportStar(require("./utils/logger.js"), exports);
@@ -0,0 +1,6 @@
1
+ import { Report, Config } from '../types/index.js';
2
+ import { FixPacketV2 } from '../types/fix-packet.js';
3
+ export declare class FixPacketService {
4
+ generate(report: Report, config: Config): FixPacketV2;
5
+ private inferSeverity;
6
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FixPacketService = void 0;
4
+ const fix_packet_js_1 = require("../types/fix-packet.js");
5
+ class FixPacketService {
6
+ generate(report, config) {
7
+ const violations = report.failures.map(f => ({
8
+ id: f.id,
9
+ gate: f.id,
10
+ severity: this.inferSeverity(f),
11
+ title: f.title,
12
+ details: f.details,
13
+ files: f.files,
14
+ hint: f.hint,
15
+ instructions: f.hint ? [f.hint] : [], // Use hint as first instruction
16
+ metrics: f.metrics,
17
+ }));
18
+ const packet = {
19
+ version: 2,
20
+ goal: "Achieve PASS state by resolving all listed engineering violations.",
21
+ violations,
22
+ constraints: {
23
+ paradigm: config.paradigm,
24
+ protected_paths: config.gates.safety?.protected_paths,
25
+ do_not_touch: config.gates.safety?.protected_paths,
26
+ max_files_changed: config.gates.safety?.max_files_changed_per_cycle,
27
+ no_new_deps: true,
28
+ },
29
+ };
30
+ return fix_packet_js_1.FixPacketV2Schema.parse(packet);
31
+ }
32
+ inferSeverity(f) {
33
+ // High complexity or God objects are usually High severity
34
+ if (f.id === 'ast-analysis')
35
+ return 'high';
36
+ // Unit test or Lint failures are Medium
37
+ if (f.id === 'test' || f.id === 'lint')
38
+ return 'medium';
39
+ // Documentation or small file size issues are Low
40
+ return 'medium';
41
+ }
42
+ }
43
+ exports.FixPacketService = FixPacketService;
@@ -1,8 +1,15 @@
1
- import { Config } from '../types/index.js';
1
+ import { Config, Commands, Gates } from '../types/index.js';
2
2
  export interface Template {
3
3
  name: string;
4
4
  markers: string[];
5
- config: Partial<Config>;
5
+ config: {
6
+ preset?: string;
7
+ paradigm?: string;
8
+ commands?: Partial<Commands>;
9
+ gates?: Partial<Gates>;
10
+ planned?: string[];
11
+ };
6
12
  }
7
13
  export declare const TEMPLATES: Template[];
14
+ export declare const PARADIGM_TEMPLATES: Template[];
8
15
  export declare const UNIVERSAL_CONFIG: Config;
@@ -1,55 +1,120 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UNIVERSAL_CONFIG = exports.TEMPLATES = void 0;
3
+ exports.UNIVERSAL_CONFIG = exports.PARADIGM_TEMPLATES = exports.TEMPLATES = void 0;
4
4
  exports.TEMPLATES = [
5
5
  {
6
- name: 'Node.js',
7
- markers: ['package.json'],
6
+ name: 'ui',
7
+ markers: [
8
+ 'package.json', // Check deps later
9
+ 'next.config.js',
10
+ 'vite.config.ts',
11
+ 'tailwind.config.js',
12
+ 'base.css',
13
+ 'index.html',
14
+ ],
8
15
  config: {
9
- commands: {
10
- lint: 'npm run lint',
11
- test: 'npm test',
16
+ preset: 'ui',
17
+ gates: {
18
+ max_file_lines: 300,
19
+ required_files: ['docs/SPEC.md', 'docs/ARCH.md', 'README.md'],
12
20
  },
21
+ planned: [
22
+ 'Layer Boundary: Components cannot import from DB',
23
+ 'Prop-Drilling Detection: Max depth 5',
24
+ ],
25
+ },
26
+ },
27
+ {
28
+ name: 'api',
29
+ markers: [
30
+ 'go.mod',
31
+ 'requirements.txt',
32
+ 'pyproject.toml',
33
+ 'package.json', // Check for backend frameworks later
34
+ 'app.py',
35
+ 'main.go',
36
+ 'index.js',
37
+ ],
38
+ config: {
39
+ preset: 'api',
13
40
  gates: {
14
- max_file_lines: 500,
15
- forbid_todos: true,
16
- forbid_fixme: true,
17
- forbid_paths: [],
41
+ max_file_lines: 400,
18
42
  required_files: ['docs/SPEC.md', 'docs/ARCH.md', 'README.md'],
19
43
  },
44
+ planned: [
45
+ 'Service Layer Enforcement: Controllers -> Services only',
46
+ 'Repo Pattern: Databases access isolated to repositories/',
47
+ ],
20
48
  },
21
49
  },
22
50
  {
23
- name: 'Python',
24
- markers: ['pyproject.toml', 'requirements.txt', 'setup.py'],
51
+ name: 'infra',
52
+ markers: [
53
+ 'Dockerfile',
54
+ 'docker-compose.yml',
55
+ 'main.tf',
56
+ 'k8s/',
57
+ 'helm/',
58
+ 'ansible/',
59
+ ],
25
60
  config: {
26
- commands: {
27
- lint: 'ruff check .',
28
- test: 'pytest',
61
+ preset: 'infra',
62
+ gates: {
63
+ max_file_lines: 300,
64
+ required_files: ['docs/RUNBOOK.md', 'docs/ARCH.md', 'README.md'],
29
65
  },
66
+ },
67
+ },
68
+ {
69
+ name: 'data',
70
+ markers: [
71
+ 'ipynb',
72
+ 'spark',
73
+ 'pandas',
74
+ 'data/',
75
+ 'dbt_project.yml',
76
+ ],
77
+ config: {
78
+ preset: 'data',
30
79
  gates: {
31
80
  max_file_lines: 500,
32
- forbid_todos: true,
33
- forbid_fixme: true,
34
- forbid_paths: [],
35
- required_files: ['docs/SPEC.md', 'docs/ARCH.md', 'README.md'],
81
+ required_files: ['docs/DATA_DICTIONARY.md', 'docs/PIPELINE.md', 'README.md'],
36
82
  },
83
+ planned: [
84
+ 'Stochastic Determinism: Seed setting enforcement',
85
+ 'Data Leaks: Detecting PII in notebook outputs',
86
+ ],
37
87
  },
38
88
  },
89
+ ];
90
+ exports.PARADIGM_TEMPLATES = [
39
91
  {
40
- name: 'Frontend (React/Vite/Next)',
41
- markers: ['next.config.js', 'vite.config.ts', 'tailwind.config.js'],
92
+ name: 'oop',
93
+ markers: [
94
+ 'class ',
95
+ 'interface ',
96
+ 'implements ',
97
+ 'extends ',
98
+ ],
42
99
  config: {
43
- commands: {
44
- lint: 'npm run lint',
45
- test: 'npm test',
100
+ paradigm: 'oop',
101
+ gates: {
102
+ // Future: class-specific gates
46
103
  },
104
+ },
105
+ },
106
+ {
107
+ name: 'functional',
108
+ markers: [
109
+ '=>',
110
+ 'export const',
111
+ 'map(',
112
+ 'filter(',
113
+ ],
114
+ config: {
115
+ paradigm: 'functional',
47
116
  gates: {
48
- max_file_lines: 300, // Frontend files often should be smaller
49
- forbid_todos: true,
50
- forbid_fixme: true,
51
- forbid_paths: [],
52
- required_files: ['docs/SPEC.md', 'docs/ARCH.md', 'README.md'],
117
+ // Future: function-specific gates
53
118
  },
54
119
  },
55
120
  },
@@ -63,8 +128,24 @@ exports.UNIVERSAL_CONFIG = {
63
128
  forbid_fixme: true,
64
129
  forbid_paths: [],
65
130
  required_files: ['docs/SPEC.md', 'docs/ARCH.md', 'docs/DECISIONS.md', 'docs/TASKS.md'],
131
+ ast: {
132
+ complexity: 10,
133
+ max_methods: 10,
134
+ max_params: 5,
135
+ },
136
+ dependencies: {
137
+ forbid: [],
138
+ },
139
+ architecture: {
140
+ boundaries: [],
141
+ },
142
+ safety: {
143
+ max_files_changed_per_cycle: 10,
144
+ protected_paths: ['.github/**', 'docs/**', 'rigour.yml'],
145
+ },
66
146
  },
67
147
  output: {
68
148
  report_path: 'rigour-report.json',
69
149
  },
150
+ planned: [],
70
151
  };