@rigour-labs/core 6.7.10 → 6.8.1-rc.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/dist/brief/briefing.d.ts +59 -0
- package/dist/brief/briefing.js +126 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/inference/cloud-provider.js +12 -2
- package/dist/review/backtest-init.d.ts +8 -5
- package/dist/review/backtest-init.js +27 -9
- package/dist/review/backtest-last.js +3 -1
- package/dist/review/reviewer/adapters.d.ts +4 -0
- package/dist/review/reviewer/adapters.js +23 -0
- package/dist/review/reviewer/inputs.d.ts +4 -1
- package/dist/review/reviewer/inputs.js +36 -2
- package/dist/review/reviewer/record.d.ts +5 -0
- package/dist/review/reviewer/record.js +3 -3
- package/dist/review/reviewer/verdict.d.ts +14 -0
- package/dist/review/reviewer/verdict.js +46 -6
- package/dist/review/reviewer.d.ts +2 -0
- package/dist/review/reviewer.js +24 -5
- package/dist/review-learning/repo-rules.d.ts +14 -2
- package/dist/review-learning/repo-rules.js +59 -9
- package/dist/task/thread.d.ts +47 -0
- package/dist/task/thread.js +234 -0
- package/dist/templates/universal-config.js +4 -0
- package/dist/types/index.d.ts +21 -0
- package/dist/types/index.js +7 -0
- package/package.json +11 -8
- package/dist/context/automatic-index-cache.test.d.ts +0 -1
- package/dist/context/automatic-index-cache.test.js +0 -30
- package/dist/context/automatic-index.test.d.ts +0 -1
- package/dist/context/automatic-index.test.js +0 -45
- package/dist/context/cache-engine.test.d.ts +0 -1
- package/dist/context/cache-engine.test.js +0 -99
- package/dist/context/dependency-graph.test.d.ts +0 -1
- package/dist/context/dependency-graph.test.js +0 -13
- package/dist/context/index-status.test.d.ts +0 -1
- package/dist/context/index-status.test.js +0 -26
- package/dist/context.test.d.ts +0 -1
- package/dist/context.test.js +0 -228
- package/dist/deep/agent-review.test.d.ts +0 -1
- package/dist/deep/agent-review.test.js +0 -53
- package/dist/deep/code-context.test.d.ts +0 -1
- package/dist/deep/code-context.test.js +0 -48
- package/dist/deep/code-pass.test.d.ts +0 -1
- package/dist/deep/code-pass.test.js +0 -112
- package/dist/deep/code-review-prompt.test.d.ts +0 -1
- package/dist/deep/code-review-prompt.test.js +0 -20
- package/dist/deep/code-verifier.test.d.ts +0 -1
- package/dist/deep/code-verifier.test.js +0 -54
- package/dist/deep/diff-tests/calls.test.d.ts +0 -1
- package/dist/deep/diff-tests/calls.test.js +0 -33
- package/dist/deep/diff-tests/run.test.d.ts +0 -1
- package/dist/deep/diff-tests/run.test.js +0 -58
- package/dist/deep/fact-extractor.test.d.ts +0 -1
- package/dist/deep/fact-extractor.test.js +0 -581
- package/dist/deep/parse-findings.test.d.ts +0 -1
- package/dist/deep/parse-findings.test.js +0 -25
- package/dist/deep/pr-review.test.d.ts +0 -1
- package/dist/deep/pr-review.test.js +0 -80
- package/dist/deep/prompts.test.d.ts +0 -1
- package/dist/deep/prompts.test.js +0 -235
- package/dist/deep/reference-pack.test.d.ts +0 -1
- package/dist/deep/reference-pack.test.js +0 -46
- package/dist/deep/related-changes.test.d.ts +0 -1
- package/dist/deep/related-changes.test.js +0 -33
- package/dist/deep/review-context-export.test.d.ts +0 -1
- package/dist/deep/review-context-export.test.js +0 -32
- package/dist/deep/review-tools.test.d.ts +0 -1
- package/dist/deep/review-tools.test.js +0 -39
- package/dist/deep/risk.test.d.ts +0 -1
- package/dist/deep/risk.test.js +0 -101
- package/dist/deep/verifier.test.d.ts +0 -1
- package/dist/deep/verifier.test.js +0 -635
- package/dist/discovery.test.d.ts +0 -1
- package/dist/discovery.test.js +0 -93
- package/dist/environment.test.d.ts +0 -1
- package/dist/environment.test.js +0 -94
- package/dist/firewall/firewall.test.d.ts +0 -1
- package/dist/firewall/firewall.test.js +0 -117
- package/dist/firewall/trust-boundaries.test.d.ts +0 -1
- package/dist/firewall/trust-boundaries.test.js +0 -64
- package/dist/firewall/trusted-control.test.d.ts +0 -1
- package/dist/firewall/trusted-control.test.js +0 -188
- package/dist/gates/agent-team.test.d.ts +0 -1
- package/dist/gates/agent-team.test.js +0 -113
- package/dist/gates/ast.test.d.ts +0 -1
- package/dist/gates/ast.test.js +0 -112
- package/dist/gates/checkpoint.test.d.ts +0 -1
- package/dist/gates/checkpoint.test.js +0 -105
- package/dist/gates/content.test.d.ts +0 -1
- package/dist/gates/content.test.js +0 -73
- package/dist/gates/coverage.test.d.ts +0 -1
- package/dist/gates/coverage.test.js +0 -53
- package/dist/gates/dedupe-failures.test.d.ts +0 -1
- package/dist/gates/dedupe-failures.test.js +0 -12
- package/dist/gates/deep-analysis.test.d.ts +0 -1
- package/dist/gates/deep-analysis.test.js +0 -86
- package/dist/gates/deep-intent.test.d.ts +0 -1
- package/dist/gates/deep-intent.test.js +0 -51
- package/dist/gates/deep-timeout.test.d.ts +0 -1
- package/dist/gates/deep-timeout.test.js +0 -10
- package/dist/gates/deprecated-apis.test.d.ts +0 -1
- package/dist/gates/deprecated-apis.test.js +0 -318
- package/dist/gates/deprecated-dependencies.test.d.ts +0 -1
- package/dist/gates/deprecated-dependencies.test.js +0 -55
- package/dist/gates/frontend-secret-exposure.test.d.ts +0 -1
- package/dist/gates/frontend-secret-exposure.test.js +0 -148
- package/dist/gates/hallucinated-imports/framework-modules-nuxt.test.d.ts +0 -1
- package/dist/gates/hallucinated-imports/framework-modules-nuxt.test.js +0 -27
- package/dist/gates/hallucinated-imports/js-resolver-types.test.d.ts +0 -1
- package/dist/gates/hallucinated-imports/js-resolver-types.test.js +0 -14
- package/dist/gates/hallucinated-imports-sveltekit.test.d.ts +0 -1
- package/dist/gates/hallucinated-imports-sveltekit.test.js +0 -132
- package/dist/gates/hallucinated-imports.test.d.ts +0 -1
- package/dist/gates/hallucinated-imports.test.js +0 -1206
- package/dist/gates/js-style-context.test.d.ts +0 -1
- package/dist/gates/js-style-context.test.js +0 -35
- package/dist/gates/logic-drift.test.d.ts +0 -1
- package/dist/gates/logic-drift.test.js +0 -52
- package/dist/gates/phantom-apis.test.d.ts +0 -1
- package/dist/gates/phantom-apis.test.js +0 -396
- package/dist/gates/promise-safety.test.d.ts +0 -1
- package/dist/gates/promise-safety.test.js +0 -34
- package/dist/gates/runner.test.d.ts +0 -1
- package/dist/gates/runner.test.js +0 -77
- package/dist/gates/scoped-gates.test.d.ts +0 -1
- package/dist/gates/scoped-gates.test.js +0 -52
- package/dist/gates/security-patterns-owasp.test.d.ts +0 -1
- package/dist/gates/security-patterns-owasp.test.js +0 -186
- package/dist/gates/security-patterns.test.d.ts +0 -1
- package/dist/gates/security-patterns.test.js +0 -194
- package/dist/gates/semantic-bugs.test.d.ts +0 -1
- package/dist/gates/semantic-bugs.test.js +0 -76
- package/dist/gates/side-effect-analysis.test.d.ts +0 -1
- package/dist/gates/side-effect-analysis.test.js +0 -162
- package/dist/gates/style-drift.test.d.ts +0 -1
- package/dist/gates/style-drift.test.js +0 -26
- package/dist/gates/test-quality.test.d.ts +0 -1
- package/dist/gates/test-quality.test.js +0 -325
- package/dist/gates/trusted-reviews.test.d.ts +0 -1
- package/dist/gates/trusted-reviews.test.js +0 -16
- package/dist/gates/unindexed-reads/queries.test.d.ts +0 -1
- package/dist/gates/unindexed-reads/queries.test.js +0 -54
- package/dist/gates/unindexed-reads/schema.test.d.ts +0 -1
- package/dist/gates/unindexed-reads/schema.test.js +0 -81
- package/dist/gates/unindexed-reads/unindexed-reads.test.d.ts +0 -1
- package/dist/gates/unindexed-reads/unindexed-reads.test.js +0 -90
- package/dist/hooks/checker.test.d.ts +0 -1
- package/dist/hooks/checker.test.js +0 -159
- package/dist/hooks/dlp-confidence.test.d.ts +0 -1
- package/dist/hooks/dlp-confidence.test.js +0 -51
- package/dist/hooks/dlp-feedback.test.d.ts +0 -1
- package/dist/hooks/dlp-feedback.test.js +0 -131
- package/dist/hooks/input-validator.test.d.ts +0 -1
- package/dist/hooks/input-validator.test.js +0 -329
- package/dist/hooks/templates.test.d.ts +0 -1
- package/dist/hooks/templates.test.js +0 -27
- package/dist/inference/brain-placeholder.test.d.ts +0 -1
- package/dist/inference/brain-placeholder.test.js +0 -28
- package/dist/inference/cloud-provider.test.d.ts +0 -1
- package/dist/inference/cloud-provider.test.js +0 -139
- package/dist/inference/executable.test.d.ts +0 -1
- package/dist/inference/executable.test.js +0 -41
- package/dist/inference/http-download.test.d.ts +0 -1
- package/dist/inference/http-download.test.js +0 -109
- package/dist/inference/llama-engine-checksum.test.d.ts +0 -1
- package/dist/inference/llama-engine-checksum.test.js +0 -27
- package/dist/inference/llama-engine.test.d.ts +0 -1
- package/dist/inference/llama-engine.test.js +0 -51
- package/dist/inference/llama-process.test.d.ts +0 -1
- package/dist/inference/llama-process.test.js +0 -61
- package/dist/inference/local-model.test.d.ts +0 -1
- package/dist/inference/local-model.test.js +0 -23
- package/dist/inference/model-download.test.d.ts +0 -1
- package/dist/inference/model-download.test.js +0 -125
- package/dist/inference/model-manager.test.d.ts +0 -1
- package/dist/inference/model-manager.test.js +0 -24
- package/dist/inference/types.test.d.ts +0 -1
- package/dist/inference/types.test.js +0 -19
- package/dist/memory/recall.test.d.ts +0 -1
- package/dist/memory/recall.test.js +0 -36
- package/dist/pattern-index/indexer.test.d.ts +0 -6
- package/dist/pattern-index/indexer.test.js +0 -197
- package/dist/pattern-index/matcher.test.d.ts +0 -6
- package/dist/pattern-index/matcher.test.js +0 -238
- package/dist/pattern-index/pattern-reuse.test.d.ts +0 -1
- package/dist/pattern-index/pattern-reuse.test.js +0 -76
- package/dist/pattern-index/semantic-runtime.test.d.ts +0 -1
- package/dist/pattern-index/semantic-runtime.test.js +0 -31
- package/dist/pattern-index/staleness.test.d.ts +0 -6
- package/dist/pattern-index/staleness.test.js +0 -211
- package/dist/review/agent-fixes.test.d.ts +0 -1
- package/dist/review/agent-fixes.test.js +0 -32
- package/dist/review/backtest-init.test.d.ts +0 -1
- package/dist/review/backtest-init.test.js +0 -98
- package/dist/review/backtest-judges.test.d.ts +0 -1
- package/dist/review/backtest-judges.test.js +0 -32
- package/dist/review/backtest-last.test.d.ts +0 -1
- package/dist/review/backtest-last.test.js +0 -109
- package/dist/review/backtest.test.d.ts +0 -1
- package/dist/review/backtest.test.js +0 -183
- package/dist/review/baseline.test.d.ts +0 -1
- package/dist/review/baseline.test.js +0 -22
- package/dist/review/branch-checks.test.d.ts +0 -1
- package/dist/review/branch-checks.test.js +0 -48
- package/dist/review/check-outcomes.test.d.ts +0 -1
- package/dist/review/check-outcomes.test.js +0 -56
- package/dist/review/code-patterns.test.d.ts +0 -1
- package/dist/review/code-patterns.test.js +0 -142
- package/dist/review/dead-code.test.d.ts +0 -1
- package/dist/review/dead-code.test.js +0 -154
- package/dist/review/deep-runs.test.d.ts +0 -1
- package/dist/review/deep-runs.test.js +0 -23
- package/dist/review/effectiveness.test.d.ts +0 -1
- package/dist/review/effectiveness.test.js +0 -42
- package/dist/review/fix-scope.test.d.ts +0 -1
- package/dist/review/fix-scope.test.js +0 -104
- package/dist/review/generated-files.test.d.ts +0 -1
- package/dist/review/generated-files.test.js +0 -25
- package/dist/review/migration-order.test.d.ts +0 -1
- package/dist/review/migration-order.test.js +0 -62
- package/dist/review/quiet.test.d.ts +0 -1
- package/dist/review/quiet.test.js +0 -38
- package/dist/review/receipt.test.d.ts +0 -1
- package/dist/review/receipt.test.js +0 -60
- package/dist/review/review-task.test.d.ts +0 -1
- package/dist/review/review-task.test.js +0 -89
- package/dist/review/review.test.d.ts +0 -1
- package/dist/review/review.test.js +0 -203
- package/dist/review/reviewer/adapters.test.d.ts +0 -1
- package/dist/review/reviewer/adapters.test.js +0 -60
- package/dist/review/reviewer/api-judge.test.d.ts +0 -1
- package/dist/review/reviewer/api-judge.test.js +0 -108
- package/dist/review/reviewer/background.test.d.ts +0 -1
- package/dist/review/reviewer/background.test.js +0 -118
- package/dist/review/reviewer/context.test.d.ts +0 -1
- package/dist/review/reviewer/context.test.js +0 -45
- package/dist/review/reviewer/exec.test.d.ts +0 -1
- package/dist/review/reviewer/exec.test.js +0 -59
- package/dist/review/reviewer/inputs.test.d.ts +0 -1
- package/dist/review/reviewer/inputs.test.js +0 -26
- package/dist/review/reviewer/panel.test.d.ts +0 -1
- package/dist/review/reviewer/panel.test.js +0 -110
- package/dist/review/reviewer/record.test.d.ts +0 -1
- package/dist/review/reviewer/record.test.js +0 -32
- package/dist/review/reviewer/rule-writer.test.d.ts +0 -1
- package/dist/review/reviewer/rule-writer.test.js +0 -52
- package/dist/review/reviewer/settings.test.d.ts +0 -1
- package/dist/review/reviewer/settings.test.js +0 -57
- package/dist/review/reviewer/usage.test.d.ts +0 -1
- package/dist/review/reviewer/usage.test.js +0 -14
- package/dist/review/reviewer.test.d.ts +0 -1
- package/dist/review/reviewer.test.js +0 -705
- package/dist/review/stories.test.d.ts +0 -1
- package/dist/review/stories.test.js +0 -58
- package/dist/review/toolchain.test.d.ts +0 -1
- package/dist/review/toolchain.test.js +0 -108
- package/dist/review/typed/redundancy.test.d.ts +0 -1
- package/dist/review/typed/redundancy.test.js +0 -315
- package/dist/review/typed/schema-nullability.test.d.ts +0 -1
- package/dist/review/typed/schema-nullability.test.js +0 -63
- package/dist/review-learning/human-edits.test.d.ts +0 -1
- package/dist/review-learning/human-edits.test.js +0 -47
- package/dist/review-learning/repo-rules.test.d.ts +0 -1
- package/dist/review-learning/repo-rules.test.js +0 -55
- package/dist/review-learning/review-learning.test.d.ts +0 -1
- package/dist/review-learning/review-learning.test.js +0 -249
- package/dist/safety.test.d.ts +0 -1
- package/dist/safety.test.js +0 -42
- package/dist/semantic/benchmark.test.d.ts +0 -1
- package/dist/semantic/benchmark.test.js +0 -22
- package/dist/semantic/intent/intent.test.d.ts +0 -1
- package/dist/semantic/intent/intent.test.js +0 -46
- package/dist/semantic/learn/learn.test.d.ts +0 -1
- package/dist/semantic/learn/learn.test.js +0 -67
- package/dist/semantic/origins.test.d.ts +0 -1
- package/dist/semantic/origins.test.js +0 -77
- package/dist/semantic/project-facts.test.d.ts +0 -1
- package/dist/semantic/project-facts.test.js +0 -45
- package/dist/semantic/sites/call-sites.test.d.ts +0 -1
- package/dist/semantic/sites/call-sites.test.js +0 -46
- package/dist/services/adaptive-thresholds.test.d.ts +0 -1
- package/dist/services/adaptive-thresholds.test.js +0 -53
- package/dist/services/agent-history.test.d.ts +0 -1
- package/dist/services/agent-history.test.js +0 -69
- package/dist/services/context-scope-summary.test.d.ts +0 -1
- package/dist/services/context-scope-summary.test.js +0 -17
- package/dist/services/context-telemetry-service.test.d.ts +0 -1
- package/dist/services/context-telemetry-service.test.js +0 -182
- package/dist/services/cursor-usage-sync.test.d.ts +0 -1
- package/dist/services/cursor-usage-sync.test.js +0 -173
- package/dist/services/engineering-knowledge-graph.test.d.ts +0 -1
- package/dist/services/engineering-knowledge-graph.test.js +0 -76
- package/dist/services/model-pricing.test.d.ts +0 -1
- package/dist/services/model-pricing.test.js +0 -44
- package/dist/services/observed-savings.test.d.ts +0 -1
- package/dist/services/observed-savings.test.js +0 -37
- package/dist/services/score-history.test.d.ts +0 -1
- package/dist/services/score-history.test.js +0 -61
- package/dist/smoke.test.d.ts +0 -1
- package/dist/smoke.test.js +0 -17
- package/dist/storage/cache-cleanup.test.d.ts +0 -1
- package/dist/storage/cache-cleanup.test.js +0 -54
- package/dist/storage/context-telemetry.test.d.ts +0 -1
- package/dist/storage/context-telemetry.test.js +0 -80
- package/dist/storage/db.test.d.ts +0 -1
- package/dist/storage/db.test.js +0 -46
- package/dist/storage/fix-lessons.test.d.ts +0 -1
- package/dist/storage/fix-lessons.test.js +0 -61
- package/dist/storage/lessons.test.d.ts +0 -1
- package/dist/storage/lessons.test.js +0 -81
- package/dist/storage/local-encryption.test.d.ts +0 -1
- package/dist/storage/local-encryption.test.js +0 -34
- package/dist/storage/local-memory.test.d.ts +0 -1
- package/dist/storage/local-memory.test.js +0 -55
- package/dist/storage/share-memory.test.d.ts +0 -1
- package/dist/storage/share-memory.test.js +0 -34
- package/dist/storage/team-diagnostics.test.d.ts +0 -1
- package/dist/storage/team-diagnostics.test.js +0 -51
- package/dist/storage/team-scope.test.d.ts +0 -1
- package/dist/storage/team-scope.test.js +0 -22
- package/dist/storage/team-store.test.d.ts +0 -1
- package/dist/storage/team-store.test.js +0 -11
- package/dist/storage/team-sync-scope.test.d.ts +0 -1
- package/dist/storage/team-sync-scope.test.js +0 -100
- package/dist/storage/team-vector-store.test.d.ts +0 -1
- package/dist/storage/team-vector-store.test.js +0 -56
- package/dist/storage/telemetry-scope.test.d.ts +0 -1
- package/dist/storage/telemetry-scope.test.js +0 -38
- package/dist/telemetry/telemetry.test.d.ts +0 -1
- package/dist/telemetry/telemetry.test.js +0 -64
- package/dist/types/index.test.d.ts +0 -1
- package/dist/types/index.test.js +0 -33
- package/dist/utils/command-line.test.d.ts +0 -1
- package/dist/utils/command-line.test.js +0 -8
- package/dist/utils/diff-removed.test.d.ts +0 -1
- package/dist/utils/diff-removed.test.js +0 -29
- package/dist/utils/diff.test.d.ts +0 -1
- package/dist/utils/diff.test.js +0 -38
- package/dist/utils/glob-paths.test.d.ts +0 -1
- package/dist/utils/glob-paths.test.js +0 -40
- package/dist/utils/profile.test.d.ts +0 -1
- package/dist/utils/profile.test.js +0 -68
- package/dist/utils/scanner.test.d.ts +0 -1
- package/dist/utils/scanner.test.js +0 -48
- package/dist/utils/scope.test.d.ts +0 -1
- package/dist/utils/scope.test.js +0 -38
|
@@ -1,45 +0,0 @@
|
|
|
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 { ProjectFacts } from './project-facts.js';
|
|
6
|
-
let root;
|
|
7
|
-
afterEach(() => { if (root)
|
|
8
|
-
fs.rmSync(root, { recursive: true, force: true }); root = undefined; });
|
|
9
|
-
function write(files) {
|
|
10
|
-
root = fs.mkdtempSync(path.join(os.tmpdir(), 'project-facts-'));
|
|
11
|
-
for (const [name, text] of Object.entries(files)) {
|
|
12
|
-
fs.mkdirSync(path.dirname(path.join(root, name)), { recursive: true });
|
|
13
|
-
fs.writeFileSync(path.join(root, name), text);
|
|
14
|
-
}
|
|
15
|
-
return root;
|
|
16
|
-
}
|
|
17
|
-
describe('ProjectFacts', () => {
|
|
18
|
-
it('reads the nearest package.json, so each monorepo package is judged by its own', () => {
|
|
19
|
-
const dir = write({
|
|
20
|
-
'package.json': JSON.stringify({ name: 'root', devDependencies: { vite: '^5.0.0' } }),
|
|
21
|
-
'packages/ui/package.json': JSON.stringify({ name: '@x/ui', peerDependencies: { 'solid-js': '^1.8.0' } }),
|
|
22
|
-
'packages/ui/src/a.tsx': '',
|
|
23
|
-
});
|
|
24
|
-
const facts = new ProjectFacts(dir);
|
|
25
|
-
expect(facts.declares('packages/ui/src/a.tsx', 'solid-js')).toBe(true);
|
|
26
|
-
expect(facts.declares('packages/ui/src/a.tsx', 'vite')).toBe(false);
|
|
27
|
-
expect(facts.majorVersion('packages/ui/src/a.tsx', 'solid-js')).toBe(1);
|
|
28
|
-
expect(facts.packageName('packages/ui/src/a.tsx')).toBe('@x/ui');
|
|
29
|
-
expect(facts.packageDir('packages/ui/src/a.tsx')).toBe(path.join(dir, 'packages/ui'));
|
|
30
|
-
});
|
|
31
|
-
it('never reads a manifest above cwd', () => {
|
|
32
|
-
const dir = write({ 'package.json': JSON.stringify({ dependencies: { react: '^19.0.0' } }), 'app/src/a.tsx': '' });
|
|
33
|
-
const facts = new ProjectFacts(path.join(dir, 'app'));
|
|
34
|
-
expect(facts.dependencies('src/a.tsx').size).toBe(0);
|
|
35
|
-
expect(facts.packageDir('src/a.tsx')).toBeUndefined();
|
|
36
|
-
});
|
|
37
|
-
it('resolves compiler options through tsconfig extends', () => {
|
|
38
|
-
const dir = write({
|
|
39
|
-
'tsconfig.base.json': JSON.stringify({ compilerOptions: { stripInternal: true } }),
|
|
40
|
-
'pkg/tsconfig.json': JSON.stringify({ extends: '../tsconfig.base.json' }),
|
|
41
|
-
'pkg/src/a.ts': '',
|
|
42
|
-
});
|
|
43
|
-
expect(new ProjectFacts(dir).compilerOptions('pkg/src/a.ts').stripInternal).toBe(true);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,46 +0,0 @@
|
|
|
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 { exportCallSites } from './export.js';
|
|
6
|
-
let dir;
|
|
7
|
-
afterEach(() => { if (dir)
|
|
8
|
-
fs.rmSync(dir, { recursive: true, force: true }); dir = undefined; });
|
|
9
|
-
function sites(code) {
|
|
10
|
-
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'call-sites-'));
|
|
11
|
-
fs.writeFileSync(path.join(dir, 'main.ts'), code);
|
|
12
|
-
return exportCallSites(dir, ['main.ts']).map(s => [s.function, s.callee, s.ordinal, s.handledBy]);
|
|
13
|
-
}
|
|
14
|
-
const DECLS = 'declare function a(): Promise<number>;\ndeclare function b(): Promise<number>;\n';
|
|
15
|
-
describe('findCallSites', () => {
|
|
16
|
-
it('reports awaited calls and says how each handles failure', () => {
|
|
17
|
-
expect(sites(`${DECLS}
|
|
18
|
-
export async function load() {
|
|
19
|
-
const x = await a();
|
|
20
|
-
try { await b(); } catch { return 0; }
|
|
21
|
-
const y = await a().catch(() => 0);
|
|
22
|
-
return x + y;
|
|
23
|
-
}`)).toEqual([
|
|
24
|
-
['load', 'a', 0, null],
|
|
25
|
-
['load', 'b', 0, 'try'],
|
|
26
|
-
['load', 'a', 1, 'call'],
|
|
27
|
-
]);
|
|
28
|
-
});
|
|
29
|
-
it('treats each element of Promise.all as a site, and allSettled as handled', () => {
|
|
30
|
-
expect(sites(`${DECLS}
|
|
31
|
-
export async function both() {
|
|
32
|
-
const [x, y] = await Promise.all([a(), b()]);
|
|
33
|
-
const settled = await Promise.allSettled([a()]);
|
|
34
|
-
return [x, y, settled];
|
|
35
|
-
}`)).toEqual([
|
|
36
|
-
['both', 'a', 0, null],
|
|
37
|
-
['both', 'b', 0, null],
|
|
38
|
-
['both', 'a', 1, 'all-settled'],
|
|
39
|
-
]);
|
|
40
|
-
});
|
|
41
|
-
it('counts a callee that catches and returns as handled', () => {
|
|
42
|
-
expect(sites(`${DECLS}
|
|
43
|
-
async function safeA() { try { return await a(); } catch { return 0; } }
|
|
44
|
-
export async function use() { return await safeA(); }`)).toContainEqual(['use', 'safeA', 0, 'call']);
|
|
45
|
-
});
|
|
46
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { recordGateRun, getQualityTrend, clearAdaptiveHistory, } from './adaptive-thresholds.js';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
|
-
import * as path from 'path';
|
|
5
|
-
import * as os from 'os';
|
|
6
|
-
describe('AdaptiveThresholds', () => {
|
|
7
|
-
let testDir;
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adaptive-test-'));
|
|
10
|
-
});
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
clearAdaptiveHistory(testDir);
|
|
13
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
14
|
-
});
|
|
15
|
-
describe('historical tracking', () => {
|
|
16
|
-
it('should record gate runs', () => {
|
|
17
|
-
recordGateRun(testDir, 5, 2, 10);
|
|
18
|
-
recordGateRun(testDir, 6, 1, 5);
|
|
19
|
-
const historyPath = path.join(testDir, '.rigour', 'adaptive-history.json');
|
|
20
|
-
expect(fs.existsSync(historyPath)).toBe(true);
|
|
21
|
-
const history = JSON.parse(fs.readFileSync(historyPath, 'utf-8'));
|
|
22
|
-
expect(history.runs).toHaveLength(2);
|
|
23
|
-
});
|
|
24
|
-
it('should return stable trend for new projects', () => {
|
|
25
|
-
const trend = getQualityTrend(testDir);
|
|
26
|
-
expect(trend).toBe('stable');
|
|
27
|
-
});
|
|
28
|
-
it('should detect improving trend', () => {
|
|
29
|
-
// Baseline: 15 runs with high failures (with variance for valid std dev)
|
|
30
|
-
for (let i = 0; i < 15; i++) {
|
|
31
|
-
recordGateRun(testDir, 3, 5, 15 + (i % 5) * 3); // 15,18,21,24,27 repeating
|
|
32
|
-
}
|
|
33
|
-
// Recent: 5 runs with very low failures (clear improvement)
|
|
34
|
-
for (let i = 0; i < 5; i++) {
|
|
35
|
-
recordGateRun(testDir, 7, 0, 1);
|
|
36
|
-
}
|
|
37
|
-
const trend = getQualityTrend(testDir);
|
|
38
|
-
expect(trend).toBe('improving');
|
|
39
|
-
});
|
|
40
|
-
it('should detect degrading trend', () => {
|
|
41
|
-
// Baseline: 15 runs with low failures (with variance for valid std dev)
|
|
42
|
-
for (let i = 0; i < 15; i++) {
|
|
43
|
-
recordGateRun(testDir, 7, 1, 1 + (i % 3)); // 1,2,3 repeating
|
|
44
|
-
}
|
|
45
|
-
// Recent: 5 runs with high failures (clear degradation)
|
|
46
|
-
for (let i = 0; i < 5; i++) {
|
|
47
|
-
recordGateRun(testDir, 3, 5, 25);
|
|
48
|
-
}
|
|
49
|
-
const trend = getQualityTrend(testDir);
|
|
50
|
-
expect(trend).toBe('degrading');
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { buildAgentRuns, normalizeAgentEvent, normalizeAgentEvents } from './agent-history.js';
|
|
3
|
-
describe('agent history', () => {
|
|
4
|
-
it('normalizes incomplete historical records without dropping them', () => {
|
|
5
|
-
const event = normalizeAgentEvent({ type: 'tool_error', arguments: { files: ['src/a.ts'] } });
|
|
6
|
-
expect(event.agentId).toBe('unknown-agent');
|
|
7
|
-
expect(event.outcome).toBe('error');
|
|
8
|
-
expect(event.files).toEqual(['src/a.ts']);
|
|
9
|
-
});
|
|
10
|
-
it('groups events into stable runs and retains outcomes', () => {
|
|
11
|
-
const events = [
|
|
12
|
-
normalizeAgentEvent({ id: '1', sessionId: 's1', agentId: 'a1', timestamp: '2026-01-01T00:00:00Z', tool: 'read' }),
|
|
13
|
-
normalizeAgentEvent({ id: '2', sessionId: 's1', agentId: 'a1', timestamp: '2026-01-01T00:01:00Z', type: 'completed', files: ['src/a.ts'] }),
|
|
14
|
-
];
|
|
15
|
-
expect(buildAgentRuns(events)).toMatchObject([{ id: 's1', eventCount: 2, status: 'success', files: ['src/a.ts'] }]);
|
|
16
|
-
});
|
|
17
|
-
it('normalizes guidance safely and builds an honest impact receipt', () => {
|
|
18
|
-
const events = [normalizeAgentEvent({
|
|
19
|
-
id: 'response-1', requestId: 'request-1', agentId: 'agent-1', timestamp: '2026-01-01T00:00:00Z',
|
|
20
|
-
type: 'tool_response', status: 'success',
|
|
21
|
-
guidance: {
|
|
22
|
-
kind: 'context-scope',
|
|
23
|
-
recommendation: 'Read only the two evidence-backed files.',
|
|
24
|
-
patternRefs: [{ id: 'p1', label: 'withTransaction' }, { bad: true }],
|
|
25
|
-
lessonRefs: [{ id: 'l1', label: 'Bound retries' }],
|
|
26
|
-
selectedFiles: ['src/a.ts', 42],
|
|
27
|
-
excludedFileCount: 18,
|
|
28
|
-
},
|
|
29
|
-
telemetry: {
|
|
30
|
-
candidateTokens: 2400,
|
|
31
|
-
returnedTokens: 400,
|
|
32
|
-
candidateFiles: 20,
|
|
33
|
-
returnedFiles: 2,
|
|
34
|
-
cacheStatus: 'exact-hit',
|
|
35
|
-
classification: 'measured estimate',
|
|
36
|
-
},
|
|
37
|
-
})];
|
|
38
|
-
expect(events[0].guidance?.selectedFiles).toEqual(['src/a.ts']);
|
|
39
|
-
expect(buildAgentRuns(events)[0].impact).toMatchObject({
|
|
40
|
-
guidanceCount: 1,
|
|
41
|
-
knowledgeItems: 2,
|
|
42
|
-
avoidedTokens: 2000,
|
|
43
|
-
excludedFiles: 18,
|
|
44
|
-
cacheHits: 1,
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
it('ignores malformed guidance rather than breaking legacy history', () => {
|
|
48
|
-
const event = normalizeAgentEvent({ guidance: { kind: 'context-scope', recommendation: 42 }, telemetry: 'bad' });
|
|
49
|
-
expect(event.guidance).toBeUndefined();
|
|
50
|
-
expect(event.telemetry).toBeUndefined();
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
describe('normalizeAgentEvents', () => {
|
|
54
|
-
it('carries request identity into the matching response', () => {
|
|
55
|
-
const events = normalizeAgentEvents([
|
|
56
|
-
{
|
|
57
|
-
type: 'tool_call', requestId: 'request-1', tool: 'rigour_checkpoint',
|
|
58
|
-
timestamp: '2026-09-10T00:00:00Z',
|
|
59
|
-
arguments: { agentId: 'agent-a', taskId: 'task-a', filesChanged: ['src/a.ts'] },
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
type: 'tool_response', requestId: 'request-1', tool: 'rigour_checkpoint', status: 'success',
|
|
63
|
-
timestamp: '2026-09-10T00:00:01Z',
|
|
64
|
-
},
|
|
65
|
-
]);
|
|
66
|
-
expect(events[1]).toMatchObject({ agentId: 'agent-a', taskId: 'task-a', files: ['src/a.ts'] });
|
|
67
|
-
expect(buildAgentRuns(events)).toHaveLength(1);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,17 +0,0 @@
|
|
|
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 { getContextScopeSummary } from './context-telemetry-service.js';
|
|
6
|
-
let root;
|
|
7
|
-
beforeEach(() => { root = fs.mkdtempSync(path.join(os.tmpdir(), 'scope-summary-')); });
|
|
8
|
-
afterEach(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
9
|
-
describe('getContextScopeSummary', () => {
|
|
10
|
-
it('counts rule files and skips a rules directory such as .clinerules/', async () => {
|
|
11
|
-
fs.mkdirSync(path.join(root, '.clinerules'));
|
|
12
|
-
fs.writeFileSync(path.join(root, '.clinerules', 'rigour.md'), 'rules');
|
|
13
|
-
fs.writeFileSync(path.join(root, 'CLAUDE.md'), 'Use small PRs.');
|
|
14
|
-
const summary = await getContextScopeSummary(root);
|
|
15
|
-
expect(summary.alwaysOnRuleTokens).toBeGreaterThan(0);
|
|
16
|
-
});
|
|
17
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import fs from 'fs-extra';
|
|
5
|
-
vi.mock('../storage/db.js', () => ({
|
|
6
|
-
isSQLiteAvailable: () => false,
|
|
7
|
-
openDatabase: async () => null,
|
|
8
|
-
DB_PATH: '/tmp/rigour-test.db',
|
|
9
|
-
RIGOUR_DIR: '/tmp/.rigour',
|
|
10
|
-
}));
|
|
11
|
-
import { getTaskContextStats, getTaskCostStats, getCacheStats, importCursorUsageCsv, importCursorUsageJson, getContextScopeSummary, getCheckpointSummary, estimateTokenCount, } from './context-telemetry-service.js';
|
|
12
|
-
import { recordContextEvent, recordCheckpointMetric } from '../storage/context-telemetry.js';
|
|
13
|
-
describe('context-telemetry-service', () => {
|
|
14
|
-
const testCwd = path.join(os.tmpdir(), `rigour-ctx-service-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
15
|
-
const taskId = `task-${Date.now()}`;
|
|
16
|
-
beforeEach(async () => {
|
|
17
|
-
await fs.ensureDir(path.join(testCwd, '.rigour'));
|
|
18
|
-
});
|
|
19
|
-
afterEach(async () => {
|
|
20
|
-
await fs.remove(testCwd);
|
|
21
|
-
});
|
|
22
|
-
it('returns zeroed stats when no telemetry exists for task', async () => {
|
|
23
|
-
const stats = await getTaskContextStats(taskId, testCwd);
|
|
24
|
-
expect(stats.retrievals).toBe(0);
|
|
25
|
-
expect(stats.candidateTokens).toBe(0);
|
|
26
|
-
expect(stats.potentialAvoidedTokens).toBe(0);
|
|
27
|
-
expect(stats.isEstimated).toBe(true);
|
|
28
|
-
});
|
|
29
|
-
it('calculates avoided context from recorded events', async () => {
|
|
30
|
-
await recordContextEvent({
|
|
31
|
-
taskId,
|
|
32
|
-
toolName: 'rigour_check_pattern',
|
|
33
|
-
cacheStatus: 'exact-hit',
|
|
34
|
-
candidateTokens: 10000,
|
|
35
|
-
returnedTokens: 2000,
|
|
36
|
-
deduplicatedTokens: 3000,
|
|
37
|
-
}, testCwd);
|
|
38
|
-
const stats = await getTaskContextStats(taskId, testCwd);
|
|
39
|
-
expect(stats.retrievals).toBe(1);
|
|
40
|
-
expect(stats.potentialAvoidedTokens).toBe(8000);
|
|
41
|
-
expect(stats.repeatedReadsPrevented).toBeGreaterThan(0);
|
|
42
|
-
expect(stats.deduplicatedTokens).toBe(3000);
|
|
43
|
-
expect(stats.isEstimated).toBe(false);
|
|
44
|
-
});
|
|
45
|
-
it('aggregates cache performance from events', async () => {
|
|
46
|
-
const cacheTaskId = `${taskId}-cache`;
|
|
47
|
-
await recordContextEvent({
|
|
48
|
-
taskId: cacheTaskId,
|
|
49
|
-
toolName: 'rigour_recall',
|
|
50
|
-
cacheStatus: 'exact-hit',
|
|
51
|
-
candidateTokens: 1000,
|
|
52
|
-
returnedTokens: 200,
|
|
53
|
-
}, testCwd);
|
|
54
|
-
await recordContextEvent({
|
|
55
|
-
taskId: cacheTaskId,
|
|
56
|
-
toolName: 'rigour_context_explain',
|
|
57
|
-
cacheStatus: 'miss',
|
|
58
|
-
candidateTokens: 500,
|
|
59
|
-
returnedTokens: 500,
|
|
60
|
-
}, testCwd);
|
|
61
|
-
const events = await import('../storage/context-telemetry.js').then(m => m.getContextEvents(cacheTaskId, testCwd));
|
|
62
|
-
const cache = await getCacheStats(testCwd);
|
|
63
|
-
const taskHits = events.filter(e => e.cacheStatus === 'exact-hit').length;
|
|
64
|
-
const taskMisses = events.filter(e => e.cacheStatus === 'miss').length;
|
|
65
|
-
expect(taskHits).toBe(1);
|
|
66
|
-
expect(taskMisses).toBe(1);
|
|
67
|
-
expect(cache.hitRate).toBe(0.5);
|
|
68
|
-
expect(cache.isEstimated).toBe(false);
|
|
69
|
-
});
|
|
70
|
-
it('imports CSV usage and marks cost stats as observed', async () => {
|
|
71
|
-
const csv = `Date,Model,Input Tokens,Output Tokens,Cost
|
|
72
|
-
2026-08-05,claude-3-5-sonnet,510000,62000,4.83`;
|
|
73
|
-
const imported = await importCursorUsageCsv(csv, testCwd);
|
|
74
|
-
expect(imported).toBe(1);
|
|
75
|
-
const cost = await getTaskCostStats('cursor-imported', testCwd);
|
|
76
|
-
expect(cost.actual.inputTokens).toBe(510000);
|
|
77
|
-
expect(cost.actual.costUsd).toBe(4.83);
|
|
78
|
-
expect(cost.actual.isEstimated).toBe(false);
|
|
79
|
-
expect(cost.actual.classification).toBe('observed');
|
|
80
|
-
});
|
|
81
|
-
it('deduplicates CSV rows on re-import', async () => {
|
|
82
|
-
const csv = `Date,Model,Input Tokens,Output Tokens,Cost
|
|
83
|
-
2026-08-05,claude-3-5-sonnet,1000,100,1.00`;
|
|
84
|
-
expect(await importCursorUsageCsv(csv, testCwd)).toBe(1);
|
|
85
|
-
expect(await importCursorUsageCsv(csv, testCwd)).toBe(0);
|
|
86
|
-
});
|
|
87
|
-
it('imports JSON usage records', async () => {
|
|
88
|
-
const jsonTaskId = `${taskId}-json-${Math.random().toString(36).slice(2)}`;
|
|
89
|
-
const imported = await importCursorUsageJson([{
|
|
90
|
-
taskId: jsonTaskId,
|
|
91
|
-
inputTokens: 2500,
|
|
92
|
-
outputTokens: 300,
|
|
93
|
-
observedCostUsd: 0.42,
|
|
94
|
-
model: 'gpt-4o',
|
|
95
|
-
}], testCwd);
|
|
96
|
-
expect(imported).toBe(1);
|
|
97
|
-
const cost = await getTaskCostStats(jsonTaskId, testCwd);
|
|
98
|
-
expect(cost.actual.inputTokens).toBe(2500);
|
|
99
|
-
});
|
|
100
|
-
it('imports Admin API JSON events with chargedCents and dedupes against sync IDs', async () => {
|
|
101
|
-
const adminEvent = {
|
|
102
|
-
timestamp: '1750979225854',
|
|
103
|
-
conversationId: 'conv-dedupe',
|
|
104
|
-
model: 'composer-2.5-standard',
|
|
105
|
-
chargedCents: 42.5,
|
|
106
|
-
tokenUsage: { inputTokens: 100, outputTokens: 50 },
|
|
107
|
-
};
|
|
108
|
-
expect(await importCursorUsageJson([adminEvent], testCwd)).toBe(1);
|
|
109
|
-
expect(await importCursorUsageJson([adminEvent], testCwd)).toBe(0);
|
|
110
|
-
const cost = await getTaskCostStats('conv-dedupe', testCwd);
|
|
111
|
-
expect(cost.actual.costUsd).toBeCloseTo(0.425, 3);
|
|
112
|
-
expect(cost.actual.isEstimated).toBe(false);
|
|
113
|
-
});
|
|
114
|
-
it('summarizes agent scope overlap from session file', async () => {
|
|
115
|
-
await fs.writeJson(path.join(testCwd, '.rigour', 'agent-session.json'), {
|
|
116
|
-
agents: [
|
|
117
|
-
{ agentId: 'agent-a', taskScope: ['src/api/**'] },
|
|
118
|
-
{ agentId: 'agent-b', taskScope: ['src/api/**', 'tests/**'] },
|
|
119
|
-
],
|
|
120
|
-
});
|
|
121
|
-
const scope = await getContextScopeSummary(testCwd);
|
|
122
|
-
expect(scope.activeAgentCount).toBe(2);
|
|
123
|
-
expect(scope.overlappingScopesResolved).toBeGreaterThan(0);
|
|
124
|
-
});
|
|
125
|
-
it('estimates governance rule token overhead from AGENTS.md', async () => {
|
|
126
|
-
await fs.writeFile(path.join(testCwd, 'AGENTS.md'), '# Agent rules\n'.repeat(40));
|
|
127
|
-
const scope = await getContextScopeSummary(testCwd);
|
|
128
|
-
expect(scope.alwaysOnRuleTokens).toBe(estimateTokenCount('# Agent rules\n'.repeat(40)));
|
|
129
|
-
});
|
|
130
|
-
it('aggregates checkpoint compression metrics', async () => {
|
|
131
|
-
const cpTaskId = `${taskId}-cp`;
|
|
132
|
-
await recordCheckpointMetric({
|
|
133
|
-
checkpointId: `checkpoint:${cpTaskId}:a1:phase`,
|
|
134
|
-
taskId: cpTaskId,
|
|
135
|
-
agentId: 'a1',
|
|
136
|
-
rawStateTokens: 10000,
|
|
137
|
-
checkpointTokens: 500,
|
|
138
|
-
replayTokensAvoided: 9500,
|
|
139
|
-
}, testCwd);
|
|
140
|
-
const summary = await getCheckpointSummary(cpTaskId, testCwd);
|
|
141
|
-
expect(summary.checkpointCount).toBe(1);
|
|
142
|
-
expect(summary.rawStateTokens).toBe(10000);
|
|
143
|
-
expect(summary.compressionRatio).toBe(20);
|
|
144
|
-
expect(summary.replayTokensAvoided).toBe(9500);
|
|
145
|
-
});
|
|
146
|
-
it('marks observed cost from usages and estimates avoided cost by model', async () => {
|
|
147
|
-
const jsonTaskId = `${taskId}-composer-${Math.random().toString(36).slice(2)}`;
|
|
148
|
-
await importCursorUsageJson([{
|
|
149
|
-
id: `json-composer-${jsonTaskId}`,
|
|
150
|
-
taskId: jsonTaskId,
|
|
151
|
-
inputTokens: 2_000_000,
|
|
152
|
-
outputTokens: 0,
|
|
153
|
-
observedCostUsd: 1.25,
|
|
154
|
-
model: 'composer-2.5-standard',
|
|
155
|
-
source: 'cursor-admin-api',
|
|
156
|
-
}], testCwd);
|
|
157
|
-
await recordContextEvent({
|
|
158
|
-
taskId: jsonTaskId,
|
|
159
|
-
toolName: 'rigour_context_scope',
|
|
160
|
-
cacheStatus: 'miss',
|
|
161
|
-
candidateTokens: 3_000_000,
|
|
162
|
-
returnedTokens: 1_000_000,
|
|
163
|
-
}, testCwd);
|
|
164
|
-
const cost = await getTaskCostStats(jsonTaskId, testCwd);
|
|
165
|
-
expect(cost.actual.isEstimated).toBe(false);
|
|
166
|
-
expect(cost.actual.costUsd).toBe(1.25);
|
|
167
|
-
expect(cost.estimated.isEstimated).toBe(true);
|
|
168
|
-
expect(cost.estimated.inputPricePerMillionUsd).toBe(0.5);
|
|
169
|
-
expect(cost.estimated.estimatedCostAvoidedUsd).toBeGreaterThan(0);
|
|
170
|
-
});
|
|
171
|
-
it('does not combine retrieval and checkpoint counterfactuals', async () => {
|
|
172
|
-
const id = `${taskId}-separate-${Math.random().toString(36).slice(2)}`;
|
|
173
|
-
await recordContextEvent({ taskId: id, toolName: 'rigour_context_scope', cacheStatus: 'miss', candidateTokens: 5000, returnedTokens: 1000 }, testCwd);
|
|
174
|
-
await recordCheckpointMetric({ checkpointId: `${id}-cp`, taskId: id, agentId: 'a1', rawStateTokens: 9000, checkpointTokens: 1000, replayTokensAvoided: 8000 }, testCwd);
|
|
175
|
-
const cost = await getTaskCostStats(id, testCwd);
|
|
176
|
-
expect(cost.estimated.retrievalAvoidedTokens).toBe(4000);
|
|
177
|
-
expect(cost.estimated.checkpointReplayAvoidedTokens).toBe(8000);
|
|
178
|
-
expect(cost.estimated.potentialContextAvoided).toBe(4000);
|
|
179
|
-
expect(cost.estimated.categoriesAreAdditive).toBe(false);
|
|
180
|
-
expect(cost.estimated.retrievalAvoidedCostRangeUsd?.max).toBeGreaterThan(cost.estimated.retrievalAvoidedCostRangeUsd?.min ?? 0);
|
|
181
|
-
});
|
|
182
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import fs from 'fs-extra';
|
|
5
|
-
vi.mock('../storage/db.js', () => ({
|
|
6
|
-
isSQLiteAvailable: () => false,
|
|
7
|
-
openDatabase: async () => null,
|
|
8
|
-
DB_PATH: '/tmp/rigour-test.db',
|
|
9
|
-
RIGOUR_DIR: '/tmp/.rigour',
|
|
10
|
-
}));
|
|
11
|
-
import { fetchCursorUsageFromAdminApi, syncCursorUsageFromAdminApi, getTaskCostStats, } from './context-telemetry-service.js';
|
|
12
|
-
import { getModelUsages } from '../storage/context-telemetry.js';
|
|
13
|
-
import { centsToUsd, normalizeCursorUsageEvent } from './cursor-usage-normalizer.js';
|
|
14
|
-
import { CURSOR_FILTERED_USAGE_URL } from './cursor-usage-client.js';
|
|
15
|
-
function buildUsageEvent(overrides = {}) {
|
|
16
|
-
return {
|
|
17
|
-
timestamp: '1750979225854',
|
|
18
|
-
userEmail: 'dev@example.com',
|
|
19
|
-
conversationId: 'conv-1',
|
|
20
|
-
model: 'composer-2.5-standard',
|
|
21
|
-
isTokenBasedCall: true,
|
|
22
|
-
tokenUsage: {
|
|
23
|
-
inputTokens: 100,
|
|
24
|
-
outputTokens: 50,
|
|
25
|
-
cacheWriteTokens: 10,
|
|
26
|
-
cacheReadTokens: 20,
|
|
27
|
-
totalCents: 12.5,
|
|
28
|
-
},
|
|
29
|
-
chargedCents: 15.25,
|
|
30
|
-
...overrides,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
describe('cursor admin usage sync', () => {
|
|
34
|
-
const testCwd = path.join(os.tmpdir(), `rigour-cursor-sync-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
35
|
-
const apiKey = 'test-cursor-admin-key';
|
|
36
|
-
beforeEach(async () => {
|
|
37
|
-
await fs.ensureDir(path.join(testCwd, '.rigour'));
|
|
38
|
-
});
|
|
39
|
-
afterEach(async () => {
|
|
40
|
-
await fs.remove(testCwd);
|
|
41
|
-
vi.restoreAllMocks();
|
|
42
|
-
});
|
|
43
|
-
it('imports a single-page response with basic auth header', async () => {
|
|
44
|
-
const fetchImpl = vi.fn(async (_url, init) => ({
|
|
45
|
-
ok: true,
|
|
46
|
-
status: 200,
|
|
47
|
-
json: async () => ({
|
|
48
|
-
usageEvents: [buildUsageEvent()],
|
|
49
|
-
pagination: { hasNextPage: false, currentPage: 1 },
|
|
50
|
-
}),
|
|
51
|
-
}));
|
|
52
|
-
const imported = await fetchCursorUsageFromAdminApi({
|
|
53
|
-
cwd: testCwd,
|
|
54
|
-
apiKey,
|
|
55
|
-
fetchImpl,
|
|
56
|
-
});
|
|
57
|
-
expect(imported).toBe(1);
|
|
58
|
-
expect(fetchImpl).toHaveBeenCalledTimes(1);
|
|
59
|
-
const [, init] = fetchImpl.mock.calls[0];
|
|
60
|
-
expect(init.method).toBe('POST');
|
|
61
|
-
expect(init.headers.Authorization).toBe(`Basic ${Buffer.from(`${apiKey}:`, 'utf8').toString('base64')}`);
|
|
62
|
-
expect(init.headers['Content-Type']).toBe('application/json');
|
|
63
|
-
const body = JSON.parse(init.body);
|
|
64
|
-
expect(body.pageSize).toBeGreaterThan(0);
|
|
65
|
-
expect(body.startDate).toBeLessThan(body.endDate);
|
|
66
|
-
});
|
|
67
|
-
it('paginates until hasNextPage is false', async () => {
|
|
68
|
-
let page = 0;
|
|
69
|
-
const fetchImpl = vi.fn(async () => {
|
|
70
|
-
page += 1;
|
|
71
|
-
return {
|
|
72
|
-
ok: true,
|
|
73
|
-
status: 200,
|
|
74
|
-
json: async () => ({
|
|
75
|
-
usageEvents: [buildUsageEvent({ conversationId: `conv-${page}` })],
|
|
76
|
-
pagination: { hasNextPage: page < 2, currentPage: page },
|
|
77
|
-
}),
|
|
78
|
-
};
|
|
79
|
-
});
|
|
80
|
-
const result = await syncCursorUsageFromAdminApi({ cwd: testCwd, apiKey, fetchImpl });
|
|
81
|
-
expect(result.importedCount).toBe(2);
|
|
82
|
-
expect(result.totalEvents).toBe(2);
|
|
83
|
-
expect(fetchImpl).toHaveBeenCalledTimes(2);
|
|
84
|
-
});
|
|
85
|
-
it('converts cents to USD and stores token fields', async () => {
|
|
86
|
-
const normalized = normalizeCursorUsageEvent(buildUsageEvent());
|
|
87
|
-
expect(normalized?.usage.observedCostUsd).toBe(centsToUsd(15.25));
|
|
88
|
-
expect(normalized?.usage.inputTokens).toBe(100);
|
|
89
|
-
expect(normalized?.usage.outputTokens).toBe(50);
|
|
90
|
-
expect(normalized?.usage.cachedInputTokens).toBe(20);
|
|
91
|
-
expect(normalized?.usage.cacheWriteTokens).toBe(10);
|
|
92
|
-
expect(normalized?.usage.source).toBe('cursor-admin-api');
|
|
93
|
-
});
|
|
94
|
-
it('deduplicates repeated syncs', async () => {
|
|
95
|
-
const fetchImpl = vi.fn(async () => ({
|
|
96
|
-
ok: true,
|
|
97
|
-
status: 200,
|
|
98
|
-
json: async () => ({
|
|
99
|
-
usageEvents: [buildUsageEvent()],
|
|
100
|
-
pagination: { hasNextPage: false },
|
|
101
|
-
}),
|
|
102
|
-
}));
|
|
103
|
-
const first = await fetchCursorUsageFromAdminApi({ cwd: testCwd, apiKey, fetchImpl });
|
|
104
|
-
const second = await fetchCursorUsageFromAdminApi({ cwd: testCwd, apiKey, fetchImpl });
|
|
105
|
-
expect(first).toBe(1);
|
|
106
|
-
expect(second).toBe(0);
|
|
107
|
-
const usages = await getModelUsages(undefined, testCwd);
|
|
108
|
-
expect(usages.filter(u => u.source === 'cursor-admin-api')).toHaveLength(1);
|
|
109
|
-
});
|
|
110
|
-
it('surfaces actionable non-2xx errors without exposing the API key', async () => {
|
|
111
|
-
const fetchImpl = vi.fn(async () => ({
|
|
112
|
-
ok: false,
|
|
113
|
-
status: 401,
|
|
114
|
-
text: async () => `Unauthorized for key ${apiKey}`,
|
|
115
|
-
}));
|
|
116
|
-
await expect(fetchCursorUsageFromAdminApi({ cwd: testCwd, apiKey, fetchImpl })).rejects.toThrow(/401/);
|
|
117
|
-
await expect(fetchCursorUsageFromAdminApi({ cwd: testCwd, apiKey, fetchImpl })).rejects.not.toThrow(apiKey);
|
|
118
|
-
});
|
|
119
|
-
it('skips malformed events and handles partial fields', async () => {
|
|
120
|
-
const fetchImpl = vi.fn(async () => ({
|
|
121
|
-
ok: true,
|
|
122
|
-
status: 200,
|
|
123
|
-
json: async () => ({
|
|
124
|
-
usageEvents: [
|
|
125
|
-
null,
|
|
126
|
-
{ model: 'composer-2.5-fast', chargedCents: 5 },
|
|
127
|
-
buildUsageEvent(),
|
|
128
|
-
],
|
|
129
|
-
pagination: { hasNextPage: false },
|
|
130
|
-
}),
|
|
131
|
-
}));
|
|
132
|
-
const imported = await fetchCursorUsageFromAdminApi({ cwd: testCwd, apiKey, fetchImpl });
|
|
133
|
-
expect(imported).toBe(2);
|
|
134
|
-
});
|
|
135
|
-
it('marks observed cost stats from imported admin API events', async () => {
|
|
136
|
-
const fetchImpl = vi.fn(async () => ({
|
|
137
|
-
ok: true,
|
|
138
|
-
status: 200,
|
|
139
|
-
json: async () => ({
|
|
140
|
-
usageEvents: [buildUsageEvent({ conversationId: 'cost-task' })],
|
|
141
|
-
pagination: { hasNextPage: false },
|
|
142
|
-
}),
|
|
143
|
-
}));
|
|
144
|
-
await fetchCursorUsageFromAdminApi({ cwd: testCwd, apiKey, fetchImpl });
|
|
145
|
-
const cost = await getTaskCostStats('cost-task', testCwd);
|
|
146
|
-
expect(cost.actual.isEstimated).toBe(false);
|
|
147
|
-
expect(cost.actual.costUsd).toBeGreaterThan(0);
|
|
148
|
-
expect(cost.actual.pricingBasis).toBe('observed-cursor-charged-cents');
|
|
149
|
-
});
|
|
150
|
-
it('targets the filtered usage events endpoint', () => {
|
|
151
|
-
expect(CURSOR_FILTERED_USAGE_URL).toBe('https://api.cursor.com/teams/filtered-usage-events');
|
|
152
|
-
});
|
|
153
|
-
it('errors when pagination exceeds the defensive page limit', async () => {
|
|
154
|
-
let page = 0;
|
|
155
|
-
const fetchImpl = vi.fn(async () => {
|
|
156
|
-
page += 1;
|
|
157
|
-
return {
|
|
158
|
-
ok: true,
|
|
159
|
-
status: 200,
|
|
160
|
-
json: async () => ({
|
|
161
|
-
usageEvents: [buildUsageEvent({ conversationId: `conv-page-${page}` })],
|
|
162
|
-
pagination: { hasNextPage: true, currentPage: page },
|
|
163
|
-
}),
|
|
164
|
-
};
|
|
165
|
-
});
|
|
166
|
-
await expect(fetchCursorUsageFromAdminApi({
|
|
167
|
-
cwd: testCwd,
|
|
168
|
-
apiKey,
|
|
169
|
-
fetchImpl,
|
|
170
|
-
maxPages: 2,
|
|
171
|
-
})).rejects.toThrow(/truncated after 2 pages/);
|
|
172
|
-
});
|
|
173
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|