@rigour-labs/core 6.11.1-rc.2 → 6.11.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.
@@ -8,6 +8,8 @@ export interface GateContext {
8
8
  patterns?: string[];
9
9
  /** Shared file cache across gates — reduces memory ~80% on large repos */
10
10
  fileCache?: FileSystemCache;
11
+ /** A gate that could not check (nothing to compare with) says why: the run shows SKIP, never PASS. */
12
+ skip?: (reason: string) => void;
11
13
  }
12
14
  export declare abstract class Gate {
13
15
  readonly id: string;
@@ -104,7 +104,8 @@ class PythonAdapter {
104
104
  if (match && !line.includes('def') && !line.includes('class')) {
105
105
  const name = match[1];
106
106
  // Distinguish constants from regular variables
107
- const kind = /^[A-Z][A-Z0-9_]*$/.test(name) ? 'constant' : 'variable';
107
+ // A private constant (`_RETRY_DELAYS`) is a constant too: the same reading classifyCasing gives it.
108
+ const kind = classifyCasing(name) === 'SCREAMING_SNAKE' ? 'constant' : 'variable';
108
109
  patterns.push({
109
110
  name, kind,
110
111
  convention: classifyCasing(name),
@@ -53,8 +53,11 @@ export interface NamingPattern {
53
53
  kind: 'function' | 'class' | 'variable' | 'constant' | 'method';
54
54
  convention: NamingConvention;
55
55
  }
56
- export type NamingConvention = 'camelCase' | 'PascalCase' | 'snake_case' | 'SCREAMING_SNAKE' | 'kebab-case' | 'other';
56
+ export type NamingConvention = 'camelCase' | 'PascalCase' | 'snake_case' | 'SCREAMING_SNAKE' | 'kebab-case' | 'other'
57
+ /** One lowercase word (`run`, `data`): camelCase and snake_case alike, so evidence of neither. */
58
+ | 'ambiguous';
57
59
  /**
58
- * Classify a name into its casing convention.
60
+ * Classify a name into its casing convention. Leading underscores mark privacy, not casing (`_load_rows` is
61
+ * snake_case, `_Private` PascalCase), and a dunder (`__init__`) is the language's own name, so it is ambiguous.
59
62
  */
60
- export declare function classifyCasing(name: string): NamingConvention;
63
+ export declare function classifyCasing(raw: string): NamingConvention;
@@ -5,13 +5,21 @@
5
5
  * Gates use these adapters instead of inline `if ext === '.py'` chains.
6
6
  */
7
7
  /**
8
- * Classify a name into its casing convention.
8
+ * Classify a name into its casing convention. Leading underscores mark privacy, not casing (`_load_rows` is
9
+ * snake_case, `_Private` PascalCase), and a dunder (`__init__`) is the language's own name, so it is ambiguous.
9
10
  */
10
- export function classifyCasing(name) {
11
+ export function classifyCasing(raw) {
12
+ if (/^__\w+__$/.test(raw))
13
+ return 'ambiguous';
14
+ const name = raw.replace(/^_+/, '');
15
+ if (!name)
16
+ return 'other';
11
17
  if (/^[A-Z][A-Z0-9_]*$/.test(name))
12
18
  return 'SCREAMING_SNAKE';
13
19
  if (/^[A-Z][a-zA-Z0-9]*$/.test(name))
14
20
  return 'PascalCase';
21
+ if (/^[a-z][a-z0-9]*$/.test(name))
22
+ return 'ambiguous';
15
23
  if (/^[a-z][a-zA-Z0-9]*$/.test(name))
16
24
  return 'camelCase';
17
25
  if (/^[a-z][a-z0-9_]*$/.test(name))
@@ -5,6 +5,8 @@ export interface GitLogicBase {
5
5
  changedFiles: Set<string>;
6
6
  readAtBase: (file: string) => string | null;
7
7
  }
8
+ /** Why a check that compares with the main branch could not run in a git checkout (resolveGitLogicBase gave null). */
9
+ export declare const NO_GIT_BASE = "nothing to compare with: no main branch (GITHUB_BASE_REF, origin/main, main, origin/master or master), or run from below the repository root";
8
10
  export declare function isGitWorktree(cwd: string): boolean;
9
11
  export interface BranchBase {
10
12
  /** The main branch as found: GITHUB_BASE_REF, else origin/main, main, origin/master, master. */
@@ -1,4 +1,6 @@
1
1
  import { execFileSync } from 'node:child_process';
2
+ /** Why a check that compares with the main branch could not run in a git checkout (resolveGitLogicBase gave null). */
3
+ export const NO_GIT_BASE = 'nothing to compare with: no main branch (GITHUB_BASE_REF, origin/main, main, origin/master or master), or run from below the repository root';
2
4
  export function isGitWorktree(cwd) {
3
5
  return git(cwd, ['rev-parse', '--is-inside-work-tree'])?.trim() === 'true';
4
6
  }
@@ -26,7 +26,7 @@ import { FileScanner } from '../utils/scanner.js';
26
26
  import { Logger } from '../utils/logger.js';
27
27
  import { languageAdapters } from './language-adapters/index.js';
28
28
  import { extractCallSequence, isDangerousMutation } from './logic-drift-extractors.js';
29
- import { isGitWorktree, resolveGitLogicBase } from './logic-drift-git-base.js';
29
+ import { NO_GIT_BASE, isGitWorktree, resolveGitLogicBase } from './logic-drift-git-base.js';
30
30
  import { isScoped } from '../utils/scope.js';
31
31
  import fs from 'fs-extra';
32
32
  import path from 'path';
@@ -60,7 +60,7 @@ export class LogicDriftGate extends Gate {
60
60
  // In Git worktrees, compare only files changed from an immutable base.
61
61
  const gitBase = resolveGitLogicBase(context.cwd);
62
62
  if (!gitBase && isGitWorktree(context.cwd)) {
63
- Logger.info('Logic Drift: no main reference available; comparison unavailable');
63
+ context.skip?.(NO_GIT_BASE);
64
64
  return [];
65
65
  }
66
66
  const scanFiles = gitBase ? files.filter(file => gitBase.changedFiles.has(file)) : files;
@@ -145,6 +145,7 @@ export class GateRunner {
145
145
  const start = Date.now();
146
146
  const failures = [];
147
147
  const summary = {};
148
+ const skips = {};
148
149
  const ignore = this.config.ignore;
149
150
  // 0. Run Context Discovery
150
151
  let record;
@@ -162,11 +163,16 @@ export class GateRunner {
162
163
  gateIndex++;
163
164
  try {
164
165
  onProgress?.(` [${gateIndex}/${totalGates}] Running ${gate.id}...`);
165
- const gateFailures = await gate.run({ cwd, record, ignore, patterns, fileCache });
166
+ let skipped;
167
+ const gateFailures = await gate.run({ cwd, record, ignore, patterns, fileCache, skip: reason => { skipped = reason; } });
166
168
  if (gateFailures.length > 0) {
167
169
  failures.push(...gateFailures);
168
170
  summary[gate.id] = 'FAIL';
169
171
  }
172
+ else if (skipped !== undefined) {
173
+ summary[gate.id] = 'SKIP';
174
+ skips[gate.id] = skipped;
175
+ }
170
176
  else {
171
177
  summary[gate.id] = 'PASS';
172
178
  }
@@ -299,6 +305,7 @@ export class GateRunner {
299
305
  const report = {
300
306
  status,
301
307
  summary,
308
+ ...(Object.keys(skips).length ? { skips } : {}),
302
309
  failures,
303
310
  stats: {
304
311
  duration_ms: Date.now() - start,
@@ -3,10 +3,6 @@
3
3
  *
4
4
  * Contains per-language naming convention rules and naming pattern regexes.
5
5
  */
6
- /**
7
- * Casing classification rules
8
- */
9
- export declare function classifyCasing(name: string): 'camelCase' | 'snake_case' | 'PascalCase' | 'SCREAMING_SNAKE' | null;
10
6
  /**
11
7
  * Function name pattern for JavaScript
12
8
  */
@@ -3,25 +3,6 @@
3
3
  *
4
4
  * Contains per-language naming convention rules and naming pattern regexes.
5
5
  */
6
- /**
7
- * Casing classification rules
8
- */
9
- export function classifyCasing(name) {
10
- if (name.startsWith('_') || name.length <= 1)
11
- return null;
12
- if (/^[A-Z][A-Z0-9_]+$/.test(name)) {
13
- return 'SCREAMING_SNAKE';
14
- }
15
- else if (/^[A-Z]/.test(name)) {
16
- return 'PascalCase';
17
- }
18
- else if (name.includes('_')) {
19
- return 'snake_case';
20
- }
21
- else {
22
- return 'camelCase';
23
- }
24
- }
25
6
  /**
26
7
  * Function name pattern for JavaScript
27
8
  */
@@ -25,7 +25,7 @@ import { Logger } from '../utils/logger.js';
25
25
  import { languageAdapters } from './language-adapters/index.js';
26
26
  import { extractComparableJsNames } from './js-style-context.js';
27
27
  import { isScoped } from '../utils/scope.js';
28
- import { filesAtCommit, isGitWorktree, readManyAtCommit, resolveGitLogicBase, untrackedFiles } from './logic-drift-git-base.js';
28
+ import { filesAtCommit, isGitWorktree, NO_GIT_BASE, readManyAtCommit, resolveGitLogicBase, untrackedFiles } from './logic-drift-git-base.js';
29
29
  import { TRY_CATCH_PATTERN, CATCH_PATTERN, RESULT_TYPE_PATTERN, NAMED_IMPORT_PATTERN, WILDCARD_IMPORT_PATTERN, SIDE_EFFECT_IMPORT_PATTERN, DEFAULT_IMPORT_PATTERN, countQuotes, } from './style-drift-rules.js';
30
30
  import fs from 'fs-extra';
31
31
  import path from 'path';
@@ -59,7 +59,7 @@ export class StyleDriftGate extends Gate {
59
59
  // can change a verdict, and a .rigour/style-baseline.json left by an older version is never read.
60
60
  const gitBase = resolveGitLogicBase(context.cwd);
61
61
  if (!gitBase && isGitWorktree(context.cwd)) {
62
- Logger.info('Style Drift: no main reference available; comparison unavailable');
62
+ context.skip?.(NO_GIT_BASE);
63
63
  return [];
64
64
  }
65
65
  if (gitBase)
@@ -210,10 +210,13 @@ export class StyleDriftGate extends Gate {
210
210
  ? extractComparableJsNames(content, filePath)
211
211
  : adapter.extractNamingPatterns(content);
212
212
  for (const pattern of namingPatterns) {
213
+ if (pattern.convention === 'ambiguous')
214
+ continue; // a one-word name says nothing about the casing
213
215
  if (pattern.kind === 'function' || pattern.kind === 'method') {
214
216
  fp.naming.functions[pattern.convention]++;
215
217
  }
216
- else if (pattern.kind === 'variable' || pattern.kind === 'constant') {
218
+ else if (pattern.kind === 'variable') {
219
+ // A constant follows the constant convention (`MAX_ROWS`), never the variables': it is not compared.
217
220
  fp.naming.variables[pattern.convention]++;
218
221
  }
219
222
  }
package/dist/index.d.ts CHANGED
@@ -11,7 +11,7 @@ export { FrontendSecretExposureGate } from './gates/frontend-secret-exposure.js'
11
11
  export * from './utils/logger.js';
12
12
  export { normalizeScopePatterns, isScoped } from './utils/scope.js';
13
13
  export { deepAnalysisError } from './utils/deep-status.js';
14
- export { rigourUserDir, repoStateDir } from './utils/user-state.js';
14
+ export { rigourUserDir, repoStateDir, legacyStateNote } from './utils/user-state.js';
15
15
  export { locateTransformers, semanticRuntimeDir, semanticRuntimeInstalled, TRANSFORMERS_SPEC } from './pattern-index/semantic-runtime.js';
16
16
  export { reviewChange, toReviewFinding, type ReviewInput, type ReviewResult, type ReviewFinding } from './review/review.js';
17
17
  export { splitByChangedLines, type ChangedLineSplit } from './review/changed-lines.js';
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ export { FrontendSecretExposureGate } from './gates/frontend-secret-exposure.js'
11
11
  export * from './utils/logger.js';
12
12
  export { normalizeScopePatterns, isScoped } from './utils/scope.js';
13
13
  export { deepAnalysisError } from './utils/deep-status.js';
14
- export { rigourUserDir, repoStateDir } from './utils/user-state.js';
14
+ export { rigourUserDir, repoStateDir, legacyStateNote } from './utils/user-state.js';
15
15
  export { locateTransformers, semanticRuntimeDir, semanticRuntimeInstalled, TRANSFORMERS_SPEC } from './pattern-index/semantic-runtime.js';
16
16
  export { reviewChange, toReviewFinding } from './review/review.js';
17
17
  export { splitByChangedLines } from './review/changed-lines.js';
@@ -16,6 +16,8 @@ export interface RenderOptions {
16
16
  export interface GateResult {
17
17
  id: string;
18
18
  status: Status;
19
+ /** Why a SKIP: the check could not check. */
20
+ reason?: string;
19
21
  }
20
22
  export declare function renderScoreGauge(score: number, label: string): string;
21
23
  export declare function renderSeveritySection(failures: Failure[], severity: Severity, maxItems?: number): string;
@@ -65,6 +65,9 @@ export function renderGateGrid(gates) {
65
65
  const row = entries.slice(i, i + 3).map(e => e.padEnd(22)).join('');
66
66
  lines.push(` ${row}`);
67
67
  }
68
+ for (const g of gates)
69
+ if (g.status === 'SKIP' && g.reason)
70
+ lines.push(chalk.dim(` \u{23ED} ${g.id} skipped: ${g.reason}`));
68
71
  return lines.join('\n');
69
72
  }
70
73
  // ── Brain Status ───────────────────────────────────────────────────
@@ -155,7 +158,7 @@ export function renderFullReport(report, options = {}) {
155
158
  }
156
159
  // Gate grid from report summary
157
160
  if (report.summary) {
158
- const gates = Object.entries(report.summary).map(([id, status]) => ({ id, status }));
161
+ const gates = Object.entries(report.summary).map(([id, status]) => ({ id, status, reason: report.skips?.[id] }));
159
162
  if (gates.length > 0) {
160
163
  boxContent.push(renderGateGrid(gates));
161
164
  boxContent.push('');
@@ -3435,6 +3435,8 @@ export type Failure = z.infer<typeof FailureSchema>;
3435
3435
  export declare const ReportSchema: z.ZodObject<{
3436
3436
  status: z.ZodEnum<["PASS", "FAIL", "SKIP", "ERROR"]>;
3437
3437
  summary: z.ZodRecord<z.ZodString, z.ZodEnum<["PASS", "FAIL", "SKIP", "ERROR"]>>;
3438
+ /** Why a check reported SKIP: it could not check (for example, no main branch to compare with). */
3439
+ skips: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
3438
3440
  failures: z.ZodArray<z.ZodObject<{
3439
3441
  id: z.ZodString;
3440
3442
  title: z.ZodString;
@@ -3764,6 +3766,7 @@ export declare const ReportSchema: z.ZodObject<{
3764
3766
  category?: string | undefined;
3765
3767
  advisory?: boolean | undefined;
3766
3768
  }[];
3769
+ skips?: Record<string, string> | undefined;
3767
3770
  }, {
3768
3771
  stats: {
3769
3772
  duration_ms: number;
@@ -3828,6 +3831,7 @@ export declare const ReportSchema: z.ZodObject<{
3828
3831
  category?: string | undefined;
3829
3832
  advisory?: boolean | undefined;
3830
3833
  }[];
3834
+ skips?: Record<string, string> | undefined;
3831
3835
  }>;
3832
3836
  export type Report = z.infer<typeof ReportSchema>;
3833
3837
  /** Options passed from CLI --deep / --pro / --max / -k flags */
@@ -521,6 +521,8 @@ export const FailureSchema = z.object({
521
521
  export const ReportSchema = z.object({
522
522
  status: StatusSchema,
523
523
  summary: z.record(StatusSchema),
524
+ /** Why a check reported SKIP: it could not check (for example, no main branch to compare with). */
525
+ skips: z.record(z.string()).optional(),
524
526
  failures: z.array(FailureSchema),
525
527
  stats: z.object({
526
528
  duration_ms: z.number(),
@@ -1,5 +1,15 @@
1
- /** The home Rigour keeps its state under: RIGOUR_HOME when set (a profile, a sandbox, tests), else the OS home. */
1
+ /**
2
+ * The home Rigour keeps its state under (`<home>/.rigour`): RIGOUR_HOME when set (a profile, a sandbox, tests),
3
+ * else the OS home. A RIGOUR_HOME ending in `.rigour` names the state directory itself, so its parent is the home.
4
+ * Earlier versions kept state one level deeper (`<RIGOUR_HOME>/.rigour`): where that holds state, it stays in use
5
+ * and nothing is moved (legacyStateNote says how).
6
+ */
2
7
  export declare function rigourHome(): string;
8
+ /**
9
+ * For `rigour doctor`: state an earlier version wrote under `<RIGOUR_HOME>/.rigour`, still in use, and how to move
10
+ * it; or, when the named directory holds state of its own too, both places. Nothing is moved for anyone.
11
+ */
12
+ export declare function legacyStateNote(): string | undefined;
3
13
  /** The user's Rigour directory: `<rigourHome()>/.rigour`. Every piece of Rigour's own state lives under it. */
4
14
  export declare function rigourUserDir(): string;
5
15
  /**
@@ -2,9 +2,49 @@ import crypto from 'crypto';
2
2
  import fs from 'fs';
3
3
  import os from 'os';
4
4
  import path from 'path';
5
- /** The home Rigour keeps its state under: RIGOUR_HOME when set (a profile, a sandbox, tests), else the OS home. */
5
+ /**
6
+ * The home Rigour keeps its state under (`<home>/.rigour`): RIGOUR_HOME when set (a profile, a sandbox, tests),
7
+ * else the OS home. A RIGOUR_HOME ending in `.rigour` names the state directory itself, so its parent is the home.
8
+ * Earlier versions kept state one level deeper (`<RIGOUR_HOME>/.rigour`): where that holds state, it stays in use
9
+ * and nothing is moved (legacyStateNote says how).
10
+ */
6
11
  export function rigourHome() {
7
- return process.env.RIGOUR_HOME || os.homedir();
12
+ const set = process.env.RIGOUR_HOME;
13
+ if (!set)
14
+ return os.homedir();
15
+ if (path.basename(path.resolve(set)) !== '.rigour')
16
+ return set;
17
+ return hasState(path.join(set, '.rigour')) ? set : path.dirname(path.resolve(set));
18
+ }
19
+ function hasState(dir) {
20
+ try {
21
+ return fs.readdirSync(dir).length > 0;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ /**
28
+ * For `rigour doctor`: state an earlier version wrote under `<RIGOUR_HOME>/.rigour`, still in use, and how to move
29
+ * it; or, when the named directory holds state of its own too, both places. Nothing is moved for anyone.
30
+ */
31
+ export function legacyStateNote() {
32
+ const set = process.env.RIGOUR_HOME;
33
+ if (!set || path.basename(path.resolve(set)) !== '.rigour')
34
+ return undefined;
35
+ const named = path.resolve(set);
36
+ const doubled = path.join(named, '.rigour');
37
+ if (!hasState(doubled))
38
+ return undefined;
39
+ const own = (() => { try {
40
+ return fs.readdirSync(named).filter(entry => entry !== '.rigour').length > 0;
41
+ }
42
+ catch {
43
+ return false;
44
+ } })();
45
+ return own
46
+ ? `Rigour state is in both ${named} and ${doubled}. This run uses ${doubled}, where your tools have been writing; merge the two by hand if you need both.`
47
+ : `Rigour state is in ${doubled}, written by an earlier version. RIGOUR_HOME=${set} now names the state directory itself: to use ${named}, move the contents of ${doubled} up one level.`;
8
48
  }
9
49
  /** The user's Rigour directory: `<rigourHome()>/.rigour`. Every piece of Rigour's own state lives under it. */
10
50
  export function rigourUserDir() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigour-labs/core",
3
- "version": "6.11.1-rc.2",
3
+ "version": "6.11.1",
4
4
  "description": "Rigour's review engine: deterministic gates on changed lines, rules and lessons learned from your team's fixes, and per-check precision from what you fix versus dismiss, across TypeScript, JavaScript, Python, Go, Ruby and C#.",
5
5
  "engines": {
6
6
  "node": ">=22.13"
@@ -75,11 +75,11 @@
75
75
  "@anthropic-ai/sdk": "^0.132.1",
76
76
  "pg": "^8.16.3",
77
77
  "openai": "^5.23.2",
78
- "@rigour-labs/brain-linux-arm64": "6.11.1-rc.2",
79
- "@rigour-labs/brain-darwin-x64": "6.11.1-rc.2",
80
- "@rigour-labs/brain-linux-x64": "6.11.1-rc.2",
81
- "@rigour-labs/brain-darwin-arm64": "6.11.1-rc.2",
82
- "@rigour-labs/brain-win-x64": "6.11.1-rc.2"
78
+ "@rigour-labs/brain-darwin-x64": "6.11.1",
79
+ "@rigour-labs/brain-linux-x64": "6.11.1",
80
+ "@rigour-labs/brain-linux-arm64": "6.11.1",
81
+ "@rigour-labs/brain-win-x64": "6.11.1",
82
+ "@rigour-labs/brain-darwin-arm64": "6.11.1"
83
83
  },
84
84
  "devDependencies": {
85
85
  "@types/fs-extra": "^11.0.4",