@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,211 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Staleness Detector Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for the staleness detection system.
|
|
5
|
-
*/
|
|
6
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
7
|
-
import * as fs from 'fs/promises';
|
|
8
|
-
import * as path from 'path';
|
|
9
|
-
import * as os from 'os';
|
|
10
|
-
import { StalenessDetector, checkCodeStaleness } from './staleness.js';
|
|
11
|
-
describe('StalenessDetector', () => {
|
|
12
|
-
let testDir;
|
|
13
|
-
beforeEach(async () => {
|
|
14
|
-
testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-staleness-'));
|
|
15
|
-
});
|
|
16
|
-
afterEach(async () => {
|
|
17
|
-
await fs.rm(testDir, { recursive: true, force: true });
|
|
18
|
-
});
|
|
19
|
-
describe('React deprecations', () => {
|
|
20
|
-
it('should detect componentWillMount', async () => {
|
|
21
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: { react: '^18.0.0' } }));
|
|
22
|
-
const detector = new StalenessDetector(testDir);
|
|
23
|
-
const result = await detector.checkStaleness(`
|
|
24
|
-
class MyComponent extends React.Component {
|
|
25
|
-
componentWillMount() {
|
|
26
|
-
console.log('mounting');
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
`);
|
|
30
|
-
expect(result.status).toBe('DEPRECATED');
|
|
31
|
-
expect(result.issues.some(i => i.pattern.includes('componentWillMount'))).toBe(true);
|
|
32
|
-
expect(result.issues[0].replacement).toContain('useEffect');
|
|
33
|
-
});
|
|
34
|
-
it('should detect ReactDOM.render', async () => {
|
|
35
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: { react: '^18.0.0', 'react-dom': '^18.0.0' } }));
|
|
36
|
-
const detector = new StalenessDetector(testDir);
|
|
37
|
-
const result = await detector.checkStaleness(`
|
|
38
|
-
ReactDOM.render(<App />, document.getElementById('root'));
|
|
39
|
-
`);
|
|
40
|
-
expect(result.status).toBe('DEPRECATED');
|
|
41
|
-
expect(result.issues.some(i => i.pattern.includes('ReactDOM.render'))).toBe(true);
|
|
42
|
-
expect(result.issues[0].replacement).toContain('createRoot');
|
|
43
|
-
});
|
|
44
|
-
it('should not flag React deprecations if React is not installed', async () => {
|
|
45
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: { express: '^4.0.0' } }));
|
|
46
|
-
const detector = new StalenessDetector(testDir);
|
|
47
|
-
const result = await detector.checkStaleness(`
|
|
48
|
-
componentWillMount() {}
|
|
49
|
-
`);
|
|
50
|
-
// Should not flag React-specific patterns if React isn't a dependency
|
|
51
|
-
expect(result.issues.filter(i => i.pattern.includes('componentWillMount'))).toHaveLength(0);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
describe('Package deprecations', () => {
|
|
55
|
-
it('should detect moment.js usage', async () => {
|
|
56
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
|
|
57
|
-
const detector = new StalenessDetector(testDir);
|
|
58
|
-
const result = await detector.checkStaleness(`
|
|
59
|
-
import moment from 'moment';
|
|
60
|
-
const date = moment().format('YYYY-MM-DD');
|
|
61
|
-
`);
|
|
62
|
-
expect(result.status).toBe('STALE');
|
|
63
|
-
expect(result.issues.some(i => i.replacement.includes('date-fns'))).toBe(true);
|
|
64
|
-
});
|
|
65
|
-
it('should detect request library usage', async () => {
|
|
66
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
|
|
67
|
-
const detector = new StalenessDetector(testDir);
|
|
68
|
-
const result = await detector.checkStaleness(`
|
|
69
|
-
const request = require('request');
|
|
70
|
-
request.get('https://example.com');
|
|
71
|
-
`);
|
|
72
|
-
expect(result.status).toBe('DEPRECATED');
|
|
73
|
-
expect(result.issues.some(i => i.replacement.includes('fetch'))).toBe(true);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
describe('JavaScript deprecations', () => {
|
|
77
|
-
it('should detect var keyword usage', async () => {
|
|
78
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
|
|
79
|
-
const detector = new StalenessDetector(testDir);
|
|
80
|
-
const result = await detector.checkStaleness(`
|
|
81
|
-
var name = 'test';
|
|
82
|
-
var count = 0;
|
|
83
|
-
`);
|
|
84
|
-
expect(result.issues.some(i => i.replacement.includes('const') || i.replacement.includes('let'))).toBe(true);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
describe('Redux deprecations', () => {
|
|
88
|
-
it('should detect createStore usage in modern Redux projects', async () => {
|
|
89
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: { redux: '^4.2.0' } }));
|
|
90
|
-
const detector = new StalenessDetector(testDir);
|
|
91
|
-
const result = await detector.checkStaleness(`
|
|
92
|
-
import { createStore } from 'redux';
|
|
93
|
-
const store = createStore(reducer);
|
|
94
|
-
`);
|
|
95
|
-
expect(result.status).toBe('STALE');
|
|
96
|
-
expect(result.issues.some(i => i.replacement.includes('configureStore'))).toBe(true);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
describe('Node.js deprecations', () => {
|
|
100
|
-
it('should detect Buffer constructor', async () => {
|
|
101
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
|
|
102
|
-
const detector = new StalenessDetector(testDir);
|
|
103
|
-
const result = await detector.checkStaleness(`
|
|
104
|
-
const buf = new Buffer('hello');
|
|
105
|
-
`);
|
|
106
|
-
expect(result.status).toBe('DEPRECATED');
|
|
107
|
-
expect(result.issues.some(i => i.replacement.includes('Buffer.from'))).toBe(true);
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
describe('Next.js deprecations', () => {
|
|
111
|
-
it('should detect getInitialProps in Next 13+ projects', async () => {
|
|
112
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: { next: '^13.0.0' } }));
|
|
113
|
-
const detector = new StalenessDetector(testDir);
|
|
114
|
-
const result = await detector.checkStaleness(`
|
|
115
|
-
MyPage.getInitialProps = async (ctx) => {
|
|
116
|
-
return { data: {} };
|
|
117
|
-
};
|
|
118
|
-
`);
|
|
119
|
-
expect(result.issues.some(i => i.pattern.includes('getInitialProps'))).toBe(true);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
describe('TypeScript best practices', () => {
|
|
123
|
-
it('should warn about enum usage', async () => {
|
|
124
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
|
|
125
|
-
const detector = new StalenessDetector(testDir);
|
|
126
|
-
const result = await detector.checkStaleness(`
|
|
127
|
-
enum Status {
|
|
128
|
-
Active = 'active',
|
|
129
|
-
Inactive = 'inactive'
|
|
130
|
-
}
|
|
131
|
-
`);
|
|
132
|
-
expect(result.issues.some(i => i.severity === 'info' && i.pattern.includes('enum'))).toBe(true);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
describe('Project context', () => {
|
|
136
|
-
it('should return project versions in context', async () => {
|
|
137
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({
|
|
138
|
-
dependencies: {
|
|
139
|
-
react: '^18.2.0',
|
|
140
|
-
next: '^14.0.0'
|
|
141
|
-
},
|
|
142
|
-
devDependencies: {
|
|
143
|
-
typescript: '^5.0.0'
|
|
144
|
-
}
|
|
145
|
-
}));
|
|
146
|
-
const detector = new StalenessDetector(testDir);
|
|
147
|
-
const result = await detector.checkStaleness('const x = 1;');
|
|
148
|
-
expect(result.projectContext.react).toBeDefined();
|
|
149
|
-
expect(result.projectContext.next).toBeDefined();
|
|
150
|
-
expect(result.projectContext.typescript).toBeDefined();
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
describe('Custom deprecations', () => {
|
|
154
|
-
it('should allow adding custom deprecation rules', async () => {
|
|
155
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
|
|
156
|
-
const detector = new StalenessDetector(testDir);
|
|
157
|
-
detector.addDeprecation({
|
|
158
|
-
pattern: 'legacyFunction',
|
|
159
|
-
deprecatedIn: '1.0.0',
|
|
160
|
-
replacement: 'newFunction()',
|
|
161
|
-
severity: 'error',
|
|
162
|
-
reason: 'Custom deprecation'
|
|
163
|
-
});
|
|
164
|
-
const result = await detector.checkStaleness(`
|
|
165
|
-
legacyFunction();
|
|
166
|
-
`);
|
|
167
|
-
expect(result.issues.some(i => i.replacement === 'newFunction()')).toBe(true);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
describe('Overall status', () => {
|
|
171
|
-
it('should return FRESH when no issues found', async () => {
|
|
172
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
|
|
173
|
-
const detector = new StalenessDetector(testDir);
|
|
174
|
-
const result = await detector.checkStaleness(`
|
|
175
|
-
const name = 'test';
|
|
176
|
-
const greet = () => console.log('Hello');
|
|
177
|
-
`);
|
|
178
|
-
expect(result.status).toBe('FRESH');
|
|
179
|
-
});
|
|
180
|
-
it('should return STALE for warnings', async () => {
|
|
181
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: { redux: '^4.2.0' } }));
|
|
182
|
-
const detector = new StalenessDetector(testDir);
|
|
183
|
-
const result = await detector.checkStaleness(`
|
|
184
|
-
createStore(reducer);
|
|
185
|
-
`);
|
|
186
|
-
expect(result.status).toBe('STALE');
|
|
187
|
-
});
|
|
188
|
-
it('should return DEPRECATED for errors', async () => {
|
|
189
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
|
|
190
|
-
const detector = new StalenessDetector(testDir);
|
|
191
|
-
const result = await detector.checkStaleness(`
|
|
192
|
-
new Buffer('hello');
|
|
193
|
-
`);
|
|
194
|
-
expect(result.status).toBe('DEPRECATED');
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
describe('checkCodeStaleness', () => {
|
|
199
|
-
let testDir;
|
|
200
|
-
beforeEach(async () => {
|
|
201
|
-
testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-staleness-'));
|
|
202
|
-
await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
|
|
203
|
-
});
|
|
204
|
-
afterEach(async () => {
|
|
205
|
-
await fs.rm(testDir, { recursive: true, force: true });
|
|
206
|
-
});
|
|
207
|
-
it('should be a quick helper for staleness checking', async () => {
|
|
208
|
-
const result = await checkCodeStaleness(testDir, 'const x = 1;');
|
|
209
|
-
expect(result.status).toBe('FRESH');
|
|
210
|
-
});
|
|
211
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,32 +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 { listResolvedFixes, openFindingCount, recordReviewOutcome } from './agent-fixes.js';
|
|
6
|
-
const finding = (file) => ({ id: 'semantic-bugs', title: 't', details: 'd', files: [file], line: 2 });
|
|
7
|
-
describe('recordReviewOutcome', () => {
|
|
8
|
-
let cwd;
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-fixes-'));
|
|
11
|
-
fs.writeFileSync(path.join(cwd, 'a.ts'), 'before\n');
|
|
12
|
-
});
|
|
13
|
-
afterEach(() => { fs.rmSync(cwd, { recursive: true, force: true }); });
|
|
14
|
-
it('stores the before and after once a reviewed file no longer has the finding', () => {
|
|
15
|
-
expect(recordReviewOutcome(cwd, [finding('a.ts')], ['a.ts'])).toMatchObject({ opened: 1, resolved: 0 });
|
|
16
|
-
fs.writeFileSync(path.join(cwd, 'a.ts'), 'after\n');
|
|
17
|
-
expect(recordReviewOutcome(cwd, [], ['a.ts'])).toMatchObject({ opened: 0, resolved: 1 });
|
|
18
|
-
const [fix] = listResolvedFixes(cwd);
|
|
19
|
-
expect(fix).toMatchObject({ file: 'a.ts', rule: 'semantic-bugs', before: 'before\n', after: 'after\n' });
|
|
20
|
-
expect(openFindingCount(cwd)).toBe(0);
|
|
21
|
-
});
|
|
22
|
-
it('keeps a finding open when its file was not part of the review (committed, not fixed)', () => {
|
|
23
|
-
recordReviewOutcome(cwd, [finding('a.ts')], ['a.ts']);
|
|
24
|
-
expect(recordReviewOutcome(cwd, [], ['other.ts'])).toMatchObject({ opened: 0, resolved: 0 });
|
|
25
|
-
expect(openFindingCount(cwd)).toBe(1);
|
|
26
|
-
});
|
|
27
|
-
it('does not count a finding that vanished without an edit as a fix', () => {
|
|
28
|
-
recordReviewOutcome(cwd, [finding('a.ts')], ['a.ts']);
|
|
29
|
-
expect(recordReviewOutcome(cwd, [], ['a.ts'])).toMatchObject({ opened: 0, resolved: 0 });
|
|
30
|
-
expect(listResolvedFixes(cwd)).toEqual([]);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from 'child_process';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
6
|
-
import { ConfigSchema } from '../types/index.js';
|
|
7
|
-
import { ledgerProblems, loadLedger, LEDGER_PATH, LedgerSchema } from './backtest.js';
|
|
8
|
-
import { scaffoldLedger } from './backtest-init.js';
|
|
9
|
-
let repo;
|
|
10
|
-
const git = (...args) => execFileSync('git', ['-C', repo, ...args], { encoding: 'utf8' }).trim();
|
|
11
|
-
const config = ConfigSchema.parse({ version: 1 });
|
|
12
|
-
const REVIEWS = [
|
|
13
|
-
{ id: 1, user: { login: 'ci-bot', type: 'Bot' }, body: 'automated', state: 'COMMENTED', submitted_at: '2026-04-12T10:00:00Z', commit_id: 'c1c1c1c1c1' },
|
|
14
|
-
{ id: 2, user: { login: 'senior', type: 'User' }, body: '## Review\n\n- A retry posts the invoice twice.\n- **Quadratic** scan in `latestPayment`.\nlooks good otherwise', state: 'CHANGES_REQUESTED', submitted_at: '2026-04-12T12:00:00Z', commit_id: 'c2c2c2c2c2' },
|
|
15
|
-
];
|
|
16
|
-
const INLINE = [
|
|
17
|
-
{ path: 'src/lib/server/invoices.ts', line: 40, body: 'The export reads every line item for ids.\n\nNarrow it.', created_at: '2026-04-12T12:00:00Z' },
|
|
18
|
-
{ path: 'src/checkout/total.ts', line: null, original_line: null, body: 'Drops the currency', created_at: '2026-04-12T12:00:00Z' },
|
|
19
|
-
];
|
|
20
|
-
function fakeExec(calls) {
|
|
21
|
-
return async (command, args, options) => {
|
|
22
|
-
calls.push([command, ...args]);
|
|
23
|
-
if (command === 'git') {
|
|
24
|
-
try {
|
|
25
|
-
return { exitCode: 0, stdout: execFileSync('git', args, { cwd: options.cwd, encoding: 'utf8', stdio: 'pipe' }), stderr: '' };
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
return { exitCode: error.status ?? 1, stdout: '', stderr: String(error.stderr ?? '') };
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (args[0] === 'auth')
|
|
32
|
-
return { exitCode: 0, stdout: 'token\n', stderr: '' };
|
|
33
|
-
if (args[0] === 'pr')
|
|
34
|
-
return { exitCode: 0, stdout: 'author\n', stderr: '' };
|
|
35
|
-
if (args[1].endsWith('/reviews'))
|
|
36
|
-
return { exitCode: 0, stdout: JSON.stringify(REVIEWS), stderr: '' };
|
|
37
|
-
return { exitCode: 0, stdout: JSON.stringify(INLINE), stderr: '' };
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
repo = fs.mkdtempSync(path.join(os.tmpdir(), 'backtest-init-'));
|
|
42
|
-
git('init', '-q', '-b', 'main');
|
|
43
|
-
git('config', 'user.email', 't@example.com');
|
|
44
|
-
git('config', 'user.name', 't');
|
|
45
|
-
git('config', 'commit.gpgsign', 'false');
|
|
46
|
-
fs.writeFileSync(path.join(repo, 'a.ts'), 'export const a = 1;\n');
|
|
47
|
-
git('add', '-A');
|
|
48
|
-
execFileSync('git', ['-C', repo, 'commit', '-qm', 'main as of the review'], { env: { ...process.env, GIT_COMMITTER_DATE: '2026-04-12T09:00:00Z', GIT_AUTHOR_DATE: '2026-04-12T09:00:00Z' } });
|
|
49
|
-
});
|
|
50
|
-
afterEach(() => { fs.rmSync(repo, { recursive: true, force: true }); });
|
|
51
|
-
describe('rigour backtest init', () => {
|
|
52
|
-
it('takes the base from where the reviewed commit left main, not from main as of the review', async () => {
|
|
53
|
-
const forked = git('rev-parse', 'HEAD');
|
|
54
|
-
git('checkout', '-q', '-b', 'feature');
|
|
55
|
-
fs.writeFileSync(path.join(repo, 'b.ts'), 'export const b = 1;\n');
|
|
56
|
-
git('add', '-A');
|
|
57
|
-
execFileSync('git', ['-C', repo, 'commit', '-qm', 'the branch'], { env: { ...process.env, GIT_COMMITTER_DATE: '2026-04-12T09:30:00Z', GIT_AUTHOR_DATE: '2026-04-12T09:30:00Z' } });
|
|
58
|
-
const reviewed = git('rev-parse', 'HEAD');
|
|
59
|
-
git('checkout', '-q', 'main');
|
|
60
|
-
fs.writeFileSync(path.join(repo, 'c.ts'), 'export const c = 1;\n');
|
|
61
|
-
git('add', '-A');
|
|
62
|
-
execFileSync('git', ['-C', repo, 'commit', '-qm', 'main moves on before the review'], { env: { ...process.env, GIT_COMMITTER_DATE: '2026-04-12T11:00:00Z', GIT_AUTHOR_DATE: '2026-04-12T11:00:00Z' } });
|
|
63
|
-
const realCommit = (calls) => async (command, args, options) => {
|
|
64
|
-
const answer = await fakeExec(calls)(command, args, options);
|
|
65
|
-
return command === 'gh' && args[1]?.endsWith('/reviews') ? { ...answer, stdout: JSON.stringify(REVIEWS.map(r => ({ ...r, commit_id: reviewed }))) } : answer;
|
|
66
|
-
};
|
|
67
|
-
const { rounds } = await scaffoldLedger(repo, 212, config, realCommit([]));
|
|
68
|
-
expect(rounds[0].base).toBe(forked);
|
|
69
|
-
});
|
|
70
|
-
it('writes one round per human review: inline comments become file-and-window points, body bullets need a pattern', async () => {
|
|
71
|
-
const calls = [];
|
|
72
|
-
const mainAtReview = git('rev-parse', 'HEAD');
|
|
73
|
-
const { rounds, incomplete } = await scaffoldLedger(repo, 212, config, fakeExec(calls));
|
|
74
|
-
expect(rounds).toHaveLength(1);
|
|
75
|
-
expect(rounds[0]).toMatchObject({ id: 'pr212-r1', commit: 'c2c2c2c2c2', base: mainAtReview, reviewed_at: '2026-04-12T12:00:00Z', pr: 212 });
|
|
76
|
-
expect(rounds[0].points).toEqual([
|
|
77
|
-
{ id: 'R1-1', point: 'The export reads every line item for ids.', file: 'src/lib/server/invoices\\.ts', lines: [30, 50] },
|
|
78
|
-
{ id: 'R1-2', point: 'Drops the currency', file: 'src/checkout/total\\.ts', needs: 'a line window or a text pattern (the comment has no line)' },
|
|
79
|
-
{ id: 'R1-B1', point: 'A retry posts the invoice twice.', file: '', needs: 'a file pattern and a text pattern (this point was made in the review body, not on a line)' },
|
|
80
|
-
{ id: 'R1-B2', point: 'Quadratic scan in latestPayment.', file: '', needs: 'a file pattern and a text pattern (this point was made in the review body, not on a line)' },
|
|
81
|
-
]);
|
|
82
|
-
expect(incomplete).toBe(3);
|
|
83
|
-
expect(calls.find(c => c[0] === 'gh' && c[1] === 'pr')).toContain('212');
|
|
84
|
-
const written = LedgerSchema.parse(JSON.parse(fs.readFileSync(path.join(repo, LEDGER_PATH), 'utf8')));
|
|
85
|
-
expect(ledgerProblems(written)).toHaveLength(3);
|
|
86
|
-
expect(() => loadLedger(repo)).toThrow('rows a person still has to complete');
|
|
87
|
-
});
|
|
88
|
-
it('keeps rounds already in the ledger and replaces its own', async () => {
|
|
89
|
-
fs.mkdirSync(path.join(repo, '.rigour'));
|
|
90
|
-
fs.writeFileSync(path.join(repo, LEDGER_PATH), JSON.stringify({ rounds: [
|
|
91
|
-
{ id: 'pr212-r1', commit: 'old0000', base: 'main', points: [], must_not_flag: [] },
|
|
92
|
-
{ id: 'pr7-r1', commit: 'other00', base: 'main', points: [{ id: 'P', point: 'p', file: 'x', text: 'x' }], must_not_flag: [] },
|
|
93
|
-
] }));
|
|
94
|
-
await scaffoldLedger(repo, 212, config, fakeExec([]));
|
|
95
|
-
const written = LedgerSchema.parse(JSON.parse(fs.readFileSync(path.join(repo, LEDGER_PATH), 'utf8')));
|
|
96
|
-
expect(written.rounds.map(r => [r.id, r.commit])).toEqual([['pr7-r1', 'other00'], ['pr212-r1', 'c2c2c2c2c2']]);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { formatJudges, judgedFrom } from './backtest-judges.js';
|
|
3
|
-
const base = { outcome: 'findings', items: [], unverified: [], resolved: [], answerInReply: [], notes: [], advisory: [], disputed: [], dropped: [], dismissed: [], reviewers: ['claude', 'codex'], cached: false };
|
|
4
|
-
describe('measuring each judge', () => {
|
|
5
|
-
it('computes Cohen\'s kappa between two judges: 1 in full agreement, 0 at chance, and says when they share blind spots', () => {
|
|
6
|
-
const round = (claude, codex) => ({ judges: { judges: ['claude', 'codex'], caught: { claude, codex }, points: ['p1', 'p2', 'p3', 'p4'], runs: 2 }, points: [] });
|
|
7
|
-
expect(formatJudges([round(['p1', 'p3'], ['p1', 'p3'])])).toContain('kappa 1.00 over 4 point(s) (they share blind spots: a second judge adds little)');
|
|
8
|
-
expect(formatJudges([round(['p1', 'p2'], ['p1', 'p3'])])).toContain('kappa 0.00 over 4 point(s)');
|
|
9
|
-
expect(formatJudges([round(['p1'], ['p2'])])).toMatch(/kappa -0\.\d\d over 4/);
|
|
10
|
-
});
|
|
11
|
-
it('takes each judge\'s own item from the panel, including what it raised in its own words', () => {
|
|
12
|
-
const lock = { id: 'a', kind: 'finding', class: 'correctness', file: 'src/job.ts', line: 2, issue: 'returns before the lock' };
|
|
13
|
-
const judged = judgedFrom({ ...base, costUsd: 2, runs: 3, panel: [
|
|
14
|
-
{ item: lock, judges: ['claude', 'codex'], calls: { claude: 'raised', codex: 'raised' }, status: 'confirmed', members: [{ judge: 'claude', item: lock }, { judge: 'codex', item: { ...lock, id: 'b', line: 3, issue: 'lock taken late' } }] },
|
|
15
|
-
] });
|
|
16
|
-
expect(judged).toEqual({ judges: ['claude', 'codex'], costUsd: 2, runs: 3, raised: [
|
|
17
|
-
{ judge: 'claude', file: 'src/job.ts', line: 2, text: 'returns before the lock' },
|
|
18
|
-
{ judge: 'codex', file: 'src/job.ts', line: 3, text: 'lock taken late' },
|
|
19
|
-
] });
|
|
20
|
-
expect(judgedFrom({ ...base, reviewers: ['claude'] })).toBeUndefined();
|
|
21
|
-
});
|
|
22
|
-
it('reports each judge, what only it found, each pair\'s agreement and the cost of a caught point', () => {
|
|
23
|
-
const round = (caught) => ({ judges: { judges: ['claude', 'codex'], caught, points: ['p1', 'p2', 'p3', 'p4'], runs: 3, costUsd: 3 }, points: ['p1', 'p2', 'p3', 'p4'].map(id => ({ id, caught: Object.values(caught).some(ids => ids.includes(id)) })) });
|
|
24
|
-
const report = formatJudges([round({ claude: ['p1', 'p2'], codex: ['p1', 'p3'] }), round({ claude: ['p1'], codex: ['p1'] })]);
|
|
25
|
-
expect(report).toContain('Judges (2 round(s), 8 human point(s))');
|
|
26
|
-
expect(report).toContain('claude: raised 3/8; 1 that no other judge raised');
|
|
27
|
-
expect(report).toContain('codex: raised 3/8; 1 that no other judge raised');
|
|
28
|
-
expect(report).toMatch(/claude and codex: kappa 0\.\d\d over 8 point\(s\)/);
|
|
29
|
-
expect(report).toContain('6 agent run(s), $6.00 recorded, $1.50 per caught point');
|
|
30
|
-
expect(formatJudges([{ points: [] }])).toBe('');
|
|
31
|
-
});
|
|
32
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from 'child_process';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
6
|
-
import { ConfigSchema } from '../types/index.js';
|
|
7
|
-
import { backtestLast, formatLast, scoreLast, LAST_LEDGER_PATH } from './backtest-last.js';
|
|
8
|
-
import { roundsForPr } from './backtest-init.js';
|
|
9
|
-
const config = ConfigSchema.parse({ version: 1 });
|
|
10
|
-
const item = (file, line, blocking = true, gate = 'reviewer:claude') => ({ gate, file, line, text: `${file} scan has no upper bound`, blocking });
|
|
11
|
-
const result = (round, items, caught = 0, costUsd) => ({ round, head: 'h', base: 'b', points: Array.from({ length: caught }, (_, i) => ({ id: `p${i}`, point: 'p', caught: true, noted: false })), falseBlocks: [], items, durationMs: 1000, ...(costUsd !== undefined ? { costUsd } : {}) });
|
|
12
|
-
describe('the front door report', () => {
|
|
13
|
-
const ledger = { rounds: [
|
|
14
|
-
{ id: 'pr7-r1', commit: 'c', base: 'b', reviewed_at: '2026-04-12T12:00:00Z', pr: 7, points: [{ id: 'R1-1', point: 'drops the currency', file: 'src/b\\.ts', lines: [1, 11] }], must_not_flag: [] },
|
|
15
|
-
{ id: 'pr7-r2', commit: 'c', base: 'b', reviewed_at: '2026-04-12T13:00:00Z', pr: 7, points: [{ id: 'R2-1', point: 'the export reads every line item', file: 'src/a\\.ts', lines: [1, 11] }], must_not_flag: [] },
|
|
16
|
-
{ id: 'pr7-r3', commit: 'c', base: 'b', reviewed_at: '2026-04-12T14:00:00Z', pr: 7, points: [{ id: 'R3-1', point: 'still unbounded', file: 'src/a\\.ts', lines: [1, 11] }], must_not_flag: [] },
|
|
17
|
-
{ id: 'pr7-approved', commit: 'c', base: 'b', reviewed_at: '2026-04-12T15:00:00Z', pr: 7, points: [], must_not_flag: [] },
|
|
18
|
-
{ id: 'pr8-approved', commit: 'c', base: 'b', reviewed_at: '2026-04-13T15:00:00Z', pr: 8, points: [], must_not_flag: [] },
|
|
19
|
-
] };
|
|
20
|
-
it('counts points raised later that an earlier round blocked, with how many rounds earlier, and every block on an approved head', () => {
|
|
21
|
-
const results = [result('pr7-r1', [item('src/a.ts', 3)]), result('pr7-r2', [item('src/a.ts', 3)], 1, 1.5), result('pr7-r3', [], 0, 0.5), result('pr7-approved', [item('src/a.ts', 3)]), result('pr8-approved', [item('src/z.ts', 9, false)])];
|
|
22
|
-
const r = scoreLast(ledger, results);
|
|
23
|
-
expect(r).toMatchObject({ prs: 2, rounds: 5, approvedHeads: 2, pointsLater: 2, sameRound: 1, costUsd: 2 });
|
|
24
|
-
expect(r.early).toEqual([
|
|
25
|
-
{ pr: 7, round: 'pr7-r1', point: 'the export reads every line item', by: 'reviewer:claude src/a.ts:3', roundsEarlier: 1 },
|
|
26
|
-
{ pr: 7, round: 'pr7-r1', point: 'still unbounded', by: 'reviewer:claude src/a.ts:3', roundsEarlier: 2 }, // the earliest round that blocked it counts
|
|
27
|
-
]);
|
|
28
|
-
expect(r.blocksOnApproved).toEqual([{ pr: 7, round: 'pr7-approved', item: item('src/a.ts', 3) }]); // an advisory item on pr8 is not a block
|
|
29
|
-
const text = formatLast({ ...r, skipped: ['pr9-r1: commit abcdef012 is not reachable (force-pushed away)'] });
|
|
30
|
-
expect(text.split('\n')[0]).toBe('1 blocking item(s) on 2 approved head(s): every one is a block the team would have overridden.'); // the worse number first
|
|
31
|
-
expect(text).toContain('2 of 2 point(s) people raised in a later round were already blocked, 1.5 round(s) earlier on average.');
|
|
32
|
-
expect(text).toContain('skipped pr9-r1: commit abcdef012');
|
|
33
|
-
const clean = formatLast({ ...scoreLast(ledger, results.map(x => (x.round.endsWith('-approved') ? { ...x, items: [] } : x))), skipped: [] });
|
|
34
|
-
expect(clean.split('\n')[0]).toContain('point(s) people raised in a later round were already blocked'); // nothing on approved heads: the catches lead
|
|
35
|
-
expect(clean).toContain('0 blocking items on 2 approved head(s).');
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
describe('rigour backtest --last', () => {
|
|
39
|
-
let repo;
|
|
40
|
-
const git = (...args) => execFileSync('git', ['-C', repo, ...args], { encoding: 'utf8' }).trim();
|
|
41
|
-
const commit = (message, date) => { git('add', '-A'); execFileSync('git', ['-C', repo, 'commit', '-qm', message], { env: { ...process.env, GIT_COMMITTER_DATE: date, GIT_AUTHOR_DATE: date } }); return git('rev-parse', 'HEAD'); };
|
|
42
|
-
let reviewed;
|
|
43
|
-
const fake = (calls) => async (command, args, options) => {
|
|
44
|
-
calls.push([command, ...args]);
|
|
45
|
-
if (command === 'git') {
|
|
46
|
-
if (args[0] === 'fetch')
|
|
47
|
-
return { exitCode: 0, stdout: '', stderr: '' }; // the head is already here
|
|
48
|
-
try {
|
|
49
|
-
return { exitCode: 0, stdout: execFileSync('git', args, { cwd: options.cwd, encoding: 'utf8', stdio: 'pipe' }), stderr: '' };
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
return { exitCode: error.status ?? 1, stdout: '', stderr: String(error.stderr ?? '') };
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
if (args[0] === 'auth')
|
|
56
|
-
return { exitCode: 0, stdout: 'token\n', stderr: '' };
|
|
57
|
-
if (args[0] === 'pr' && args[1] === 'list')
|
|
58
|
-
return { exitCode: 0, stdout: JSON.stringify([{ number: 7, mergedAt: '2026-04-12T16:00:00Z' }, { number: 6, mergedAt: '2026-04-11T16:00:00Z' }]), stderr: '' };
|
|
59
|
-
if (args[0] === 'pr')
|
|
60
|
-
return { exitCode: 0, stdout: 'author\n', stderr: '' };
|
|
61
|
-
const pr = /pulls\/(\d+)\//.exec(args[1])?.[1];
|
|
62
|
-
if (args[1].endsWith('/reviews')) {
|
|
63
|
-
if (pr === '6')
|
|
64
|
-
return { exitCode: 0, stdout: JSON.stringify([{ id: 60, user: { login: 'author', type: 'User' }, body: 'self note', state: 'COMMENTED', submitted_at: '2026-04-11T12:00:00Z', commit_id: reviewed }]), stderr: '' };
|
|
65
|
-
return { exitCode: 0, stdout: JSON.stringify([
|
|
66
|
-
{ id: 1, user: { login: 'senior', type: 'User' }, body: 'first pass', state: 'CHANGES_REQUESTED', submitted_at: '2026-04-12T12:00:00Z', commit_id: reviewed },
|
|
67
|
-
{ id: 2, user: { login: 'senior', type: 'User' }, body: 'second pass', state: 'CHANGES_REQUESTED', submitted_at: '2026-04-12T13:00:00Z', commit_id: reviewed },
|
|
68
|
-
{ id: 3, user: { login: 'senior', type: 'User' }, body: '', state: 'APPROVED', submitted_at: '2026-04-12T14:00:00Z', commit_id: reviewed },
|
|
69
|
-
]), stderr: '' };
|
|
70
|
-
}
|
|
71
|
-
if (args[1].includes('/reviews/1/'))
|
|
72
|
-
return { exitCode: 0, stdout: JSON.stringify([{ path: 'b.ts', line: 1, body: 'Drops the currency', created_at: '2026-04-12T12:00:00Z' }]), stderr: '' };
|
|
73
|
-
if (args[1].includes('/reviews/2/'))
|
|
74
|
-
return { exitCode: 0, stdout: JSON.stringify([{ path: 'a.ts', line: 1, body: 'The export reads every line item.', created_at: '2026-04-12T13:00:00Z' }]), stderr: '' };
|
|
75
|
-
return { exitCode: 0, stdout: '[]', stderr: '' };
|
|
76
|
-
};
|
|
77
|
-
beforeEach(() => {
|
|
78
|
-
repo = fs.mkdtempSync(path.join(os.tmpdir(), 'backtest-last-'));
|
|
79
|
-
git('init', '-q', '-b', 'main');
|
|
80
|
-
git('config', 'user.email', 't@example.com');
|
|
81
|
-
git('config', 'user.name', 't');
|
|
82
|
-
git('config', 'commit.gpgsign', 'false');
|
|
83
|
-
fs.writeFileSync(path.join(repo, 'a.ts'), 'export const a = 1;\n');
|
|
84
|
-
commit('main', '2026-04-12T09:00:00Z');
|
|
85
|
-
git('checkout', '-q', '-b', 'feature');
|
|
86
|
-
fs.writeFileSync(path.join(repo, 'b.ts'), 'export const b = 1;\n');
|
|
87
|
-
reviewed = commit('the branch', '2026-04-12T09:30:00Z');
|
|
88
|
-
});
|
|
89
|
-
afterEach(() => { fs.rmSync(repo, { recursive: true, force: true }); });
|
|
90
|
-
it('builds the ledger from the last merged pull requests, the approved head included, runs it and leads with the two numbers', async () => {
|
|
91
|
-
const calls = [];
|
|
92
|
-
const report = await backtestLast(repo, config, { last: 2, exec: fake(calls), collect: async () => ({ items: [item('a.ts', 1)], costUsd: 0.25 }) });
|
|
93
|
-
expect(report).toMatchObject({ prs: 1, rounds: 3, approvedHeads: 1, pointsLater: 1, sameRound: 1, costUsd: 0.75 });
|
|
94
|
-
expect(report.early).toEqual([{ pr: 7, round: 'pr7-r1', point: 'The export reads every line item.', by: 'reviewer:claude a.ts:1', roundsEarlier: 1 }]);
|
|
95
|
-
expect(report.blocksOnApproved.map(b => b.round)).toEqual(['pr7-approved']);
|
|
96
|
-
expect(report.skipped).toEqual(['pull request 6: pull request 6 has no review by a person yet']); // the author's own note is not a review
|
|
97
|
-
expect(calls.filter(c => c[0] === 'git' && c[1] === 'fetch').map(c => c.at(-1))).toEqual(['pull/7/head', 'pull/6/head']);
|
|
98
|
-
const written = JSON.parse(fs.readFileSync(path.join(repo, LAST_LEDGER_PATH), 'utf8'));
|
|
99
|
-
expect(written.rounds.map(r => [r.id, r.points.length])).toEqual([['pr7-r1', 1], ['pr7-r2', 1], ['pr7-approved', 0]]);
|
|
100
|
-
expect(formatLast(report).split('\n')[0]).toContain('1 blocking item(s) on 1 approved head(s)');
|
|
101
|
-
});
|
|
102
|
-
it('gives the scaffolder the approved head only when asked, with no points', async () => {
|
|
103
|
-
const calls = [];
|
|
104
|
-
const without = await roundsForPr(repo, 7, config, fake(calls));
|
|
105
|
-
expect(without.approved).toBeUndefined();
|
|
106
|
-
const withHead = await roundsForPr(repo, 7, config, fake(calls), { approvedHead: true });
|
|
107
|
-
expect(withHead.approved).toMatchObject({ id: 'pr7-approved', commit: reviewed, reviewed_at: '2026-04-12T14:00:00Z', points: [] });
|
|
108
|
-
});
|
|
109
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|