@rigour-labs/core 2.14.0 → 2.16.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.
@@ -14,14 +14,89 @@ export class TypeScriptHandler extends ASTHandler {
14
14
  }
15
15
  analyzeSourceFile(sourceFile, relativePath, failures) {
16
16
  const astConfig = this.config.ast || {};
17
+ const stalenessConfig = this.config.staleness || {};
18
+ const stalenessRules = stalenessConfig.rules || {};
17
19
  const maxComplexity = astConfig.complexity || 10;
18
20
  const maxMethods = astConfig.max_methods || 10;
19
21
  const maxParams = astConfig.max_params || 5;
22
+ // Limit failures per file to avoid output bloat on large files
23
+ const MAX_FAILURES_PER_FILE = 50;
24
+ const fileFailureCount = {};
25
+ const addFailure = (failure) => {
26
+ const ruleId = failure.id;
27
+ fileFailureCount[ruleId] = (fileFailureCount[ruleId] || 0) + 1;
28
+ if (fileFailureCount[ruleId] <= MAX_FAILURES_PER_FILE) {
29
+ failures.push(failure);
30
+ return true;
31
+ }
32
+ // Add summary failure once when limit is reached
33
+ if (fileFailureCount[ruleId] === MAX_FAILURES_PER_FILE + 1) {
34
+ failures.push({
35
+ id: `${ruleId}_LIMIT_EXCEEDED`,
36
+ title: `More than ${MAX_FAILURES_PER_FILE} ${ruleId} violations in ${relativePath}`,
37
+ details: `Truncated output: showing first ${MAX_FAILURES_PER_FILE} violations. Consider fixing the root cause.`,
38
+ files: [relativePath],
39
+ hint: `This file has many violations. Fix them systematically or exclude the file if it's legacy code.`
40
+ });
41
+ }
42
+ return false;
43
+ };
44
+ // Helper to check if a staleness rule is enabled
45
+ const isRuleEnabled = (rule) => {
46
+ if (!stalenessConfig.enabled)
47
+ return false;
48
+ return stalenessRules[rule] !== false; // Enabled by default if staleness is on
49
+ };
20
50
  const visit = (node) => {
51
+ // === STALENESS CHECKS (Rule-based) ===
52
+ // no-var: Forbid legacy 'var' keyword
53
+ if (isRuleEnabled('no-var') && ts.isVariableStatement(node)) {
54
+ const declarationList = node.declarationList;
55
+ // NodeFlags: Let = 1, Const = 2, None = 0 (var)
56
+ if ((declarationList.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) === 0) {
57
+ const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
58
+ addFailure({
59
+ id: 'STALENESS_NO_VAR',
60
+ title: `Stale 'var' keyword at line ${line}`,
61
+ details: `Use 'const' or 'let' instead of 'var' in ${relativePath}:${line}`,
62
+ files: [relativePath],
63
+ hint: `Replace 'var' with 'const' (preferred) or 'let' for modern JavaScript.`
64
+ });
65
+ }
66
+ }
67
+ // no-commonjs: Forbid require() in favor of import
68
+ if (isRuleEnabled('no-commonjs') && ts.isCallExpression(node)) {
69
+ if (ts.isIdentifier(node.expression) && node.expression.text === 'require') {
70
+ const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
71
+ addFailure({
72
+ id: 'STALENESS_NO_COMMONJS',
73
+ title: `CommonJS require() at line ${line}`,
74
+ details: `Use ES6 'import' instead of 'require()' in ${relativePath}:${line}`,
75
+ files: [relativePath],
76
+ hint: `Replace require('module') with import module from 'module'.`
77
+ });
78
+ }
79
+ }
80
+ // no-arguments: Forbid 'arguments' object (use rest params)
81
+ if (isRuleEnabled('no-arguments') && ts.isIdentifier(node) && node.text === 'arguments') {
82
+ // Check if it's actually the arguments keyword and not a variable named arguments
83
+ const parent = node.parent;
84
+ if (!ts.isVariableDeclaration(parent) && !ts.isPropertyAccessExpression(parent)) {
85
+ const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
86
+ addFailure({
87
+ id: 'STALENESS_NO_ARGUMENTS',
88
+ title: `Legacy 'arguments' object at line ${line}`,
89
+ details: `Use rest parameters (...args) instead of 'arguments' in ${relativePath}:${line}`,
90
+ files: [relativePath],
91
+ hint: `Replace 'arguments' with rest parameters: function(...args) { }`
92
+ });
93
+ }
94
+ }
95
+ // === COMPLEXITY CHECKS ===
21
96
  if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isArrowFunction(node)) {
22
97
  const name = this.getNodeName(node);
23
98
  if (node.parameters.length > maxParams) {
24
- failures.push({
99
+ addFailure({
25
100
  id: 'AST_MAX_PARAMS',
26
101
  title: `Function '${name}' has ${node.parameters.length} parameters (max: ${maxParams})`,
27
102
  details: `High parameter count detected in ${relativePath}`,
@@ -46,7 +121,7 @@ export class TypeScriptHandler extends ASTHandler {
46
121
  };
47
122
  ts.forEachChild(node, countComplexity);
48
123
  if (complexity > maxComplexity) {
49
- failures.push({
124
+ addFailure({
50
125
  id: 'AST_COMPLEXITY',
51
126
  title: `Function '${name}' has cyclomatic complexity of ${complexity} (max: ${maxComplexity})`,
52
127
  details: `High complexity detected in ${relativePath}`,
@@ -59,7 +134,7 @@ export class TypeScriptHandler extends ASTHandler {
59
134
  const name = node.name?.text || 'Anonymous Class';
60
135
  const methods = node.members.filter(ts.isMethodDeclaration);
61
136
  if (methods.length > maxMethods) {
62
- failures.push({
137
+ addFailure({
63
138
  id: 'AST_MAX_METHODS',
64
139
  title: `Class '${name}' has ${methods.length} methods (max: ${maxMethods})`,
65
140
  details: `God Object pattern detected in ${relativePath}`,
@@ -31,7 +31,7 @@ export class ContextEngine {
31
31
  }
32
32
  catch (e) { }
33
33
  }
34
- console.log(`[ContextEngine] Discovered ${envVars.size} env anchors`);
34
+ // Logs removed to avoid stdout pollution in JSON mode
35
35
  // Convert envVars to anchors
36
36
  for (const [name, count] of envVars.entries()) {
37
37
  const confidence = count >= 2 ? 1 : 0.5;
@@ -192,6 +192,19 @@ export const UNIVERSAL_CONFIG = {
192
192
  auto_classify: true,
193
193
  doc_sources: {},
194
194
  },
195
+ staleness: {
196
+ enabled: false,
197
+ rules: {
198
+ 'no-var': true,
199
+ 'no-commonjs': false,
200
+ 'no-arguments': false,
201
+ 'prefer-arrow': false,
202
+ 'prefer-template': false,
203
+ 'prefer-spread': false,
204
+ 'prefer-rest': false,
205
+ 'prefer-const': false,
206
+ },
207
+ },
195
208
  },
196
209
  output: {
197
210
  report_path: 'rigour-report.json',
@@ -30,6 +30,16 @@ export declare const GatesSchema: z.ZodObject<{
30
30
  max_class_dependencies?: number | undefined;
31
31
  max_function_lines?: number | undefined;
32
32
  }>>>;
33
+ staleness: z.ZodDefault<z.ZodOptional<z.ZodObject<{
34
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
35
+ rules: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>>;
36
+ }, "strip", z.ZodTypeAny, {
37
+ enabled: boolean;
38
+ rules: Record<string, boolean>;
39
+ }, {
40
+ enabled?: boolean | undefined;
41
+ rules?: Record<string, boolean> | undefined;
42
+ }>>>;
33
43
  dependencies: z.ZodDefault<z.ZodOptional<z.ZodObject<{
34
44
  forbid: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
35
45
  trusted_registry: z.ZodOptional<z.ZodString>;
@@ -140,6 +150,10 @@ export declare const GatesSchema: z.ZodObject<{
140
150
  max_class_dependencies: number;
141
151
  max_function_lines: number;
142
152
  };
153
+ staleness: {
154
+ enabled: boolean;
155
+ rules: Record<string, boolean>;
156
+ };
143
157
  dependencies: {
144
158
  forbid: string[];
145
159
  trusted_registry?: string | undefined;
@@ -188,6 +202,10 @@ export declare const GatesSchema: z.ZodObject<{
188
202
  max_class_dependencies?: number | undefined;
189
203
  max_function_lines?: number | undefined;
190
204
  } | undefined;
205
+ staleness?: {
206
+ enabled?: boolean | undefined;
207
+ rules?: Record<string, boolean> | undefined;
208
+ } | undefined;
191
209
  dependencies?: {
192
210
  forbid?: string[] | undefined;
193
211
  trusted_registry?: string | undefined;
@@ -289,6 +307,16 @@ export declare const ConfigSchema: z.ZodObject<{
289
307
  max_class_dependencies?: number | undefined;
290
308
  max_function_lines?: number | undefined;
291
309
  }>>>;
310
+ staleness: z.ZodDefault<z.ZodOptional<z.ZodObject<{
311
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
312
+ rules: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>>;
313
+ }, "strip", z.ZodTypeAny, {
314
+ enabled: boolean;
315
+ rules: Record<string, boolean>;
316
+ }, {
317
+ enabled?: boolean | undefined;
318
+ rules?: Record<string, boolean> | undefined;
319
+ }>>>;
292
320
  dependencies: z.ZodDefault<z.ZodOptional<z.ZodObject<{
293
321
  forbid: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
294
322
  trusted_registry: z.ZodOptional<z.ZodString>;
@@ -399,6 +427,10 @@ export declare const ConfigSchema: z.ZodObject<{
399
427
  max_class_dependencies: number;
400
428
  max_function_lines: number;
401
429
  };
430
+ staleness: {
431
+ enabled: boolean;
432
+ rules: Record<string, boolean>;
433
+ };
402
434
  dependencies: {
403
435
  forbid: string[];
404
436
  trusted_registry?: string | undefined;
@@ -447,6 +479,10 @@ export declare const ConfigSchema: z.ZodObject<{
447
479
  max_class_dependencies?: number | undefined;
448
480
  max_function_lines?: number | undefined;
449
481
  } | undefined;
482
+ staleness?: {
483
+ enabled?: boolean | undefined;
484
+ rules?: Record<string, boolean> | undefined;
485
+ } | undefined;
450
486
  dependencies?: {
451
487
  forbid?: string[] | undefined;
452
488
  trusted_registry?: string | undefined;
@@ -514,6 +550,10 @@ export declare const ConfigSchema: z.ZodObject<{
514
550
  max_class_dependencies: number;
515
551
  max_function_lines: number;
516
552
  };
553
+ staleness: {
554
+ enabled: boolean;
555
+ rules: Record<string, boolean>;
556
+ };
517
557
  dependencies: {
518
558
  forbid: string[];
519
559
  trusted_registry?: string | undefined;
@@ -580,6 +620,10 @@ export declare const ConfigSchema: z.ZodObject<{
580
620
  max_class_dependencies?: number | undefined;
581
621
  max_function_lines?: number | undefined;
582
622
  } | undefined;
623
+ staleness?: {
624
+ enabled?: boolean | undefined;
625
+ rules?: Record<string, boolean> | undefined;
626
+ } | undefined;
583
627
  dependencies?: {
584
628
  forbid?: string[] | undefined;
585
629
  trusted_registry?: string | undefined;
@@ -19,6 +19,20 @@ export const GatesSchema = z.object({
19
19
  max_class_dependencies: z.number().optional().default(5),
20
20
  max_function_lines: z.number().optional().default(50),
21
21
  }).optional().default({}),
22
+ staleness: z.object({
23
+ enabled: z.boolean().optional().default(false),
24
+ // Rule-based staleness detection (toggle individual rules)
25
+ rules: z.record(z.boolean()).optional().default({
26
+ 'no-var': true, // var → const/let (ES6+)
27
+ 'no-commonjs': false, // require() → import
28
+ 'no-arguments': false, // arguments → rest params
29
+ 'prefer-arrow': false, // function → arrow function
30
+ 'prefer-template': false, // 'a' + b → `a${b}`
31
+ 'prefer-spread': false, // apply() → spread
32
+ 'prefer-rest': false, // arguments → ...args
33
+ 'prefer-const': false, // let (unchanged) → const
34
+ }),
35
+ }).optional().default({}),
22
36
  dependencies: z.object({
23
37
  forbid: z.array(z.string()).optional().default([]),
24
38
  trusted_registry: z.string().optional(),
@@ -13,12 +13,12 @@ export class Logger {
13
13
  }
14
14
  static info(message) {
15
15
  if (this.level <= LogLevel.INFO) {
16
- console.log(chalk.blue('info: ') + message);
16
+ console.error(chalk.blue('info: ') + message);
17
17
  }
18
18
  }
19
19
  static warn(message) {
20
20
  if (this.level <= LogLevel.WARN) {
21
- console.log(chalk.yellow('warn: ') + message);
21
+ console.error(chalk.yellow('warn: ') + message);
22
22
  }
23
23
  }
24
24
  static error(message, error) {
@@ -31,7 +31,7 @@ export class Logger {
31
31
  }
32
32
  static debug(message) {
33
33
  if (this.level <= LogLevel.DEBUG) {
34
- console.log(chalk.dim('debug: ') + message);
34
+ console.error(chalk.dim('debug: ') + message);
35
35
  }
36
36
  }
37
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigour-labs/core",
3
- "version": "2.14.0",
3
+ "version": "2.16.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,16 +18,98 @@ export class TypeScriptHandler extends ASTHandler {
18
18
 
19
19
  private analyzeSourceFile(sourceFile: ts.SourceFile, relativePath: string, failures: Failure[]) {
20
20
  const astConfig = this.config.ast || {};
21
+ const stalenessConfig = (this.config as any).staleness || {};
22
+ const stalenessRules = stalenessConfig.rules || {};
21
23
  const maxComplexity = astConfig.complexity || 10;
22
24
  const maxMethods = astConfig.max_methods || 10;
23
25
  const maxParams = astConfig.max_params || 5;
24
26
 
27
+ // Limit failures per file to avoid output bloat on large files
28
+ const MAX_FAILURES_PER_FILE = 50;
29
+ const fileFailureCount: Record<string, number> = {};
30
+
31
+ const addFailure = (failure: Failure): boolean => {
32
+ const ruleId = failure.id;
33
+ fileFailureCount[ruleId] = (fileFailureCount[ruleId] || 0) + 1;
34
+ if (fileFailureCount[ruleId] <= MAX_FAILURES_PER_FILE) {
35
+ failures.push(failure);
36
+ return true;
37
+ }
38
+ // Add summary failure once when limit is reached
39
+ if (fileFailureCount[ruleId] === MAX_FAILURES_PER_FILE + 1) {
40
+ failures.push({
41
+ id: `${ruleId}_LIMIT_EXCEEDED`,
42
+ title: `More than ${MAX_FAILURES_PER_FILE} ${ruleId} violations in ${relativePath}`,
43
+ details: `Truncated output: showing first ${MAX_FAILURES_PER_FILE} violations. Consider fixing the root cause.`,
44
+ files: [relativePath],
45
+ hint: `This file has many violations. Fix them systematically or exclude the file if it's legacy code.`
46
+ });
47
+ }
48
+ return false;
49
+ };
50
+
51
+ // Helper to check if a staleness rule is enabled
52
+ const isRuleEnabled = (rule: string): boolean => {
53
+ if (!stalenessConfig.enabled) return false;
54
+ return stalenessRules[rule] !== false; // Enabled by default if staleness is on
55
+ };
56
+
25
57
  const visit = (node: ts.Node) => {
58
+ // === STALENESS CHECKS (Rule-based) ===
59
+
60
+ // no-var: Forbid legacy 'var' keyword
61
+ if (isRuleEnabled('no-var') && ts.isVariableStatement(node)) {
62
+ const declarationList = node.declarationList;
63
+ // NodeFlags: Let = 1, Const = 2, None = 0 (var)
64
+ if ((declarationList.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) === 0) {
65
+ const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
66
+ addFailure({
67
+ id: 'STALENESS_NO_VAR',
68
+ title: `Stale 'var' keyword at line ${line}`,
69
+ details: `Use 'const' or 'let' instead of 'var' in ${relativePath}:${line}`,
70
+ files: [relativePath],
71
+ hint: `Replace 'var' with 'const' (preferred) or 'let' for modern JavaScript.`
72
+ });
73
+ }
74
+ }
75
+
76
+ // no-commonjs: Forbid require() in favor of import
77
+ if (isRuleEnabled('no-commonjs') && ts.isCallExpression(node)) {
78
+ if (ts.isIdentifier(node.expression) && node.expression.text === 'require') {
79
+ const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
80
+ addFailure({
81
+ id: 'STALENESS_NO_COMMONJS',
82
+ title: `CommonJS require() at line ${line}`,
83
+ details: `Use ES6 'import' instead of 'require()' in ${relativePath}:${line}`,
84
+ files: [relativePath],
85
+ hint: `Replace require('module') with import module from 'module'.`
86
+ });
87
+ }
88
+ }
89
+
90
+ // no-arguments: Forbid 'arguments' object (use rest params)
91
+ if (isRuleEnabled('no-arguments') && ts.isIdentifier(node) && node.text === 'arguments') {
92
+ // Check if it's actually the arguments keyword and not a variable named arguments
93
+ const parent = node.parent;
94
+ if (!ts.isVariableDeclaration(parent) && !ts.isPropertyAccessExpression(parent)) {
95
+ const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
96
+ addFailure({
97
+ id: 'STALENESS_NO_ARGUMENTS',
98
+ title: `Legacy 'arguments' object at line ${line}`,
99
+ details: `Use rest parameters (...args) instead of 'arguments' in ${relativePath}:${line}`,
100
+ files: [relativePath],
101
+ hint: `Replace 'arguments' with rest parameters: function(...args) { }`
102
+ });
103
+ }
104
+ }
105
+
106
+ // === COMPLEXITY CHECKS ===
107
+
26
108
  if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isArrowFunction(node)) {
27
109
  const name = this.getNodeName(node);
28
110
 
29
111
  if (node.parameters.length > maxParams) {
30
- failures.push({
112
+ addFailure({
31
113
  id: 'AST_MAX_PARAMS',
32
114
  title: `Function '${name}' has ${node.parameters.length} parameters (max: ${maxParams})`,
33
115
  details: `High parameter count detected in ${relativePath}`,
@@ -54,7 +136,7 @@ export class TypeScriptHandler extends ASTHandler {
54
136
  ts.forEachChild(node, countComplexity);
55
137
 
56
138
  if (complexity > maxComplexity) {
57
- failures.push({
139
+ addFailure({
58
140
  id: 'AST_COMPLEXITY',
59
141
  title: `Function '${name}' has cyclomatic complexity of ${complexity} (max: ${maxComplexity})`,
60
142
  details: `High complexity detected in ${relativePath}`,
@@ -69,7 +151,7 @@ export class TypeScriptHandler extends ASTHandler {
69
151
  const methods = node.members.filter(ts.isMethodDeclaration);
70
152
 
71
153
  if (methods.length > maxMethods) {
72
- failures.push({
154
+ addFailure({
73
155
  id: 'AST_MAX_METHODS',
74
156
  title: `Class '${name}' has ${methods.length} methods (max: ${maxMethods})`,
75
157
  details: `God Object pattern detected in ${relativePath}`,
@@ -50,7 +50,7 @@ export class ContextEngine {
50
50
  } catch (e) { }
51
51
  }
52
52
 
53
- console.log(`[ContextEngine] Discovered ${envVars.size} env anchors`);
53
+ // Logs removed to avoid stdout pollution in JSON mode
54
54
 
55
55
  // Convert envVars to anchors
56
56
  for (const [name, count] of envVars.entries()) {
@@ -208,6 +208,19 @@ export const UNIVERSAL_CONFIG: Config = {
208
208
  auto_classify: true,
209
209
  doc_sources: {},
210
210
  },
211
+ staleness: {
212
+ enabled: false,
213
+ rules: {
214
+ 'no-var': true,
215
+ 'no-commonjs': false,
216
+ 'no-arguments': false,
217
+ 'prefer-arrow': false,
218
+ 'prefer-template': false,
219
+ 'prefer-spread': false,
220
+ 'prefer-rest': false,
221
+ 'prefer-const': false,
222
+ },
223
+ },
211
224
  },
212
225
  output: {
213
226
  report_path: 'rigour-report.json',
@@ -20,6 +20,20 @@ export const GatesSchema = z.object({
20
20
  max_class_dependencies: z.number().optional().default(5),
21
21
  max_function_lines: z.number().optional().default(50),
22
22
  }).optional().default({}),
23
+ staleness: z.object({
24
+ enabled: z.boolean().optional().default(false),
25
+ // Rule-based staleness detection (toggle individual rules)
26
+ rules: z.record(z.boolean()).optional().default({
27
+ 'no-var': true, // var → const/let (ES6+)
28
+ 'no-commonjs': false, // require() → import
29
+ 'no-arguments': false, // arguments → rest params
30
+ 'prefer-arrow': false, // function → arrow function
31
+ 'prefer-template': false, // 'a' + b → `a${b}`
32
+ 'prefer-spread': false, // apply() → spread
33
+ 'prefer-rest': false, // arguments → ...args
34
+ 'prefer-const': false, // let (unchanged) → const
35
+ }),
36
+ }).optional().default({}),
23
37
  dependencies: z.object({
24
38
  forbid: z.array(z.string()).optional().default([]),
25
39
  trusted_registry: z.string().optional(),
@@ -16,13 +16,13 @@ export class Logger {
16
16
 
17
17
  static info(message: string) {
18
18
  if (this.level <= LogLevel.INFO) {
19
- console.log(chalk.blue('info: ') + message);
19
+ console.error(chalk.blue('info: ') + message);
20
20
  }
21
21
  }
22
22
 
23
23
  static warn(message: string) {
24
24
  if (this.level <= LogLevel.WARN) {
25
- console.log(chalk.yellow('warn: ') + message);
25
+ console.error(chalk.yellow('warn: ') + message);
26
26
  }
27
27
  }
28
28
 
@@ -37,7 +37,7 @@ export class Logger {
37
37
 
38
38
  static debug(message: string) {
39
39
  if (this.level <= LogLevel.DEBUG) {
40
- console.log(chalk.dim('debug: ') + message);
40
+ console.error(chalk.dim('debug: ') + message);
41
41
  }
42
42
  }
43
43
  }