@rigour-labs/core 6.4.4 → 6.5.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 (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 +16 -0
  86. package/dist/review/quiet.js +71 -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 +12 -1
  96. package/dist/review/review.js +16 -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/settings.js +4 -1
  114. package/dist/telemetry/telemetry.d.ts +28 -0
  115. package/dist/telemetry/telemetry.js +152 -0
  116. package/dist/telemetry/telemetry.test.d.ts +1 -0
  117. package/dist/telemetry/telemetry.test.js +59 -0
  118. package/dist/telemetry/token.d.ts +1 -0
  119. package/dist/telemetry/token.js +1 -0
  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,152 @@
1
+ /**
2
+ * Anonymous, opt-in usage telemetry: how Rigour is used and where it is wrong,
3
+ * so the next release fixes what people actually hit. Every field is listed in
4
+ * TELEMETRY.md.
5
+ *
6
+ * Never sent: code, file or repository names, paths, git remotes, finding
7
+ * text, emails, keys, IP addresses (Mixpanel is told not to record them). The
8
+ * only identity is a random install id. Nothing is sent unless the person said
9
+ * yes, a token is built in (release builds only), and neither DO_NOT_TRACK nor
10
+ * RIGOUR_TELEMETRY=0 is set. CI never sends unless RIGOUR_TELEMETRY=1. A send
11
+ * that fails or is slow is dropped: telemetry never fails or slows a command.
12
+ *
13
+ * Two shapes: an event per CLI command or review (`trackUsage`), and, for the
14
+ * hot paths an agent drives on every edit (hooks, MCP tools), local counters
15
+ * (`countUsage`, no network) sent as one `daily_usage` event a day.
16
+ */
17
+ import crypto from 'crypto';
18
+ import fs from 'fs';
19
+ import os from 'os';
20
+ import path from 'path';
21
+ import { MIXPANEL_TOKEN } from './token.js';
22
+ const ENDPOINT = 'https://api.mixpanel.com/track?ip=0';
23
+ const TIMEOUT_MS = 2000;
24
+ const DAY_MS = 24 * 60 * 60 * 1000;
25
+ export function telemetryToken(env = process.env) {
26
+ return env.RIGOUR_MIXPANEL_TOKEN?.trim() || MIXPANEL_TOKEN;
27
+ }
28
+ function file(home, name) {
29
+ return path.join(home, '.rigour', name);
30
+ }
31
+ export function readTelemetryState(home = os.homedir()) {
32
+ try {
33
+ const parsed = JSON.parse(fs.readFileSync(file(home, 'telemetry.json'), 'utf8'));
34
+ if (typeof parsed?.installId === 'string')
35
+ return { installId: parsed.installId, enabled: typeof parsed.enabled === 'boolean' ? parsed.enabled : undefined };
36
+ }
37
+ catch {
38
+ // Not asked yet.
39
+ }
40
+ return { installId: crypto.randomUUID() };
41
+ }
42
+ export function setTelemetryEnabled(enabled, home = os.homedir()) {
43
+ const state = { ...readTelemetryState(home), enabled };
44
+ writeJson(file(home, 'telemetry.json'), state);
45
+ return state;
46
+ }
47
+ function isCi(env) {
48
+ return !!(env.CI || env.GITHUB_ACTIONS || env.BUILDKITE || env.GITLAB_CI || env.CIRCLECI);
49
+ }
50
+ /** Opted out by the environment, whatever the stored answer. */
51
+ function vetoed(env) {
52
+ return (!!env.DO_NOT_TRACK && env.DO_NOT_TRACK !== '0') || env.RIGOUR_TELEMETRY === '0' || !telemetryToken(env);
53
+ }
54
+ export function isTelemetryEnabled(deps = {}) {
55
+ const env = deps.env ?? process.env;
56
+ if (vetoed(env))
57
+ return false;
58
+ if (env.RIGOUR_TELEMETRY === '1')
59
+ return true;
60
+ if (isCi(env))
61
+ return false;
62
+ return readTelemetryState(deps.home).enabled === true;
63
+ }
64
+ /** Ask only once, only a person at a terminal, only when sending would be possible at all. */
65
+ export function shouldAskTelemetry(isTty, deps = {}) {
66
+ const env = deps.env ?? process.env;
67
+ return isTty && !isCi(env) && !vetoed(env) && env.RIGOUR_TELEMETRY === undefined && readTelemetryState(deps.home).enabled === undefined;
68
+ }
69
+ export async function trackUsage(event, properties, deps = {}) {
70
+ if (!isTelemetryEnabled(deps))
71
+ return;
72
+ const env = deps.env ?? process.env;
73
+ const body = [{
74
+ event,
75
+ properties: {
76
+ token: telemetryToken(env),
77
+ distinct_id: readTelemetryState(deps.home).installId,
78
+ time: Math.floor((deps.now ?? Date.now()) / 1000),
79
+ $insert_id: crypto.randomUUID(),
80
+ version: deps.version,
81
+ os: process.platform,
82
+ node_major: Number(process.versions.node.split('.')[0]),
83
+ ci: isCi(env),
84
+ ...properties,
85
+ },
86
+ }];
87
+ try {
88
+ await (deps.fetch ?? fetch)(ENDPOINT, {
89
+ method: 'POST',
90
+ headers: { 'Content-Type': 'application/json', Accept: 'text/plain' },
91
+ body: JSON.stringify(body),
92
+ signal: AbortSignal.timeout(TIMEOUT_MS),
93
+ });
94
+ }
95
+ catch {
96
+ // Dropped: telemetry never affects the command.
97
+ }
98
+ }
99
+ /** Count something an agent does on a hot path (a hook check, an MCP tool call). Local only; no network. */
100
+ export function countUsage(name, by = 1, deps = {}) {
101
+ if (!isTelemetryEnabled(deps))
102
+ return;
103
+ try {
104
+ const home = deps.home ?? os.homedir();
105
+ const counters = readCounters(home, deps.now ?? Date.now());
106
+ counters.counts[name] = (counters.counts[name] ?? 0) + by;
107
+ writeJson(file(home, 'telemetry-counters.json'), counters);
108
+ }
109
+ catch {
110
+ // Counting never affects the agent.
111
+ }
112
+ }
113
+ /** Send the day's counters as one `daily_usage` event once they are a day old, then start a new day. */
114
+ export async function flushDailyUsage(deps = {}) {
115
+ if (!isTelemetryEnabled(deps))
116
+ return false;
117
+ const home = deps.home ?? os.homedir();
118
+ const now = deps.now ?? Date.now();
119
+ const counters = readCounters(home, now);
120
+ if (now - counters.since < DAY_MS || Object.keys(counters.counts).length === 0)
121
+ return false;
122
+ writeJson(file(home, 'telemetry-counters.json'), { since: now, counts: {} });
123
+ await trackUsage('daily_usage', { ...counters.counts }, deps);
124
+ return true;
125
+ }
126
+ function readCounters(home, now) {
127
+ try {
128
+ const parsed = JSON.parse(fs.readFileSync(file(home, 'telemetry-counters.json'), 'utf8'));
129
+ if (typeof parsed?.since === 'number' && parsed.counts && typeof parsed.counts === 'object')
130
+ return parsed;
131
+ }
132
+ catch {
133
+ // First count.
134
+ }
135
+ return { since: now, counts: {} };
136
+ }
137
+ function writeJson(target, value) {
138
+ fs.mkdirSync(path.dirname(target), { recursive: true });
139
+ fs.writeFileSync(target, JSON.stringify(value, null, 2) + '\n');
140
+ }
141
+ /** A duration as a coarse bucket, so timing never identifies a run. */
142
+ export function durationBucket(ms) {
143
+ if (ms < 1000)
144
+ return '<1s';
145
+ if (ms < 5000)
146
+ return '1-5s';
147
+ if (ms < 30000)
148
+ return '5-30s';
149
+ if (ms < 120000)
150
+ return '30s-2m';
151
+ return '>2m';
152
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import { countUsage, durationBucket, flushDailyUsage, isTelemetryEnabled, readTelemetryState, setTelemetryEnabled, shouldAskTelemetry, trackUsage } from './telemetry.js';
6
+ let home;
7
+ const env = { RIGOUR_MIXPANEL_TOKEN: 'tok' };
8
+ beforeEach(() => { home = fs.mkdtempSync(path.join(os.tmpdir(), 'telemetry-')); });
9
+ afterEach(() => { fs.rmSync(home, { recursive: true, force: true }); });
10
+ describe('telemetry consent', () => {
11
+ it('sends nothing until the person says yes, and the environment always wins', () => {
12
+ expect(isTelemetryEnabled({ env, home })).toBe(false);
13
+ expect(shouldAskTelemetry(true, { env, home })).toBe(true);
14
+ setTelemetryEnabled(true, home);
15
+ expect(isTelemetryEnabled({ env, home })).toBe(true);
16
+ expect(shouldAskTelemetry(true, { env, home })).toBe(false); // asked once
17
+ expect(isTelemetryEnabled({ env: { ...env, DO_NOT_TRACK: '1' }, home })).toBe(false);
18
+ expect(isTelemetryEnabled({ env: { ...env, RIGOUR_TELEMETRY: '0' }, home })).toBe(false);
19
+ expect(isTelemetryEnabled({ env: { ...env, CI: 'true' }, home })).toBe(false);
20
+ expect(isTelemetryEnabled({ env: {}, home })).toBe(false); // a build without a token never sends
21
+ });
22
+ it('never asks in CI or without a terminal, and keeps one random install id', () => {
23
+ expect(shouldAskTelemetry(false, { env, home })).toBe(false);
24
+ expect(shouldAskTelemetry(true, { env: { ...env, GITHUB_ACTIONS: 'true' }, home })).toBe(false);
25
+ const first = setTelemetryEnabled(false, home).installId;
26
+ expect(readTelemetryState(home).installId).toBe(first);
27
+ expect(first).toMatch(/^[0-9a-f-]{36}$/);
28
+ });
29
+ });
30
+ describe('telemetry sending', () => {
31
+ it('sends one event with the install id and no IP, and swallows a failing network', async () => {
32
+ setTelemetryEnabled(true, home);
33
+ const fetch = vi.fn().mockResolvedValue({ ok: true });
34
+ await trackUsage('review_completed', { status: 'PASS', findings_by_gate: { 'semantic-bugs': 1 } }, { env, home, fetch, version: '6.5.0' });
35
+ const [url, init] = fetch.mock.calls[0];
36
+ expect(url).toBe('https://api.mixpanel.com/track?ip=0');
37
+ const [event] = JSON.parse(init.body);
38
+ expect(event.event).toBe('review_completed');
39
+ expect(event.properties).toMatchObject({ token: 'tok', distinct_id: readTelemetryState(home).installId, version: '6.5.0', status: 'PASS' });
40
+ expect(JSON.stringify(event)).not.toContain(home);
41
+ await expect(trackUsage('x', {}, { env, home, fetch: vi.fn().mockRejectedValue(new Error('offline')) })).resolves.toBeUndefined();
42
+ });
43
+ it('counts hot-path activity locally and sends it as one daily event', async () => {
44
+ setTelemetryEnabled(true, home);
45
+ const fetch = vi.fn().mockResolvedValue({ ok: true });
46
+ const day = 24 * 60 * 60 * 1000;
47
+ countUsage('hook_check', 1, { env, home, now: 0 });
48
+ countUsage('hook_check', 1, { env, home, now: 1000 });
49
+ countUsage('mcp:rigour_review', 1, { env, home, now: 2000 });
50
+ expect(await flushDailyUsage({ env, home, fetch, now: 5000 })).toBe(false); // not a day yet
51
+ expect(await flushDailyUsage({ env, home, fetch, now: day + 1 })).toBe(true);
52
+ const [event] = JSON.parse(fetch.mock.calls[0][1].body);
53
+ expect(event).toMatchObject({ event: 'daily_usage', properties: { hook_check: 2, 'mcp:rigour_review': 1 } });
54
+ expect(await flushDailyUsage({ env, home, fetch, now: 2 * day + 2 })).toBe(false); // counters reset
55
+ });
56
+ it('buckets durations so timing never identifies a run', () => {
57
+ expect([500, 3000, 20000, 60000, 600000].map(durationBucket)).toEqual(['<1s', '1-5s', '5-30s', '30s-2m', '>2m']);
58
+ });
59
+ });
@@ -0,0 +1 @@
1
+ export declare const MIXPANEL_TOKEN = "53ab28669fd4bf80e2dbe5605eabacc5";
@@ -0,0 +1 @@
1
+ export const MIXPANEL_TOKEN = '53ab28669fd4bf80e2dbe5605eabacc5';
@@ -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
  };