@rigour-labs/core 6.4.3 → 6.5.0

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 (123) hide show
  1. package/dist/deep/agent-review.d.ts +19 -0
  2. package/dist/deep/agent-review.js +19 -0
  3. package/dist/deep/agent-review.test.d.ts +1 -0
  4. package/dist/deep/agent-review.test.js +53 -0
  5. package/dist/deep/code-pass.d.ts +10 -0
  6. package/dist/deep/code-pass.js +64 -33
  7. package/dist/deep/code-pass.test.js +53 -12
  8. package/dist/deep/code-verifier.d.ts +3 -1
  9. package/dist/deep/code-verifier.js +11 -11
  10. package/dist/deep/code-verifier.test.js +8 -0
  11. package/dist/deep/pr-diff.d.ts +7 -0
  12. package/dist/deep/pr-diff.js +47 -0
  13. package/dist/deep/pr-review.d.ts +33 -0
  14. package/dist/deep/pr-review.js +81 -0
  15. package/dist/deep/pr-review.test.d.ts +1 -0
  16. package/dist/deep/pr-review.test.js +80 -0
  17. package/dist/deep/related-changes.d.ts +13 -0
  18. package/dist/deep/related-changes.js +56 -0
  19. package/dist/deep/related-changes.test.d.ts +1 -0
  20. package/dist/deep/related-changes.test.js +33 -0
  21. package/dist/deep/review-tools.d.ts +16 -0
  22. package/dist/deep/review-tools.js +111 -0
  23. package/dist/deep/review-tools.test.d.ts +1 -0
  24. package/dist/deep/review-tools.test.js +39 -0
  25. package/dist/deep/risk.d.ts +41 -0
  26. package/dist/deep/risk.js +135 -0
  27. package/dist/deep/risk.test.d.ts +1 -0
  28. package/dist/deep/risk.test.js +63 -0
  29. package/dist/deep/router.d.ts +27 -0
  30. package/dist/deep/router.js +15 -0
  31. package/dist/deep/self-check.d.ts +14 -9
  32. package/dist/deep/self-check.js +41 -32
  33. package/dist/deep/tool-loop.d.ts +19 -0
  34. package/dist/deep/tool-loop.js +23 -0
  35. package/dist/gates/deep-analysis.d.ts +27 -0
  36. package/dist/gates/deep-analysis.js +73 -2
  37. package/dist/gates/deep-runner.js +14 -0
  38. package/dist/gates/frontend-secret-exposure.d.ts +2 -0
  39. package/dist/gates/frontend-secret-exposure.js +8 -0
  40. package/dist/gates/frontend-secret-exposure.test.js +14 -0
  41. package/dist/gates/hallucinated-imports/framework-modules-nuxt.test.d.ts +1 -0
  42. package/dist/gates/hallucinated-imports/framework-modules-nuxt.test.js +27 -0
  43. package/dist/gates/hallucinated-imports/framework-modules.d.ts +12 -3
  44. package/dist/gates/hallucinated-imports/framework-modules.js +32 -6
  45. package/dist/gates/hallucinated-imports/index.js +2 -1
  46. package/dist/gates/hallucinated-imports/js-resolver-types.test.d.ts +1 -0
  47. package/dist/gates/hallucinated-imports/js-resolver-types.test.js +14 -0
  48. package/dist/gates/hallucinated-imports/js-resolver.d.ts +6 -1
  49. package/dist/gates/hallucinated-imports/js-resolver.js +25 -3
  50. package/dist/gates/phantom-apis.js +2 -2
  51. package/dist/gates/phantom-apis.test.js +10 -0
  52. package/dist/gates/promise-safety.js +1 -1
  53. package/dist/gates/test-quality-matchers.js +3 -0
  54. package/dist/gates/test-quality.test.js +13 -0
  55. package/dist/hooks/checker.test.js +9 -0
  56. package/dist/hooks/stop-review.d.ts +2 -0
  57. package/dist/hooks/stop-review.js +19 -3
  58. package/dist/hooks/templates.d.ts +2 -0
  59. package/dist/hooks/templates.js +16 -2
  60. package/dist/hooks/templates.test.d.ts +1 -0
  61. package/dist/hooks/templates.test.js +16 -0
  62. package/dist/index.d.ts +13 -0
  63. package/dist/index.js +13 -0
  64. package/dist/inference/cloud-provider.d.ts +12 -1
  65. package/dist/inference/cloud-provider.js +137 -2
  66. package/dist/inference/cloud-provider.test.d.ts +1 -0
  67. package/dist/inference/cloud-provider.test.js +118 -0
  68. package/dist/inference/pricing.d.ts +15 -0
  69. package/dist/inference/pricing.js +13 -0
  70. package/dist/inference/types.d.ts +42 -0
  71. package/dist/review/changed-function-spans.d.ts +4 -0
  72. package/dist/review/changed-function-spans.js +48 -0
  73. package/dist/review/changed-lines.d.ts +9 -3
  74. package/dist/review/changed-lines.js +19 -2
  75. package/dist/review/deep-runs.d.ts +22 -0
  76. package/dist/review/deep-runs.js +59 -0
  77. package/dist/review/deep-runs.test.d.ts +1 -0
  78. package/dist/review/deep-runs.test.js +23 -0
  79. package/dist/review/generated-files.d.ts +3 -0
  80. package/dist/review/generated-files.js +35 -0
  81. package/dist/review/generated-files.test.d.ts +1 -0
  82. package/dist/review/generated-files.test.js +25 -0
  83. package/dist/review/ledger.d.ts +27 -0
  84. package/dist/review/ledger.js +72 -0
  85. package/dist/review/quiet.d.ts +14 -0
  86. package/dist/review/quiet.js +69 -0
  87. package/dist/review/quiet.test.d.ts +1 -0
  88. package/dist/review/quiet.test.js +27 -0
  89. package/dist/review/review-ack.d.ts +16 -0
  90. package/dist/review/review-ack.js +25 -0
  91. package/dist/review/review-task.d.ts +40 -0
  92. package/dist/review/review-task.js +50 -0
  93. package/dist/review/review-task.test.d.ts +1 -0
  94. package/dist/review/review-task.test.js +80 -0
  95. package/dist/review/review.d.ts +11 -1
  96. package/dist/review/review.js +15 -6
  97. package/dist/review/review.test.js +25 -0
  98. package/dist/review-learning/acted-on.d.ts +25 -0
  99. package/dist/review-learning/acted-on.js +69 -0
  100. package/dist/review-learning/learn-from-reviews.d.ts +34 -0
  101. package/dist/review-learning/learn-from-reviews.js +65 -0
  102. package/dist/review-learning/lessons.d.ts +52 -0
  103. package/dist/review-learning/lessons.js +149 -0
  104. package/dist/review-learning/repo-rules.d.ts +16 -0
  105. package/dist/review-learning/repo-rules.js +84 -0
  106. package/dist/review-learning/repo-rules.test.d.ts +1 -0
  107. package/dist/review-learning/repo-rules.test.js +39 -0
  108. package/dist/review-learning/review-learning.test.d.ts +1 -0
  109. package/dist/review-learning/review-learning.test.js +104 -0
  110. package/dist/review-learning/team-lessons.d.ts +4 -0
  111. package/dist/review-learning/team-lessons.js +24 -0
  112. package/dist/semantic/rules/credential-redirect.js +4 -1
  113. package/dist/services/context-telemetry-service.d.ts +9 -0
  114. package/dist/services/context-telemetry-service.js +4 -0
  115. package/dist/services/observed-savings.d.ts +36 -0
  116. package/dist/services/observed-savings.js +94 -0
  117. package/dist/services/observed-savings.test.d.ts +1 -0
  118. package/dist/services/observed-savings.test.js +37 -0
  119. package/dist/settings.js +4 -1
  120. package/dist/templates/universal-config.js +7 -3
  121. package/dist/types/index.d.ts +276 -10
  122. package/dist/types/index.js +38 -5
  123. package/package.json +6 -6
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Observed context savings: tokens an agent did not read because Rigour scoped its context.
3
+ *
4
+ * The estimate in context telemetry assumes the agent would otherwise read every
5
+ * scoped file whole, and does not read them afterwards anyway. Neither is
6
+ * measured. Here both sides are observed:
7
+ *
8
+ * .rigour/context-scopes.jsonl files rigour_context_scope summarised, with their tokens
9
+ * .rigour/agent-activity.jsonl every tool call the agent's hook saw (and the file it read)
10
+ *
11
+ * A scope is tracked when the hook recorded any agent activity after it, so
12
+ * "the agent read nothing" is told apart from "no hook is installed". For a
13
+ * tracked scope, the saving is the tokens of scoped files the agent did not
14
+ * read within the window, less the summary it read instead.
15
+ */
16
+ import fs from 'fs';
17
+ import path from 'path';
18
+ const SCOPES = 'context-scopes.jsonl';
19
+ const ACTIVITY = 'agent-activity.jsonl';
20
+ const MAX_LOG_BYTES = 2 * 1024 * 1024;
21
+ export const OBSERVATION_WINDOW_MS = 30 * 60 * 1000;
22
+ const READ_TOOLS = /^(?:read|read_file|readfile|view|open_file|cat)$/i;
23
+ export function recordScopeOffer(cwd, files, summaryTokens, ts = Date.now()) {
24
+ append(cwd, SCOPES, { ts, files: files.map(f => ({ path: normalize(cwd, f.path), tokens: f.tokens })), summaryTokens });
25
+ }
26
+ /** One tool call an agent hook saw. Read-like tools carry the file they read. */
27
+ export function recordAgentActivity(cwd, tool, filePath, ts = Date.now()) {
28
+ const entry = { ts, tool };
29
+ if (filePath && READ_TOOLS.test(tool))
30
+ entry.path = normalize(cwd, filePath);
31
+ append(cwd, ACTIVITY, entry);
32
+ }
33
+ /**
34
+ * A hook payload for one agent tool call (Claude Code PreToolUse sends tool_name,
35
+ * tool_input and cwd); anything else is ignored.
36
+ */
37
+ export function recordHookPayload(payload, fallbackCwd, ts = Date.now()) {
38
+ const event = payload;
39
+ if (!event || typeof event.tool_name !== 'string')
40
+ return;
41
+ const input = event.tool_input ?? {};
42
+ const file = [input.file_path, input.path, input.target_file].find((v) => typeof v === 'string');
43
+ recordAgentActivity(typeof event.cwd === 'string' ? event.cwd : fallbackCwd, event.tool_name, file, ts);
44
+ }
45
+ export function observedSavings(cwd, windowMs = OBSERVATION_WINDOW_MS) {
46
+ const scopes = readLog(cwd, SCOPES);
47
+ const activity = readLog(cwd, ACTIVITY).sort((a, b) => a.ts - b.ts);
48
+ const result = { scopes: scopes.length, trackedScopes: 0, offeredTokens: 0, readBackTokens: 0, avoidedTokens: 0 };
49
+ for (const scope of scopes) {
50
+ const after = activity.filter(a => a.ts >= scope.ts && a.ts <= scope.ts + windowMs);
51
+ if (after.length === 0)
52
+ continue;
53
+ result.trackedScopes++;
54
+ const read = new Set(after.filter(a => a.path).map(a => a.path));
55
+ const offered = scope.files.reduce((sum, f) => sum + f.tokens, 0);
56
+ const readBack = scope.files.filter(f => read.has(f.path)).reduce((sum, f) => sum + f.tokens, 0);
57
+ result.offeredTokens += offered;
58
+ result.readBackTokens += readBack;
59
+ result.avoidedTokens += Math.max(0, offered - readBack - scope.summaryTokens);
60
+ }
61
+ return result;
62
+ }
63
+ function normalize(cwd, file) {
64
+ return path.relative(cwd, path.resolve(cwd, file)).split(path.sep).join('/');
65
+ }
66
+ function append(cwd, name, entry) {
67
+ try {
68
+ const dir = path.join(cwd, '.rigour');
69
+ fs.mkdirSync(dir, { recursive: true });
70
+ const file = path.join(dir, name);
71
+ fs.appendFileSync(file, JSON.stringify(entry) + '\n');
72
+ if (fs.statSync(file).size > MAX_LOG_BYTES) {
73
+ const lines = fs.readFileSync(file, 'utf8').trimEnd().split('\n');
74
+ fs.writeFileSync(file, lines.slice(Math.floor(lines.length / 2)).join('\n') + '\n');
75
+ }
76
+ }
77
+ catch {
78
+ // Measurement must never break the agent's tool call.
79
+ }
80
+ }
81
+ function readLog(cwd, name) {
82
+ try {
83
+ return fs.readFileSync(path.join(cwd, '.rigour', name), 'utf8').split('\n').filter(Boolean)
84
+ .flatMap(line => { try {
85
+ return [JSON.parse(line)];
86
+ }
87
+ catch {
88
+ return [];
89
+ } });
90
+ }
91
+ catch {
92
+ return [];
93
+ }
94
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
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 { observedSavings, recordAgentActivity, recordHookPayload, recordScopeOffer } from './observed-savings.js';
6
+ let cwd;
7
+ beforeEach(() => { cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'observed-savings-')); });
8
+ afterEach(() => { fs.rmSync(cwd, { recursive: true, force: true }); });
9
+ const T = 1_000_000;
10
+ describe('observedSavings', () => {
11
+ it('counts only scoped files the agent did not read afterwards, less the summary', () => {
12
+ recordScopeOffer(cwd, [{ path: 'src/a.ts', tokens: 3000 }, { path: 'src/b.ts', tokens: 2000 }], 400, T);
13
+ recordAgentActivity(cwd, 'Read', path.join(cwd, 'src/a.ts'), T + 60_000); // read anyway
14
+ recordAgentActivity(cwd, 'Edit', path.join(cwd, 'src/b.ts'), T + 90_000); // an edit is not a read
15
+ expect(observedSavings(cwd)).toEqual({ scopes: 1, trackedScopes: 1, offeredTokens: 5000, readBackTokens: 3000, avoidedTokens: 1600 });
16
+ });
17
+ it('claims nothing for a scope no hook observed, so a missing hook never reads as a saving', () => {
18
+ recordScopeOffer(cwd, [{ path: 'src/a.ts', tokens: 3000 }], 400, T);
19
+ expect(observedSavings(cwd)).toMatchObject({ scopes: 1, trackedScopes: 0, avoidedTokens: 0 });
20
+ });
21
+ it('ignores activity outside the observation window', () => {
22
+ recordScopeOffer(cwd, [{ path: 'src/a.ts', tokens: 3000 }], 400, T);
23
+ recordAgentActivity(cwd, 'Read', 'src/a.ts', T + 2 * 60 * 60 * 1000);
24
+ expect(observedSavings(cwd).trackedScopes).toBe(0);
25
+ });
26
+ it('never claims more than was offered when the summary outweighs unread files', () => {
27
+ recordScopeOffer(cwd, [{ path: 'src/a.ts', tokens: 100 }], 400, T);
28
+ recordAgentActivity(cwd, 'Grep', undefined, T + 1000);
29
+ expect(observedSavings(cwd).avoidedTokens).toBe(0);
30
+ });
31
+ it('reads a Claude Code PreToolUse payload: the tool, the file it reads, and its working directory', () => {
32
+ recordScopeOffer(cwd, [{ path: 'src/a.ts', tokens: 3000 }], 400, T);
33
+ recordHookPayload({ tool_name: 'Read', tool_input: { file_path: path.join(cwd, 'src/a.ts') }, cwd }, '/elsewhere', T + 1000);
34
+ recordHookPayload({ hello: 'not a tool call' }, cwd, T + 2000);
35
+ expect(observedSavings(cwd)).toMatchObject({ trackedScopes: 1, readBackTokens: 3000, avoidedTokens: 0 });
36
+ });
37
+ });
package/dist/settings.js CHANGED
@@ -123,10 +123,13 @@ export function resolveDeepOptions(cliOptions) {
123
123
  result.modelName = cliOptions.modelName;
124
124
  }
125
125
  // 4. Resolve API key
126
- // CLI flag takes highest precedence
126
+ // CLI flag takes highest precedence, then RIGOUR_API_KEY (CI secrets: never on a command line)
127
127
  if (cliOptions.apiKey) {
128
128
  result.apiKey = cliOptions.apiKey;
129
129
  }
130
+ else if (process.env.RIGOUR_API_KEY?.trim()) {
131
+ result.apiKey = process.env.RIGOUR_API_KEY.trim();
132
+ }
130
133
  else if (settings.providers) {
131
134
  // Otherwise look up provider key in settings.providers
132
135
  const normalizedProvider = normalizeProviderName(selectedProvider);
@@ -154,7 +154,7 @@ export const UNIVERSAL_CONFIG = {
154
154
  promise_safety: {
155
155
  enabled: true,
156
156
  check_unhandled_then: true,
157
- check_unsafe_parse: true,
157
+ check_unsafe_parse: false,
158
158
  check_async_without_await: true,
159
159
  check_unsafe_fetch: true,
160
160
  ignore_patterns: [],
@@ -191,8 +191,8 @@ export const UNIVERSAL_CONFIG = {
191
191
  },
192
192
  governance: {
193
193
  enabled: true,
194
- enforce_memory: true,
195
- enforce_skills: true,
194
+ enforce_memory: false,
195
+ enforce_skills: false,
196
196
  block_native_memory: true,
197
197
  protected_memory_paths: [
198
198
  'CLAUDE.md', '.claude/CLAUDE.md',
@@ -275,11 +275,15 @@ export const UNIVERSAL_CONFIG = {
275
275
  fast_gates: ['hallucinated-imports', 'phantom-apis', 'deprecated-apis', 'promise-safety', 'security-patterns', 'file-size'],
276
276
  timeout_ms: 5000,
277
277
  block_on_failure: false,
278
+ require_review_ack: false,
278
279
  dlp: true,
279
280
  },
280
281
  output: {
281
282
  report_path: 'rigour-report.json',
282
283
  },
284
+ review: {
285
+ include_heuristics: false,
286
+ },
283
287
  planned: [],
284
288
  ignore: [],
285
289
  };