@rigour-labs/core 6.4.0 → 6.4.2

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 (117) hide show
  1. package/dist/deep/changed-functions.d.ts +15 -0
  2. package/dist/deep/changed-functions.js +34 -0
  3. package/dist/deep/code-pass.d.ts +10 -0
  4. package/dist/deep/code-pass.js +37 -2
  5. package/dist/deep/code-pass.test.d.ts +1 -0
  6. package/dist/deep/code-pass.test.js +69 -0
  7. package/dist/deep/code-review-prompt.d.ts +1 -1
  8. package/dist/deep/code-review-prompt.js +3 -2
  9. package/dist/deep/diff-tests/calls.d.ts +8 -0
  10. package/dist/deep/diff-tests/calls.js +85 -0
  11. package/dist/deep/diff-tests/calls.test.d.ts +1 -0
  12. package/dist/deep/diff-tests/calls.test.js +33 -0
  13. package/dist/deep/diff-tests/run.d.ts +21 -0
  14. package/dist/deep/diff-tests/run.js +178 -0
  15. package/dist/deep/diff-tests/run.test.d.ts +1 -0
  16. package/dist/deep/diff-tests/run.test.js +58 -0
  17. package/dist/deep/reference-pack.d.ts +19 -0
  18. package/dist/deep/reference-pack.js +101 -0
  19. package/dist/deep/reference-pack.test.d.ts +1 -0
  20. package/dist/deep/reference-pack.test.js +46 -0
  21. package/dist/deep/review-context-export.d.ts +11 -0
  22. package/dist/deep/review-context-export.js +31 -0
  23. package/dist/deep/review-context-export.test.d.ts +1 -0
  24. package/dist/deep/review-context-export.test.js +32 -0
  25. package/dist/deep/self-check.d.ts +21 -0
  26. package/dist/deep/self-check.js +49 -0
  27. package/dist/firewall/trusted-control.d.ts +1 -2
  28. package/dist/gates/deep-analysis.d.ts +5 -0
  29. package/dist/gates/deep-analysis.js +19 -1
  30. package/dist/gates/deep-runner.js +2 -1
  31. package/dist/gates/deep-timeout.test.d.ts +1 -0
  32. package/dist/gates/deep-timeout.test.js +10 -0
  33. package/dist/gates/logic-drift.d.ts +8 -3
  34. package/dist/gates/logic-drift.js +10 -5
  35. package/dist/gates/logic-drift.test.js +7 -0
  36. package/dist/gates/promise-safety.js +3 -1
  37. package/dist/gates/promise-safety.test.d.ts +1 -0
  38. package/dist/gates/promise-safety.test.js +34 -0
  39. package/dist/gates/security-patterns-data.js +2 -1
  40. package/dist/gates/security-patterns.test.js +8 -0
  41. package/dist/gates/semantic-bugs.js +4 -3
  42. package/dist/gates/semantic-bugs.test.js +6 -0
  43. package/dist/hooks/checker.d.ts +1 -2
  44. package/dist/hooks/index.d.ts +2 -0
  45. package/dist/index.d.ts +20 -1
  46. package/dist/index.js +4 -5
  47. package/dist/inference/index.d.ts +1 -1
  48. package/dist/inference/index.js +3 -5
  49. package/dist/inference/local-model.test.d.ts +1 -0
  50. package/dist/inference/local-model.test.js +23 -0
  51. package/dist/inference/sidecar-provider.d.ts +10 -1
  52. package/dist/inference/sidecar-provider.js +28 -6
  53. package/dist/inference/types.d.ts +8 -1
  54. package/dist/inference/types.js +14 -0
  55. package/dist/inference/types.test.d.ts +1 -0
  56. package/dist/inference/types.test.js +19 -0
  57. package/dist/review/diff-test-findings.d.ts +3 -0
  58. package/dist/review/diff-test-findings.js +41 -0
  59. package/dist/review/review.d.ts +4 -2
  60. package/dist/review/review.js +5 -2
  61. package/dist/semantic/benchmark.js +2 -2
  62. package/dist/semantic/benchmark.test.js +5 -1
  63. package/dist/semantic/engine.d.ts +3 -0
  64. package/dist/semantic/engine.js +8 -2
  65. package/dist/semantic/failure-handling.d.ts +12 -0
  66. package/dist/semantic/failure-handling.js +41 -0
  67. package/dist/semantic/intent/parallel-reads.js +2 -30
  68. package/dist/semantic/project-facts.d.ts +25 -0
  69. package/dist/semantic/project-facts.js +104 -0
  70. package/dist/semantic/project-facts.test.d.ts +1 -0
  71. package/dist/semantic/project-facts.test.js +45 -0
  72. package/dist/semantic/rules/ecosystem/deps-codegen-undeclared-import.d.ts +2 -0
  73. package/dist/semantic/rules/ecosystem/deps-codegen-undeclared-import.js +71 -0
  74. package/dist/semantic/rules/ecosystem/deps-internal-symbol.d.ts +2 -0
  75. package/dist/semantic/rules/ecosystem/deps-internal-symbol.js +36 -0
  76. package/dist/semantic/rules/ecosystem/dom-self-query-in-component.d.ts +2 -0
  77. package/dist/semantic/rules/ecosystem/dom-self-query-in-component.js +60 -0
  78. package/dist/semantic/rules/ecosystem/env-bare-browser-global.d.ts +2 -0
  79. package/dist/semantic/rules/ecosystem/env-bare-browser-global.js +57 -0
  80. package/dist/semantic/rules/ecosystem/exports-condition-parity.d.ts +2 -0
  81. package/dist/semantic/rules/ecosystem/exports-condition-parity.js +57 -0
  82. package/dist/semantic/rules/ecosystem/exports-forgotten-type.d.ts +2 -0
  83. package/dist/semantic/rules/ecosystem/exports-forgotten-type.js +128 -0
  84. package/dist/semantic/rules/ecosystem/imports-barrel-cycle.d.ts +2 -0
  85. package/dist/semantic/rules/ecosystem/imports-barrel-cycle.js +78 -0
  86. package/dist/semantic/rules/ecosystem/index.d.ts +15 -0
  87. package/dist/semantic/rules/ecosystem/index.js +22 -0
  88. package/dist/semantic/rules/ecosystem/paths-unnormalized-module-key.d.ts +2 -0
  89. package/dist/semantic/rules/ecosystem/paths-unnormalized-module-key.js +87 -0
  90. package/dist/semantic/rules/ecosystem/react-inline-html-object.d.ts +2 -0
  91. package/dist/semantic/rules/ecosystem/react-inline-html-object.js +30 -0
  92. package/dist/semantic/rules/ecosystem/shared.d.ts +18 -0
  93. package/dist/semantic/rules/ecosystem/shared.js +43 -0
  94. package/dist/semantic/rules/ecosystem/solid-jsx-and-conditional.d.ts +2 -0
  95. package/dist/semantic/rules/ecosystem/solid-jsx-and-conditional.js +36 -0
  96. package/dist/semantic/rules/ecosystem/ts-internal-in-public-signature.d.ts +2 -0
  97. package/dist/semantic/rules/ecosystem/ts-internal-in-public-signature.js +66 -0
  98. package/dist/semantic/rules/ecosystem/vite-rollup-only-hook-field.d.ts +2 -0
  99. package/dist/semantic/rules/ecosystem/vite-rollup-only-hook-field.js +31 -0
  100. package/dist/semantic/rules/ecosystem/vue-static-computed.d.ts +2 -0
  101. package/dist/semantic/rules/ecosystem/vue-static-computed.js +51 -0
  102. package/dist/semantic/sites/call-sites.d.ts +25 -0
  103. package/dist/semantic/sites/call-sites.js +69 -0
  104. package/dist/semantic/sites/call-sites.test.d.ts +1 -0
  105. package/dist/semantic/sites/call-sites.test.js +46 -0
  106. package/dist/semantic/sites/export.d.ts +2 -0
  107. package/dist/semantic/sites/export.js +22 -0
  108. package/dist/semantic/types.d.ts +4 -0
  109. package/dist/services/engineering-knowledge-graph.d.ts +1 -2
  110. package/dist/templates/universal-config.js +3 -2
  111. package/dist/types/index.d.ts +31 -8
  112. package/dist/types/index.js +4 -3
  113. package/dist/utils/diff-removed.test.d.ts +1 -0
  114. package/dist/utils/diff-removed.test.js +29 -0
  115. package/dist/utils/diff.d.ts +12 -0
  116. package/dist/utils/diff.js +26 -0
  117. package/package.json +6 -6
package/dist/index.d.ts CHANGED
@@ -25,7 +25,7 @@ export type { RigourSettings, ResolvedDeepOptions, CLIDeepOptions } from './sett
25
25
  export { DeepAnalysisGate } from './gates/deep-analysis.js';
26
26
  export { createProvider } from './inference/index.js';
27
27
  export type { InferenceProvider, DeepFinding, DeepAnalysisResult, ModelTier } from './inference/types.js';
28
- export { MODELS } from './inference/types.js';
28
+ export { MODELS, localTier } from './inference/types.js';
29
29
  export { isModelCached, getModelsDir, getModelInfo, getCachedModel, ensureModel } from './inference/model-manager.js';
30
30
  export { installLlamaEngine, probeBinary, managedEnginePath, LLAMA_RELEASE_TAG } from './inference/llama-engine.js';
31
31
  export { SidecarProvider } from './inference/sidecar-provider.js';
@@ -56,8 +56,27 @@ export * from './services/system-health.js';
56
56
  export * from './services/agent-history.js';
57
57
  export * from './services/engineering-knowledge-graph.js';
58
58
  export * from './firewall/index.js';
59
+ export { exportCallSites } from './semantic/sites/export.js';
60
+ export { ALL_RULES, analyzeFiles, BUILT_IN_RULES } from './semantic/engine.js';
61
+ export { exportReviewContexts, type ReviewContextExport, type ReviewContextOptions } from './deep/review-context-export.js';
62
+ export type { CallSite, HandledBy } from './semantic/sites/call-sites.js';
59
63
  export { learnFromFix, learnFromFileChange, type LearnReport, type Rejection } from './semantic/learn/learn.js';
60
64
  export { extractFixTrees, type FixTrees } from './semantic/learn/git-trees.js';
61
65
  export { saveLearnedRule, loadLearnedRules, LEARNED_RULES_DIR } from './semantic/learn/store.js';
62
66
  export type { LearnedRule, LearnedPattern } from './semantic/learn/types.js';
63
67
  export { recordContextEvent, recordModelUsage, recordCheckpointMetric, getContextEvents, getModelUsages, getCheckpointMetrics, } from './storage/index.js';
68
+ export type { ModelInfo } from './inference/types.js';
69
+ export type { HttpGet } from './inference/http-download.js';
70
+ export type { FileFacts } from './deep/fact-extractor.js';
71
+ export type { ScanRecord } from './storage/scans.js';
72
+ export type { QualityTrend } from './services/adaptive-thresholds.js';
73
+ export type { EngineOptions } from './semantic/engine.js';
74
+ export type { LearnInput } from './semantic/learn/learn.js';
75
+ export type { ContextEvent } from './storage/index.js';
76
+ export type { CachedModel } from './inference/model-manager.js';
77
+ export type { ProbeResult } from './inference/llama-engine.js';
78
+ export type { PatternRecord } from './storage/patterns.js';
79
+ export type { SemanticFinding } from './semantic/types.js';
80
+ export type { ModelUsage } from './storage/index.js';
81
+ export type { CheckpointMetric } from './storage/index.js';
82
+ export type { CursorUsageSyncOptions } from './services/cursor-usage-client.js';
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ export { loadSettings, saveSettings, getSettingsPath, resolveDeepOptions, getPro
25
25
  // Deep Analysis Pipeline (v4.0+)
26
26
  export { DeepAnalysisGate } from './gates/deep-analysis.js';
27
27
  export { createProvider } from './inference/index.js';
28
- export { MODELS } from './inference/types.js';
28
+ export { MODELS, localTier } from './inference/types.js';
29
29
  export { isModelCached, getModelsDir, getModelInfo, getCachedModel, ensureModel } from './inference/model-manager.js';
30
30
  export { installLlamaEngine, probeBinary, managedEnginePath, LLAMA_RELEASE_TAG } from './inference/llama-engine.js';
31
31
  export { SidecarProvider } from './inference/sidecar-provider.js';
@@ -55,11 +55,10 @@ export * from './services/system-health.js';
55
55
  export * from './services/agent-history.js';
56
56
  export * from './services/engineering-knowledge-graph.js';
57
57
  export * from './firewall/index.js';
58
+ export { exportCallSites } from './semantic/sites/export.js';
59
+ export { ALL_RULES, analyzeFiles, BUILT_IN_RULES } from './semantic/engine.js';
60
+ export { exportReviewContexts } from './deep/review-context-export.js';
58
61
  export { learnFromFix, learnFromFileChange } from './semantic/learn/learn.js';
59
62
  export { extractFixTrees } from './semantic/learn/git-trees.js';
60
63
  export { saveLearnedRule, loadLearnedRules, LEARNED_RULES_DIR } from './semantic/learn/store.js';
61
64
  export { recordContextEvent, recordModelUsage, recordCheckpointMetric, getContextEvents, getModelUsages, getCheckpointMetrics, } from './storage/index.js';
62
- // Pattern Index is intentionally NOT exported here to prevent
63
- // native dependency issues (sharp/transformers) from leaking into
64
- // non-AI parts of the system.
65
- // Import from @rigour-labs/core/pattern-index instead.
@@ -10,7 +10,7 @@ export type { ProbeResult } from './llama-engine.js';
10
10
  export { CloudProvider } from './cloud-provider.js';
11
11
  export { ensureModel, isModelCached, getModelPath, getModelInfo, downloadModel, getModelsDir, getCachedModel } from './model-manager.js';
12
12
  export type { CachedModel } from './model-manager.js';
13
- import type { InferenceProvider } from './types.js';
13
+ import { type InferenceProvider } from './types.js';
14
14
  import type { DeepOptions } from '../types/index.js';
15
15
  /**
16
16
  * Create the appropriate inference provider based on options.
@@ -3,6 +3,7 @@ export { SidecarProvider } from './sidecar-provider.js';
3
3
  export { installLlamaEngine, probeBinary, managedEnginePath, LLAMA_RELEASE_TAG } from './llama-engine.js';
4
4
  export { CloudProvider } from './cloud-provider.js';
5
5
  export { ensureModel, isModelCached, getModelPath, getModelInfo, downloadModel, getModelsDir, getCachedModel } from './model-manager.js';
6
+ import { localTier } from './types.js';
6
7
  import { SidecarProvider } from './sidecar-provider.js';
7
8
  import { CloudProvider } from './cloud-provider.js';
8
9
  /**
@@ -18,9 +19,6 @@ export function createProvider(options) {
18
19
  modelName: options.modelName,
19
20
  });
20
21
  }
21
- // Default: local sidecar
22
- // deep = Qwen2.5-Coder-1.5B (full power, company-hosted)
23
- // lite = Qwen2.5-Coder-0.5B (lightweight, default CLI sidecar)
24
- const tier = options.pro ? 'deep' : 'lite';
25
- return new SidecarProvider(tier);
22
+ // Default: local sidecar (lite 0.5B, deep 1.5B with --pro, max 7B with --max)
23
+ return new SidecarProvider(localTier(options), undefined, options.modelPath);
26
24
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { afterEach, describe, expect, it } from 'vitest';
5
+ import { localModel } from './sidecar-provider.js';
6
+ let dir;
7
+ afterEach(() => { if (dir)
8
+ fs.rmSync(dir, { recursive: true, force: true }); dir = undefined; });
9
+ describe('a local GGUF for --model-path', () => {
10
+ it('is used as is, never reported as a fallback', async () => {
11
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'local-model-'));
12
+ const file = path.join(dir, 'rigour-max-candidate-q4_k_m.gguf');
13
+ fs.writeFileSync(file, Buffer.alloc(2_000_000));
14
+ expect(await localModel('max', file)).toEqual({
15
+ path: file, fallback: false,
16
+ info: { tier: 'max', name: 'rigour-max-candidate-q4_k_m.gguf (local)', filename: 'rigour-max-candidate-q4_k_m.gguf', url: '', sizeBytes: 2_000_000, sizeHuman: '2MB' },
17
+ });
18
+ });
19
+ it('fails clearly when the file is missing, rather than downloading something else', async () => {
20
+ const missing = path.resolve('/nonexistent/model.gguf'); // D:\\nonexistent\\model.gguf on Windows
21
+ await expect(localModel('max', missing)).rejects.toThrow(`Model file not found: ${missing}`);
22
+ });
23
+ });
@@ -1,17 +1,23 @@
1
1
  import type { InferenceProvider, InferenceOptions, ModelTier } from './types.js';
2
+ import { type CachedModel } from './model-manager.js';
2
3
  export interface ActiveModel {
3
4
  name: string;
4
5
  filename: string;
5
6
  fallback: boolean;
6
7
  }
7
8
  export declare class SidecarProvider implements InferenceProvider {
9
+ private readonly modelPath?;
8
10
  readonly name = "sidecar";
9
11
  private binaryPath;
10
12
  private model;
11
13
  private schemaPath;
12
14
  private tier;
13
15
  private threads;
14
- constructor(tier?: ModelTier, threads?: number);
16
+ /**
17
+ * @param modelPath a local GGUF to load instead of the tier's published model:
18
+ * how a fine-tune candidate is measured before it is published.
19
+ */
20
+ constructor(tier?: ModelTier, threads?: number, modelPath?: string | undefined);
15
21
  /** True when a llama-cli that actually runs is installed. */
16
22
  isAvailable(): Promise<boolean>;
17
23
  /** The llama-cli this provider would use and its version, or null if none runs. */
@@ -22,6 +28,7 @@ export declare class SidecarProvider implements InferenceProvider {
22
28
  /** Install the engine and model for this tier without running inference. */
23
29
  prepare(onProgress?: (message: string) => void): Promise<void>;
24
30
  setup(onProgress?: (message: string) => void): Promise<void>;
31
+ private publishedModel;
25
32
  /** The model file setup() loaded, or null before setup. */
26
33
  getActiveModel(): ActiveModel | null;
27
34
  analyze(prompt: string, options?: InferenceOptions): Promise<string>;
@@ -30,3 +37,5 @@ export declare class SidecarProvider implements InferenceProvider {
30
37
  private resolveBinaryPath;
31
38
  private candidateBinaries;
32
39
  }
40
+ /** A GGUF on disk, used as is: no download, no version check. */
41
+ export declare function localModel(tier: ModelTier, modelPath: string): Promise<CachedModel>;
@@ -29,13 +29,19 @@ const PLATFORM_PACKAGES = {
29
29
  'win32-x64': '@rigour-labs/brain-win-x64',
30
30
  };
31
31
  export class SidecarProvider {
32
+ modelPath;
32
33
  name = 'sidecar';
33
34
  binaryPath = null;
34
35
  model = null;
35
36
  schemaPath = null;
36
37
  tier;
37
38
  threads;
38
- constructor(tier = 'lite', threads = 4) {
39
+ /**
40
+ * @param modelPath a local GGUF to load instead of the tier's published model:
41
+ * how a fine-tune candidate is measured before it is published.
42
+ */
43
+ constructor(tier = 'lite', threads = 4, modelPath) {
44
+ this.modelPath = modelPath;
39
45
  this.tier = tier;
40
46
  this.threads = threads;
41
47
  }
@@ -58,11 +64,7 @@ export class SidecarProvider {
58
64
  async setup(onProgress) {
59
65
  this.binaryPath = await this.resolveBinaryPath() ?? await installLlamaEngine(onProgress);
60
66
  onProgress?.('✓ Inference engine ready');
61
- await ensureModel(this.tier, (msg, percent) => {
62
- if (percent !== undefined && percent < 100)
63
- onProgress?.(` ${msg}`);
64
- });
65
- this.model = await getCachedModel(this.tier);
67
+ this.model = this.modelPath ? await localModel(this.tier, this.modelPath) : await this.publishedModel(onProgress);
66
68
  if (!this.model) {
67
69
  throw new Error(`Model for tier "${this.tier}" is not cached after download.`);
68
70
  }
@@ -72,6 +74,13 @@ export class SidecarProvider {
72
74
  this.schemaPath = await writeFindingsSchema();
73
75
  onProgress?.('✓ Model ready');
74
76
  }
77
+ async publishedModel(onProgress) {
78
+ await ensureModel(this.tier, (msg, percent) => {
79
+ if (percent !== undefined && percent < 100)
80
+ onProgress?.(` ${msg}`);
81
+ });
82
+ return getCachedModel(this.tier);
83
+ }
75
84
  /** The model file setup() loaded, or null before setup. */
76
85
  getActiveModel() {
77
86
  if (!this.model)
@@ -176,3 +185,16 @@ async function writeFindingsSchema() {
176
185
  await fs.writeJson(schemaPath, FINDINGS_JSON_SCHEMA);
177
186
  return schemaPath;
178
187
  }
188
+ /** A GGUF on disk, used as is: no download, no version check. */
189
+ export async function localModel(tier, modelPath) {
190
+ const resolved = path.resolve(modelPath);
191
+ const stat = await fs.stat(resolved).catch(() => null);
192
+ if (!stat?.isFile())
193
+ throw new Error(`Model file not found: ${resolved}`);
194
+ const filename = path.basename(resolved);
195
+ return {
196
+ path: resolved,
197
+ info: { tier, name: `${filename} (local)`, filename, url: '', sizeBytes: stat.size, sizeHuman: `${Math.round(stat.size / 1e6)}MB` },
198
+ fallback: false,
199
+ };
200
+ }
@@ -66,8 +66,15 @@ export interface DeepAnalysisResult {
66
66
  * - deep: Qwen2.5-Coder-1.5B fine-tuned — full power, company-hosted
67
67
  * - lite: Qwen2.5-Coder-0.5B fine-tuned — lightweight, ships as default CLI sidecar
68
68
  * - legacy: Qwen2.5-Coder-0.5B fine-tuned — previous default, reproducibility
69
+ * - max: Qwen2.5-Coder-7B (4.7GB q4) — the strongest model a 16GB laptop runs;
70
+ * stock until a fine-tune passes the eval gate and the Review Arena
69
71
  */
70
- export type ModelTier = 'deep' | 'lite' | 'legacy';
72
+ export type ModelTier = 'deep' | 'lite' | 'legacy' | 'max';
73
+ /** The local tier for a run: --max, then --pro, then the lite default. */
74
+ export declare function localTier(options: {
75
+ max?: boolean;
76
+ pro?: boolean;
77
+ }): ModelTier;
71
78
  /**
72
79
  * Model info for download/caching.
73
80
  */
@@ -1,3 +1,7 @@
1
+ /** The local tier for a run: --max, then --pro, then the lite default. */
2
+ export function localTier(options) {
3
+ return options.max ? 'max' : options.pro ? 'deep' : 'lite';
4
+ }
1
5
  /**
2
6
  * Minimum bundled model version — used as fallback when the auto-update
3
7
  * check fails (offline, HF down, first run). The RLAIF training pipeline
@@ -23,6 +27,7 @@ export function buildModelInfo(tier, version) {
23
27
  deep: { base: 'Qwen2.5-Coder-1.5B', size: 900_000_000, sizeH: '900MB' },
24
28
  lite: { base: 'Qwen2.5-Coder-0.5B', size: 400_000_000, sizeH: '400MB' },
25
29
  legacy: { base: 'Qwen2.5-Coder-0.5B', size: 350_000_000, sizeH: '350MB' },
30
+ max: { base: 'Qwen2.5-Coder-7B', size: 4_700_000_000, sizeH: '4.7GB' },
26
31
  };
27
32
  const m = meta[tier];
28
33
  return {
@@ -39,6 +44,7 @@ export const MODELS = {
39
44
  deep: buildModelInfo('deep', BUNDLED_MODEL_VERSION),
40
45
  lite: buildModelInfo('lite', BUNDLED_MODEL_VERSION),
41
46
  legacy: buildModelInfo('legacy', BUNDLED_MODEL_VERSION),
47
+ max: buildModelInfo('max', BUNDLED_MODEL_VERSION),
42
48
  };
43
49
  /**
44
50
  * Update MODELS in-place to point to a newer version.
@@ -79,4 +85,12 @@ export const FALLBACK_MODELS = {
79
85
  sizeBytes: 350_000_000,
80
86
  sizeHuman: '350MB',
81
87
  },
88
+ max: {
89
+ tier: 'max',
90
+ name: 'Qwen2.5-Coder-7B-Instruct (stock)',
91
+ filename: 'qwen2.5-coder-7b-instruct-q4_k_m.gguf',
92
+ url: 'https://huggingface.co/Qwen/Qwen2.5-Coder-7B-Instruct-GGUF/resolve/main/qwen2.5-coder-7b-instruct-q4_k_m.gguf',
93
+ sizeBytes: 4_683_073_536,
94
+ sizeHuman: '4.7GB',
95
+ },
82
96
  };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { buildModelInfo, FALLBACK_MODELS, localTier, MODELS } from './types.js';
3
+ describe('local model tiers', () => {
4
+ it('picks max over pro over the lite default', () => {
5
+ expect(localTier({})).toBe('lite');
6
+ expect(localTier({ pro: true })).toBe('deep');
7
+ expect(localTier({ max: true })).toBe('max');
8
+ expect(localTier({ max: true, pro: true })).toBe('max');
9
+ });
10
+ it('asks for a published max fine-tune and falls back to the stock 7B coder model', () => {
11
+ expect(buildModelInfo('max', '6.0.0').url)
12
+ .toBe('https://huggingface.co/rigour-labs/rigour-max-v6.0.0-gguf/resolve/main/rigour-max-v6.0.0-q4_k_m.gguf');
13
+ expect(MODELS.max.tier).toBe('max');
14
+ expect(FALLBACK_MODELS.max).toMatchObject({
15
+ filename: 'qwen2.5-coder-7b-instruct-q4_k_m.gguf',
16
+ url: 'https://huggingface.co/Qwen/Qwen2.5-Coder-7B-Instruct-GGUF/resolve/main/qwen2.5-coder-7b-instruct-q4_k_m.gguf',
17
+ });
18
+ });
19
+ });
@@ -0,0 +1,3 @@
1
+ import type { DeepOptions, Failure } from '../types/index.js';
2
+ import type { DiffSource } from './git-diff.js';
3
+ export declare function diffTestFailures(cwd: string, source: DiffSource | undefined, deep: DeepOptions): Promise<Failure[]>;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * `rigour review --diff-tests`: behaviour changes found by running changed
3
+ * functions before and after the change, as review findings on the changed line.
4
+ */
5
+ import { execFileSync } from 'child_process';
6
+ import { runDiffTests } from '../deep/diff-tests/run.js';
7
+ import { createProvider } from '../inference/index.js';
8
+ export async function diffTestFailures(cwd, source, deep) {
9
+ const provider = createProvider(deep);
10
+ await provider.setup();
11
+ try {
12
+ const changes = await runDiffTests({
13
+ cwd, baseRef: baseRef(cwd, source), focusLines: deep.focusLines ?? {}, prBody: deep.prBody,
14
+ provider, inference: { maxTokens: 1024, temperature: 0.2 },
15
+ });
16
+ return changes.map(toFailure);
17
+ }
18
+ finally {
19
+ provider.dispose();
20
+ }
21
+ }
22
+ /** What the change is compared with: the merge base for a branch review, HEAD for working changes. */
23
+ function baseRef(cwd, source) {
24
+ if (source?.mode !== 'base')
25
+ return 'HEAD';
26
+ return execFileSync('git', ['merge-base', source.base, 'HEAD'], { cwd, encoding: 'utf8' }).trim();
27
+ }
28
+ function toFailure(change) {
29
+ return {
30
+ id: 'diff-tests',
31
+ title: `[diff-tests] ${change.name} behaves differently after this change`,
32
+ details: `\`${change.call}\` ${change.before} before this change and ${change.after} after it.`,
33
+ severity: 'medium',
34
+ provenance: 'deep-analysis',
35
+ files: [change.file],
36
+ line: change.line,
37
+ hint: 'If this is intended, say so in the PR description; otherwise restore the old behaviour for this input.',
38
+ source: 'hybrid',
39
+ verified: true,
40
+ };
41
+ }
@@ -8,8 +8,10 @@ export interface ReviewInput {
8
8
  source?: DiffSource;
9
9
  /** Review exactly these files instead of the ones the diff touches. */
10
10
  files?: string[];
11
- /** Deep analysis; `focusLines` is filled from the diff. */
12
- deep?: Omit<DeepOptions, 'focusLines'>;
11
+ /** Run changed functions before and after the change (needs deep, on the max or a cloud tier). */
12
+ diffTests?: boolean;
13
+ /** Deep analysis; `focusLines` and `removedLines` are filled from the diff. */
14
+ deep?: Omit<DeepOptions, 'focusLines' | 'removedLines'>;
13
15
  }
14
16
  export interface ReviewResult {
15
17
  status: 'PASS' | 'FAIL' | 'ERROR';
@@ -7,11 +7,12 @@
7
7
  * but could not analyze anything makes the result ERROR, never a clean PASS.
8
8
  */
9
9
  import { GateRunner } from '../gates/runner.js';
10
- import { changedLinesByFile, parseDiff } from '../utils/diff.js';
10
+ import { changedLinesByFile, parseDiff, removedByFile } from '../utils/diff.js';
11
11
  import { normalizeScopePatterns } from '../utils/scope.js';
12
12
  import { deepAnalysisError } from '../utils/deep-status.js';
13
13
  import { splitByChangedLines } from './changed-lines.js';
14
14
  import { diffFromGit } from './git-diff.js';
15
+ import { diffTestFailures } from './diff-test-findings.js';
15
16
  export async function reviewChange(input) {
16
17
  const diff = input.diff ?? diffFromGit(input.cwd, input.source);
17
18
  const changedLines = parseDiff(diff);
@@ -19,8 +20,10 @@ export async function reviewChange(input) {
19
20
  if (targets.length === 0) {
20
21
  return { status: 'PASS', findings: [], fileFindings: [], unlocated: 0, excludedOutsideChangedLines: 0, changedLines, report: null };
21
22
  }
22
- const deep = input.deep ? { ...input.deep, focusLines: changedLinesByFile(changedLines) } : undefined;
23
+ const deep = input.deep ? { ...input.deep, focusLines: changedLinesByFile(changedLines), removedLines: removedByFile(diff) } : undefined;
23
24
  const report = await new GateRunner(input.config).run(input.cwd, await normalizeScopePatterns(input.cwd, targets), deep);
25
+ if (input.diffTests && deep)
26
+ report.failures.push(...await diffTestFailures(input.cwd, input.source, deep));
24
27
  const split = splitByChangedLines(report.failures, changedLines);
25
28
  const deepError = deepAnalysisError(report);
26
29
  return {
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import fs from 'fs';
8
8
  import path from 'path';
9
- import { analyzeFiles } from './engine.js';
9
+ import { ALL_RULES, analyzeFiles } from './engine.js';
10
10
  export function loadCases(root) {
11
11
  return fs.readdirSync(root, { withFileTypes: true })
12
12
  .filter(entry => entry.isDirectory() && fs.existsSync(path.join(root, entry.name, 'case.json')))
@@ -27,5 +27,5 @@ function findingsIn(dir) {
27
27
  if (!fs.existsSync(dir))
28
28
  return [];
29
29
  const files = fs.readdirSync(dir).filter(f => /\.[cm]?[jt]sx?$/.test(f));
30
- return analyzeFiles(dir, files).map(f => `${f.rule} ${f.file}:${f.line}`);
30
+ return analyzeFiles(dir, files, { rules: ALL_RULES }).map(f => `${f.rule} ${f.file}:${f.line}`);
31
31
  }
@@ -1,12 +1,16 @@
1
1
  import path from 'path';
2
2
  import { describe, expect, it } from 'vitest';
3
+ import { CANDIDATE_RULES, ECOSYSTEM_RULES } from './rules/ecosystem/index.js';
3
4
  import { loadCases, runCase } from './benchmark.js';
4
5
  const ROOT = path.resolve(__dirname, '../../../../benchmarks/semantic');
5
6
  describe('semantic benchmark', () => {
6
7
  const cases = loadCases(ROOT);
7
8
  it('has cases for every built-in rule, including negatives', () => {
8
9
  const rules = new Set(cases.map(c => c.rule));
9
- expect(rules).toEqual(new Set(['in-memory-aggregation', 'credential-redirect', 'degraded-response-cached', null]));
10
+ expect(rules).toEqual(new Set([
11
+ 'in-memory-aggregation', 'credential-redirect', 'degraded-response-cached',
12
+ ...[...ECOSYSTEM_RULES, ...CANDIDATE_RULES].map(rule => rule.id), null,
13
+ ]));
10
14
  });
11
15
  for (const bench of cases) {
12
16
  it(`${bench.name}: ${bench.note}`, () => {
@@ -1,5 +1,8 @@
1
1
  import type { SemanticFinding, SemanticRule } from './types.js';
2
+ /** Rules that run by default. */
2
3
  export declare const BUILT_IN_RULES: SemanticRule[];
4
+ /** Every rule Rigour ships, candidates included: what can be named to run. */
5
+ export declare const ALL_RULES: SemanticRule[];
3
6
  export interface EngineOptions {
4
7
  rules?: SemanticRule[];
5
8
  batchSize?: number;
@@ -3,11 +3,16 @@
3
3
  */
4
4
  import path from 'path';
5
5
  import { loadProjectConfig, programBatches } from './program.js';
6
+ import { ProjectFacts } from './project-facts.js';
6
7
  import { Summaries } from './summaries.js';
7
8
  import { inMemoryAggregation } from './rules/in-memory-aggregation.js';
8
9
  import { credentialRedirect } from './rules/credential-redirect.js';
9
10
  import { degradedResponseCached } from './rules/degraded-response-cached.js';
10
- export const BUILT_IN_RULES = [inMemoryAggregation, credentialRedirect, degradedResponseCached];
11
+ import { CANDIDATE_RULES, ECOSYSTEM_RULES } from './rules/ecosystem/index.js';
12
+ /** Rules that run by default. */
13
+ export const BUILT_IN_RULES = [inMemoryAggregation, credentialRedirect, degradedResponseCached, ...ECOSYSTEM_RULES];
14
+ /** Every rule Rigour ships, candidates included: what can be named to run. */
15
+ export const ALL_RULES = [...BUILT_IN_RULES, ...CANDIDATE_RULES];
11
16
  /** Analyze `files` (cwd-relative or absolute). Declaration files are skipped. */
12
17
  export function analyzeFiles(cwd, files, opts = {}) {
13
18
  const rules = opts.rules ?? BUILT_IN_RULES;
@@ -19,6 +24,7 @@ export function analyzeFiles(cwd, files, opts = {}) {
19
24
  const { options } = loadProjectConfig(cwd);
20
25
  const rootSet = new Set(roots.map(r => path.normalize(r)));
21
26
  const findings = [];
27
+ const project = new ProjectFacts(cwd);
22
28
  for (const program of programBatches(roots, options, opts.batchSize)) {
23
29
  const checker = program.getTypeChecker();
24
30
  const summaries = new Summaries(checker);
@@ -26,7 +32,7 @@ export function analyzeFiles(cwd, files, opts = {}) {
26
32
  if (!rootSet.has(path.normalize(sourceFile.fileName)))
27
33
  continue;
28
34
  for (const rule of rules) {
29
- findings.push(...rule.check({ cwd, checker, sourceFile, summaries }));
35
+ findings.push(...rule.check({ cwd, program, checker, sourceFile, summaries, project }));
30
36
  }
31
37
  rootSet.delete(path.normalize(sourceFile.fileName));
32
38
  }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Does a call handle its own failure? Shared by the intent checks and the
3
+ * training-site export, so both see a call the same way.
4
+ */
5
+ import ts from 'typescript';
6
+ import { type FunctionLike } from './ast.js';
7
+ export declare function isPromiseAll(call: ts.CallExpression): boolean;
8
+ export declare function isPromiseAllSettled(call: ts.CallExpression): boolean;
9
+ /** Inside a `try` (with a catch) of the same function: the rejection is handled there. */
10
+ export declare function isInsideTry(node: ts.Node, fn: FunctionLike): boolean;
11
+ /** `x().catch(...)`, `x().then(ok, fail)`, or a callee whose body catches and returns. */
12
+ export declare function handlesOwnFailure(checker: ts.TypeChecker, call: ts.CallExpression): boolean;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Does a call handle its own failure? Shared by the intent checks and the
3
+ * training-site export, so both see a call the same way.
4
+ */
5
+ import ts from 'typescript';
6
+ import { calledFunction } from './ast.js';
7
+ export function isPromiseAll(call) {
8
+ return isPromiseMethod(call, 'all');
9
+ }
10
+ export function isPromiseAllSettled(call) {
11
+ return isPromiseMethod(call, 'allSettled');
12
+ }
13
+ function isPromiseMethod(call, name) {
14
+ const callee = call.expression;
15
+ return ts.isPropertyAccessExpression(callee) && callee.name.text === name
16
+ && ts.isIdentifier(callee.expression) && callee.expression.text === 'Promise';
17
+ }
18
+ /** Inside a `try` (with a catch) of the same function: the rejection is handled there. */
19
+ export function isInsideTry(node, fn) {
20
+ for (let child = node, parent = node.parent; parent && parent !== fn; child = parent, parent = parent.parent) {
21
+ if (ts.isTryStatement(parent) && parent.tryBlock === child && parent.catchClause)
22
+ return true;
23
+ }
24
+ return false;
25
+ }
26
+ /** `x().catch(...)`, `x().then(ok, fail)`, or a callee whose body catches and returns. */
27
+ export function handlesOwnFailure(checker, call) {
28
+ const callee = call.expression;
29
+ if (ts.isPropertyAccessExpression(callee)) {
30
+ if (callee.name.text === 'catch' || callee.name.text === 'finally')
31
+ return true;
32
+ if (callee.name.text === 'then' && call.arguments.length >= 2)
33
+ return true;
34
+ }
35
+ const target = calledFunction(checker, call);
36
+ const body = target?.body;
37
+ return !!body && ts.isBlock(body) && body.statements.some(s => ts.isTryStatement(s) && !!s.catchClause && catchReturns(s.catchClause));
38
+ }
39
+ function catchReturns(clause) {
40
+ return clause.block.statements.some(s => ts.isReturnStatement(s));
41
+ }
@@ -7,7 +7,8 @@
7
7
  * which the code cannot say; see intent-check.ts.
8
8
  */
9
9
  import ts from 'typescript';
10
- import { calledFunction, enclosingFunction, forEachNode, lineOf, relativeFile, unwrap } from '../ast.js';
10
+ import { enclosingFunction, forEachNode, lineOf, relativeFile, unwrap } from '../ast.js';
11
+ import { handlesOwnFailure, isInsideTry, isPromiseAll } from '../failure-handling.js';
11
12
  import { promiseAllItems } from '../origins.js';
12
13
  import { functionKeyOf } from '../learn/identity.js';
13
14
  export function findParallelReads(cwd, checker, sourceFile) {
@@ -28,32 +29,3 @@ export function findParallelReads(cwd, checker, sourceFile) {
28
29
  });
29
30
  return sites;
30
31
  }
31
- function isPromiseAll(call) {
32
- const callee = call.expression;
33
- return ts.isPropertyAccessExpression(callee) && callee.name.text === 'all'
34
- && ts.isIdentifier(callee.expression) && callee.expression.text === 'Promise';
35
- }
36
- /** Inside a `try` (with a catch) of the same function: the rejection is handled there. */
37
- function isInsideTry(node, fn) {
38
- for (let child = node, parent = node.parent; parent && parent !== fn; child = parent, parent = parent.parent) {
39
- if (ts.isTryStatement(parent) && parent.tryBlock === child && parent.catchClause)
40
- return true;
41
- }
42
- return false;
43
- }
44
- /** `x().catch(...)`, `x().then(ok, fail)`, or a callee whose body catches and returns. */
45
- function handlesOwnFailure(checker, call) {
46
- const callee = call.expression;
47
- if (ts.isPropertyAccessExpression(callee)) {
48
- if (callee.name.text === 'catch' || callee.name.text === 'finally')
49
- return true;
50
- if (callee.name.text === 'then' && call.arguments.length >= 2)
51
- return true;
52
- }
53
- const target = calledFunction(checker, call);
54
- const body = target?.body;
55
- return !!body && ts.isBlock(body) && body.statements.some(s => ts.isTryStatement(s) && !!s.catchClause && catchReturns(s.catchClause));
56
- }
57
- function catchReturns(clause) {
58
- return clause.block.statements.some(s => ts.isReturnStatement(s));
59
- }
@@ -0,0 +1,25 @@
1
+ import ts from 'typescript';
2
+ export declare class ProjectFacts {
3
+ private readonly cwd;
4
+ private readonly packages;
5
+ private readonly options;
6
+ constructor(cwd: string);
7
+ /** Declared dependency names and version ranges of the package that owns `file`. */
8
+ dependencies(file: string): Map<string, string>;
9
+ /** Directory of the package.json that owns `file`. */
10
+ packageDir(file: string): string | undefined;
11
+ /** The `name` of the package that owns `file`. */
12
+ packageName(file: string): string | undefined;
13
+ declares(file: string, ...names: string[]): boolean;
14
+ /**
15
+ * Highest major version a declared dependency may run on, across every field
16
+ * and `||` alternative (`>=18.0.0 || >=19.0.0` -> 19). A package that supports
17
+ * a major must work on it. Undefined when no range names a version (`catalog:`, `workspace:*`).
18
+ */
19
+ majorVersion(file: string, name: string): number | undefined;
20
+ /** Compiler options of the nearest tsconfig.json above `file`, extends resolved. */
21
+ compilerOptions(file: string): ts.CompilerOptions;
22
+ private dirsUp;
23
+ private nearestPackage;
24
+ private readPackage;
25
+ }