@rigour-labs/core 6.6.6 → 6.7.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.
- package/README.md +16 -92
- package/dist/environment.test.js +0 -4
- package/dist/gates/agent-team.d.ts +0 -2
- package/dist/gates/agent-team.js +0 -8
- package/dist/gates/checkpoint.d.ts +0 -1
- package/dist/gates/checkpoint.js +0 -1
- package/dist/gates/context.d.ts +0 -4
- package/dist/gates/context.js +7 -17
- package/dist/gates/deep-analysis.d.ts +0 -1
- package/dist/gates/deep-runner.js +0 -1
- package/dist/gates/frontend-secret-exposure.test.js +2 -2
- package/dist/gates/hallucinated-imports/index.d.ts +0 -2
- package/dist/gates/hallucinated-imports/index.js +2 -2
- package/dist/gates/hallucinated-imports/js-resolver.js +1 -0
- package/dist/gates/hallucinated-imports.test.js +10 -0
- package/dist/gates/runner.js +3 -4
- package/dist/gates/test-quality-matchers.d.ts +0 -4
- package/dist/gates/test-quality-matchers.js +0 -7
- package/dist/gates/test-quality.d.ts +0 -2
- package/dist/gates/test-quality.js +1 -10
- package/dist/hooks/checker.js +1 -1
- package/dist/hooks/index.d.ts +1 -2
- package/dist/hooks/index.js +0 -1
- package/dist/hooks/types.d.ts +0 -14
- package/dist/hooks/types.js +1 -15
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/inference/cloud-provider.d.ts +2 -0
- package/dist/inference/cloud-provider.js +16 -6
- package/dist/inference/cloud-provider.test.js +21 -0
- package/dist/pattern-index/embeddings.d.ts +0 -5
- package/dist/pattern-index/embeddings.js +11 -4
- package/dist/pattern-index/semantic-runtime.d.ts +10 -0
- package/dist/pattern-index/semantic-runtime.js +46 -0
- package/dist/pattern-index/semantic-runtime.test.d.ts +1 -0
- package/dist/pattern-index/semantic-runtime.test.js +31 -0
- package/dist/review/backtest-judges.d.ts +38 -0
- package/dist/review/backtest-judges.js +63 -0
- package/dist/review/backtest-judges.test.d.ts +1 -0
- package/dist/review/backtest-judges.test.js +32 -0
- package/dist/review/backtest.d.ts +9 -4
- package/dist/review/backtest.js +19 -9
- package/dist/review/backtest.test.js +2 -2
- package/dist/review/baseline.js +2 -1
- package/dist/review/dead-code.test.js +7 -0
- package/dist/review/fix-scope.d.ts +21 -0
- package/dist/review/fix-scope.js +94 -0
- package/dist/review/fix-scope.test.d.ts +1 -0
- package/dist/review/fix-scope.test.js +104 -0
- package/dist/review/lint-overlay.js +1 -1
- package/dist/review/orphan-files.js +3 -2
- package/dist/review/partial-wiring.js +5 -3
- package/dist/review/query-patterns.js +3 -2
- package/dist/review/review.js +2 -1
- package/dist/review/reviewer/adapters.d.ts +23 -7
- package/dist/review/reviewer/adapters.js +32 -7
- package/dist/review/reviewer/adapters.test.d.ts +1 -0
- package/dist/review/reviewer/adapters.test.js +23 -0
- package/dist/review/reviewer/background.d.ts +8 -1
- package/dist/review/reviewer/background.js +34 -9
- package/dist/review/reviewer/background.test.js +16 -2
- package/dist/review/reviewer/consensus.d.ts +36 -0
- package/dist/review/reviewer/consensus.js +144 -0
- package/dist/review/reviewer/context.d.ts +75 -0
- package/dist/review/reviewer/context.js +140 -0
- package/dist/review/reviewer/context.test.d.ts +1 -0
- package/dist/review/reviewer/context.test.js +18 -0
- package/dist/review/reviewer/exec.js +6 -2
- package/dist/review/reviewer/exec.test.d.ts +1 -0
- package/dist/review/reviewer/exec.test.js +23 -0
- package/dist/review/reviewer/inputs.d.ts +2 -0
- package/dist/review/reviewer/inputs.js +5 -1
- package/dist/review/reviewer/panel.d.ts +67 -0
- package/dist/review/reviewer/panel.js +101 -0
- package/dist/review/reviewer/panel.test.d.ts +1 -0
- package/dist/review/reviewer/panel.test.js +110 -0
- package/dist/review/reviewer/prompt.d.ts +11 -0
- package/dist/review/reviewer/prompt.js +26 -4
- package/dist/review/reviewer/settings.d.ts +58 -0
- package/dist/review/reviewer/settings.js +152 -0
- package/dist/review/reviewer/settings.test.d.ts +1 -0
- package/dist/review/reviewer/settings.test.js +57 -0
- package/dist/review/reviewer/store.d.ts +25 -2
- package/dist/review/reviewer/store.js +43 -1
- package/dist/review/reviewer/usage.d.ts +2 -0
- package/dist/review/reviewer/usage.js +28 -0
- package/dist/review/reviewer/usage.test.d.ts +1 -0
- package/dist/review/reviewer/usage.test.js +14 -0
- package/dist/review/reviewer/verdict.d.ts +15 -1
- package/dist/review/reviewer/verdict.js +17 -7
- package/dist/review/reviewer.d.ts +45 -2
- package/dist/review/reviewer.js +186 -25
- package/dist/review/reviewer.test.js +127 -12
- package/dist/review/test-files.d.ts +5 -1
- package/dist/review/test-files.js +6 -2
- package/dist/review/toolchain.js +10 -3
- package/dist/review/toolchain.test.js +6 -3
- package/dist/review/typed/program.d.ts +0 -1
- package/dist/review/typed/program.js +3 -3
- package/dist/review/typed/redundancy.js +43 -4
- package/dist/review/typed/redundancy.test.js +58 -0
- package/dist/review/typed/schema-nullability.d.ts +6 -0
- package/dist/review/typed/schema-nullability.js +218 -0
- package/dist/review/typed/schema-nullability.test.d.ts +1 -0
- package/dist/review/typed/schema-nullability.test.js +63 -0
- package/dist/review/unused-exports.js +5 -6
- package/dist/semantic/learn/types.d.ts +2 -2
- package/dist/services/adaptive-thresholds.d.ts +2 -52
- package/dist/services/adaptive-thresholds.js +2 -147
- package/dist/services/adaptive-thresholds.test.js +1 -81
- package/dist/settings.d.ts +14 -6
- package/dist/settings.js +0 -12
- package/dist/storage/team-diagnostics.js +1 -1
- package/dist/storage/team-schema.d.ts +1 -1
- package/dist/storage/team-schema.js +2 -0
- package/dist/storage/team-store.js +26 -3
- package/dist/storage/team-sync-scope.test.js +35 -2
- package/dist/telemetry/telemetry.d.ts +5 -0
- package/dist/telemetry/telemetry.js +11 -1
- package/dist/templates/paradigms.js +0 -8
- package/dist/templates/presets.d.ts +0 -1
- package/dist/templates/presets.js +0 -23
- package/dist/templates/universal-config.js +8 -45
- package/dist/types/index.d.ts +108 -566
- package/dist/types/index.js +41 -66
- package/dist/types/index.test.d.ts +1 -0
- package/dist/types/index.test.js +33 -0
- package/dist/utils/command-line.d.ts +8 -0
- package/dist/utils/command-line.js +8 -0
- package/dist/utils/command-line.test.d.ts +1 -0
- package/dist/utils/command-line.test.js +8 -0
- package/dist/utils/profile.js +1 -1
- package/package.json +6 -7
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Dynamically adjusts quality gate thresholds based on:
|
|
5
|
-
* - Project maturity (age, commit count, file count)
|
|
6
|
-
* - Historical failure rates with Z-score anomaly detection
|
|
7
|
-
* - Complexity tier (hobby/startup/enterprise)
|
|
8
|
-
* - Per-provenance trend analysis (ai-drift, structural, security separate)
|
|
9
|
-
*
|
|
10
|
-
* v2 upgrades:
|
|
11
|
-
* - Z-score replaces naive delta comparison for trend detection
|
|
12
|
-
* - Per-provenance failure tracking (AI drift vs structural vs security)
|
|
13
|
-
* - Statistical anomaly detection normalizes across project sizes
|
|
14
|
-
*
|
|
2
|
+
* Quality history: every gate run's failures by provenance, kept in .rigour/adaptive-history.json, and the
|
|
3
|
+
* trends read from it (Z-score of recent runs against the earlier baseline), overall and per provenance.
|
|
15
4
|
*/
|
|
16
5
|
import * as fs from 'fs';
|
|
17
6
|
import * as path from 'path';
|
|
@@ -182,123 +171,6 @@ export function getProvenanceTrends(cwd) {
|
|
|
182
171
|
securityZScore: security.z,
|
|
183
172
|
};
|
|
184
173
|
}
|
|
185
|
-
/**
|
|
186
|
-
* Detect project complexity tier based on metrics
|
|
187
|
-
*/
|
|
188
|
-
export function detectComplexityTier(metrics) {
|
|
189
|
-
// Enterprise: Large teams, many files, mature codebase
|
|
190
|
-
if (metrics.fileCount > 500 || (metrics.commitCount && metrics.commitCount > 1000)) {
|
|
191
|
-
return 'enterprise';
|
|
192
|
-
}
|
|
193
|
-
// Startup: Growing codebase, active development
|
|
194
|
-
if (metrics.fileCount > 50 || (metrics.commitCount && metrics.commitCount > 100)) {
|
|
195
|
-
return 'startup';
|
|
196
|
-
}
|
|
197
|
-
// Hobby: Small projects, early stage
|
|
198
|
-
return 'hobby';
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Calculate adaptive thresholds based on project state.
|
|
202
|
-
* v5: Uses Z-score trending and per-provenance analysis.
|
|
203
|
-
*/
|
|
204
|
-
export function calculateAdaptiveThresholds(cwd, metrics, config = {}) {
|
|
205
|
-
const reasoning = [];
|
|
206
|
-
// Determine tier
|
|
207
|
-
const tier = config.forced_tier ??
|
|
208
|
-
(config.auto_detect_tier !== false ? detectComplexityTier(metrics) : 'startup');
|
|
209
|
-
reasoning.push(`Complexity tier: ${tier} (files: ${metrics.fileCount})`);
|
|
210
|
-
// Get overall trend (Z-score based)
|
|
211
|
-
const trend = getQualityTrend(cwd);
|
|
212
|
-
reasoning.push(`Quality trend: ${trend} (Z-score analysis)`);
|
|
213
|
-
// Get per-provenance trends
|
|
214
|
-
const provenanceTrends = getProvenanceTrends(cwd);
|
|
215
|
-
if (provenanceTrends.aiDrift !== 'stable') {
|
|
216
|
-
reasoning.push(`AI drift trend: ${provenanceTrends.aiDrift} (Z=${provenanceTrends.aiDriftZScore})`);
|
|
217
|
-
}
|
|
218
|
-
if (provenanceTrends.structural !== 'stable') {
|
|
219
|
-
reasoning.push(`Structural trend: ${provenanceTrends.structural} (Z=${provenanceTrends.structuralZScore})`);
|
|
220
|
-
}
|
|
221
|
-
if (provenanceTrends.security !== 'stable') {
|
|
222
|
-
reasoning.push(`Security trend: ${provenanceTrends.security} (Z=${provenanceTrends.securityZScore})`);
|
|
223
|
-
}
|
|
224
|
-
// Base thresholds
|
|
225
|
-
let coverageThreshold = config.base_coverage_threshold ?? 80;
|
|
226
|
-
let qualityThreshold = config.base_quality_threshold ?? 80;
|
|
227
|
-
let securityBlockLevel = 'high';
|
|
228
|
-
let leniencyFactor = 0.5;
|
|
229
|
-
// Adjust by tier
|
|
230
|
-
switch (tier) {
|
|
231
|
-
case 'hobby':
|
|
232
|
-
coverageThreshold = Math.max(50, coverageThreshold - 30);
|
|
233
|
-
qualityThreshold = Math.max(60, qualityThreshold - 20);
|
|
234
|
-
securityBlockLevel = 'critical';
|
|
235
|
-
leniencyFactor = 0.8;
|
|
236
|
-
reasoning.push('Hobby tier: relaxed thresholds, only critical security blocks');
|
|
237
|
-
break;
|
|
238
|
-
case 'startup':
|
|
239
|
-
coverageThreshold = Math.max(60, coverageThreshold - 15);
|
|
240
|
-
qualityThreshold = Math.max(70, qualityThreshold - 10);
|
|
241
|
-
securityBlockLevel = 'high';
|
|
242
|
-
leniencyFactor = 0.5;
|
|
243
|
-
reasoning.push('Startup tier: moderate thresholds, high+ security blocks');
|
|
244
|
-
break;
|
|
245
|
-
case 'enterprise':
|
|
246
|
-
coverageThreshold = coverageThreshold;
|
|
247
|
-
qualityThreshold = qualityThreshold;
|
|
248
|
-
securityBlockLevel = 'medium';
|
|
249
|
-
leniencyFactor = 0.2;
|
|
250
|
-
reasoning.push('Enterprise tier: strict thresholds, medium+ security blocks');
|
|
251
|
-
break;
|
|
252
|
-
}
|
|
253
|
-
// Adjust by overall trend
|
|
254
|
-
if (trend === 'improving') {
|
|
255
|
-
coverageThreshold = Math.max(50, coverageThreshold - 5);
|
|
256
|
-
qualityThreshold = Math.max(60, qualityThreshold - 5);
|
|
257
|
-
leniencyFactor = Math.min(1, leniencyFactor + 0.1);
|
|
258
|
-
reasoning.push('Improving trend: bonus threshold relaxation (+5%)');
|
|
259
|
-
}
|
|
260
|
-
else if (trend === 'degrading') {
|
|
261
|
-
coverageThreshold = Math.min(95, coverageThreshold + 5);
|
|
262
|
-
qualityThreshold = Math.min(95, qualityThreshold + 5);
|
|
263
|
-
leniencyFactor = Math.max(0, leniencyFactor - 0.1);
|
|
264
|
-
reasoning.push('Degrading trend: tightened thresholds (-5%)');
|
|
265
|
-
}
|
|
266
|
-
// v5: Per-provenance adjustments
|
|
267
|
-
// If AI drift is degrading but structural is stable, tighten AI-specific gates
|
|
268
|
-
if (provenanceTrends.aiDrift === 'degrading' && provenanceTrends.structural !== 'degrading') {
|
|
269
|
-
leniencyFactor = Math.max(0, leniencyFactor - 0.15);
|
|
270
|
-
reasoning.push('AI drift degrading while structural stable: AI is the problem, tightening AI gates');
|
|
271
|
-
}
|
|
272
|
-
// If security is degrading, escalate security block level
|
|
273
|
-
if (provenanceTrends.security === 'degrading') {
|
|
274
|
-
if (securityBlockLevel === 'critical')
|
|
275
|
-
securityBlockLevel = 'high';
|
|
276
|
-
else if (securityBlockLevel === 'high')
|
|
277
|
-
securityBlockLevel = 'medium';
|
|
278
|
-
reasoning.push(`Security trend degrading: escalated block level to ${securityBlockLevel}+`);
|
|
279
|
-
}
|
|
280
|
-
// Recent failure rate adjustment
|
|
281
|
-
if (metrics.recentFailureRate !== undefined) {
|
|
282
|
-
if (metrics.recentFailureRate > 50) {
|
|
283
|
-
leniencyFactor = Math.min(1, leniencyFactor + 0.2);
|
|
284
|
-
reasoning.push(`High failure rate (${metrics.recentFailureRate.toFixed(0)}%): increased leniency`);
|
|
285
|
-
}
|
|
286
|
-
else if (metrics.recentFailureRate < 10) {
|
|
287
|
-
leniencyFactor = Math.max(0, leniencyFactor - 0.1);
|
|
288
|
-
reasoning.push(`Low failure rate (${metrics.recentFailureRate.toFixed(0)}%): stricter enforcement`);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return {
|
|
292
|
-
tier,
|
|
293
|
-
trend,
|
|
294
|
-
coverageThreshold: Math.round(coverageThreshold),
|
|
295
|
-
qualityThreshold: Math.round(qualityThreshold),
|
|
296
|
-
securityBlockLevel,
|
|
297
|
-
leniencyFactor: Math.round(leniencyFactor * 100) / 100,
|
|
298
|
-
reasoning,
|
|
299
|
-
provenanceTrends,
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
174
|
/**
|
|
303
175
|
* Clear adaptive history (for testing)
|
|
304
176
|
*/
|
|
@@ -309,20 +181,3 @@ export function clearAdaptiveHistory(cwd) {
|
|
|
309
181
|
fs.unlinkSync(historyPath);
|
|
310
182
|
}
|
|
311
183
|
}
|
|
312
|
-
/**
|
|
313
|
-
* Get summary of adaptive thresholds for logging
|
|
314
|
-
*/
|
|
315
|
-
export function getAdaptiveSummary(adjustments) {
|
|
316
|
-
let summary = `[${adjustments.tier.toUpperCase()}] ` +
|
|
317
|
-
`Coverage: ${adjustments.coverageThreshold}%, ` +
|
|
318
|
-
`Quality: ${adjustments.qualityThreshold}%, ` +
|
|
319
|
-
`Security: ${adjustments.securityBlockLevel}+, ` +
|
|
320
|
-
`Trend: ${adjustments.trend}`;
|
|
321
|
-
if (adjustments.provenanceTrends) {
|
|
322
|
-
const pt = adjustments.provenanceTrends;
|
|
323
|
-
if (pt.aiDrift !== 'stable' || pt.structural !== 'stable' || pt.security !== 'stable') {
|
|
324
|
-
summary += ` | AI:${pt.aiDrift}(Z=${pt.aiDriftZScore}) Struct:${pt.structural}(Z=${pt.structuralZScore}) Sec:${pt.security}(Z=${pt.securityZScore})`;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return summary;
|
|
328
|
-
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { recordGateRun, getQualityTrend, clearAdaptiveHistory, } from './adaptive-thresholds.js';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import * as os from 'os';
|
|
@@ -12,49 +12,6 @@ describe('AdaptiveThresholds', () => {
|
|
|
12
12
|
clearAdaptiveHistory(testDir);
|
|
13
13
|
fs.rmSync(testDir, { recursive: true, force: true });
|
|
14
14
|
});
|
|
15
|
-
describe('detectComplexityTier', () => {
|
|
16
|
-
it('should detect hobby tier for small projects', () => {
|
|
17
|
-
const tier = detectComplexityTier({ fileCount: 20 });
|
|
18
|
-
expect(tier).toBe('hobby');
|
|
19
|
-
});
|
|
20
|
-
it('should detect startup tier for medium projects', () => {
|
|
21
|
-
const tier = detectComplexityTier({ fileCount: 100 });
|
|
22
|
-
expect(tier).toBe('startup');
|
|
23
|
-
});
|
|
24
|
-
it('should detect enterprise tier for large projects', () => {
|
|
25
|
-
const tier = detectComplexityTier({ fileCount: 600 });
|
|
26
|
-
expect(tier).toBe('enterprise');
|
|
27
|
-
});
|
|
28
|
-
it('should consider commit count for tier detection', () => {
|
|
29
|
-
const tier = detectComplexityTier({ fileCount: 50, commitCount: 1500 });
|
|
30
|
-
expect(tier).toBe('enterprise');
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
describe('calculateAdaptiveThresholds', () => {
|
|
34
|
-
it('should return lenient thresholds for hobby tier', () => {
|
|
35
|
-
const adjustments = calculateAdaptiveThresholds(testDir, { fileCount: 20 });
|
|
36
|
-
expect(adjustments.tier).toBe('hobby');
|
|
37
|
-
expect(adjustments.coverageThreshold).toBeLessThan(80);
|
|
38
|
-
expect(adjustments.securityBlockLevel).toBe('critical');
|
|
39
|
-
expect(adjustments.leniencyFactor).toBeGreaterThan(0.5);
|
|
40
|
-
});
|
|
41
|
-
it('should return strict thresholds for enterprise tier', () => {
|
|
42
|
-
const adjustments = calculateAdaptiveThresholds(testDir, { fileCount: 600 });
|
|
43
|
-
expect(adjustments.tier).toBe('enterprise');
|
|
44
|
-
expect(adjustments.coverageThreshold).toBe(80);
|
|
45
|
-
expect(adjustments.securityBlockLevel).toBe('medium');
|
|
46
|
-
expect(adjustments.leniencyFactor).toBeLessThan(0.5);
|
|
47
|
-
});
|
|
48
|
-
it('should respect forced_tier config', () => {
|
|
49
|
-
const adjustments = calculateAdaptiveThresholds(testDir, { fileCount: 20 }, { forced_tier: 'enterprise' });
|
|
50
|
-
expect(adjustments.tier).toBe('enterprise');
|
|
51
|
-
});
|
|
52
|
-
it('should include reasoning for adjustments', () => {
|
|
53
|
-
const adjustments = calculateAdaptiveThresholds(testDir, { fileCount: 100 });
|
|
54
|
-
expect(adjustments.reasoning.length).toBeGreaterThan(0);
|
|
55
|
-
expect(adjustments.reasoning.some(r => r.includes('tier'))).toBe(true);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
15
|
describe('historical tracking', () => {
|
|
59
16
|
it('should record gate runs', () => {
|
|
60
17
|
recordGateRun(testDir, 5, 2, 10);
|
|
@@ -93,41 +50,4 @@ describe('AdaptiveThresholds', () => {
|
|
|
93
50
|
expect(trend).toBe('degrading');
|
|
94
51
|
});
|
|
95
52
|
});
|
|
96
|
-
describe('trend-based adjustments', () => {
|
|
97
|
-
it('should relax thresholds for improving trend', () => {
|
|
98
|
-
// Baseline: 15 runs with high failures (with variance)
|
|
99
|
-
for (let i = 0; i < 15; i++) {
|
|
100
|
-
recordGateRun(testDir, 3, 5, 15 + (i % 5) * 3);
|
|
101
|
-
}
|
|
102
|
-
// Recent: 5 runs with very low failures
|
|
103
|
-
for (let i = 0; i < 5; i++) {
|
|
104
|
-
recordGateRun(testDir, 7, 0, 1);
|
|
105
|
-
}
|
|
106
|
-
const adjustments = calculateAdaptiveThresholds(testDir, { fileCount: 100 });
|
|
107
|
-
expect(adjustments.trend).toBe('improving');
|
|
108
|
-
expect(adjustments.reasoning.some(r => r.includes('bonus'))).toBe(true);
|
|
109
|
-
});
|
|
110
|
-
it('should tighten thresholds for degrading trend', () => {
|
|
111
|
-
// Baseline: 15 runs with low failures (with variance)
|
|
112
|
-
for (let i = 0; i < 15; i++) {
|
|
113
|
-
recordGateRun(testDir, 7, 1, 1 + (i % 3));
|
|
114
|
-
}
|
|
115
|
-
// Recent: 5 runs with high failures
|
|
116
|
-
for (let i = 0; i < 5; i++) {
|
|
117
|
-
recordGateRun(testDir, 3, 5, 25);
|
|
118
|
-
}
|
|
119
|
-
const adjustments = calculateAdaptiveThresholds(testDir, { fileCount: 100 });
|
|
120
|
-
expect(adjustments.trend).toBe('degrading');
|
|
121
|
-
expect(adjustments.reasoning.some(r => r.includes('tightened'))).toBe(true);
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
describe('getAdaptiveSummary', () => {
|
|
125
|
-
it('should return formatted summary string', () => {
|
|
126
|
-
const adjustments = calculateAdaptiveThresholds(testDir, { fileCount: 100 });
|
|
127
|
-
const summary = getAdaptiveSummary(adjustments);
|
|
128
|
-
expect(summary).toContain('STARTUP');
|
|
129
|
-
expect(summary).toContain('Coverage:');
|
|
130
|
-
expect(summary).toContain('Quality:');
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
53
|
});
|
package/dist/settings.d.ts
CHANGED
|
@@ -18,8 +18,6 @@ export interface RigourSettings {
|
|
|
18
18
|
defaultProvider?: string;
|
|
19
19
|
defaultModel?: string;
|
|
20
20
|
apiBaseUrl?: string;
|
|
21
|
-
maxTokens?: number;
|
|
22
|
-
temperature?: number;
|
|
23
21
|
};
|
|
24
22
|
agents?: {
|
|
25
23
|
[agentName: string]: {
|
|
@@ -33,10 +31,24 @@ export interface RigourSettings {
|
|
|
33
31
|
colorOutput?: boolean;
|
|
34
32
|
verboseOutput?: boolean;
|
|
35
33
|
};
|
|
34
|
+
reviewer?: UserReviewerSettings;
|
|
36
35
|
cursor?: {
|
|
37
36
|
apiKey?: string;
|
|
38
37
|
};
|
|
39
38
|
}
|
|
39
|
+
/** What a person may set for their own reviewer runs. They cannot set a team floor (`required`). */
|
|
40
|
+
export interface UserReviewerSettings {
|
|
41
|
+
enabled?: boolean;
|
|
42
|
+
mode?: 'single' | 'cross' | 'full';
|
|
43
|
+
panel?: boolean;
|
|
44
|
+
judges?: 2 | 3;
|
|
45
|
+
escalate?: 'always' | 'risk';
|
|
46
|
+
/** A lower cap for your own runs; never above the team's. */
|
|
47
|
+
max_runs_per_day?: number;
|
|
48
|
+
max_usd_per_day?: number;
|
|
49
|
+
reviewers?: string[];
|
|
50
|
+
models?: Record<string, string>;
|
|
51
|
+
}
|
|
40
52
|
/**
|
|
41
53
|
* Resolved deep options from CLI flags merged with settings.json
|
|
42
54
|
*/
|
|
@@ -45,8 +57,6 @@ export interface ResolvedDeepOptions {
|
|
|
45
57
|
provider?: string;
|
|
46
58
|
apiBaseUrl?: string;
|
|
47
59
|
modelName?: string;
|
|
48
|
-
maxTokens?: number;
|
|
49
|
-
temperature?: number;
|
|
50
60
|
}
|
|
51
61
|
/**
|
|
52
62
|
* CLI options that may override settings
|
|
@@ -56,8 +66,6 @@ export interface CLIDeepOptions {
|
|
|
56
66
|
provider?: string;
|
|
57
67
|
apiBaseUrl?: string;
|
|
58
68
|
modelName?: string;
|
|
59
|
-
maxTokens?: number;
|
|
60
|
-
temperature?: number;
|
|
61
69
|
}
|
|
62
70
|
/**
|
|
63
71
|
* Get the settings file path: ~/.rigour/settings.json
|
package/dist/settings.js
CHANGED
|
@@ -102,12 +102,6 @@ export function resolveDeepOptions(cliOptions) {
|
|
|
102
102
|
if (settings.deep?.apiBaseUrl) {
|
|
103
103
|
result.apiBaseUrl = settings.deep.apiBaseUrl;
|
|
104
104
|
}
|
|
105
|
-
if (settings.deep?.maxTokens) {
|
|
106
|
-
result.maxTokens = settings.deep.maxTokens;
|
|
107
|
-
}
|
|
108
|
-
if (settings.deep?.temperature) {
|
|
109
|
-
result.temperature = settings.deep.temperature;
|
|
110
|
-
}
|
|
111
105
|
// 2. Apply provider selection (settings or default)
|
|
112
106
|
let selectedProvider = settings.deep?.defaultProvider || 'anthropic';
|
|
113
107
|
if (cliOptions.provider) {
|
|
@@ -141,12 +135,6 @@ export function resolveDeepOptions(cliOptions) {
|
|
|
141
135
|
if (cliOptions.apiBaseUrl) {
|
|
142
136
|
result.apiBaseUrl = cliOptions.apiBaseUrl;
|
|
143
137
|
}
|
|
144
|
-
if (cliOptions.maxTokens) {
|
|
145
|
-
result.maxTokens = cliOptions.maxTokens;
|
|
146
|
-
}
|
|
147
|
-
if (cliOptions.temperature) {
|
|
148
|
-
result.temperature = cliOptions.temperature;
|
|
149
|
-
}
|
|
150
138
|
return result;
|
|
151
139
|
}
|
|
152
140
|
/**
|
|
@@ -54,5 +54,5 @@ export async function diagnoseMissingMembership(pool) {
|
|
|
54
54
|
+ `cannot see its membership. Run \`rigour team init-schema\` with the administrator URL to add the read policies.`;
|
|
55
55
|
}
|
|
56
56
|
return 'This database role has no membership for the configured organization, team and actor. '
|
|
57
|
-
+ 'Ask the administrator to add it (see docs/
|
|
57
|
+
+ 'Ask the administrator to add it (see docs/TEAM_DATABASE.md).';
|
|
58
58
|
}
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* (`rigour team init-schema`) adds anything missing, including the RLS
|
|
4
4
|
* policies, without touching data.
|
|
5
5
|
*/
|
|
6
|
-
export declare const TEAM_SCHEMA = "\nCREATE SCHEMA IF NOT EXISTS rigour;\nCREATE TABLE IF NOT EXISTS rigour.meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS rigour.memberships (\n db_role NAME NOT NULL,\n organization_id TEXT NOT NULL,\n team_id TEXT NOT NULL,\n actor_id TEXT NOT NULL,\n role TEXT NOT NULL CHECK (role IN ('member', 'sme', 'owner')),\n PRIMARY KEY (db_role, team_id)\n);\nCREATE TABLE IF NOT EXISTS rigour.lessons (\n id TEXT PRIMARY KEY,\n organization_id TEXT NOT NULL,\n team_id TEXT NOT NULL,\n repository_id TEXT NOT NULL,\n actor_id TEXT NOT NULL,\n visibility TEXT NOT NULL CHECK (visibility IN ('personal', 'team')),\n state TEXT NOT NULL CHECK (state IN ('candidate', 'validated', 'promoted', 'rejected', 'superseded')),\n kind TEXT NOT NULL,\n subject TEXT NOT NULL,\n evidence_json JSONB NOT NULL,\n confidence DOUBLE PRECISION NOT NULL,\n source TEXT NOT NULL,\n supersedes_id TEXT,\n created_at BIGINT NOT NULL,\n updated_at BIGINT NOT NULL\n);\nALTER TABLE rigour.lessons ENABLE ROW LEVEL SECURITY;\nDROP POLICY IF EXISTS lessons_read ON rigour.lessons;\nCREATE POLICY lessons_read ON rigour.lessons FOR SELECT USING (\n EXISTS (\n SELECT 1 FROM rigour.memberships membership\n WHERE membership.db_role = current_user\n AND membership.organization_id = lessons.organization_id\n AND membership.team_id = lessons.team_id\n AND ((lessons.visibility = 'team' AND lessons.state = 'promoted')\n OR membership.actor_id = lessons.actor_id)\n )\n);\nDROP POLICY IF EXISTS lessons_write ON rigour.lessons;\nCREATE POLICY lessons_write ON rigour.lessons FOR ALL USING (\n EXISTS (\n SELECT 1 FROM rigour.memberships membership\n WHERE membership.db_role = current_user\n AND membership.organization_id = lessons.organization_id\n AND membership.team_id = lessons.team_id\n AND membership.actor_id = lessons.actor_id\n AND (lessons.visibility = 'personal' OR membership.role IN ('sme', 'owner'))\n )\n) WITH CHECK (\n EXISTS (\n SELECT 1 FROM rigour.memberships membership\n WHERE membership.db_role = current_user\n AND membership.organization_id = lessons.organization_id\n AND membership.team_id = lessons.team_id\n AND membership.actor_id = lessons.actor_id\n AND (lessons.visibility = 'personal' OR membership.role IN ('sme', 'owner'))\n )\n);\n-- Row-level security on every team table. A host (for example Supabase's\n-- security advisor) may enable RLS on its own; without a policy each member\n-- login would then see no rows at all. Members read only their own\n-- membership rows (the lessons policies read memberships the same way), and\n-- meta holds only the schema version.\nALTER TABLE rigour.memberships ENABLE ROW LEVEL SECURITY;\nDROP POLICY IF EXISTS memberships_read_own ON rigour.memberships;\nCREATE POLICY memberships_read_own ON rigour.memberships FOR SELECT USING (db_role = current_user);\nALTER TABLE rigour.meta ENABLE ROW LEVEL SECURITY;\nDROP POLICY IF EXISTS meta_read ON rigour.meta;\nCREATE POLICY meta_read ON rigour.meta FOR SELECT USING (true);\nINSERT INTO rigour.meta (key, value) VALUES ('schema_version', '1')\nON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;\n";
|
|
6
|
+
export declare const TEAM_SCHEMA = "\nCREATE SCHEMA IF NOT EXISTS rigour;\nCREATE TABLE IF NOT EXISTS rigour.meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS rigour.memberships (\n db_role NAME NOT NULL,\n organization_id TEXT NOT NULL,\n team_id TEXT NOT NULL,\n actor_id TEXT NOT NULL,\n role TEXT NOT NULL CHECK (role IN ('member', 'sme', 'owner')),\n PRIMARY KEY (db_role, team_id)\n);\nCREATE TABLE IF NOT EXISTS rigour.lessons (\n id TEXT PRIMARY KEY,\n organization_id TEXT NOT NULL,\n team_id TEXT NOT NULL,\n repository_id TEXT NOT NULL,\n actor_id TEXT NOT NULL,\n visibility TEXT NOT NULL CHECK (visibility IN ('personal', 'team')),\n state TEXT NOT NULL CHECK (state IN ('candidate', 'validated', 'promoted', 'rejected', 'superseded')),\n kind TEXT NOT NULL,\n subject TEXT NOT NULL,\n evidence_json JSONB NOT NULL,\n confidence DOUBLE PRECISION NOT NULL,\n source TEXT NOT NULL,\n supersedes_id TEXT,\n created_at BIGINT NOT NULL,\n updated_at BIGINT NOT NULL\n);\n-- Every sync reads a team's lessons changed since its last read.\nCREATE INDEX IF NOT EXISTS lessons_team_updated ON rigour.lessons (organization_id, team_id, updated_at);\nALTER TABLE rigour.lessons ENABLE ROW LEVEL SECURITY;\nDROP POLICY IF EXISTS lessons_read ON rigour.lessons;\nCREATE POLICY lessons_read ON rigour.lessons FOR SELECT USING (\n EXISTS (\n SELECT 1 FROM rigour.memberships membership\n WHERE membership.db_role = current_user\n AND membership.organization_id = lessons.organization_id\n AND membership.team_id = lessons.team_id\n AND ((lessons.visibility = 'team' AND lessons.state = 'promoted')\n OR membership.actor_id = lessons.actor_id)\n )\n);\nDROP POLICY IF EXISTS lessons_write ON rigour.lessons;\nCREATE POLICY lessons_write ON rigour.lessons FOR ALL USING (\n EXISTS (\n SELECT 1 FROM rigour.memberships membership\n WHERE membership.db_role = current_user\n AND membership.organization_id = lessons.organization_id\n AND membership.team_id = lessons.team_id\n AND membership.actor_id = lessons.actor_id\n AND (lessons.visibility = 'personal' OR membership.role IN ('sme', 'owner'))\n )\n) WITH CHECK (\n EXISTS (\n SELECT 1 FROM rigour.memberships membership\n WHERE membership.db_role = current_user\n AND membership.organization_id = lessons.organization_id\n AND membership.team_id = lessons.team_id\n AND membership.actor_id = lessons.actor_id\n AND (lessons.visibility = 'personal' OR membership.role IN ('sme', 'owner'))\n )\n);\n-- Row-level security on every team table. A host (for example Supabase's\n-- security advisor) may enable RLS on its own; without a policy each member\n-- login would then see no rows at all. Members read only their own\n-- membership rows (the lessons policies read memberships the same way), and\n-- meta holds only the schema version.\nALTER TABLE rigour.memberships ENABLE ROW LEVEL SECURITY;\nDROP POLICY IF EXISTS memberships_read_own ON rigour.memberships;\nCREATE POLICY memberships_read_own ON rigour.memberships FOR SELECT USING (db_role = current_user);\nALTER TABLE rigour.meta ENABLE ROW LEVEL SECURITY;\nDROP POLICY IF EXISTS meta_read ON rigour.meta;\nCREATE POLICY meta_read ON rigour.meta FOR SELECT USING (true);\nINSERT INTO rigour.meta (key, value) VALUES ('schema_version', '1')\nON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;\n";
|
|
@@ -34,6 +34,8 @@ CREATE TABLE IF NOT EXISTS rigour.lessons (
|
|
|
34
34
|
created_at BIGINT NOT NULL,
|
|
35
35
|
updated_at BIGINT NOT NULL
|
|
36
36
|
);
|
|
37
|
+
-- Every sync reads a team's lessons changed since its last read.
|
|
38
|
+
CREATE INDEX IF NOT EXISTS lessons_team_updated ON rigour.lessons (organization_id, team_id, updated_at);
|
|
37
39
|
ALTER TABLE rigour.lessons ENABLE ROW LEVEL SECURITY;
|
|
38
40
|
DROP POLICY IF EXISTS lessons_read ON rigour.lessons;
|
|
39
41
|
CREATE POLICY lessons_read ON rigour.lessons FOR SELECT USING (
|
|
@@ -208,6 +208,13 @@ async function splitOutbox(db, queued, config) {
|
|
|
208
208
|
}
|
|
209
209
|
return { sendable, held };
|
|
210
210
|
}
|
|
211
|
+
/** How far before the last pull a sync reads again, for members whose clocks run behind. */
|
|
212
|
+
const PULL_OVERLAP_MS = 15 * 60_000;
|
|
213
|
+
/** Postgres refused the row itself: insufficient privilege (row-level security) or an integrity constraint (class 23). */
|
|
214
|
+
function refusedRow(error) {
|
|
215
|
+
const code = error?.code;
|
|
216
|
+
return typeof code === 'string' && (code === '42501' || code.startsWith('23'));
|
|
217
|
+
}
|
|
211
218
|
export async function syncTeamOutbox(options = {}) {
|
|
212
219
|
const config = await loadTeamConfiguration();
|
|
213
220
|
if (!config?.databaseUrl)
|
|
@@ -263,14 +270,27 @@ export async function syncTeamOutbox(options = {}) {
|
|
|
263
270
|
synced++;
|
|
264
271
|
}
|
|
265
272
|
catch (error) {
|
|
266
|
-
|
|
273
|
+
const message = String(error instanceof Error ? error.message : error).slice(0, 1_000);
|
|
274
|
+
// The database refused this one row (row-level security, a constraint): it will refuse it every time,
|
|
275
|
+
// so it is set aside with the reason and the rest of the queue goes on. Anything else (the
|
|
276
|
+
// connection, the server) stops the sync and the item is tried again next time.
|
|
277
|
+
if (refusedRow(error)) {
|
|
278
|
+
await db.run('UPDATE sync_outbox SET synced_at = ?, last_error = ? WHERE id = ?', Date.now(), `refused by the team database: ${message}`, item.id);
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
await db.run('UPDATE sync_outbox SET attempts = attempts + 1, last_error = ? WHERE id = ?', message, item.id);
|
|
267
282
|
throw error;
|
|
268
283
|
}
|
|
269
284
|
}
|
|
285
|
+
// Only what changed since the last pull, less a margin: updated_at comes from each member's clock, and
|
|
286
|
+
// re-reading a few minutes is harmless (the local write is an upsert) where a skewed clock would lose a lesson.
|
|
287
|
+
const mark = `team_pulled:${config.organizationId}/${config.teamId}/${config.actorId}`;
|
|
288
|
+
const last = Number((await db.get('SELECT value FROM meta WHERE key = ?', mark))?.value ?? 0);
|
|
270
289
|
const remote = await pool.query(`SELECT * FROM rigour.lessons
|
|
271
290
|
WHERE team_id = $1 AND organization_id = $3
|
|
272
291
|
AND ((actor_id = $2) OR (visibility = 'team' AND state = 'promoted'))
|
|
273
|
-
|
|
292
|
+
AND updated_at > $4
|
|
293
|
+
ORDER BY updated_at ASC`, [config.teamId, config.actorId, config.organizationId, Math.max(0, last - PULL_OVERLAP_MS)]);
|
|
274
294
|
for (const row of remote.rows) {
|
|
275
295
|
const evidence = typeof row.evidence_json === 'string' ? JSON.parse(row.evidence_json) : row.evidence_json;
|
|
276
296
|
await db.run(`INSERT INTO lessons (
|
|
@@ -283,8 +303,11 @@ export async function syncTeamOutbox(options = {}) {
|
|
|
283
303
|
updated_at=excluded.updated_at`, row.id, row.repository_id, row.actor_id, row.team_id, row.visibility, row.state, row.kind, row.subject, await encryptLocalPayload(evidence), row.confidence, row.source, row.supersedes_id, Number(row.created_at), Number(row.updated_at));
|
|
284
304
|
pulled++;
|
|
285
305
|
}
|
|
286
|
-
if (pulled > 0)
|
|
306
|
+
if (pulled > 0) {
|
|
307
|
+
const newest = Math.max(last, ...remote.rows.map((row) => Number(row.updated_at)));
|
|
308
|
+
await db.run('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value', mark, String(newest));
|
|
287
309
|
await db.run("DELETE FROM context_cache WHERE cache_type = 'semantic'");
|
|
310
|
+
}
|
|
288
311
|
}
|
|
289
312
|
finally {
|
|
290
313
|
await pool.end();
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
/** The outbox and repositories a machine holds, and what reached the team database. */
|
|
3
|
-
const state = vi.hoisted(() => ({ outbox: [], repos: new Map(), sent: [] }));
|
|
3
|
+
const state = vi.hoisted(() => ({ outbox: [], repos: new Map(), sent: [], refuse: new Set(), meta: new Map(), remote: [], since: [] }));
|
|
4
4
|
vi.mock('./db.js', () => ({
|
|
5
5
|
openDatabase: async () => ({
|
|
6
6
|
all: async () => state.outbox.filter(o => o.synced_at == null),
|
|
7
|
-
get: async (
|
|
7
|
+
get: async (sql, id) => sql.includes('FROM meta')
|
|
8
|
+
? (state.meta.has(id) ? { value: state.meta.get(id) } : undefined)
|
|
9
|
+
: (state.repos.has(id) ? { canonical_uri: state.repos.get(id) } : undefined),
|
|
8
10
|
run: async (sql, ...p) => {
|
|
11
|
+
if (sql.startsWith('INSERT INTO meta'))
|
|
12
|
+
state.meta.set(p[0], p[1]);
|
|
9
13
|
if (sql.startsWith('UPDATE sync_outbox SET synced_at')) {
|
|
10
14
|
const id = p.at(-1);
|
|
11
15
|
Object.assign(state.outbox.find(o => o.id === id) ?? {}, { synced_at: p[0], last_error: p.length === 3 ? p[1] : null });
|
|
@@ -20,9 +24,15 @@ vi.mock('pg', () => ({
|
|
|
20
24
|
Pool: class {
|
|
21
25
|
async query(sql, params) {
|
|
22
26
|
if (sql.includes('INSERT INTO rigour.lessons')) {
|
|
27
|
+
if (state.refuse.has(params[0]))
|
|
28
|
+
throw Object.assign(new Error('new row violates row-level security policy for table "lessons"'), { code: '42501' });
|
|
23
29
|
state.sent.push(params[0]);
|
|
24
30
|
return { rowCount: 1, rows: [] };
|
|
25
31
|
}
|
|
32
|
+
if (sql.includes('FROM rigour.lessons')) {
|
|
33
|
+
state.since.push(params[3]);
|
|
34
|
+
return { rowCount: 0, rows: state.remote.filter(row => row.updated_at > params[3]) };
|
|
35
|
+
}
|
|
26
36
|
return { rowCount: 1, rows: [] };
|
|
27
37
|
}
|
|
28
38
|
async end() { }
|
|
@@ -40,6 +50,10 @@ beforeEach(() => {
|
|
|
40
50
|
delete process.env.RIGOUR_TEAM_SYNC_PERSONAL;
|
|
41
51
|
state.outbox = [];
|
|
42
52
|
state.sent = [];
|
|
53
|
+
state.refuse = new Set();
|
|
54
|
+
state.meta = new Map();
|
|
55
|
+
state.remote = [];
|
|
56
|
+
state.since = [];
|
|
43
57
|
state.repos = new Map([['r-team', 'https://github.com/acme/web'], ['r-other', 'https://github.com/other-org/app']]);
|
|
44
58
|
queue('l-team', 'r-team', 'team');
|
|
45
59
|
queue('l-other', 'r-other', 'team');
|
|
@@ -60,6 +74,25 @@ describe('team sync scope', () => {
|
|
|
60
74
|
expect(reasons['outbox-l-unknown']).toContain('repository unknown');
|
|
61
75
|
expect(await syncTeamOutbox({ dryRun: true })).toMatchObject({ pending: 0, withheld: 0 }); // never retried
|
|
62
76
|
});
|
|
77
|
+
it('sets aside a lesson the database refuses and sends the rest of the queue', async () => {
|
|
78
|
+
state.outbox = [];
|
|
79
|
+
queue('l-refused', 'r-team', 'team'); // queued first: it must not hold back what comes after
|
|
80
|
+
queue('l-team', 'r-team', 'team');
|
|
81
|
+
state.refuse.add('l-refused');
|
|
82
|
+
expect(await syncTeamOutbox()).toMatchObject({ synced: 1 });
|
|
83
|
+
expect(state.sent).toEqual(['l-team']);
|
|
84
|
+
expect(state.outbox.find(o => o.id === 'outbox-l-refused').last_error).toContain('refused by the team database: new row violates row-level security');
|
|
85
|
+
expect(await syncTeamOutbox({ dryRun: true })).toMatchObject({ pending: 0 }); // not tried again
|
|
86
|
+
});
|
|
87
|
+
it('pulls only what changed since the last pull, re-reading a margin for slow clocks', async () => {
|
|
88
|
+
const lesson = (id, updated) => ({ id, repository_id: 'r-team', actor_id: 'dev-2', team_id: 'web', visibility: 'team', state: 'promoted', kind: 'k', subject: 's', evidence_json: {}, confidence: 1, source: 'x', supersedes_id: null, created_at: updated, updated_at: updated });
|
|
89
|
+
const HOUR = 3_600_000;
|
|
90
|
+
state.remote = [lesson('a', 10 * HOUR), lesson('b', 20 * HOUR)];
|
|
91
|
+
expect(await syncTeamOutbox()).toMatchObject({ pulled: 2 });
|
|
92
|
+
expect(state.since).toEqual([0]); // the first pull reads everything
|
|
93
|
+
expect(await syncTeamOutbox()).toMatchObject({ pulled: 1 }); // only b, inside the 15-minute margin
|
|
94
|
+
expect(state.since[1]).toBe(20 * HOUR - 15 * 60_000);
|
|
95
|
+
});
|
|
63
96
|
it('sends nothing when the team lists no repositories', async () => {
|
|
64
97
|
delete process.env.RIGOUR_TEAM_REPOSITORIES;
|
|
65
98
|
expect(await syncTeamOutbox({ dryRun: true })).toMatchObject({ pending: 0, withheld: 4 });
|
|
@@ -15,6 +15,9 @@ export interface TelemetryDeps {
|
|
|
15
15
|
export declare function telemetryToken(env?: Env): string;
|
|
16
16
|
export declare function readTelemetryState(home?: string): TelemetryState;
|
|
17
17
|
export declare function setTelemetryEnabled(enabled: boolean, home?: string): TelemetryState;
|
|
18
|
+
/** Opted out by the environment, whatever the stored answer. */
|
|
19
|
+
/** The cross-tool opt-out (consoledonottrack.com): any value but "0" means no telemetry and no update check. */
|
|
20
|
+
export declare function doNotTrack(env?: Env): boolean;
|
|
18
21
|
export declare function isTelemetryEnabled(deps?: TelemetryDeps): boolean;
|
|
19
22
|
/** Ask only once, only a person at a terminal, only when sending would be possible at all. */
|
|
20
23
|
export declare function shouldAskTelemetry(isTty: boolean, deps?: TelemetryDeps): boolean;
|
|
@@ -24,5 +27,7 @@ export declare function countUsage(name: string, by?: number, deps?: TelemetryDe
|
|
|
24
27
|
/** Send the day's counters as one `daily_usage` event once they are a day old, then start a new day. */
|
|
25
28
|
export declare function flushDailyUsage(deps?: TelemetryDeps): Promise<boolean>;
|
|
26
29
|
/** A duration as a coarse bucket, so timing never identifies a run. */
|
|
30
|
+
/** A model run's cost as a coarse bucket: never the amount. */
|
|
31
|
+
export declare function costBucket(usd: number | undefined): string | undefined;
|
|
27
32
|
export declare function durationBucket(ms: number): string;
|
|
28
33
|
export {};
|
|
@@ -50,8 +50,12 @@ function isCi(env) {
|
|
|
50
50
|
return CI_MARKERS.some(name => !!env[name]);
|
|
51
51
|
}
|
|
52
52
|
/** Opted out by the environment, whatever the stored answer. */
|
|
53
|
+
/** The cross-tool opt-out (consoledonottrack.com): any value but "0" means no telemetry and no update check. */
|
|
54
|
+
export function doNotTrack(env = process.env) {
|
|
55
|
+
return !!env.DO_NOT_TRACK && env.DO_NOT_TRACK !== '0';
|
|
56
|
+
}
|
|
53
57
|
function vetoed(env) {
|
|
54
|
-
return (
|
|
58
|
+
return doNotTrack(env) || env.RIGOUR_TELEMETRY === '0' || !telemetryToken(env);
|
|
55
59
|
}
|
|
56
60
|
export function isTelemetryEnabled(deps = {}) {
|
|
57
61
|
const env = deps.env ?? process.env;
|
|
@@ -141,6 +145,12 @@ function writeJson(target, value) {
|
|
|
141
145
|
fs.writeFileSync(target, JSON.stringify(value, null, 2) + '\n');
|
|
142
146
|
}
|
|
143
147
|
/** A duration as a coarse bucket, so timing never identifies a run. */
|
|
148
|
+
/** A model run's cost as a coarse bucket: never the amount. */
|
|
149
|
+
export function costBucket(usd) {
|
|
150
|
+
if (typeof usd !== 'number')
|
|
151
|
+
return undefined;
|
|
152
|
+
return usd < 0.1 ? '<$0.10' : usd < 0.5 ? '$0.10-0.50' : usd < 2 ? '$0.50-2' : '>$2';
|
|
153
|
+
}
|
|
144
154
|
export function durationBucket(ms) {
|
|
145
155
|
if (ms < 1000)
|
|
146
156
|
return '<1s';
|
|
@@ -13,10 +13,6 @@ export const PARADIGM_TEMPLATES = [
|
|
|
13
13
|
complexity: 10,
|
|
14
14
|
max_methods: 10,
|
|
15
15
|
max_params: 5,
|
|
16
|
-
max_nesting: 4,
|
|
17
|
-
max_inheritance_depth: 3,
|
|
18
|
-
max_class_dependencies: 5,
|
|
19
|
-
max_function_lines: 60,
|
|
20
16
|
},
|
|
21
17
|
},
|
|
22
18
|
},
|
|
@@ -35,10 +31,6 @@ export const PARADIGM_TEMPLATES = [
|
|
|
35
31
|
complexity: 8,
|
|
36
32
|
max_methods: 15, // Functions, not classes
|
|
37
33
|
max_params: 4,
|
|
38
|
-
max_nesting: 3,
|
|
39
|
-
max_inheritance_depth: 3,
|
|
40
|
-
max_class_dependencies: 5,
|
|
41
|
-
max_function_lines: 40,
|
|
42
34
|
},
|
|
43
35
|
},
|
|
44
36
|
},
|