@rigour-labs/core 6.2.4 → 6.2.5

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 (80) hide show
  1. package/dist/deep/code-context.d.ts +28 -0
  2. package/dist/deep/code-context.js +93 -0
  3. package/dist/deep/code-context.test.d.ts +1 -0
  4. package/dist/deep/code-context.test.js +48 -0
  5. package/dist/deep/code-pass.d.ts +15 -0
  6. package/dist/deep/code-pass.js +44 -0
  7. package/dist/deep/code-review-prompt.d.ts +17 -0
  8. package/dist/deep/code-review-prompt.js +30 -0
  9. package/dist/deep/code-review-prompt.test.d.ts +1 -0
  10. package/dist/deep/code-review-prompt.test.js +20 -0
  11. package/dist/deep/code-verifier.d.ts +19 -0
  12. package/dist/deep/code-verifier.js +48 -0
  13. package/dist/deep/code-verifier.test.d.ts +1 -0
  14. package/dist/deep/code-verifier.test.js +46 -0
  15. package/dist/deep/fact-extractor.d.ts +7 -1
  16. package/dist/deep/fact-extractor.js +16 -8
  17. package/dist/deep/fact-extractor.test.js +34 -1
  18. package/dist/deep/facts-pass.d.ts +24 -0
  19. package/dist/deep/facts-pass.js +69 -0
  20. package/dist/deep/index.d.ts +6 -1
  21. package/dist/deep/index.js +5 -1
  22. package/dist/deep/parse-findings.d.ts +12 -0
  23. package/dist/deep/parse-findings.js +73 -0
  24. package/dist/deep/parse-findings.test.d.ts +1 -0
  25. package/dist/deep/parse-findings.test.js +25 -0
  26. package/dist/deep/prompts.d.ts +1 -1
  27. package/dist/deep/prompts.js +7 -8
  28. package/dist/deep/prompts.test.js +15 -0
  29. package/dist/gates/context.js +7 -0
  30. package/dist/gates/deep-analysis.d.ts +31 -8
  31. package/dist/gates/deep-analysis.js +165 -306
  32. package/dist/gates/deep-analysis.test.d.ts +1 -0
  33. package/dist/gates/deep-analysis.test.js +86 -0
  34. package/dist/gates/deep-runner.d.ts +15 -0
  35. package/dist/gates/deep-runner.js +65 -0
  36. package/dist/gates/dependency.js +4 -0
  37. package/dist/gates/logic-drift.js +7 -1
  38. package/dist/gates/runner.js +5 -40
  39. package/dist/gates/runner.test.js +37 -25
  40. package/dist/gates/scoped-gates.test.d.ts +1 -0
  41. package/dist/gates/scoped-gates.test.js +52 -0
  42. package/dist/gates/style-drift.js +3 -0
  43. package/dist/index.d.ts +6 -1
  44. package/dist/index.js +6 -1
  45. package/dist/inference/http-download.d.ts +22 -0
  46. package/dist/inference/http-download.js +132 -0
  47. package/dist/inference/http-download.test.d.ts +1 -0
  48. package/dist/inference/http-download.test.js +109 -0
  49. package/dist/inference/index.d.ts +5 -1
  50. package/dist/inference/index.js +2 -1
  51. package/dist/inference/llama-engine.d.ts +27 -0
  52. package/dist/inference/llama-engine.js +152 -0
  53. package/dist/inference/llama-engine.test.d.ts +1 -0
  54. package/dist/inference/llama-engine.test.js +51 -0
  55. package/dist/inference/llama-process.d.ts +72 -0
  56. package/dist/inference/llama-process.js +126 -0
  57. package/dist/inference/llama-process.test.d.ts +1 -0
  58. package/dist/inference/llama-process.test.js +61 -0
  59. package/dist/inference/model-download.test.d.ts +1 -0
  60. package/dist/inference/model-download.test.js +73 -0
  61. package/dist/inference/model-manager.d.ts +14 -1
  62. package/dist/inference/model-manager.js +65 -80
  63. package/dist/inference/sidecar-provider.d.ts +19 -10
  64. package/dist/inference/sidecar-provider.js +115 -343
  65. package/dist/templates/universal-config.js +0 -2
  66. package/dist/types/index.d.ts +58 -14
  67. package/dist/types/index.js +9 -2
  68. package/dist/utils/deep-status.d.ts +7 -0
  69. package/dist/utils/deep-status.js +11 -0
  70. package/dist/utils/diff.d.ts +3 -0
  71. package/dist/utils/diff.js +52 -0
  72. package/dist/utils/diff.test.d.ts +1 -0
  73. package/dist/utils/diff.test.js +38 -0
  74. package/dist/utils/scanner.js +13 -2
  75. package/dist/utils/scanner.test.js +22 -8
  76. package/dist/utils/scope.d.ts +15 -0
  77. package/dist/utils/scope.js +51 -0
  78. package/dist/utils/scope.test.d.ts +1 -0
  79. package/dist/utils/scope.test.js +38 -0
  80. package/package.json +6 -6
@@ -573,9 +573,9 @@ export declare const GatesSchema: z.ZodObject<{
573
573
  model_name: z.ZodOptional<z.ZodString>;
574
574
  model_path: z.ZodOptional<z.ZodString>;
575
575
  threads: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
576
- max_tokens: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
576
+ max_tokens: z.ZodOptional<z.ZodNumber>;
577
577
  temperature: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
578
- timeout_ms: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
578
+ timeout_ms: z.ZodOptional<z.ZodNumber>;
579
579
  checks: z.ZodDefault<z.ZodOptional<z.ZodObject<{
580
580
  solid: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
581
581
  dry: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
@@ -609,9 +609,7 @@ export declare const GatesSchema: z.ZodObject<{
609
609
  pro: boolean;
610
610
  provider: string;
611
611
  threads: number;
612
- max_tokens: number;
613
612
  temperature: number;
614
- timeout_ms: number;
615
613
  checks: {
616
614
  architecture: boolean;
617
615
  test_quality: boolean;
@@ -626,6 +624,8 @@ export declare const GatesSchema: z.ZodObject<{
626
624
  api_base_url?: string | undefined;
627
625
  model_name?: string | undefined;
628
626
  model_path?: string | undefined;
627
+ max_tokens?: number | undefined;
628
+ timeout_ms?: number | undefined;
629
629
  }, {
630
630
  enabled?: boolean | undefined;
631
631
  pro?: boolean | undefined;
@@ -655,9 +655,7 @@ export declare const GatesSchema: z.ZodObject<{
655
655
  pro: boolean;
656
656
  provider: string;
657
657
  threads: number;
658
- max_tokens: number;
659
658
  temperature: number;
660
- timeout_ms: number;
661
659
  checks: {
662
660
  architecture: boolean;
663
661
  test_quality: boolean;
@@ -672,6 +670,8 @@ export declare const GatesSchema: z.ZodObject<{
672
670
  api_base_url?: string | undefined;
673
671
  model_name?: string | undefined;
674
672
  model_path?: string | undefined;
673
+ max_tokens?: number | undefined;
674
+ timeout_ms?: number | undefined;
675
675
  };
676
676
  max_file_lines: number;
677
677
  forbid_todos: boolean;
@@ -1739,9 +1739,9 @@ export declare const ConfigSchema: z.ZodObject<{
1739
1739
  model_name: z.ZodOptional<z.ZodString>;
1740
1740
  model_path: z.ZodOptional<z.ZodString>;
1741
1741
  threads: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
1742
- max_tokens: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
1742
+ max_tokens: z.ZodOptional<z.ZodNumber>;
1743
1743
  temperature: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
1744
- timeout_ms: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
1744
+ timeout_ms: z.ZodOptional<z.ZodNumber>;
1745
1745
  checks: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1746
1746
  solid: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1747
1747
  dry: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
@@ -1775,9 +1775,7 @@ export declare const ConfigSchema: z.ZodObject<{
1775
1775
  pro: boolean;
1776
1776
  provider: string;
1777
1777
  threads: number;
1778
- max_tokens: number;
1779
1778
  temperature: number;
1780
- timeout_ms: number;
1781
1779
  checks: {
1782
1780
  architecture: boolean;
1783
1781
  test_quality: boolean;
@@ -1792,6 +1790,8 @@ export declare const ConfigSchema: z.ZodObject<{
1792
1790
  api_base_url?: string | undefined;
1793
1791
  model_name?: string | undefined;
1794
1792
  model_path?: string | undefined;
1793
+ max_tokens?: number | undefined;
1794
+ timeout_ms?: number | undefined;
1795
1795
  }, {
1796
1796
  enabled?: boolean | undefined;
1797
1797
  pro?: boolean | undefined;
@@ -1821,9 +1821,7 @@ export declare const ConfigSchema: z.ZodObject<{
1821
1821
  pro: boolean;
1822
1822
  provider: string;
1823
1823
  threads: number;
1824
- max_tokens: number;
1825
1824
  temperature: number;
1826
- timeout_ms: number;
1827
1825
  checks: {
1828
1826
  architecture: boolean;
1829
1827
  test_quality: boolean;
@@ -1838,6 +1836,8 @@ export declare const ConfigSchema: z.ZodObject<{
1838
1836
  api_base_url?: string | undefined;
1839
1837
  model_name?: string | undefined;
1840
1838
  model_path?: string | undefined;
1839
+ max_tokens?: number | undefined;
1840
+ timeout_ms?: number | undefined;
1841
1841
  };
1842
1842
  max_file_lines: number;
1843
1843
  forbid_todos: boolean;
@@ -2319,9 +2319,7 @@ export declare const ConfigSchema: z.ZodObject<{
2319
2319
  pro: boolean;
2320
2320
  provider: string;
2321
2321
  threads: number;
2322
- max_tokens: number;
2323
2322
  temperature: number;
2324
- timeout_ms: number;
2325
2323
  checks: {
2326
2324
  architecture: boolean;
2327
2325
  test_quality: boolean;
@@ -2336,6 +2334,8 @@ export declare const ConfigSchema: z.ZodObject<{
2336
2334
  api_base_url?: string | undefined;
2337
2335
  model_name?: string | undefined;
2338
2336
  model_path?: string | undefined;
2337
+ max_tokens?: number | undefined;
2338
+ timeout_ms?: number | undefined;
2339
2339
  };
2340
2340
  max_file_lines: number;
2341
2341
  forbid_todos: boolean;
@@ -2944,26 +2944,45 @@ export declare const ReportSchema: z.ZodObject<{
2944
2944
  }>>;
2945
2945
  deep: z.ZodOptional<z.ZodObject<{
2946
2946
  enabled: z.ZodBoolean;
2947
+ /** ok: every inference ran; partial: some failed; error: deep did not run. */
2948
+ status: z.ZodOptional<z.ZodEnum<["ok", "partial", "error"]>>;
2949
+ mode: z.ZodOptional<z.ZodEnum<["facts", "code"]>>;
2947
2950
  tier: z.ZodOptional<z.ZodEnum<["deep", "lite", "legacy", "cloud"]>>;
2948
2951
  model: z.ZodOptional<z.ZodString>;
2952
+ model_fallback: z.ZodOptional<z.ZodBoolean>;
2949
2953
  total_ms: z.ZodOptional<z.ZodNumber>;
2950
2954
  files_analyzed: z.ZodOptional<z.ZodNumber>;
2955
+ chunks_total: z.ZodOptional<z.ZodNumber>;
2956
+ chunks_failed: z.ZodOptional<z.ZodNumber>;
2957
+ error: z.ZodOptional<z.ZodString>;
2951
2958
  findings_count: z.ZodOptional<z.ZodNumber>;
2952
2959
  findings_verified: z.ZodOptional<z.ZodNumber>;
2953
2960
  }, "strip", z.ZodTypeAny, {
2954
2961
  enabled: boolean;
2962
+ status?: "ok" | "partial" | "error" | undefined;
2963
+ mode?: "code" | "facts" | undefined;
2964
+ error?: string | undefined;
2955
2965
  tier?: "deep" | "lite" | "legacy" | "cloud" | undefined;
2956
2966
  model?: string | undefined;
2967
+ model_fallback?: boolean | undefined;
2957
2968
  total_ms?: number | undefined;
2958
2969
  files_analyzed?: number | undefined;
2970
+ chunks_total?: number | undefined;
2971
+ chunks_failed?: number | undefined;
2959
2972
  findings_count?: number | undefined;
2960
2973
  findings_verified?: number | undefined;
2961
2974
  }, {
2962
2975
  enabled: boolean;
2976
+ status?: "ok" | "partial" | "error" | undefined;
2977
+ mode?: "code" | "facts" | undefined;
2978
+ error?: string | undefined;
2963
2979
  tier?: "deep" | "lite" | "legacy" | "cloud" | undefined;
2964
2980
  model?: string | undefined;
2981
+ model_fallback?: boolean | undefined;
2965
2982
  total_ms?: number | undefined;
2966
2983
  files_analyzed?: number | undefined;
2984
+ chunks_total?: number | undefined;
2985
+ chunks_failed?: number | undefined;
2967
2986
  findings_count?: number | undefined;
2968
2987
  findings_verified?: number | undefined;
2969
2988
  }>>;
@@ -2971,10 +2990,16 @@ export declare const ReportSchema: z.ZodObject<{
2971
2990
  duration_ms: number;
2972
2991
  deep?: {
2973
2992
  enabled: boolean;
2993
+ status?: "ok" | "partial" | "error" | undefined;
2994
+ mode?: "code" | "facts" | undefined;
2995
+ error?: string | undefined;
2974
2996
  tier?: "deep" | "lite" | "legacy" | "cloud" | undefined;
2975
2997
  model?: string | undefined;
2998
+ model_fallback?: boolean | undefined;
2976
2999
  total_ms?: number | undefined;
2977
3000
  files_analyzed?: number | undefined;
3001
+ chunks_total?: number | undefined;
3002
+ chunks_failed?: number | undefined;
2978
3003
  findings_count?: number | undefined;
2979
3004
  findings_verified?: number | undefined;
2980
3005
  } | undefined;
@@ -2994,10 +3019,16 @@ export declare const ReportSchema: z.ZodObject<{
2994
3019
  duration_ms: number;
2995
3020
  deep?: {
2996
3021
  enabled: boolean;
3022
+ status?: "ok" | "partial" | "error" | undefined;
3023
+ mode?: "code" | "facts" | undefined;
3024
+ error?: string | undefined;
2997
3025
  tier?: "deep" | "lite" | "legacy" | "cloud" | undefined;
2998
3026
  model?: string | undefined;
3027
+ model_fallback?: boolean | undefined;
2999
3028
  total_ms?: number | undefined;
3000
3029
  files_analyzed?: number | undefined;
3030
+ chunks_total?: number | undefined;
3031
+ chunks_failed?: number | undefined;
3001
3032
  findings_count?: number | undefined;
3002
3033
  findings_verified?: number | undefined;
3003
3034
  } | undefined;
@@ -3019,10 +3050,16 @@ export declare const ReportSchema: z.ZodObject<{
3019
3050
  duration_ms: number;
3020
3051
  deep?: {
3021
3052
  enabled: boolean;
3053
+ status?: "ok" | "partial" | "error" | undefined;
3054
+ mode?: "code" | "facts" | undefined;
3055
+ error?: string | undefined;
3022
3056
  tier?: "deep" | "lite" | "legacy" | "cloud" | undefined;
3023
3057
  model?: string | undefined;
3058
+ model_fallback?: boolean | undefined;
3024
3059
  total_ms?: number | undefined;
3025
3060
  files_analyzed?: number | undefined;
3061
+ chunks_total?: number | undefined;
3062
+ chunks_failed?: number | undefined;
3026
3063
  findings_count?: number | undefined;
3027
3064
  findings_verified?: number | undefined;
3028
3065
  } | undefined;
@@ -3061,10 +3098,16 @@ export declare const ReportSchema: z.ZodObject<{
3061
3098
  duration_ms: number;
3062
3099
  deep?: {
3063
3100
  enabled: boolean;
3101
+ status?: "ok" | "partial" | "error" | undefined;
3102
+ mode?: "code" | "facts" | undefined;
3103
+ error?: string | undefined;
3064
3104
  tier?: "deep" | "lite" | "legacy" | "cloud" | undefined;
3065
3105
  model?: string | undefined;
3106
+ model_fallback?: boolean | undefined;
3066
3107
  total_ms?: number | undefined;
3067
3108
  files_analyzed?: number | undefined;
3109
+ chunks_total?: number | undefined;
3110
+ chunks_failed?: number | undefined;
3068
3111
  findings_count?: number | undefined;
3069
3112
  findings_verified?: number | undefined;
3070
3113
  } | undefined;
@@ -3110,4 +3153,5 @@ export interface DeepOptions {
3110
3153
  modelName?: string;
3111
3154
  agents?: number;
3112
3155
  maxFiles?: number;
3156
+ focusLines?: Record<string, number[]>;
3113
3157
  }
@@ -303,9 +303,9 @@ export const GatesSchema = z.object({
303
303
  model_name: z.string().optional(), // cloud model name override (e.g. 'gpt-4o', 'claude-sonnet-4-5-20250929', 'gemini-pro')
304
304
  model_path: z.string().optional(), // custom local GGUF model path override
305
305
  threads: z.number().optional().default(4),
306
- max_tokens: z.number().optional().default(512),
306
+ max_tokens: z.number().optional(), // default per provider: local 1024, cloud 4096
307
307
  temperature: z.number().optional().default(0.1),
308
- timeout_ms: z.number().optional().default(60000),
308
+ timeout_ms: z.number().optional(), // per inference call; default per provider: local 60s, cloud 120s
309
309
  checks: z.object({
310
310
  solid: z.boolean().optional().default(true),
311
311
  dry: z.boolean().optional().default(true),
@@ -402,10 +402,17 @@ export const ReportSchema = z.object({
402
402
  }).optional(),
403
403
  deep: z.object({
404
404
  enabled: z.boolean(),
405
+ /** ok: every inference ran; partial: some failed; error: deep did not run. */
406
+ status: z.enum(['ok', 'partial', 'error']).optional(),
407
+ mode: z.enum(['facts', 'code']).optional(),
405
408
  tier: z.enum(['deep', 'lite', 'legacy', 'cloud']).optional(),
406
409
  model: z.string().optional(),
410
+ model_fallback: z.boolean().optional(),
407
411
  total_ms: z.number().optional(),
408
412
  files_analyzed: z.number().optional(),
413
+ chunks_total: z.number().optional(),
414
+ chunks_failed: z.number().optional(),
415
+ error: z.string().optional(),
409
416
  findings_count: z.number().optional(),
410
417
  findings_verified: z.number().optional(),
411
418
  }).optional(),
@@ -0,0 +1,7 @@
1
+ import type { Report } from '../types/index.js';
2
+ /**
3
+ * Why deep analysis did not run, or undefined when it ran (fully or
4
+ * partially) or was not requested. Callers use this to fail loudly instead
5
+ * of presenting an empty deep result as a clean pass.
6
+ */
7
+ export declare function deepAnalysisError(report: Pick<Report, 'stats'>): string | undefined;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Why deep analysis did not run, or undefined when it ran (fully or
3
+ * partially) or was not requested. Callers use this to fail loudly instead
4
+ * of presenting an empty deep result as a clean pass.
5
+ */
6
+ export function deepAnalysisError(report) {
7
+ const deep = report.stats.deep;
8
+ if (deep?.status !== 'error')
9
+ return undefined;
10
+ return deep.error || 'unknown error';
11
+ }
@@ -0,0 +1,3 @@
1
+ export declare function parseDiff(diff: string): Record<string, Set<number>>;
2
+ /** Changed lines as sorted arrays, the shape DeepOptions.focusLines takes. */
3
+ export declare function changedLinesByFile(mapping: Record<string, Set<number>>): Record<string, number[]>;
@@ -0,0 +1,52 @@
1
+ export function parseDiff(diff) {
2
+ const state = { mapping: {}, file: '', newLine: 0, oldLeft: 0, newLeft: 0 };
3
+ for (const line of diff.split('\n')) {
4
+ if (state.oldLeft > 0 || state.newLeft > 0)
5
+ readHunkLine(state, line);
6
+ else
7
+ readHeaderLine(state, line);
8
+ }
9
+ return state.mapping;
10
+ }
11
+ function readHunkLine(state, line) {
12
+ if (line.startsWith('\\'))
13
+ return; // ""
14
+ if (line.startsWith('-')) {
15
+ state.oldLeft--;
16
+ return;
17
+ }
18
+ if (line.startsWith('+')) {
19
+ if (state.file)
20
+ state.mapping[state.file].add(state.newLine);
21
+ }
22
+ else {
23
+ state.oldLeft--;
24
+ }
25
+ state.newLine++;
26
+ state.newLeft--;
27
+ }
28
+ function readHeaderLine(state, line) {
29
+ if (line.startsWith('diff --git ')) {
30
+ state.file = '';
31
+ }
32
+ else if (line.startsWith('+++ ')) {
33
+ state.file = line.startsWith('+++ b/') ? line.slice(6).trim() : '';
34
+ if (state.file)
35
+ state.mapping[state.file] ??= new Set();
36
+ }
37
+ else if (line.startsWith('@@')) {
38
+ readHunkHeader(state, line);
39
+ }
40
+ }
41
+ function readHunkHeader(state, line) {
42
+ const hunk = line.match(/^@@ -\d+(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
43
+ if (!hunk)
44
+ return;
45
+ state.oldLeft = hunk[1] === undefined ? 1 : parseInt(hunk[1], 10);
46
+ state.newLine = parseInt(hunk[2], 10);
47
+ state.newLeft = hunk[3] === undefined ? 1 : parseInt(hunk[3], 10);
48
+ }
49
+ /** Changed lines as sorted arrays, the shape DeepOptions.focusLines takes. */
50
+ export function changedLinesByFile(mapping) {
51
+ return Object.fromEntries(Object.entries(mapping).map(([file, lines]) => [file, [...lines].sort((a, b) => a - b)]));
52
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { changedLinesByFile, parseDiff } from './diff.js';
3
+ const lines = (m) => changedLinesByFile(m);
4
+ describe('parseDiff', () => {
5
+ it('maps added lines to new-side line numbers', () => {
6
+ const diff = [
7
+ 'diff --git a/src/a.ts b/src/a.ts',
8
+ '--- a/src/a.ts',
9
+ '+++ b/src/a.ts',
10
+ '@@ -1,3 +1,4 @@',
11
+ ' const a = 1;',
12
+ '-const b = 2;',
13
+ '+const b = 3;',
14
+ '+const c = 4;',
15
+ ' export { a };',
16
+ ].join('\n');
17
+ expect(lines(parseDiff(diff))).toEqual({ 'src/a.ts': [2, 3] });
18
+ });
19
+ it('handles several files and hunks', () => {
20
+ const diff = [
21
+ '+++ b/a.ts', '@@ -10,2 +10,3 @@', ' x', '+y', ' z',
22
+ '+++ b/b.ts', '@@ -1 +1 @@', '-old', '+new',
23
+ ].join('\n');
24
+ expect(lines(parseDiff(diff))).toEqual({ 'a.ts': [11], 'b.ts': [1] });
25
+ });
26
+ it('does not mistake content lines starting with +++ or --- for headers', () => {
27
+ const diff = ['+++ b/a.ts', '@@ -1,2 +1,3 @@', ' let i = 0;', '+++i;', '---j;', ' done();'].join('\n');
28
+ expect(lines(parseDiff(diff))).toEqual({ 'a.ts': [2] });
29
+ });
30
+ it('does not count "No newline at end of file" as a line', () => {
31
+ const diff = ['+++ b/a.ts', '@@ -1,1 +1,2 @@', ' a', '\', '+b'].join('\n');
32
+ expect(lines(parseDiff(diff))).toEqual({ 'a.ts': [2] });
33
+ });
34
+ it('ignores deleted files', () => {
35
+ const diff = ['diff --git a/gone.ts b/gone.ts', '--- a/gone.ts', '+++ /dev/null', '@@ -1,2 +0,0 @@', '-a', '-b'].join('\n');
36
+ expect(parseDiff(diff)).toEqual({});
37
+ });
38
+ });
@@ -1,6 +1,17 @@
1
1
  import { globby } from 'globby';
2
2
  import fs from 'fs-extra';
3
3
  import path from 'path';
4
+ /**
5
+ * Glob patterns use `/` as the separator and `\` as the escape character.
6
+ * Only on Windows can a backslash be a path separator, and even there one
7
+ * that escapes a glob character (`\[` in `app/\[key\]/route.ts`) must stay.
8
+ * Same rule as fast-glob's convertPathToPattern for Windows paths.
9
+ */
10
+ function toGlobSeparators(pattern) {
11
+ if (process.platform !== 'win32')
12
+ return pattern;
13
+ return pattern.replace(/\\(?![!()+@[\]{}])/g, '/');
14
+ }
4
15
  export class FileScanner {
5
16
  static DEFAULT_PATTERNS = ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,rb,cs,java,kt,css,html,md,yaml,yml,toml,json}'];
6
17
  static DEFAULT_IGNORE = [
@@ -18,9 +29,9 @@ export class FileScanner {
18
29
  'rigour-report.json'
19
30
  ];
20
31
  static async findFiles(options) {
21
- const patterns = (options.patterns || this.DEFAULT_PATTERNS).map(p => p.replace(/\\/g, '/'));
32
+ const patterns = (options.patterns || this.DEFAULT_PATTERNS).map(toGlobSeparators);
22
33
  const userIgnore = options.ignore || [];
23
- const ignore = [...new Set([...this.DEFAULT_IGNORE, ...userIgnore])].map(p => p.replace(/\\/g, '/'));
34
+ const ignore = [...new Set([...this.DEFAULT_IGNORE, ...userIgnore])].map(toGlobSeparators);
24
35
  const normalizedCwd = options.cwd.replace(/\\/g, '/');
25
36
  return globby(patterns, {
26
37
  cwd: normalizedCwd,
@@ -16,14 +16,28 @@ describe('FileScanner', () => {
16
16
  expect(ignore).toContain('**/node_modules/**');
17
17
  expect(ignore).toContain('custom-ignore');
18
18
  });
19
- it('should normalize paths to forward slashes', async () => {
20
- const options = {
21
- cwd: 'C:\\test\\path',
22
- patterns: ['**\\*.ts']
23
- };
24
- await FileScanner.findFiles(options);
25
- const call = vi.mocked(globby).mock.calls[1];
26
- expect(call[0][0]).toBe('**/*.ts');
19
+ it('should normalize Windows paths to forward slashes', async () => {
20
+ const platform = Object.getOwnPropertyDescriptor(process, 'platform');
21
+ Object.defineProperty(process, 'platform', { value: 'win32' });
22
+ try {
23
+ await FileScanner.findFiles({ cwd: 'C:\\test\\path', patterns: ['**\\*.ts', 'app\\\\[key\\]\\route.ts'] });
24
+ }
25
+ finally {
26
+ Object.defineProperty(process, 'platform', platform);
27
+ }
28
+ const call = vi.mocked(globby).mock.calls.at(-1);
29
+ expect(call[0]).toEqual(['**/*.ts', 'app/\\[key\\]/route.ts']);
27
30
  expect(call[1]?.cwd).toBe('C:/test/path');
28
31
  });
32
+ it('should keep glob escapes on POSIX', async () => {
33
+ const platform = Object.getOwnPropertyDescriptor(process, 'platform');
34
+ Object.defineProperty(process, 'platform', { value: 'linux' });
35
+ try {
36
+ await FileScanner.findFiles({ cwd: '/repo', patterns: ['src/app/\\[key\\]/route.ts'] });
37
+ }
38
+ finally {
39
+ Object.defineProperty(process, 'platform', platform);
40
+ }
41
+ expect(vi.mocked(globby).mock.calls.at(-1)[0]).toEqual(['src/app/\\[key\\]/route.ts']);
42
+ });
29
43
  });
@@ -0,0 +1,15 @@
1
+ /** Convert CLI path arguments into cwd-relative, escaped glob patterns. */
2
+ export declare function normalizeScopePatterns(cwd: string, inputs: string[]): Promise<string[]>;
3
+ /** True when a gate context carries an explicit file scope. */
4
+ export declare function isScoped(patterns: string[] | undefined): patterns is string[];
5
+ /** True when any of `files` is in the scoped file set (paths compared cwd-relative). */
6
+ export declare function touchesScope(files: string[] | undefined, scopedFiles: Set<string>): boolean;
7
+ /** The concrete cwd-relative files matched by a scoped run's patterns. */
8
+ export declare function resolveScopedFiles(cwd: string, patterns: string[], ignore?: string[]): Promise<Set<string>>;
9
+ /** True when a scoped run does not include `file` (a manifest such as package.json). */
10
+ export declare function scopeExcludes(context: {
11
+ cwd: string;
12
+ patterns?: string[];
13
+ ignore?: string[];
14
+ }, file: string): Promise<boolean>;
15
+ export declare function toPosix(p: string): string;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * File scope for `rigour check <paths...>` and `rigour review`.
3
+ *
4
+ * CLI inputs may be absolute, relative or directories. Gates glob with
5
+ * globby, so each input becomes a cwd-relative glob with glob-special
6
+ * characters escaped (Next.js routes such as `app/[key]/route.ts` would
7
+ * otherwise be read as a character class).
8
+ */
9
+ import path from 'path';
10
+ import fs from 'fs-extra';
11
+ import { convertPathToPattern } from 'globby';
12
+ import { FileScanner } from './scanner.js';
13
+ /** Convert CLI path arguments into cwd-relative, escaped glob patterns. */
14
+ export async function normalizeScopePatterns(cwd, inputs) {
15
+ const patterns = [];
16
+ for (const input of inputs) {
17
+ const absolute = path.resolve(cwd, input);
18
+ const relative = toPosix(path.relative(cwd, absolute)) || '.';
19
+ const isDirectory = await fs.stat(absolute).then(s => s.isDirectory()).catch(() => false);
20
+ if (!isDirectory && !(await fs.pathExists(absolute))) {
21
+ // Not a path on disk: keep it as the glob the user wrote.
22
+ patterns.push(toPosix(input));
23
+ continue;
24
+ }
25
+ const escaped = relative === '.' ? '' : convertPathToPattern(relative);
26
+ patterns.push(isDirectory ? (escaped ? `${escaped}/**/*` : '**/*') : escaped);
27
+ }
28
+ return patterns;
29
+ }
30
+ /** True when a gate context carries an explicit file scope. */
31
+ export function isScoped(patterns) {
32
+ return Array.isArray(patterns) && patterns.length > 0;
33
+ }
34
+ /** True when any of `files` is in the scoped file set (paths compared cwd-relative). */
35
+ export function touchesScope(files, scopedFiles) {
36
+ return (files ?? []).some(f => scopedFiles.has(toPosix(f).replace(/^\.\//, '')));
37
+ }
38
+ /** The concrete cwd-relative files matched by a scoped run's patterns. */
39
+ export async function resolveScopedFiles(cwd, patterns, ignore) {
40
+ const files = await FileScanner.findFiles({ cwd, patterns, ignore });
41
+ return new Set(files.map(toPosix));
42
+ }
43
+ /** True when a scoped run does not include `file` (a manifest such as package.json). */
44
+ export async function scopeExcludes(context, file) {
45
+ if (!isScoped(context.patterns))
46
+ return false;
47
+ return !(await resolveScopedFiles(context.cwd, context.patterns, context.ignore)).has(file);
48
+ }
49
+ export function toPosix(p) {
50
+ return p.replace(/\\/g, '/');
51
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ import fs from 'fs-extra';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
+ import { isScoped, normalizeScopePatterns, resolveScopedFiles, touchesScope } from './scope.js';
6
+ describe('scope', () => {
7
+ let cwd;
8
+ beforeEach(async () => {
9
+ cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-scope-'));
10
+ await fs.outputFile(path.join(cwd, 'src/app/[key]/route.ts'), 'export {}');
11
+ await fs.outputFile(path.join(cwd, 'src/app/k/route.ts'), 'export {}');
12
+ await fs.outputFile(path.join(cwd, 'src/lib/a.ts'), 'export {}');
13
+ });
14
+ afterEach(async () => { await fs.remove(cwd); });
15
+ it('turns absolute, relative and directory inputs into cwd-relative patterns', async () => {
16
+ const patterns = await normalizeScopePatterns(cwd, [path.join(cwd, 'src/lib/a.ts'), './src/lib', 'src/lib/a.ts']);
17
+ expect(patterns).toEqual(['src/lib/a.ts', 'src/lib/**/*', 'src/lib/a.ts']);
18
+ });
19
+ it('escapes glob characters so a Next.js [key] route matches only itself', async () => {
20
+ const patterns = await normalizeScopePatterns(cwd, ['src/app/[key]/route.ts']);
21
+ expect([...await resolveScopedFiles(cwd, patterns)]).toEqual(['src/app/[key]/route.ts']);
22
+ });
23
+ it('keeps a non-existent input as the glob the user wrote', async () => {
24
+ expect(await normalizeScopePatterns(cwd, ['src/**/*.ts'])).toEqual(['src/**/*.ts']);
25
+ });
26
+ it('treats the repo root as everything', async () => {
27
+ expect(await normalizeScopePatterns(cwd, ['.'])).toEqual(['**/*']);
28
+ });
29
+ it('detects scoped runs and scope membership', () => {
30
+ expect(isScoped(undefined)).toBe(false);
31
+ expect(isScoped([])).toBe(false);
32
+ expect(isScoped(['a.ts'])).toBe(true);
33
+ const scoped = new Set(['src/lib/a.ts']);
34
+ expect(touchesScope(['./src/lib/a.ts', 'b.ts'], scoped)).toBe(true);
35
+ expect(touchesScope(['b.ts'], scoped)).toBe(false);
36
+ expect(touchesScope(undefined, scoped)).toBe(false);
37
+ });
38
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigour-labs/core",
3
- "version": "6.2.4",
3
+ "version": "6.2.5",
4
4
  "description": "AI-native quality gate engine with local Bayesian learning. AST analysis, drift detection, Fix Packet generation, and agent self-healing across TypeScript, JavaScript, Python, Go, Ruby, and C#.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://rigour.run",
@@ -67,11 +67,11 @@
67
67
  "pg": "^8.16.3",
68
68
  "sqlite3": "^5.1.7",
69
69
  "openai": "^4.104.0",
70
- "@rigour-labs/brain-darwin-arm64": "6.2.4",
71
- "@rigour-labs/brain-darwin-x64": "6.2.4",
72
- "@rigour-labs/brain-linux-x64": "6.2.4",
73
- "@rigour-labs/brain-win-x64": "6.2.4",
74
- "@rigour-labs/brain-linux-arm64": "6.2.4"
70
+ "@rigour-labs/brain-darwin-arm64": "6.2.5",
71
+ "@rigour-labs/brain-darwin-x64": "6.2.5",
72
+ "@rigour-labs/brain-linux-arm64": "6.2.5",
73
+ "@rigour-labs/brain-win-x64": "6.2.5",
74
+ "@rigour-labs/brain-linux-x64": "6.2.5"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@types/fs-extra": "^11.0.4",