@rigour-labs/core 6.3.1 → 6.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +1 -1
  2. package/dist/deep/changed-functions.d.ts +15 -0
  3. package/dist/deep/changed-functions.js +34 -0
  4. package/dist/deep/code-pass.d.ts +10 -0
  5. package/dist/deep/code-pass.js +37 -2
  6. package/dist/deep/code-pass.test.d.ts +1 -0
  7. package/dist/deep/code-pass.test.js +69 -0
  8. package/dist/deep/code-review-prompt.d.ts +1 -1
  9. package/dist/deep/code-review-prompt.js +3 -2
  10. package/dist/deep/diff-tests/calls.d.ts +8 -0
  11. package/dist/deep/diff-tests/calls.js +85 -0
  12. package/dist/deep/diff-tests/calls.test.d.ts +1 -0
  13. package/dist/deep/diff-tests/calls.test.js +33 -0
  14. package/dist/deep/diff-tests/run.d.ts +21 -0
  15. package/dist/deep/diff-tests/run.js +178 -0
  16. package/dist/deep/diff-tests/run.test.d.ts +1 -0
  17. package/dist/deep/diff-tests/run.test.js +58 -0
  18. package/dist/deep/reference-pack.d.ts +19 -0
  19. package/dist/deep/reference-pack.js +101 -0
  20. package/dist/deep/reference-pack.test.d.ts +1 -0
  21. package/dist/deep/reference-pack.test.js +46 -0
  22. package/dist/deep/review-context-export.d.ts +11 -0
  23. package/dist/deep/review-context-export.js +31 -0
  24. package/dist/deep/review-context-export.test.d.ts +1 -0
  25. package/dist/deep/review-context-export.test.js +32 -0
  26. package/dist/deep/self-check.d.ts +21 -0
  27. package/dist/deep/self-check.js +49 -0
  28. package/dist/firewall/trusted-control.d.ts +1 -2
  29. package/dist/gates/deep-analysis.d.ts +2 -0
  30. package/dist/gates/deep-analysis.js +12 -0
  31. package/dist/gates/deep-runner.js +2 -1
  32. package/dist/gates/hallucinated-imports/js-resolver.d.ts +7 -0
  33. package/dist/gates/hallucinated-imports/js-resolver.js +17 -1
  34. package/dist/gates/hallucinated-imports-sveltekit.test.js +24 -0
  35. package/dist/gates/semantic-bugs.js +4 -3
  36. package/dist/gates/semantic-bugs.test.js +11 -5
  37. package/dist/hooks/checker.d.ts +1 -2
  38. package/dist/hooks/index.d.ts +4 -0
  39. package/dist/hooks/index.js +2 -0
  40. package/dist/hooks/stop-review.d.ts +25 -0
  41. package/dist/hooks/stop-review.js +31 -0
  42. package/dist/index.d.ts +26 -2
  43. package/dist/index.js +10 -6
  44. package/dist/inference/index.d.ts +1 -1
  45. package/dist/inference/index.js +3 -5
  46. package/dist/inference/local-model.test.d.ts +1 -0
  47. package/dist/inference/local-model.test.js +23 -0
  48. package/dist/inference/sidecar-provider.d.ts +10 -1
  49. package/dist/inference/sidecar-provider.js +28 -6
  50. package/dist/inference/types.d.ts +8 -1
  51. package/dist/inference/types.js +14 -0
  52. package/dist/inference/types.test.d.ts +1 -0
  53. package/dist/inference/types.test.js +19 -0
  54. package/dist/review/agent-fixes.d.ts +18 -0
  55. package/dist/review/agent-fixes.js +118 -0
  56. package/dist/review/agent-fixes.test.d.ts +1 -0
  57. package/dist/review/agent-fixes.test.js +32 -0
  58. package/dist/review/changed-lines.d.ts +19 -0
  59. package/dist/review/changed-lines.js +22 -0
  60. package/dist/review/diff-test-findings.d.ts +3 -0
  61. package/dist/review/diff-test-findings.js +41 -0
  62. package/dist/review/effectiveness.d.ts +34 -0
  63. package/dist/review/effectiveness.js +114 -0
  64. package/dist/review/effectiveness.test.d.ts +1 -0
  65. package/dist/review/effectiveness.test.js +42 -0
  66. package/dist/review/git-diff.d.ts +9 -0
  67. package/dist/review/git-diff.js +42 -0
  68. package/dist/review/review.d.ts +38 -0
  69. package/dist/review/review.js +52 -0
  70. package/dist/review/review.test.d.ts +1 -0
  71. package/dist/review/review.test.js +87 -0
  72. package/dist/semantic/benchmark.js +2 -2
  73. package/dist/semantic/benchmark.test.js +5 -1
  74. package/dist/semantic/engine.d.ts +3 -0
  75. package/dist/semantic/engine.js +8 -2
  76. package/dist/semantic/failure-handling.d.ts +12 -0
  77. package/dist/semantic/failure-handling.js +41 -0
  78. package/dist/semantic/intent/parallel-reads.js +2 -30
  79. package/dist/semantic/learn/learn.d.ts +2 -0
  80. package/dist/semantic/learn/learn.js +18 -0
  81. package/dist/semantic/project-facts.d.ts +25 -0
  82. package/dist/semantic/project-facts.js +104 -0
  83. package/dist/semantic/project-facts.test.d.ts +1 -0
  84. package/dist/semantic/project-facts.test.js +45 -0
  85. package/dist/semantic/rules/ecosystem/deps-codegen-undeclared-import.d.ts +2 -0
  86. package/dist/semantic/rules/ecosystem/deps-codegen-undeclared-import.js +71 -0
  87. package/dist/semantic/rules/ecosystem/deps-internal-symbol.d.ts +2 -0
  88. package/dist/semantic/rules/ecosystem/deps-internal-symbol.js +36 -0
  89. package/dist/semantic/rules/ecosystem/dom-self-query-in-component.d.ts +2 -0
  90. package/dist/semantic/rules/ecosystem/dom-self-query-in-component.js +60 -0
  91. package/dist/semantic/rules/ecosystem/env-bare-browser-global.d.ts +2 -0
  92. package/dist/semantic/rules/ecosystem/env-bare-browser-global.js +57 -0
  93. package/dist/semantic/rules/ecosystem/exports-condition-parity.d.ts +2 -0
  94. package/dist/semantic/rules/ecosystem/exports-condition-parity.js +57 -0
  95. package/dist/semantic/rules/ecosystem/exports-forgotten-type.d.ts +2 -0
  96. package/dist/semantic/rules/ecosystem/exports-forgotten-type.js +128 -0
  97. package/dist/semantic/rules/ecosystem/imports-barrel-cycle.d.ts +2 -0
  98. package/dist/semantic/rules/ecosystem/imports-barrel-cycle.js +78 -0
  99. package/dist/semantic/rules/ecosystem/index.d.ts +15 -0
  100. package/dist/semantic/rules/ecosystem/index.js +22 -0
  101. package/dist/semantic/rules/ecosystem/paths-unnormalized-module-key.d.ts +2 -0
  102. package/dist/semantic/rules/ecosystem/paths-unnormalized-module-key.js +87 -0
  103. package/dist/semantic/rules/ecosystem/react-inline-html-object.d.ts +2 -0
  104. package/dist/semantic/rules/ecosystem/react-inline-html-object.js +30 -0
  105. package/dist/semantic/rules/ecosystem/shared.d.ts +18 -0
  106. package/dist/semantic/rules/ecosystem/shared.js +43 -0
  107. package/dist/semantic/rules/ecosystem/solid-jsx-and-conditional.d.ts +2 -0
  108. package/dist/semantic/rules/ecosystem/solid-jsx-and-conditional.js +36 -0
  109. package/dist/semantic/rules/ecosystem/ts-internal-in-public-signature.d.ts +2 -0
  110. package/dist/semantic/rules/ecosystem/ts-internal-in-public-signature.js +66 -0
  111. package/dist/semantic/rules/ecosystem/vite-rollup-only-hook-field.d.ts +2 -0
  112. package/dist/semantic/rules/ecosystem/vite-rollup-only-hook-field.js +31 -0
  113. package/dist/semantic/rules/ecosystem/vue-static-computed.d.ts +2 -0
  114. package/dist/semantic/rules/ecosystem/vue-static-computed.js +51 -0
  115. package/dist/semantic/sites/call-sites.d.ts +25 -0
  116. package/dist/semantic/sites/call-sites.js +69 -0
  117. package/dist/semantic/sites/call-sites.test.d.ts +1 -0
  118. package/dist/semantic/sites/call-sites.test.js +46 -0
  119. package/dist/semantic/sites/export.d.ts +2 -0
  120. package/dist/semantic/sites/export.js +22 -0
  121. package/dist/semantic/types.d.ts +4 -0
  122. package/dist/services/engineering-knowledge-graph.d.ts +1 -2
  123. package/dist/templates/universal-config.js +2 -1
  124. package/dist/types/index.d.ts +31 -8
  125. package/dist/types/index.js +4 -2
  126. package/dist/utils/diff-removed.test.d.ts +1 -0
  127. package/dist/utils/diff-removed.test.js +29 -0
  128. package/dist/utils/diff.d.ts +12 -0
  129. package/dist/utils/diff.js +26 -0
  130. package/package.json +6 -6
package/dist/index.d.ts CHANGED
@@ -11,6 +11,11 @@ export { FrontendSecretExposureGate } from './gates/frontend-secret-exposure.js'
11
11
  export * from './utils/logger.js';
12
12
  export { normalizeScopePatterns, isScoped } from './utils/scope.js';
13
13
  export { deepAnalysisError } from './utils/deep-status.js';
14
+ export { reviewChange, toReviewFinding, type ReviewInput, type ReviewResult, type ReviewFinding } from './review/review.js';
15
+ export { splitByChangedLines, type ChangedLineSplit } from './review/changed-lines.js';
16
+ export { diffFromGit, GitDiffError, type DiffSource } from './review/git-diff.js';
17
+ export { recordReviewOutcome, listResolvedFixes, removeResolvedFix, openFindingCount, type ResolvedFix, type FixCapture } from './review/agent-fixes.js';
18
+ export { computeEffectiveness, readAgentEvents, appendAgentEvent, findingKeys, type AgentEvent, type ReviewEffectiveness } from './review/effectiveness.js';
14
19
  export { parseDiff, changedLinesByFile } from './utils/diff.js';
15
20
  export { FileScanner } from './utils/scanner.js';
16
21
  export * from './services/score-history.js';
@@ -20,7 +25,7 @@ export type { RigourSettings, ResolvedDeepOptions, CLIDeepOptions } from './sett
20
25
  export { DeepAnalysisGate } from './gates/deep-analysis.js';
21
26
  export { createProvider } from './inference/index.js';
22
27
  export type { InferenceProvider, DeepFinding, DeepAnalysisResult, ModelTier } from './inference/types.js';
23
- export { MODELS } from './inference/types.js';
28
+ export { MODELS, localTier } from './inference/types.js';
24
29
  export { isModelCached, getModelsDir, getModelInfo, getCachedModel, ensureModel } from './inference/model-manager.js';
25
30
  export { installLlamaEngine, probeBinary, managedEnginePath, LLAMA_RELEASE_TAG } from './inference/llama-engine.js';
26
31
  export { SidecarProvider } from './inference/sidecar-provider.js';
@@ -51,8 +56,27 @@ export * from './services/system-health.js';
51
56
  export * from './services/agent-history.js';
52
57
  export * from './services/engineering-knowledge-graph.js';
53
58
  export * from './firewall/index.js';
54
- export { learnFromFix, type LearnReport, type Rejection } from './semantic/learn/learn.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';
63
+ export { learnFromFix, learnFromFileChange, type LearnReport, type Rejection } from './semantic/learn/learn.js';
55
64
  export { extractFixTrees, type FixTrees } from './semantic/learn/git-trees.js';
56
65
  export { saveLearnedRule, loadLearnedRules, LEARNED_RULES_DIR } from './semantic/learn/store.js';
57
66
  export type { LearnedRule, LearnedPattern } from './semantic/learn/types.js';
58
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
@@ -11,6 +11,11 @@ export { FrontendSecretExposureGate } from './gates/frontend-secret-exposure.js'
11
11
  export * from './utils/logger.js';
12
12
  export { normalizeScopePatterns, isScoped } from './utils/scope.js';
13
13
  export { deepAnalysisError } from './utils/deep-status.js';
14
+ export { reviewChange, toReviewFinding } from './review/review.js';
15
+ export { splitByChangedLines } from './review/changed-lines.js';
16
+ export { diffFromGit, GitDiffError } from './review/git-diff.js';
17
+ export { recordReviewOutcome, listResolvedFixes, removeResolvedFix, openFindingCount } from './review/agent-fixes.js';
18
+ export { computeEffectiveness, readAgentEvents, appendAgentEvent, findingKeys } from './review/effectiveness.js';
14
19
  export { parseDiff, changedLinesByFile } from './utils/diff.js';
15
20
  export { FileScanner } from './utils/scanner.js';
16
21
  export * from './services/score-history.js';
@@ -20,7 +25,7 @@ export { loadSettings, saveSettings, getSettingsPath, resolveDeepOptions, getPro
20
25
  // Deep Analysis Pipeline (v4.0+)
21
26
  export { DeepAnalysisGate } from './gates/deep-analysis.js';
22
27
  export { createProvider } from './inference/index.js';
23
- export { MODELS } from './inference/types.js';
28
+ export { MODELS, localTier } from './inference/types.js';
24
29
  export { isModelCached, getModelsDir, getModelInfo, getCachedModel, ensureModel } from './inference/model-manager.js';
25
30
  export { installLlamaEngine, probeBinary, managedEnginePath, LLAMA_RELEASE_TAG } from './inference/llama-engine.js';
26
31
  export { SidecarProvider } from './inference/sidecar-provider.js';
@@ -50,11 +55,10 @@ export * from './services/system-health.js';
50
55
  export * from './services/agent-history.js';
51
56
  export * from './services/engineering-knowledge-graph.js';
52
57
  export * from './firewall/index.js';
53
- export { learnFromFix } from './semantic/learn/learn.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';
61
+ export { learnFromFix, learnFromFileChange } from './semantic/learn/learn.js';
54
62
  export { extractFixTrees } from './semantic/learn/git-trees.js';
55
63
  export { saveLearnedRule, loadLearnedRules, LEARNED_RULES_DIR } from './semantic/learn/store.js';
56
64
  export { recordContextEvent, recordModelUsage, recordCheckpointMetric, getContextEvents, getModelUsages, getCheckpointMetrics, } from './storage/index.js';
57
- // Pattern Index is intentionally NOT exported here to prevent
58
- // native dependency issues (sharp/transformers) from leaking into
59
- // non-AI parts of the system.
60
- // 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,18 @@
1
+ import type { Failure } from '../types/index.js';
2
+ export interface ResolvedFix {
3
+ id: string;
4
+ file: string;
5
+ rule: string;
6
+ before: string;
7
+ after: string;
8
+ resolvedAt: string;
9
+ }
10
+ export interface FixCapture {
11
+ opened: number;
12
+ resolved: number;
13
+ }
14
+ /** Record a review's findings: open new ones, resolve open ones the review no longer reports. */
15
+ export declare function recordReviewOutcome(cwd: string, findings: Failure[], reviewedFiles: string[]): FixCapture;
16
+ export declare function listResolvedFixes(cwd: string): ResolvedFix[];
17
+ export declare function removeResolvedFix(cwd: string, id: string): void;
18
+ export declare function openFindingCount(cwd: string): number;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Fixes agents make to Rigour findings, captured so they can become rules.
3
+ *
4
+ * Capture is cheap and happens in the review loop (MCP review, stop hook):
5
+ * a finding's file content is kept when the finding appears, and when a later
6
+ * review of that file no longer reports it, the before and after are stored as
7
+ * a resolved fix. Learning is expensive and happens later, outside the loop
8
+ * (`rigour learn --agent-fixes`), with the same validation as any learned rule.
9
+ *
10
+ * A finding only counts as resolved when its file was part of the review that
11
+ * no longer reports it: a file committed out of the working tree was not fixed.
12
+ */
13
+ import crypto from 'crypto';
14
+ import fs from 'fs';
15
+ import path from 'path';
16
+ const DIR = path.join('.rigour', 'agent-fixes');
17
+ const MAX_FILE_BYTES = 200_000;
18
+ const OPEN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
19
+ /** Record a review's findings: open new ones, resolve open ones the review no longer reports. */
20
+ export function recordReviewOutcome(cwd, findings, reviewedFiles) {
21
+ const open = readOpen(cwd);
22
+ const current = new Map(findings.flatMap(f => (f.files?.[0] ? [[`${f.id}:${f.files[0]}`, f]] : [])));
23
+ const capture = { resolved: resolveGone(cwd, open, current, new Set(reviewedFiles)), opened: openNew(cwd, open, current) };
24
+ writeOpen(cwd, open);
25
+ return capture;
26
+ }
27
+ /** Open findings this review no longer reports: resolved when their file was reviewed and changed. */
28
+ function resolveGone(cwd, open, current, reviewed) {
29
+ let resolved = 0;
30
+ for (const [key, entry] of Object.entries(open)) {
31
+ if (current.has(key))
32
+ continue;
33
+ if (!reviewed.has(entry.file)) {
34
+ if (Date.now() - Date.parse(entry.openedAt) > OPEN_TTL_MS)
35
+ delete open[key];
36
+ continue;
37
+ }
38
+ const after = readSmall(cwd, entry.file);
39
+ if (after !== null && after !== entry.before) {
40
+ writeResolved(cwd, entry, after);
41
+ resolved++;
42
+ }
43
+ delete open[key];
44
+ }
45
+ return resolved;
46
+ }
47
+ function openNew(cwd, open, current) {
48
+ let opened = 0;
49
+ for (const [key, finding] of current) {
50
+ if (open[key])
51
+ continue;
52
+ const file = finding.files[0];
53
+ const before = readSmall(cwd, file);
54
+ if (before === null)
55
+ continue;
56
+ open[key] = { file, rule: finding.id, before, openedAt: new Date().toISOString() };
57
+ opened++;
58
+ }
59
+ return opened;
60
+ }
61
+ export function listResolvedFixes(cwd) {
62
+ const dir = path.join(cwd, DIR, 'resolved');
63
+ if (!fs.existsSync(dir))
64
+ return [];
65
+ return fs.readdirSync(dir).filter(n => n.endsWith('.json')).sort().flatMap(name => {
66
+ try {
67
+ return [JSON.parse(fs.readFileSync(path.join(dir, name), 'utf8'))];
68
+ }
69
+ catch {
70
+ return [];
71
+ }
72
+ });
73
+ }
74
+ export function removeResolvedFix(cwd, id) {
75
+ fs.rmSync(path.join(cwd, DIR, 'resolved', `${id}.json`), { force: true });
76
+ }
77
+ export function openFindingCount(cwd) {
78
+ return Object.keys(readOpen(cwd)).length;
79
+ }
80
+ function readOpen(cwd) {
81
+ try {
82
+ return JSON.parse(fs.readFileSync(path.join(cwd, DIR, 'open.json'), 'utf8'));
83
+ }
84
+ catch {
85
+ return {};
86
+ }
87
+ }
88
+ function writeOpen(cwd, open) {
89
+ try {
90
+ fs.mkdirSync(path.join(cwd, DIR), { recursive: true });
91
+ fs.writeFileSync(path.join(cwd, DIR, 'open.json'), JSON.stringify(open));
92
+ }
93
+ catch {
94
+ // Capture is best-effort; it must never break a review.
95
+ }
96
+ }
97
+ function writeResolved(cwd, entry, after) {
98
+ const id = crypto.createHash('sha256').update(`${entry.rule}\0${entry.file}\0${entry.before}\0${after}`).digest('hex').slice(0, 16);
99
+ const fix = { id, file: entry.file, rule: entry.rule, before: entry.before, after, resolvedAt: new Date().toISOString() };
100
+ try {
101
+ fs.mkdirSync(path.join(cwd, DIR, 'resolved'), { recursive: true });
102
+ fs.writeFileSync(path.join(cwd, DIR, 'resolved', `${id}.json`), JSON.stringify(fix));
103
+ }
104
+ catch {
105
+ // Best-effort, as above.
106
+ }
107
+ }
108
+ function readSmall(cwd, file) {
109
+ try {
110
+ const full = path.join(cwd, file);
111
+ if (fs.statSync(full).size > MAX_FILE_BYTES)
112
+ return null;
113
+ return fs.readFileSync(full, 'utf8');
114
+ }
115
+ catch {
116
+ return null;
117
+ }
118
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
+ import { listResolvedFixes, openFindingCount, recordReviewOutcome } from './agent-fixes.js';
6
+ const finding = (file) => ({ id: 'semantic-bugs', title: 't', details: 'd', files: [file], line: 2 });
7
+ describe('recordReviewOutcome', () => {
8
+ let cwd;
9
+ beforeEach(() => {
10
+ cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-fixes-'));
11
+ fs.writeFileSync(path.join(cwd, 'a.ts'), 'before\n');
12
+ });
13
+ afterEach(() => { fs.rmSync(cwd, { recursive: true, force: true }); });
14
+ it('stores the before and after once a reviewed file no longer has the finding', () => {
15
+ expect(recordReviewOutcome(cwd, [finding('a.ts')], ['a.ts'])).toEqual({ opened: 1, resolved: 0 });
16
+ fs.writeFileSync(path.join(cwd, 'a.ts'), 'after\n');
17
+ expect(recordReviewOutcome(cwd, [], ['a.ts'])).toEqual({ opened: 0, resolved: 1 });
18
+ const [fix] = listResolvedFixes(cwd);
19
+ expect(fix).toMatchObject({ file: 'a.ts', rule: 'semantic-bugs', before: 'before\n', after: 'after\n' });
20
+ expect(openFindingCount(cwd)).toBe(0);
21
+ });
22
+ it('keeps a finding open when its file was not part of the review (committed, not fixed)', () => {
23
+ recordReviewOutcome(cwd, [finding('a.ts')], ['a.ts']);
24
+ expect(recordReviewOutcome(cwd, [], ['other.ts'])).toEqual({ opened: 0, resolved: 0 });
25
+ expect(openFindingCount(cwd)).toBe(1);
26
+ });
27
+ it('does not count a finding that vanished without an edit as a fix', () => {
28
+ recordReviewOutcome(cwd, [finding('a.ts')], ['a.ts']);
29
+ expect(recordReviewOutcome(cwd, [], ['a.ts'])).toEqual({ opened: 0, resolved: 0 });
30
+ expect(listResolvedFixes(cwd)).toEqual([]);
31
+ });
32
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Which findings belong to a change.
3
+ *
4
+ * - findings: on a changed line; these decide PASS/FAIL.
5
+ * - fileFindings: about a changed file as a whole (no line); shown as context.
6
+ * - unlocated: no file at all; counted, never shown as a changed-line finding.
7
+ * - outside: on an unchanged line; counted.
8
+ *
9
+ * The CLI, the MCP tool and the GitHub integration all use this, so the same
10
+ * change gets the same verdict everywhere.
11
+ */
12
+ import type { Failure } from '../types/index.js';
13
+ export interface ChangedLineSplit {
14
+ findings: Failure[];
15
+ fileFindings: Failure[];
16
+ unlocated: number;
17
+ outside: number;
18
+ }
19
+ export declare function splitByChangedLines(failures: Failure[], changedLines: Record<string, Set<number>>): ChangedLineSplit;
@@ -0,0 +1,22 @@
1
+ export function splitByChangedLines(failures, changedLines) {
2
+ const split = { findings: [], fileFindings: [], unlocated: 0, outside: 0 };
3
+ for (const failure of failures) {
4
+ const files = failure.files ?? [];
5
+ if (files.length === 0) {
6
+ split.unlocated++;
7
+ }
8
+ else if (failure.line === undefined) {
9
+ if (files.some(file => changedLines[file]))
10
+ split.fileFindings.push(failure);
11
+ else
12
+ split.outside++;
13
+ }
14
+ else if (files.some(file => changedLines[file]?.has(failure.line))) {
15
+ split.findings.push(failure);
16
+ }
17
+ else {
18
+ split.outside++;
19
+ }
20
+ }
21
+ return split;
22
+ }
@@ -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
+ }
@@ -0,0 +1,34 @@
1
+ export interface AgentEvent {
2
+ type: string;
3
+ timestamp?: string;
4
+ tool?: string;
5
+ status?: string;
6
+ content?: Array<{
7
+ type: string;
8
+ text?: string;
9
+ }>;
10
+ session?: string;
11
+ blocked?: boolean;
12
+ /** Findings that blocked a stop. */
13
+ blocking?: number;
14
+ }
15
+ export interface ReviewEffectiveness {
16
+ toolCalls: Record<string, number>;
17
+ reviews: number;
18
+ failedReviews: number;
19
+ findingsReported: number;
20
+ findingsResolved: number;
21
+ stopChecks: number;
22
+ stopBlocks: number;
23
+ blockedStopsFollowedThrough: number;
24
+ stopsWithSelfReview: number;
25
+ }
26
+ export declare function readAgentEvents(cwd: string): AgentEvent[];
27
+ /** Append an event in the same format the MCP server writes; never throws. */
28
+ export declare function appendAgentEvent(cwd: string, event: AgentEvent): void;
29
+ /** `rule:file` keys, so a finding that moved lines is still the same finding. */
30
+ export declare function findingKeys(findings: Array<{
31
+ id?: string;
32
+ file?: string;
33
+ }>): string[];
34
+ export declare function computeEffectiveness(events: AgentEvent[]): ReviewEffectiveness;