@rigour-labs/core 6.6.2 → 6.6.3

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 (83) hide show
  1. package/dist/deep/diff-tests/run.js +2 -12
  2. package/dist/firewall/firewall.test.js +4 -0
  3. package/dist/firewall/trusted-control.js +2 -2
  4. package/dist/firewall/typed-command.js +2 -1
  5. package/dist/gates/logic-drift-git-base.d.ts +9 -0
  6. package/dist/gates/logic-drift-git-base.js +16 -11
  7. package/dist/gates/runner.d.ts +3 -0
  8. package/dist/gates/runner.js +4 -1
  9. package/dist/hooks/index.d.ts +2 -2
  10. package/dist/hooks/index.js +2 -2
  11. package/dist/hooks/session-state.d.ts +9 -0
  12. package/dist/hooks/session-state.js +33 -0
  13. package/dist/hooks/stop-review.d.ts +18 -11
  14. package/dist/hooks/stop-review.js +24 -8
  15. package/dist/hooks/templates.d.ts +8 -1
  16. package/dist/hooks/templates.js +19 -2
  17. package/dist/hooks/templates.test.js +12 -1
  18. package/dist/index.d.ts +4 -0
  19. package/dist/index.js +4 -0
  20. package/dist/review/baseline.d.ts +16 -0
  21. package/dist/review/baseline.js +96 -0
  22. package/dist/review/baseline.test.d.ts +1 -0
  23. package/dist/review/baseline.test.js +22 -0
  24. package/dist/review/branch-checks.d.ts +6 -0
  25. package/dist/review/branch-checks.js +83 -0
  26. package/dist/review/branch-checks.test.d.ts +1 -0
  27. package/dist/review/branch-checks.test.js +48 -0
  28. package/dist/review/code-patterns.test.d.ts +1 -0
  29. package/dist/review/code-patterns.test.js +84 -0
  30. package/dist/review/dead-code.test.d.ts +1 -0
  31. package/dist/review/dead-code.test.js +126 -0
  32. package/dist/review/duplicate-functions.d.ts +2 -0
  33. package/dist/review/duplicate-functions.js +98 -0
  34. package/dist/review/effectiveness.d.ts +3 -0
  35. package/dist/review/optional-params.d.ts +2 -0
  36. package/dist/review/optional-params.js +123 -0
  37. package/dist/review/orphan-files.d.ts +2 -0
  38. package/dist/review/orphan-files.js +125 -0
  39. package/dist/review/package-layout.d.ts +9 -0
  40. package/dist/review/package-layout.js +108 -0
  41. package/dist/review/query-patterns.d.ts +2 -0
  42. package/dist/review/query-patterns.js +153 -0
  43. package/dist/review/quiet.js +1 -1
  44. package/dist/review/review.d.ts +2 -0
  45. package/dist/review/review.js +29 -1
  46. package/dist/review/review.test.js +28 -2
  47. package/dist/review/reviewer.d.ts +55 -0
  48. package/dist/review/reviewer.js +224 -0
  49. package/dist/review/reviewer.test.d.ts +1 -0
  50. package/dist/review/reviewer.test.js +99 -0
  51. package/dist/review/test-files.d.ts +2 -0
  52. package/dist/review/test-files.js +4 -0
  53. package/dist/review/toolchain.d.ts +11 -0
  54. package/dist/review/toolchain.js +126 -0
  55. package/dist/review/toolchain.test.d.ts +1 -0
  56. package/dist/review/toolchain.test.js +67 -0
  57. package/dist/review/unused-exports.d.ts +4 -0
  58. package/dist/review/unused-exports.js +189 -0
  59. package/dist/settings.js +2 -3
  60. package/dist/storage/db.js +1 -2
  61. package/dist/storage/team-scope.d.ts +20 -0
  62. package/dist/storage/team-scope.js +32 -0
  63. package/dist/storage/team-scope.test.d.ts +1 -0
  64. package/dist/storage/team-scope.test.js +22 -0
  65. package/dist/storage/team-store.d.ts +5 -0
  66. package/dist/storage/team-store.js +42 -7
  67. package/dist/storage/team-sync-scope.test.d.ts +1 -0
  68. package/dist/storage/team-sync-scope.test.js +67 -0
  69. package/dist/telemetry/telemetry.js +5 -5
  70. package/dist/templates/universal-config.js +23 -0
  71. package/dist/types/index.d.ts +253 -6
  72. package/dist/types/index.js +27 -0
  73. package/dist/utils/installed-bin.d.ts +2 -0
  74. package/dist/utils/installed-bin.js +15 -0
  75. package/dist/utils/profile.d.ts +30 -0
  76. package/dist/utils/profile.js +116 -0
  77. package/dist/utils/profile.test.d.ts +1 -0
  78. package/dist/utils/profile.test.js +58 -0
  79. package/dist/utils/scanner.js +3 -1
  80. package/dist/utils/scanner.test.js +6 -2
  81. package/dist/utils/user-state.d.ts +3 -1
  82. package/dist/utils/user-state.js +6 -2
  83. package/package.json +10 -6
@@ -518,6 +518,52 @@ export declare const GatesSchema: z.ZodObject<{
518
518
  enabled?: boolean | undefined;
519
519
  registry?: string | undefined;
520
520
  }>>>;
521
+ /** Exports the change adds that no other file names (review/unused-exports.ts). */
522
+ unused_exports: z.ZodDefault<z.ZodOptional<z.ZodObject<{
523
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
524
+ allow: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
525
+ }, "strip", z.ZodTypeAny, {
526
+ enabled: boolean;
527
+ allow: string[];
528
+ }, {
529
+ enabled?: boolean | undefined;
530
+ allow?: string[] | undefined;
531
+ }>>>;
532
+ /** Query shapes that cost production: offset paging in a loop, a time window with no upper bound (review/query-patterns.ts). */
533
+ query_patterns: z.ZodDefault<z.ZodOptional<z.ZodObject<{
534
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
535
+ }, "strip", z.ZodTypeAny, {
536
+ enabled: boolean;
537
+ }, {
538
+ enabled?: boolean | undefined;
539
+ }>>>;
540
+ /** A parameter the change adds as optional that only tests omit (review/optional-params.ts). */
541
+ optional_params: z.ZodDefault<z.ZodOptional<z.ZodObject<{
542
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
543
+ }, "strip", z.ZodTypeAny, {
544
+ enabled: boolean;
545
+ }, {
546
+ enabled?: boolean | undefined;
547
+ }>>>;
548
+ /** A changed function whose body duplicates another in the files the change touched (review/duplicate-functions.ts). */
549
+ duplicate_functions: z.ZodDefault<z.ZodOptional<z.ZodObject<{
550
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
551
+ }, "strip", z.ZodTypeAny, {
552
+ enabled: boolean;
553
+ }, {
554
+ enabled?: boolean | undefined;
555
+ }>>>;
556
+ /** Code files the change adds that nothing imports or runs (review/orphan-files.ts). */
557
+ orphan_files: z.ZodDefault<z.ZodOptional<z.ZodObject<{
558
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
559
+ allow: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
560
+ }, "strip", z.ZodTypeAny, {
561
+ enabled: boolean;
562
+ allow: string[];
563
+ }, {
564
+ enabled?: boolean | undefined;
565
+ allow?: string[] | undefined;
566
+ }>>>;
521
567
  migration_order: z.ZodDefault<z.ZodOptional<z.ZodObject<{
522
568
  enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
523
569
  dirs: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
@@ -955,6 +1001,23 @@ export declare const GatesSchema: z.ZodObject<{
955
1001
  enabled: boolean;
956
1002
  registry?: string | undefined;
957
1003
  };
1004
+ unused_exports: {
1005
+ enabled: boolean;
1006
+ allow: string[];
1007
+ };
1008
+ query_patterns: {
1009
+ enabled: boolean;
1010
+ };
1011
+ optional_params: {
1012
+ enabled: boolean;
1013
+ };
1014
+ duplicate_functions: {
1015
+ enabled: boolean;
1016
+ };
1017
+ orphan_files: {
1018
+ enabled: boolean;
1019
+ allow: string[];
1020
+ };
958
1021
  migration_order: {
959
1022
  enabled: boolean;
960
1023
  dirs: string[];
@@ -1210,6 +1273,23 @@ export declare const GatesSchema: z.ZodObject<{
1210
1273
  enabled?: boolean | undefined;
1211
1274
  registry?: string | undefined;
1212
1275
  } | undefined;
1276
+ unused_exports?: {
1277
+ enabled?: boolean | undefined;
1278
+ allow?: string[] | undefined;
1279
+ } | undefined;
1280
+ query_patterns?: {
1281
+ enabled?: boolean | undefined;
1282
+ } | undefined;
1283
+ optional_params?: {
1284
+ enabled?: boolean | undefined;
1285
+ } | undefined;
1286
+ duplicate_functions?: {
1287
+ enabled?: boolean | undefined;
1288
+ } | undefined;
1289
+ orphan_files?: {
1290
+ enabled?: boolean | undefined;
1291
+ allow?: string[] | undefined;
1292
+ } | undefined;
1213
1293
  migration_order?: {
1214
1294
  enabled?: boolean | undefined;
1215
1295
  dirs?: string[] | undefined;
@@ -1826,6 +1906,52 @@ export declare const ConfigSchema: z.ZodObject<{
1826
1906
  enabled?: boolean | undefined;
1827
1907
  registry?: string | undefined;
1828
1908
  }>>>;
1909
+ /** Exports the change adds that no other file names (review/unused-exports.ts). */
1910
+ unused_exports: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1911
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1912
+ allow: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
1913
+ }, "strip", z.ZodTypeAny, {
1914
+ enabled: boolean;
1915
+ allow: string[];
1916
+ }, {
1917
+ enabled?: boolean | undefined;
1918
+ allow?: string[] | undefined;
1919
+ }>>>;
1920
+ /** Query shapes that cost production: offset paging in a loop, a time window with no upper bound (review/query-patterns.ts). */
1921
+ query_patterns: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1922
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1923
+ }, "strip", z.ZodTypeAny, {
1924
+ enabled: boolean;
1925
+ }, {
1926
+ enabled?: boolean | undefined;
1927
+ }>>>;
1928
+ /** A parameter the change adds as optional that only tests omit (review/optional-params.ts). */
1929
+ optional_params: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1930
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1931
+ }, "strip", z.ZodTypeAny, {
1932
+ enabled: boolean;
1933
+ }, {
1934
+ enabled?: boolean | undefined;
1935
+ }>>>;
1936
+ /** A changed function whose body duplicates another in the files the change touched (review/duplicate-functions.ts). */
1937
+ duplicate_functions: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1938
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1939
+ }, "strip", z.ZodTypeAny, {
1940
+ enabled: boolean;
1941
+ }, {
1942
+ enabled?: boolean | undefined;
1943
+ }>>>;
1944
+ /** Code files the change adds that nothing imports or runs (review/orphan-files.ts). */
1945
+ orphan_files: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1946
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1947
+ allow: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
1948
+ }, "strip", z.ZodTypeAny, {
1949
+ enabled: boolean;
1950
+ allow: string[];
1951
+ }, {
1952
+ enabled?: boolean | undefined;
1953
+ allow?: string[] | undefined;
1954
+ }>>>;
1829
1955
  migration_order: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1830
1956
  enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1831
1957
  dirs: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
@@ -2263,6 +2389,23 @@ export declare const ConfigSchema: z.ZodObject<{
2263
2389
  enabled: boolean;
2264
2390
  registry?: string | undefined;
2265
2391
  };
2392
+ unused_exports: {
2393
+ enabled: boolean;
2394
+ allow: string[];
2395
+ };
2396
+ query_patterns: {
2397
+ enabled: boolean;
2398
+ };
2399
+ optional_params: {
2400
+ enabled: boolean;
2401
+ };
2402
+ duplicate_functions: {
2403
+ enabled: boolean;
2404
+ };
2405
+ orphan_files: {
2406
+ enabled: boolean;
2407
+ allow: string[];
2408
+ };
2266
2409
  migration_order: {
2267
2410
  enabled: boolean;
2268
2411
  dirs: string[];
@@ -2518,6 +2661,23 @@ export declare const ConfigSchema: z.ZodObject<{
2518
2661
  enabled?: boolean | undefined;
2519
2662
  registry?: string | undefined;
2520
2663
  } | undefined;
2664
+ unused_exports?: {
2665
+ enabled?: boolean | undefined;
2666
+ allow?: string[] | undefined;
2667
+ } | undefined;
2668
+ query_patterns?: {
2669
+ enabled?: boolean | undefined;
2670
+ } | undefined;
2671
+ optional_params?: {
2672
+ enabled?: boolean | undefined;
2673
+ } | undefined;
2674
+ duplicate_functions?: {
2675
+ enabled?: boolean | undefined;
2676
+ } | undefined;
2677
+ orphan_files?: {
2678
+ enabled?: boolean | undefined;
2679
+ allow?: string[] | undefined;
2680
+ } | undefined;
2521
2681
  migration_order?: {
2522
2682
  enabled?: boolean | undefined;
2523
2683
  dirs?: string[] | undefined;
@@ -2590,10 +2750,47 @@ export declare const ConfigSchema: z.ZodObject<{
2590
2750
  review: z.ZodDefault<z.ZodOptional<z.ZodObject<{
2591
2751
  /** Let heuristic gates decide the verdict too; by default only findings that prove a defect do (quiet.ts). */
2592
2752
  include_heuristics: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
2753
+ /** Also report findings the base already had; by default only what the change introduced is (baseline.ts). */
2754
+ show_preexisting: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
2755
+ /** The GitHub account whose token fetches the pull request's previous review (`gh auth token --user`). */
2756
+ github_account: z.ZodOptional<z.ZodString>;
2757
+ /** The fresh reviewer (review/reviewer.ts): the person's own coding agent CLI, headless. `enabled` runs it at every push; `rigour review --reviewer` runs it on request. */
2758
+ reviewer: z.ZodDefault<z.ZodOptional<z.ZodObject<{
2759
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
2760
+ command: z.ZodDefault<z.ZodOptional<z.ZodString>>;
2761
+ model: z.ZodOptional<z.ZodString>;
2762
+ timeout_ms: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
2763
+ }, "strip", z.ZodTypeAny, {
2764
+ enabled: boolean;
2765
+ timeout_ms: number;
2766
+ command: string;
2767
+ model?: string | undefined;
2768
+ }, {
2769
+ enabled?: boolean | undefined;
2770
+ timeout_ms?: number | undefined;
2771
+ command?: string | undefined;
2772
+ model?: string | undefined;
2773
+ }>>>;
2593
2774
  }, "strip", z.ZodTypeAny, {
2594
2775
  include_heuristics: boolean;
2776
+ show_preexisting: boolean;
2777
+ reviewer: {
2778
+ enabled: boolean;
2779
+ timeout_ms: number;
2780
+ command: string;
2781
+ model?: string | undefined;
2782
+ };
2783
+ github_account?: string | undefined;
2595
2784
  }, {
2596
2785
  include_heuristics?: boolean | undefined;
2786
+ show_preexisting?: boolean | undefined;
2787
+ github_account?: string | undefined;
2788
+ reviewer?: {
2789
+ enabled?: boolean | undefined;
2790
+ timeout_ms?: number | undefined;
2791
+ command?: string | undefined;
2792
+ model?: string | undefined;
2793
+ } | undefined;
2597
2794
  }>>>;
2598
2795
  planned: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
2599
2796
  ignore: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
@@ -2828,6 +3025,23 @@ export declare const ConfigSchema: z.ZodObject<{
2828
3025
  enabled: boolean;
2829
3026
  registry?: string | undefined;
2830
3027
  };
3028
+ unused_exports: {
3029
+ enabled: boolean;
3030
+ allow: string[];
3031
+ };
3032
+ query_patterns: {
3033
+ enabled: boolean;
3034
+ };
3035
+ optional_params: {
3036
+ enabled: boolean;
3037
+ };
3038
+ duplicate_functions: {
3039
+ enabled: boolean;
3040
+ };
3041
+ orphan_files: {
3042
+ enabled: boolean;
3043
+ allow: string[];
3044
+ };
2831
3045
  migration_order: {
2832
3046
  enabled: boolean;
2833
3047
  dirs: string[];
@@ -2876,6 +3090,14 @@ export declare const ConfigSchema: z.ZodObject<{
2876
3090
  };
2877
3091
  review: {
2878
3092
  include_heuristics: boolean;
3093
+ show_preexisting: boolean;
3094
+ reviewer: {
3095
+ enabled: boolean;
3096
+ timeout_ms: number;
3097
+ command: string;
3098
+ model?: string | undefined;
3099
+ };
3100
+ github_account?: string | undefined;
2879
3101
  };
2880
3102
  planned: string[];
2881
3103
  preset?: string | undefined;
@@ -3113,6 +3335,23 @@ export declare const ConfigSchema: z.ZodObject<{
3113
3335
  enabled?: boolean | undefined;
3114
3336
  registry?: string | undefined;
3115
3337
  } | undefined;
3338
+ unused_exports?: {
3339
+ enabled?: boolean | undefined;
3340
+ allow?: string[] | undefined;
3341
+ } | undefined;
3342
+ query_patterns?: {
3343
+ enabled?: boolean | undefined;
3344
+ } | undefined;
3345
+ optional_params?: {
3346
+ enabled?: boolean | undefined;
3347
+ } | undefined;
3348
+ duplicate_functions?: {
3349
+ enabled?: boolean | undefined;
3350
+ } | undefined;
3351
+ orphan_files?: {
3352
+ enabled?: boolean | undefined;
3353
+ allow?: string[] | undefined;
3354
+ } | undefined;
3116
3355
  migration_order?: {
3117
3356
  enabled?: boolean | undefined;
3118
3357
  dirs?: string[] | undefined;
@@ -3161,6 +3400,14 @@ export declare const ConfigSchema: z.ZodObject<{
3161
3400
  } | undefined;
3162
3401
  review?: {
3163
3402
  include_heuristics?: boolean | undefined;
3403
+ show_preexisting?: boolean | undefined;
3404
+ github_account?: string | undefined;
3405
+ reviewer?: {
3406
+ enabled?: boolean | undefined;
3407
+ timeout_ms?: number | undefined;
3408
+ command?: string | undefined;
3409
+ model?: string | undefined;
3410
+ } | undefined;
3164
3411
  } | undefined;
3165
3412
  planned?: string[] | undefined;
3166
3413
  }>;
@@ -3359,9 +3606,9 @@ export declare const ReportSchema: z.ZodObject<{
3359
3606
  routed: number;
3360
3607
  already_reviewed: number;
3361
3608
  } | undefined;
3609
+ model?: string | undefined;
3362
3610
  error?: string | undefined;
3363
3611
  tier?: "deep" | "max" | "lite" | "legacy" | "cloud" | undefined;
3364
- model?: string | undefined;
3365
3612
  model_fallback?: boolean | undefined;
3366
3613
  total_ms?: number | undefined;
3367
3614
  files_analyzed?: number | undefined;
@@ -3387,9 +3634,9 @@ export declare const ReportSchema: z.ZodObject<{
3387
3634
  routed: number;
3388
3635
  already_reviewed: number;
3389
3636
  } | undefined;
3637
+ model?: string | undefined;
3390
3638
  error?: string | undefined;
3391
3639
  tier?: "deep" | "max" | "lite" | "legacy" | "cloud" | undefined;
3392
- model?: string | undefined;
3393
3640
  model_fallback?: boolean | undefined;
3394
3641
  total_ms?: number | undefined;
3395
3642
  files_analyzed?: number | undefined;
@@ -3418,9 +3665,9 @@ export declare const ReportSchema: z.ZodObject<{
3418
3665
  routed: number;
3419
3666
  already_reviewed: number;
3420
3667
  } | undefined;
3668
+ model?: string | undefined;
3421
3669
  error?: string | undefined;
3422
3670
  tier?: "deep" | "max" | "lite" | "legacy" | "cloud" | undefined;
3423
- model?: string | undefined;
3424
3671
  model_fallback?: boolean | undefined;
3425
3672
  total_ms?: number | undefined;
3426
3673
  files_analyzed?: number | undefined;
@@ -3461,9 +3708,9 @@ export declare const ReportSchema: z.ZodObject<{
3461
3708
  routed: number;
3462
3709
  already_reviewed: number;
3463
3710
  } | undefined;
3711
+ model?: string | undefined;
3464
3712
  error?: string | undefined;
3465
3713
  tier?: "deep" | "max" | "lite" | "legacy" | "cloud" | undefined;
3466
- model?: string | undefined;
3467
3714
  model_fallback?: boolean | undefined;
3468
3715
  total_ms?: number | undefined;
3469
3716
  files_analyzed?: number | undefined;
@@ -3506,9 +3753,9 @@ export declare const ReportSchema: z.ZodObject<{
3506
3753
  routed: number;
3507
3754
  already_reviewed: number;
3508
3755
  } | undefined;
3756
+ model?: string | undefined;
3509
3757
  error?: string | undefined;
3510
3758
  tier?: "deep" | "max" | "lite" | "legacy" | "cloud" | undefined;
3511
- model?: string | undefined;
3512
3759
  model_fallback?: boolean | undefined;
3513
3760
  total_ms?: number | undefined;
3514
3761
  files_analyzed?: number | undefined;
@@ -3569,9 +3816,9 @@ export declare const ReportSchema: z.ZodObject<{
3569
3816
  routed: number;
3570
3817
  already_reviewed: number;
3571
3818
  } | undefined;
3819
+ model?: string | undefined;
3572
3820
  error?: string | undefined;
3573
3821
  tier?: "deep" | "max" | "lite" | "legacy" | "cloud" | undefined;
3574
- model?: string | undefined;
3575
3822
  model_fallback?: boolean | undefined;
3576
3823
  total_ms?: number | undefined;
3577
3824
  files_analyzed?: number | undefined;
@@ -281,6 +281,22 @@ export const GatesSchema = z.object({
281
281
  registry: z.string().optional(),
282
282
  }).optional().default({}),
283
283
  // A migration added on a branch that sorts before the newest one on the base (advisory, off by default).
284
+ /** Exports the change adds that no other file names (review/unused-exports.ts). */
285
+ unused_exports: z.object({
286
+ enabled: z.boolean().optional().default(true),
287
+ allow: z.array(z.string()).optional().default([]), // export names a tool loads by name
288
+ }).optional().default({}),
289
+ /** Query shapes that cost production: offset paging in a loop, a time window with no upper bound (review/query-patterns.ts). */
290
+ query_patterns: z.object({ enabled: z.boolean().optional().default(true) }).optional().default({}),
291
+ /** A parameter the change adds as optional that only tests omit (review/optional-params.ts). */
292
+ optional_params: z.object({ enabled: z.boolean().optional().default(true) }).optional().default({}),
293
+ /** A changed function whose body duplicates another in the files the change touched (review/duplicate-functions.ts). */
294
+ duplicate_functions: z.object({ enabled: z.boolean().optional().default(true) }).optional().default({}),
295
+ /** Code files the change adds that nothing imports or runs (review/orphan-files.ts). */
296
+ orphan_files: z.object({
297
+ enabled: z.boolean().optional().default(true),
298
+ allow: z.array(z.string()).optional().default([]), // globs for files a tool loads by path
299
+ }).optional().default({}),
284
300
  migration_order: z.object({
285
301
  enabled: z.boolean().optional().default(false),
286
302
  dirs: z.array(z.string()).optional().default(['**/supabase/migrations']),
@@ -393,6 +409,17 @@ export const ConfigSchema = z.object({
393
409
  review: z.object({
394
410
  /** Let heuristic gates decide the verdict too; by default only findings that prove a defect do (quiet.ts). */
395
411
  include_heuristics: z.boolean().optional().default(false),
412
+ /** Also report findings the base already had; by default only what the change introduced is (baseline.ts). */
413
+ show_preexisting: z.boolean().optional().default(false),
414
+ /** The GitHub account whose token fetches the pull request's previous review (`gh auth token --user`). */
415
+ github_account: z.string().optional(),
416
+ /** The fresh reviewer (review/reviewer.ts): the person's own coding agent CLI, headless. `enabled` runs it at every push; `rigour review --reviewer` runs it on request. */
417
+ reviewer: z.object({
418
+ enabled: z.boolean().optional().default(false),
419
+ command: z.string().optional().default('claude'),
420
+ model: z.string().optional(),
421
+ timeout_ms: z.number().optional().default(15 * 60_000),
422
+ }).optional().default({}),
396
423
  }).optional().default({}),
397
424
  planned: z.array(z.string()).optional().default([]),
398
425
  ignore: z.array(z.string()).optional().default([]),
@@ -0,0 +1,2 @@
1
+ /** A tool the project installed itself, never a download: node_modules/.bin from `dir` up to `root`. */
2
+ export declare function installedBin(dir: string, root: string, name: string): string | undefined;
@@ -0,0 +1,15 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ /** A tool the project installed itself, never a download: node_modules/.bin from `dir` up to `root`. */
4
+ export function installedBin(dir, root, name) {
5
+ for (let current = dir; current.startsWith(root); current = path.dirname(current)) {
6
+ for (const candidate of process.platform === 'win32' ? [`${name}.cmd`, name] : [name]) {
7
+ const bin = path.join(current, 'node_modules', '.bin', candidate);
8
+ if (fs.existsSync(bin))
9
+ return bin;
10
+ }
11
+ if (current === root)
12
+ break;
13
+ }
14
+ return undefined;
15
+ }
@@ -0,0 +1,30 @@
1
+ export interface RigourProfile {
2
+ name: string;
3
+ /** Path prefixes (`~/work/acme`) or origin remotes (`github.com/acme/*`). */
4
+ match: string[];
5
+ /** The home whose `.rigour/` holds this profile's state. */
6
+ home?: string;
7
+ team?: {
8
+ organization: string;
9
+ team: string;
10
+ actor: string;
11
+ /** The team's repositories; lessons from any other repository stay local. */
12
+ repositories?: string[];
13
+ /** A command that prints the team database URL (from a keychain, a vault); never stored in the file. */
14
+ databaseUrlCommand?: string;
15
+ };
16
+ /** The GitHub account whose token fetches pull request reviews. */
17
+ githubAccount?: string;
18
+ }
19
+ /** Where profiles are listed: always the person's real home, never a profile's. */
20
+ export declare function profilesPath(): string;
21
+ export declare function readProfiles(): RigourProfile[];
22
+ export declare function writeProfiles(profiles: RigourProfile[]): void;
23
+ /** The first profile whose match fits `cwd`, by path or by origin remote. */
24
+ export declare function profileFor(cwd: string, profiles?: RigourProfile[]): RigourProfile | undefined;
25
+ /** Apply the profile for `cwd` to this process's environment; returns it, or undefined when none matches. */
26
+ export declare function applyProfile(cwd: string, profiles?: RigourProfile[]): RigourProfile | undefined;
27
+ /** Why this process must not act for `cwd` (it belongs to another profile than the one applied at start), or null. */
28
+ export declare function profileMismatch(cwd: string, profiles?: RigourProfile[]): string | null;
29
+ /** `git@github.com:acme/api.git` and `https://github.com/acme/api` both become `github.com/acme/api`. */
30
+ export declare function hostPath(remote: string): string;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Profiles: one machine, many organizations.
3
+ *
4
+ * A person who works for more than one organization needs Rigour's memory, lessons and team
5
+ * database kept apart per organization, chosen by the repository, not by whichever server or shell
6
+ * happened to start. `~/.rigour/profiles.json` (or RIGOUR_PROFILES) lists profiles; the first whose
7
+ * `match` fits the repository (a path prefix, or an origin remote like `github.com/acme/*`) is
8
+ * applied before the rest of Rigour loads: its home becomes RIGOUR_HOME, and team settings come
9
+ * from the profile alone (any inherited from the shell are cleared, so another organization's team
10
+ * can never apply). A long-running server records the profile it started with, and refuses a call
11
+ * for a repository of another profile instead of answering it with the wrong home.
12
+ *
13
+ * Dependency-free on purpose: it runs before the rest of the package is imported.
14
+ */
15
+ import { spawnSync } from 'child_process';
16
+ import fs from 'fs';
17
+ import os from 'os';
18
+ import path from 'path';
19
+ const TEAM_ENV = ['RIGOUR_ORGANIZATION_ID', 'RIGOUR_TEAM_ID', 'RIGOUR_ACTOR_ID', 'RIGOUR_TEAM_REPOSITORIES', 'RIGOUR_TEAM_DATABASE_URL', 'RIGOUR_TEAM_DATABASE_URL_COMMAND', 'RIGOUR_TEAM_SEMANTIC'];
20
+ /** Where profiles are listed: always the person's real home, never a profile's. */
21
+ export function profilesPath() {
22
+ return process.env.RIGOUR_PROFILES || path.join(os.homedir(), '.rigour', 'profiles.json');
23
+ }
24
+ export function readProfiles() {
25
+ try {
26
+ const parsed = JSON.parse(fs.readFileSync(profilesPath(), 'utf8'));
27
+ return Array.isArray(parsed?.profiles) ? parsed.profiles.filter((p) => p && typeof p.name === 'string' && Array.isArray(p.match)) : [];
28
+ }
29
+ catch {
30
+ return [];
31
+ }
32
+ }
33
+ export function writeProfiles(profiles) {
34
+ fs.mkdirSync(path.dirname(profilesPath()), { recursive: true, mode: 0o700 });
35
+ fs.writeFileSync(profilesPath(), JSON.stringify({ profiles }, null, 2) + '\n', { mode: 0o600 });
36
+ }
37
+ /** The first profile whose match fits `cwd`, by path or by origin remote. */
38
+ export function profileFor(cwd, profiles = readProfiles()) {
39
+ if (profiles.length === 0)
40
+ return undefined;
41
+ const where = real(cwd);
42
+ const remote = originRemote(cwd);
43
+ return profiles.find(profile => profile.match.some(entry => isRemotePattern(entry)
44
+ ? !!remote && remoteMatches(remote, entry)
45
+ : isInside(where, real(expandHome(entry)))));
46
+ }
47
+ /** Apply the profile for `cwd` to this process's environment; returns it, or undefined when none matches. */
48
+ export function applyProfile(cwd, profiles) {
49
+ const profile = profileFor(cwd, profiles);
50
+ if (!profile)
51
+ return undefined;
52
+ process.env.RIGOUR_PROFILE = profile.name;
53
+ if (profile.home)
54
+ process.env.RIGOUR_HOME = expandHome(profile.home);
55
+ for (const name of TEAM_ENV)
56
+ delete process.env[name];
57
+ if (profile.team) {
58
+ process.env.RIGOUR_ORGANIZATION_ID = profile.team.organization;
59
+ process.env.RIGOUR_TEAM_ID = profile.team.team;
60
+ process.env.RIGOUR_ACTOR_ID = profile.team.actor;
61
+ if (profile.team.repositories?.length)
62
+ process.env.RIGOUR_TEAM_REPOSITORIES = profile.team.repositories.join(',');
63
+ if (profile.team.databaseUrlCommand)
64
+ process.env.RIGOUR_TEAM_DATABASE_URL_COMMAND = profile.team.databaseUrlCommand;
65
+ }
66
+ if (profile.githubAccount)
67
+ process.env.RIGOUR_GITHUB_ACCOUNT = profile.githubAccount;
68
+ return profile;
69
+ }
70
+ /** Why this process must not act for `cwd` (it belongs to another profile than the one applied at start), or null. */
71
+ export function profileMismatch(cwd, profiles) {
72
+ const belongs = profileFor(cwd, profiles)?.name;
73
+ const running = process.env.RIGOUR_PROFILE;
74
+ if (belongs === running)
75
+ return null;
76
+ return `This repository belongs to the Rigour profile "${belongs ?? '(none)'}", but this server started as "${running ?? '(none)'}". `
77
+ + 'Refusing so one profile\'s memory and team never reach another: start Rigour from this repository (or with RIGOUR_CWD set to it).';
78
+ }
79
+ function isRemotePattern(entry) {
80
+ return /^[a-z0-9.-]+\.[a-z]{2,}\//i.test(entry.replace(/^[a-z]+:\/\//i, ''));
81
+ }
82
+ function remoteMatches(remote, pattern) {
83
+ const want = hostPath(pattern);
84
+ return want.endsWith('/*') ? remote.startsWith(want.slice(0, -1)) : remote === want;
85
+ }
86
+ /** `git@github.com:acme/api.git` and `https://github.com/acme/api` both become `github.com/acme/api`. */
87
+ export function hostPath(remote) {
88
+ return remote.trim().toLowerCase()
89
+ .replace(/^[a-z][a-z0-9+.-]*:\/\//, '')
90
+ .replace(/^[^@/]+@/, '')
91
+ .replace(/^([^/:]+):(?!\d)/, '$1/')
92
+ .replace(/\/+$/, '')
93
+ .replace(/\.git$/, '');
94
+ }
95
+ function originRemote(cwd) {
96
+ const result = spawnSync('git', ['config', '--get', 'remote.origin.url'], { cwd, encoding: 'utf8' });
97
+ return result.status === 0 && result.stdout.trim() ? hostPath(result.stdout) : undefined;
98
+ }
99
+ function expandHome(entry) {
100
+ return entry.replace(/^~(?=$|[\\/])/, os.homedir());
101
+ }
102
+ /** The real path; for a path that does not exist yet, its nearest existing parent's real path plus the rest. */
103
+ function real(dir) {
104
+ const absolute = path.resolve(dir);
105
+ try {
106
+ return fs.realpathSync.native(absolute);
107
+ }
108
+ catch {
109
+ const parent = path.dirname(absolute);
110
+ return parent === absolute ? absolute : path.join(real(parent), path.basename(absolute));
111
+ }
112
+ }
113
+ function isInside(child, parent) {
114
+ const relative = path.relative(parent, child);
115
+ return relative === '' || (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
116
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,58 @@
1
+ import { execFileSync } from 'child_process';
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
+ import { applyProfile, hostPath, profileFor, profileMismatch } from './profile.js';
7
+ let root;
8
+ const saved = { ...process.env };
9
+ const repo = (rel, remote) => {
10
+ const dir = path.join(root, rel);
11
+ fs.mkdirSync(dir, { recursive: true });
12
+ execFileSync('git', ['init', '-q'], { cwd: dir });
13
+ if (remote)
14
+ execFileSync('git', ['remote', 'add', 'origin', remote], { cwd: dir });
15
+ return dir;
16
+ };
17
+ beforeEach(() => { root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'profiles-'))); });
18
+ afterEach(() => { process.env = { ...saved }; fs.rmSync(root, { recursive: true, force: true }); });
19
+ describe('profiles', () => {
20
+ it('matches a repository by path prefix or by origin remote, first match winning', () => {
21
+ const byPath = repo('work/acme/api');
22
+ const byRemote = repo('elsewhere/web', 'git@github.com:Acme/web.git');
23
+ const other = repo('personal/tool', 'https://github.com/me/tool');
24
+ const profiles = [
25
+ { name: 'acme-path', match: [path.join(root, 'work/acme')] },
26
+ { name: 'acme-remote', match: ['github.com/acme/*'] },
27
+ ];
28
+ expect(profileFor(path.join(byPath, 'src'), profiles)?.name).toBe('acme-path');
29
+ expect(profileFor(byRemote, profiles)?.name).toBe('acme-remote');
30
+ expect(profileFor(other, profiles)).toBeUndefined();
31
+ expect(profileFor(path.join(root, 'work/acme-evil'), profiles)).toBeUndefined();
32
+ expect(hostPath('https://GitHub.com/Acme/web.git/')).toBe('github.com/acme/web');
33
+ });
34
+ it("applies the profile's home and team, clearing team settings inherited from the shell", () => {
35
+ const dir = repo('work/acme/api');
36
+ process.env.RIGOUR_TEAM_DATABASE_URL = 'postgres://other-org';
37
+ process.env.RIGOUR_ORGANIZATION_ID = 'other-org';
38
+ applyProfile(dir, [{ name: 'acme', match: [path.join(root, 'work/acme')], home: path.join(root, 'acme-home'), githubAccount: 'acme-dev' }]);
39
+ expect(process.env.RIGOUR_HOME).toBe(path.join(root, 'acme-home'));
40
+ expect(process.env.RIGOUR_PROFILE).toBe('acme');
41
+ expect(process.env.RIGOUR_GITHUB_ACCOUNT).toBe('acme-dev');
42
+ expect(process.env.RIGOUR_TEAM_DATABASE_URL).toBeUndefined();
43
+ expect(process.env.RIGOUR_ORGANIZATION_ID).toBeUndefined();
44
+ applyProfile(dir, [{ name: 'acme', match: [dir], team: { organization: 'acme', team: 'web', actor: 'dev', repositories: ['github.com/acme/*'], databaseUrlCommand: 'echo url' } }]);
45
+ expect([process.env.RIGOUR_ORGANIZATION_ID, process.env.RIGOUR_TEAM_REPOSITORIES, process.env.RIGOUR_TEAM_DATABASE_URL_COMMAND]).toEqual(['acme', 'github.com/acme/*', 'echo url']);
46
+ });
47
+ it('refuses to act for a repository of another profile than the one the process started with', () => {
48
+ const acme = repo('work/acme/api');
49
+ const mine = repo('personal/tool');
50
+ const profiles = [{ name: 'acme', match: [path.join(root, 'work/acme')] }];
51
+ delete process.env.RIGOUR_PROFILE;
52
+ expect(profileMismatch(mine, profiles)).toBeNull();
53
+ expect(profileMismatch(acme, profiles)).toContain('belongs to the Rigour profile "acme"');
54
+ process.env.RIGOUR_PROFILE = 'acme';
55
+ expect(profileMismatch(acme, profiles)).toBeNull();
56
+ expect(profileMismatch(mine, profiles)).toContain('started as "acme"');
57
+ });
58
+ });
@@ -34,11 +34,13 @@ export class FileScanner {
34
34
  const userIgnore = options.ignore || [];
35
35
  const ignore = [...new Set([...this.DEFAULT_IGNORE, ...userIgnore])].map(toGlobSeparators);
36
36
  const normalizedCwd = options.cwd.replace(/\\/g, '/');
37
- return globby(directoriesAsGlobs(normalizedCwd, patterns), {
37
+ // Sorted: the walk's order varies run to run, and a check that names "the first file" must name the same one.
38
+ const files = await globby(directoriesAsGlobs(normalizedCwd, patterns), {
38
39
  cwd: normalizedCwd,
39
40
  ignore: withDirectoryForms(ignore),
40
41
  expandDirectories: false,
41
42
  });
43
+ return files.sort();
42
44
  }
43
45
  /**
44
46
  * Read file contents. When a FileSystemCache is provided, uses it for