@rigour-labs/core 6.8.0 → 6.8.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/inference/cloud-provider.js +12 -2
- package/package.json +11 -8
- package/dist/brief/briefing.test.d.ts +0 -1
- package/dist/brief/briefing.test.js +0 -107
- 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 -136
- 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 -100
- 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 -52
- 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 -40
- 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 -792
- 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 -93
- 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/task/thread.test.d.ts +0 -1
- package/dist/task/thread.test.js +0 -133
- 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
package/dist/context.test.js
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
-
import { GateRunner } from '../src/gates/runner.js';
|
|
3
|
-
import fs from 'fs-extra';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import os from 'os';
|
|
6
|
-
const TEST_CWD = path.join(os.tmpdir(), 'rigour-temp-test-context-' + process.pid);
|
|
7
|
-
describe('Context Awareness Engine', () => {
|
|
8
|
-
beforeAll(async () => {
|
|
9
|
-
await fs.ensureDir(TEST_CWD);
|
|
10
|
-
});
|
|
11
|
-
afterAll(async () => {
|
|
12
|
-
await fs.remove(TEST_CWD);
|
|
13
|
-
});
|
|
14
|
-
it('should detect context drift for redundant env suffixes (Golden Example)', async () => {
|
|
15
|
-
// Setup: Define standard GCP_PROJECT_ID
|
|
16
|
-
await fs.writeFile(path.join(TEST_CWD, '.env.example'), 'GCP_PROJECT_ID=my-project\n');
|
|
17
|
-
// Setup: Use drifted GCP_PROJECT_ID_PRODUCTION
|
|
18
|
-
await fs.writeFile(path.join(TEST_CWD, 'feature.js'), `
|
|
19
|
-
const id = process.env.GCP_PROJECT_ID_PRODUCTION;
|
|
20
|
-
console.log(id);
|
|
21
|
-
`);
|
|
22
|
-
const config = {
|
|
23
|
-
version: 1,
|
|
24
|
-
commands: {},
|
|
25
|
-
gates: {
|
|
26
|
-
context: {
|
|
27
|
-
enabled: true,
|
|
28
|
-
sensitivity: 0.8,
|
|
29
|
-
mining_depth: 10,
|
|
30
|
-
ignored_patterns: [],
|
|
31
|
-
cross_file_patterns: true,
|
|
32
|
-
naming_consistency: true,
|
|
33
|
-
import_relationships: true,
|
|
34
|
-
max_cross_file_depth: 50,
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
output: { report_path: 'rigour-report.json' }
|
|
38
|
-
};
|
|
39
|
-
const runner = new GateRunner(config);
|
|
40
|
-
const report = await runner.run(TEST_CWD);
|
|
41
|
-
const driftFailures = report.failures.filter(f => f.id === 'context-drift');
|
|
42
|
-
expect(driftFailures.length).toBeGreaterThan(0);
|
|
43
|
-
expect(driftFailures[0].details).toContain('GCP_PROJECT_ID_PRODUCTION');
|
|
44
|
-
expect(driftFailures[0].hint).toContain('GCP_PROJECT_ID');
|
|
45
|
-
});
|
|
46
|
-
it('should not flag valid environment variables', async () => {
|
|
47
|
-
await fs.writeFile(path.join(TEST_CWD, 'valid.js'), `
|
|
48
|
-
const id = process.env.GCP_PROJECT_ID;
|
|
49
|
-
`);
|
|
50
|
-
const config = {
|
|
51
|
-
version: 1,
|
|
52
|
-
commands: {},
|
|
53
|
-
gates: {
|
|
54
|
-
context: {
|
|
55
|
-
enabled: true,
|
|
56
|
-
sensitivity: 0.8,
|
|
57
|
-
mining_depth: 100,
|
|
58
|
-
ignored_patterns: [],
|
|
59
|
-
cross_file_patterns: true,
|
|
60
|
-
naming_consistency: true,
|
|
61
|
-
import_relationships: true,
|
|
62
|
-
max_cross_file_depth: 50,
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
output: { report_path: 'rigour-report.json' }
|
|
66
|
-
};
|
|
67
|
-
const runner = new GateRunner(config);
|
|
68
|
-
const report = await runner.run(TEST_CWD);
|
|
69
|
-
const driftFailures = report.failures.filter(f => f.id === 'context-drift');
|
|
70
|
-
// Filter out failures from other files if they still exist in TEST_CWD
|
|
71
|
-
const specificFailures = driftFailures.filter(f => f.files?.includes('valid.js'));
|
|
72
|
-
expect(specificFailures.length).toBe(0);
|
|
73
|
-
});
|
|
74
|
-
it('should classify arrow function exports as camelCase, not unknown', async () => {
|
|
75
|
-
// Create files with arrow function patterns that previously returned 'unknown'
|
|
76
|
-
await fs.writeFile(path.join(TEST_CWD, 'api.ts'), `
|
|
77
|
-
export const fetchData = async () => { return []; };
|
|
78
|
-
export const getUserProfile = async (id: string) => { return {}; };
|
|
79
|
-
export const use = () => {};
|
|
80
|
-
export const get = async () => {};
|
|
81
|
-
const handleClick = (e: Event) => {};
|
|
82
|
-
let processItem = async (item: any) => {};
|
|
83
|
-
`);
|
|
84
|
-
// Create a second file with consistent arrow function naming
|
|
85
|
-
await fs.writeFile(path.join(TEST_CWD, 'service.ts'), `
|
|
86
|
-
export const createUser = async (data: any) => {};
|
|
87
|
-
export const deleteUser = async (id: string) => {};
|
|
88
|
-
export const updateUser = async (id: string, data: any) => {};
|
|
89
|
-
`);
|
|
90
|
-
const config = {
|
|
91
|
-
version: 1,
|
|
92
|
-
commands: {},
|
|
93
|
-
gates: {
|
|
94
|
-
context: {
|
|
95
|
-
enabled: true,
|
|
96
|
-
sensitivity: 0.8,
|
|
97
|
-
mining_depth: 10,
|
|
98
|
-
ignored_patterns: [],
|
|
99
|
-
cross_file_patterns: true,
|
|
100
|
-
naming_consistency: true,
|
|
101
|
-
import_relationships: true,
|
|
102
|
-
max_cross_file_depth: 50,
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
output: { report_path: 'rigour-report.json' }
|
|
106
|
-
};
|
|
107
|
-
const runner = new GateRunner(config);
|
|
108
|
-
const report = await runner.run(TEST_CWD);
|
|
109
|
-
// Should NOT have any "unknown" naming convention failures
|
|
110
|
-
const namingFailures = report.failures.filter(f => f.id === 'context-drift' && f.details?.includes('unknown'));
|
|
111
|
-
expect(namingFailures.length).toBe(0);
|
|
112
|
-
});
|
|
113
|
-
it('should not classify plain variable declarations as function patterns', async () => {
|
|
114
|
-
// Create file with non-function const declarations
|
|
115
|
-
await fs.writeFile(path.join(TEST_CWD, 'constants.ts'), `
|
|
116
|
-
export const API_URL = 'https://api.example.com';
|
|
117
|
-
export const MAX_RETRIES = 3;
|
|
118
|
-
const config = { timeout: 5000 };
|
|
119
|
-
let count = 0;
|
|
120
|
-
`);
|
|
121
|
-
// Create file with actual functions for a dominant pattern
|
|
122
|
-
await fs.writeFile(path.join(TEST_CWD, 'utils.ts'), `
|
|
123
|
-
function getData() { return []; }
|
|
124
|
-
function setData(d: any) { return d; }
|
|
125
|
-
function processRequest(req: any) { return req; }
|
|
126
|
-
`);
|
|
127
|
-
const config = {
|
|
128
|
-
version: 1,
|
|
129
|
-
commands: {},
|
|
130
|
-
gates: {
|
|
131
|
-
context: {
|
|
132
|
-
enabled: true,
|
|
133
|
-
sensitivity: 0.8,
|
|
134
|
-
mining_depth: 10,
|
|
135
|
-
ignored_patterns: [],
|
|
136
|
-
cross_file_patterns: true,
|
|
137
|
-
naming_consistency: true,
|
|
138
|
-
import_relationships: true,
|
|
139
|
-
max_cross_file_depth: 50,
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
output: { report_path: 'rigour-report.json' }
|
|
143
|
-
};
|
|
144
|
-
const runner = new GateRunner(config);
|
|
145
|
-
const report = await runner.run(TEST_CWD);
|
|
146
|
-
// SCREAMING_SNAKE constants should NOT create naming drift failures
|
|
147
|
-
// because they should not be in the 'function' pattern bucket at all
|
|
148
|
-
const namingFailures = report.failures.filter(f => f.id === 'context-drift' && f.details?.includes('SCREAMING_SNAKE'));
|
|
149
|
-
expect(namingFailures.length).toBe(0);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
/**
|
|
153
|
-
* Direct unit tests for detectCasing logic
|
|
154
|
-
*/
|
|
155
|
-
describe('detectCasing classification', () => {
|
|
156
|
-
// We test the regex rules directly since detectCasing is private
|
|
157
|
-
function detectCasing(name) {
|
|
158
|
-
if (/^[A-Z][a-z]/.test(name) && /[a-z][A-Z]/.test(name))
|
|
159
|
-
return 'PascalCase';
|
|
160
|
-
if (/^[a-z]/.test(name) && /[a-z][A-Z]/.test(name))
|
|
161
|
-
return 'camelCase';
|
|
162
|
-
if (/^[a-z][a-zA-Z0-9]*$/.test(name))
|
|
163
|
-
return 'camelCase'; // single-word lowercase
|
|
164
|
-
if (/^[a-z]+(_[a-z]+)+$/.test(name))
|
|
165
|
-
return 'snake_case';
|
|
166
|
-
if (/^[A-Z]+(_[A-Z]+)*$/.test(name))
|
|
167
|
-
return 'SCREAMING_SNAKE';
|
|
168
|
-
if (/^[A-Z][a-zA-Z]*$/.test(name))
|
|
169
|
-
return 'PascalCase';
|
|
170
|
-
return 'unknown';
|
|
171
|
-
}
|
|
172
|
-
// Multi-word camelCase
|
|
173
|
-
it('classifies multi-word camelCase', () => {
|
|
174
|
-
expect(detectCasing('fetchData')).toBe('camelCase');
|
|
175
|
-
expect(detectCasing('getUserProfile')).toBe('camelCase');
|
|
176
|
-
expect(detectCasing('handleClick')).toBe('camelCase');
|
|
177
|
-
expect(detectCasing('processItem')).toBe('camelCase');
|
|
178
|
-
expect(detectCasing('createNewUser')).toBe('camelCase');
|
|
179
|
-
});
|
|
180
|
-
// Single-word lowercase (the bug fix)
|
|
181
|
-
it('classifies single-word lowercase as camelCase', () => {
|
|
182
|
-
expect(detectCasing('fetch')).toBe('camelCase');
|
|
183
|
-
expect(detectCasing('use')).toBe('camelCase');
|
|
184
|
-
expect(detectCasing('get')).toBe('camelCase');
|
|
185
|
-
expect(detectCasing('set')).toBe('camelCase');
|
|
186
|
-
expect(detectCasing('run')).toBe('camelCase');
|
|
187
|
-
expect(detectCasing('a')).toBe('camelCase');
|
|
188
|
-
expect(detectCasing('x')).toBe('camelCase');
|
|
189
|
-
expect(detectCasing('id')).toBe('camelCase');
|
|
190
|
-
});
|
|
191
|
-
// Single-word lowercase with digits
|
|
192
|
-
it('classifies lowercase with digits as camelCase', () => {
|
|
193
|
-
expect(detectCasing('handler2')).toBe('camelCase');
|
|
194
|
-
expect(detectCasing('config3')).toBe('camelCase');
|
|
195
|
-
expect(detectCasing('v2')).toBe('camelCase');
|
|
196
|
-
});
|
|
197
|
-
// PascalCase
|
|
198
|
-
it('classifies PascalCase', () => {
|
|
199
|
-
expect(detectCasing('MyComponent')).toBe('PascalCase');
|
|
200
|
-
expect(detectCasing('UserService')).toBe('PascalCase');
|
|
201
|
-
expect(detectCasing('App')).toBe('PascalCase');
|
|
202
|
-
expect(detectCasing('A')).toBe('SCREAMING_SNAKE'); // single uppercase letter
|
|
203
|
-
});
|
|
204
|
-
// snake_case
|
|
205
|
-
it('classifies snake_case', () => {
|
|
206
|
-
expect(detectCasing('my_func')).toBe('snake_case');
|
|
207
|
-
expect(detectCasing('get_data')).toBe('snake_case');
|
|
208
|
-
expect(detectCasing('process_all_items')).toBe('snake_case');
|
|
209
|
-
});
|
|
210
|
-
// SCREAMING_SNAKE
|
|
211
|
-
it('classifies SCREAMING_SNAKE_CASE', () => {
|
|
212
|
-
expect(detectCasing('API_URL')).toBe('SCREAMING_SNAKE');
|
|
213
|
-
expect(detectCasing('MAX_RETRIES')).toBe('SCREAMING_SNAKE');
|
|
214
|
-
expect(detectCasing('A')).toBe('SCREAMING_SNAKE');
|
|
215
|
-
expect(detectCasing('DB')).toBe('SCREAMING_SNAKE');
|
|
216
|
-
});
|
|
217
|
-
// Edge cases that should NOT be unknown
|
|
218
|
-
it('does not return unknown for valid identifiers', () => {
|
|
219
|
-
const validIdentifiers = [
|
|
220
|
-
'fetch', 'getData', 'MyClass', 'my_func', 'API_KEY',
|
|
221
|
-
'use', 'run', 'a', 'x', 'id', 'App', 'handler2',
|
|
222
|
-
'processItem', 'UserProfile', 'get_all_data', 'MAX_SIZE',
|
|
223
|
-
];
|
|
224
|
-
for (const name of validIdentifiers) {
|
|
225
|
-
expect(detectCasing(name)).not.toBe('unknown');
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,53 +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 { reviewWithTools } from './agent-review.js';
|
|
6
|
-
import { verifyCodeFindings } from './code-verifier.js';
|
|
7
|
-
let repo;
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
repo = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-review-'));
|
|
10
|
-
fs.writeFileSync(path.join(repo, 'limits.ts'), 'export const MAX_ROWS = 0;\n');
|
|
11
|
-
fs.writeFileSync(path.join(repo, 'page.ts'), Array.from({ length: 60 }, (_, i) => `// line ${i + 1}`).join('\n'));
|
|
12
|
-
});
|
|
13
|
-
afterEach(() => { fs.rmSync(repo, { recursive: true, force: true }); });
|
|
14
|
-
const context = { file: 'page.ts', language: 'typescript', text: '1| export function page() {}', ranges: [[1, 5]], source: 'export function page() {}' };
|
|
15
|
-
function scripted(replies) {
|
|
16
|
-
const seen = [];
|
|
17
|
-
const provider = {
|
|
18
|
-
name: 'fake', isAvailable: async () => true, setup: async () => { }, dispose: () => { }, analyze: async () => '',
|
|
19
|
-
chat: async (messages, tools, options) => {
|
|
20
|
-
seen.push({ messages: [...messages], tools: tools.length, toolChoice: options?.toolChoice });
|
|
21
|
-
return replies.shift() ?? { text: '{"findings": []}', toolCalls: [] };
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
return { provider, seen };
|
|
25
|
-
}
|
|
26
|
-
const findingJson = (line, description) => JSON.stringify({ findings: [{ category: 'correctness', severity: 'high', file: 'page.ts', line, description, suggestion: 's', confidence: 0.9 }] });
|
|
27
|
-
describe('reviewWithTools', () => {
|
|
28
|
-
it('runs the tools the model asks for, then grounds its finding in what it read', async () => {
|
|
29
|
-
const { provider, seen } = scripted([
|
|
30
|
-
{ text: '', toolCalls: [{ id: 't1', name: 'read_file', arguments: { path: 'limits.ts' } }, { id: 't2', name: 'read_file', arguments: { path: 'page.ts', start_line: 40, end_line: 45 } }] },
|
|
31
|
-
{ text: findingJson(42, '`MAX_ROWS` is 0, so the page loop never runs.'), toolCalls: [] },
|
|
32
|
-
]);
|
|
33
|
-
const result = await reviewWithTools(provider, context, '', repo, {});
|
|
34
|
-
expect(result.toolCalls).toBe(2);
|
|
35
|
-
const toolResults = seen[1].messages.filter(m => m.role === 'tool').map(m => m.content);
|
|
36
|
-
expect(toolResults[0]).toContain('export const MAX_ROWS = 0;');
|
|
37
|
-
// Line 42 was outside the sent source, but the model read it, and it read MAX_ROWS.
|
|
38
|
-
expect(verifyCodeFindings(result.findings, [result.context])).toHaveLength(1);
|
|
39
|
-
expect(verifyCodeFindings(result.findings, [context])).toHaveLength(0);
|
|
40
|
-
});
|
|
41
|
-
it('forbids tool calls after the budget, keeping the tools defined for the history, and asks for the answer', async () => {
|
|
42
|
-
const greedy = Array.from({ length: 20 }, (_, i) => ({ text: '', toolCalls: [{ id: `t${i}`, name: 'grep', arguments: { pattern: 'line' } }] }));
|
|
43
|
-
const { provider, seen } = scripted(greedy);
|
|
44
|
-
const result = await reviewWithTools(provider, context, '', repo, {});
|
|
45
|
-
expect(result.toolCalls).toBeLessThanOrEqual(12);
|
|
46
|
-
expect(seen.at(-1)).toMatchObject({ tools: 2, toolChoice: 'none' });
|
|
47
|
-
expect(result.findings).toEqual([]);
|
|
48
|
-
});
|
|
49
|
-
it('refuses a provider that cannot call tools', async () => {
|
|
50
|
-
const provider = { name: 'local', isAvailable: async () => true, setup: async () => { }, dispose: () => { }, analyze: async () => '' };
|
|
51
|
-
await expect(reviewWithTools(provider, context, '', repo, {})).rejects.toThrow('cannot call tools');
|
|
52
|
-
});
|
|
53
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { buildCodeContext } from './code-context.js';
|
|
3
|
-
function fn(name, lineStart, lineEnd) {
|
|
4
|
-
return {
|
|
5
|
-
name, lineStart, lineEnd, lineCount: lineEnd - lineStart + 1, paramCount: 0, params: [],
|
|
6
|
-
maxNesting: 1, hasReturn: true, isAsync: false, isExported: true,
|
|
7
|
-
};
|
|
8
|
-
}
|
|
9
|
-
function facts(path, functions) {
|
|
10
|
-
return {
|
|
11
|
-
path, language: 'typescript', lineCount: 0, classes: [], functions, imports: [], exports: [],
|
|
12
|
-
errorHandling: [], testAssertions: 0, hasTests: false,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
const source = (n) => Array.from({ length: n }, (_, i) => `const line${i + 1} = ${i + 1};`).join('\n');
|
|
16
|
-
describe('buildCodeContext', () => {
|
|
17
|
-
it('sends a small file whole, with line numbers', () => {
|
|
18
|
-
const ctx = buildCodeContext(facts('src/a.ts', []), 'const a = 1;\nexport { a };', { maxChars: 10_000 });
|
|
19
|
-
expect(ctx.text).toBe('FILE: src/a.ts (typescript, 2 lines)\n1| const a = 1;\n2| export { a };');
|
|
20
|
-
expect(ctx.ranges).toEqual([[1, 2]]);
|
|
21
|
-
expect(ctx.source).toContain('export { a }');
|
|
22
|
-
});
|
|
23
|
-
it('cuts a large file into function segments and names what it omitted', () => {
|
|
24
|
-
const content = source(300);
|
|
25
|
-
const ctx = buildCodeContext(facts('src/big.ts', [fn('first', 1, 100), fn('second', 101, 200), fn('third', 201, 300)]), content, { maxChars: 4_000 });
|
|
26
|
-
expect(ctx.ranges[0]).toEqual([1, 100]);
|
|
27
|
-
expect(ctx.text).toMatch(/omitted for size: .*function second \(lines 101-200\)/);
|
|
28
|
-
expect(ctx.text).toContain('function third (lines 201-300)');
|
|
29
|
-
expect(ctx.source).not.toContain('line150 ');
|
|
30
|
-
});
|
|
31
|
-
it('puts the segment around changed lines first', () => {
|
|
32
|
-
const content = source(300);
|
|
33
|
-
const ctx = buildCodeContext(facts('src/big.ts', [fn('first', 1, 100), fn('second', 101, 200), fn('third', 201, 300)]), content, {
|
|
34
|
-
maxChars: 4_000, focusLines: [250],
|
|
35
|
-
});
|
|
36
|
-
expect(ctx.ranges[0]).toEqual([201, 300]);
|
|
37
|
-
expect(ctx.text).toContain('function first (lines 1-100)');
|
|
38
|
-
});
|
|
39
|
-
it('truncates a single oversized function explicitly instead of dropping it', () => {
|
|
40
|
-
const content = source(2_000);
|
|
41
|
-
const ctx = buildCodeContext(facts('src/huge.ts', [fn('huge', 1, 2_000)]), content, { maxChars: 2_000 });
|
|
42
|
-
expect(ctx.ranges).toHaveLength(1);
|
|
43
|
-
expect(ctx.ranges[0][0]).toBe(1);
|
|
44
|
-
expect(ctx.ranges[0][1]).toBeLessThan(2_000);
|
|
45
|
-
expect(ctx.text).toMatch(/… \[\d+ lines omitted\]$/);
|
|
46
|
-
expect(ctx.text.length).toBeLessThanOrEqual(2_000);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,112 +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 { runCodePass } from './code-pass.js';
|
|
6
|
-
import { parseVerdicts } from './self-check.js';
|
|
7
|
-
let dir;
|
|
8
|
-
afterEach(() => { if (dir)
|
|
9
|
-
fs.rmSync(dir, { recursive: true, force: true }); dir = undefined; });
|
|
10
|
-
const SOURCE = 'export function total(items: number[]) {\n let sum = 0;\n for (let i = 0; i <= items.length; i++) sum += items[i];\n return sum;\n}\n';
|
|
11
|
-
const facts = { path: 'total.ts', language: 'typescript', classes: [], functions: [] };
|
|
12
|
-
function finding(line, category, description) {
|
|
13
|
-
return { category, severity: 'high', file: 'total.ts', line, description, suggestion: 's', confidence: 0.9 };
|
|
14
|
-
}
|
|
15
|
-
const isCheck = (prompt) => /^You reported \d+ defect/.test(prompt);
|
|
16
|
-
/** Review passes answer from `reviews` in turn; the self-check withdraws findings whose line is in `withdraw`. */
|
|
17
|
-
function fakeProvider(reviews, withdraw) {
|
|
18
|
-
const prompts = [];
|
|
19
|
-
const provider = {
|
|
20
|
-
name: 'fake', isAvailable: async () => true, setup: async () => { }, dispose: () => { },
|
|
21
|
-
analyze: async (prompt) => {
|
|
22
|
-
prompts.push(prompt);
|
|
23
|
-
if (isCheck(prompt)) {
|
|
24
|
-
const listed = [...prompt.matchAll(/^(\d+)\. total\.ts:(\d+):/gm)];
|
|
25
|
-
return JSON.stringify({ verdicts: listed.map(m => ({ n: Number(m[1]), real: !withdraw.includes(Number(m[2])) })) });
|
|
26
|
-
}
|
|
27
|
-
return JSON.stringify({ findings: reviews.shift() ?? [] });
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
return { provider, prompts };
|
|
31
|
-
}
|
|
32
|
-
function run(provider, reference) {
|
|
33
|
-
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'code-pass-'));
|
|
34
|
-
fs.writeFileSync(path.join(dir, 'total.ts'), SOURCE);
|
|
35
|
-
return runCodePass(provider, [facts], { cwd: dir, inference: {}, maxSourceChars: 10_000, focusLines: { 'total.ts': [3] }, reference });
|
|
36
|
-
}
|
|
37
|
-
describe('runCodePass', () => {
|
|
38
|
-
it('reviews once without reference material (the small tiers)', async () => {
|
|
39
|
-
const { provider, prompts } = fakeProvider([[finding(3, 'correctness', 'off by one on `items`')]], []);
|
|
40
|
-
const result = await run(provider);
|
|
41
|
-
expect(prompts).toHaveLength(1);
|
|
42
|
-
expect(result.findings.map(f => f.line)).toEqual([3]);
|
|
43
|
-
expect([result.proposed, result.withdrawn]).toEqual([1, 0]);
|
|
44
|
-
});
|
|
45
|
-
it('does not repeat an identical pass when there is only one reference section', async () => {
|
|
46
|
-
const { provider, prompts } = fakeProvider([[]], []);
|
|
47
|
-
await run(provider, { maxChars: 4000, prBody: 'Sum the basket.' });
|
|
48
|
-
expect(prompts).toHaveLength(1);
|
|
49
|
-
});
|
|
50
|
-
it('with reference material, reviews twice in opposite orders, merges, and keeps what one self-check call confirms', async () => {
|
|
51
|
-
const { provider, prompts } = fakeProvider([
|
|
52
|
-
[finding(3, 'correctness', 'reads past the end of `items`')],
|
|
53
|
-
[finding(3, 'correctness', 'same bug seen again'), finding(2, 'consistency', 'style-ish claim about `sum`')],
|
|
54
|
-
], [2]);
|
|
55
|
-
const result = await run(provider, {
|
|
56
|
-
maxChars: 4000, prBody: 'Sum the basket.',
|
|
57
|
-
removed: { 'total.ts': [{ line: 3, text: [' for (let i = 0; i < items.length; i++) sum += items[i];'] }] },
|
|
58
|
-
});
|
|
59
|
-
const reviews = prompts.filter(p => !isCheck(p));
|
|
60
|
-
expect(reviews).toHaveLength(2);
|
|
61
|
-
expect(reviews[0].indexOf('PR DESCRIPTION')).toBeLessThan(reviews[0].indexOf('REMOVED by this change'));
|
|
62
|
-
expect(reviews[1].indexOf('REMOVED by this change')).toBeLessThan(reviews[1].indexOf('PR DESCRIPTION'));
|
|
63
|
-
expect(prompts.filter(isCheck)).toHaveLength(1);
|
|
64
|
-
expect(result.findings.map(f => [f.line, f.category])).toEqual([[3, 'correctness']]);
|
|
65
|
-
expect([result.proposed, result.withdrawn]).toEqual([2, 1]); // two distinct findings, one withdrawn
|
|
66
|
-
});
|
|
67
|
-
it('reads only well-formed verdicts from a self-check reply', () => {
|
|
68
|
-
expect([...parseVerdicts('Sure! {"verdicts": [{"n": 1, "real": false}, {"n": 2, "real": true}, {"n": "3", "real": false}]}')])
|
|
69
|
-
.toEqual([[1, false], [2, true]]);
|
|
70
|
-
expect(parseVerdicts('I am not sure').size).toBe(0);
|
|
71
|
-
});
|
|
72
|
-
it('keeps every finding when the self-check call fails', async () => {
|
|
73
|
-
const { provider } = fakeProvider([[finding(3, 'correctness', 'reads past the end of `items`')], []], []);
|
|
74
|
-
const analyze = provider.analyze;
|
|
75
|
-
provider.analyze = async (prompt, options) => isCheck(prompt) ? Promise.reject(new Error('timeout')) : analyze(prompt, options);
|
|
76
|
-
const result = await run(provider, { maxChars: 4000, prBody: 'Sum the basket.', removed: { 'total.ts': [{ line: 3, text: ['x'] }] } });
|
|
77
|
-
expect(result.findings.map(f => f.line)).toEqual([3]);
|
|
78
|
-
expect(result.withdrawn).toBe(0);
|
|
79
|
-
});
|
|
80
|
-
it('starts no file after the deadline, and counts what it skipped', async () => {
|
|
81
|
-
const { provider, prompts } = fakeProvider([[]], []);
|
|
82
|
-
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'code-pass-'));
|
|
83
|
-
fs.writeFileSync(path.join(dir, 'total.ts'), SOURCE);
|
|
84
|
-
const result = await runCodePass(provider, [facts, facts], { cwd: dir, inference: {}, maxSourceChars: 10_000, deadline: Date.now() - 1 });
|
|
85
|
-
expect(prompts).toHaveLength(0);
|
|
86
|
-
expect(result.skipped).toBe(2);
|
|
87
|
-
});
|
|
88
|
-
it('reviews files concurrently but reports them in file order', async () => {
|
|
89
|
-
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'code-pass-'));
|
|
90
|
-
const names = ['a.ts', 'b.ts', 'c.ts'];
|
|
91
|
-
for (const name of names)
|
|
92
|
-
fs.writeFileSync(path.join(dir, name), SOURCE);
|
|
93
|
-
let inFlight = 0;
|
|
94
|
-
let peak = 0;
|
|
95
|
-
const provider = {
|
|
96
|
-
name: 'fake', isAvailable: async () => true, setup: async () => { }, dispose: () => { },
|
|
97
|
-
analyze: async (prompt) => {
|
|
98
|
-
inFlight++;
|
|
99
|
-
peak = Math.max(peak, inFlight);
|
|
100
|
-
const file = names.find(n => prompt.includes(n));
|
|
101
|
-
await new Promise(resolve => setTimeout(resolve, file === 'a.ts' ? 30 : 5));
|
|
102
|
-
inFlight--;
|
|
103
|
-
return JSON.stringify({ findings: [{ ...finding(3, 'correctness', 'off by one'), file }] });
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
const many = names.map(name => ({ ...facts, path: name }));
|
|
107
|
-
const result = await runCodePass(provider, many, { cwd: dir, inference: {}, maxSourceChars: 10_000, concurrency: 2 });
|
|
108
|
-
expect(peak).toBe(2);
|
|
109
|
-
expect(result.findings.map(f => f.file)).toEqual(names);
|
|
110
|
-
expect(result.contexts.map(c => c.file)).toEqual(names);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { buildCodeReviewPrompt, REVIEW_CATEGORIES } from './code-review-prompt.js';
|
|
3
|
-
const context = { file: 'src/a.ts', language: 'typescript', text: 'FILE: src/a.ts\n1| export const a = 1;', ranges: [[1, 1]], source: '' };
|
|
4
|
-
describe('buildCodeReviewPrompt', () => {
|
|
5
|
-
it('includes every category, the rules and the numbered source', () => {
|
|
6
|
-
const prompt = buildCodeReviewPrompt(context);
|
|
7
|
-
for (const c of REVIEW_CATEGORIES)
|
|
8
|
-
expect(prompt).toContain(`- ${c.key}: ${c.focus}`);
|
|
9
|
-
expect(prompt).toContain('Report only defects you can point to');
|
|
10
|
-
expect(prompt).toContain('1| export const a = 1;');
|
|
11
|
-
expect(prompt).toContain('Review src/a.ts');
|
|
12
|
-
});
|
|
13
|
-
it('allows an empty answer so the model is not pushed to invent findings', () => {
|
|
14
|
-
expect(buildCodeReviewPrompt(context)).toContain('{"findings": []}');
|
|
15
|
-
});
|
|
16
|
-
it('uses unique category keys', () => {
|
|
17
|
-
const keys = REVIEW_CATEGORIES.map(c => c.key);
|
|
18
|
-
expect(new Set(keys).size).toBe(keys.length);
|
|
19
|
-
});
|
|
20
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { quotedIdentifiers, verifyCodeFindings } from './code-verifier.js';
|
|
3
|
-
const context = {
|
|
4
|
-
file: 'src/api/report.ts',
|
|
5
|
-
language: 'typescript',
|
|
6
|
-
text: '',
|
|
7
|
-
ranges: [[10, 40]],
|
|
8
|
-
source: 'export async function loadReport(days) {\n const rows = await fetchAll(days);\n}',
|
|
9
|
-
};
|
|
10
|
-
function finding(overrides) {
|
|
11
|
-
return {
|
|
12
|
-
category: 'scalability', severity: 'high', file: 'src/api/report.ts', line: 12,
|
|
13
|
-
description: '`fetchAll` loads every row for the window into memory.', suggestion: 'Aggregate in the database.',
|
|
14
|
-
confidence: 0.8, ...overrides,
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
describe('verifyCodeFindings', () => {
|
|
18
|
-
it('counts every dropped finding by reason, so a drop is never silent', () => {
|
|
19
|
-
const rejected = {};
|
|
20
|
-
verifyCodeFindings([
|
|
21
|
-
finding({}), finding({ file: 'src/other.ts' }), finding({ line: 80 }), finding({ line: undefined }),
|
|
22
|
-
finding({ confidence: 0.1 }), finding({ description: '`fetchEverything` has no limit.' }),
|
|
23
|
-
], [context], rejected);
|
|
24
|
-
expect(rejected).toEqual({ no_context: 1, out_of_range: 1, no_line: 1, low_confidence: 1, ungrounded_identifier: 1 });
|
|
25
|
-
});
|
|
26
|
-
it('keeps a finding grounded in the sent source', () => {
|
|
27
|
-
const [kept] = verifyCodeFindings([finding({})], [context]);
|
|
28
|
-
expect(kept).toMatchObject({ file: 'src/api/report.ts', verified: true });
|
|
29
|
-
});
|
|
30
|
-
it('resolves a basename-only file for a single-file review', () => {
|
|
31
|
-
expect(verifyCodeFindings([finding({ file: 'report.ts' })], [context])).toHaveLength(1);
|
|
32
|
-
});
|
|
33
|
-
it('drops findings for files that were not sent', () => {
|
|
34
|
-
expect(verifyCodeFindings([finding({ file: 'src/other.ts' })], [context])).toHaveLength(0);
|
|
35
|
-
});
|
|
36
|
-
it('drops findings whose line is outside the sent ranges', () => {
|
|
37
|
-
expect(verifyCodeFindings([finding({ line: 80 })], [context])).toHaveLength(0);
|
|
38
|
-
});
|
|
39
|
-
it('drops findings that cite an identifier the model was never shown', () => {
|
|
40
|
-
expect(verifyCodeFindings([finding({ description: '`fetchEverything` has no limit.' })], [context])).toHaveLength(0);
|
|
41
|
-
});
|
|
42
|
-
it('drops findings that cite no line, however confident', () => {
|
|
43
|
-
expect(verifyCodeFindings([finding({ line: undefined, confidence: 0.9 })], [context])).toHaveLength(0);
|
|
44
|
-
expect(verifyCodeFindings([finding({ line: null, description: 'The code is well-structured.' })], [context])).toHaveLength(0);
|
|
45
|
-
});
|
|
46
|
-
it('drops very low confidence findings', () => {
|
|
47
|
-
expect(verifyCodeFindings([finding({ confidence: 0.2 })], [context])).toHaveLength(0);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
describe('quotedIdentifiers', () => {
|
|
51
|
-
it('extracts identifier-like backticked tokens only', () => {
|
|
52
|
-
expect(quotedIdentifiers('`fetchAll()` and `rows.length` but not `a + b` or `"quoted"`')).toEqual(['fetchAll', 'rows.length']);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { safeCalls, testFileSource } from './calls.js';
|
|
3
|
-
describe('safeCalls', () => {
|
|
4
|
-
it('keeps calls of the function with literal arguments only', () => {
|
|
5
|
-
expect(safeCalls('encodePath', [
|
|
6
|
-
"encodePath('/a b')",
|
|
7
|
-
"encodePath('/x?y', { keepQuery: true, depth: -1 }, [1, null, undefined]);",
|
|
8
|
-
'encodePath(`/%`)',
|
|
9
|
-
])).toEqual(["encodePath('/a b')", "encodePath('/x?y', { keepQuery: true, depth: -1 }, [1, null, undefined])", 'encodePath(`/%`)']);
|
|
10
|
-
});
|
|
11
|
-
it('drops anything that could do more than call the function on data', () => {
|
|
12
|
-
expect(safeCalls('encodePath', [
|
|
13
|
-
"require('fs').rmSync('/')",
|
|
14
|
-
"encodePath(process.env.HOME)",
|
|
15
|
-
"encodePath((() => { fetch('x') })())",
|
|
16
|
-
'encodePath(`${secret}`)',
|
|
17
|
-
"other('/a')",
|
|
18
|
-
"encodePath('/a'); encodePath('/b')",
|
|
19
|
-
"encodePath({ [key]: 1 })",
|
|
20
|
-
])).toEqual([]);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
describe('testFileSource', () => {
|
|
24
|
-
it('imports the function, runs each call and records return values or errors without asserting', () => {
|
|
25
|
-
const source = testFileSource('vitest', 'encodePath', '../src/path', ["encodePath('/a b')"]);
|
|
26
|
-
expect(source).toContain("import { test } from 'vitest';");
|
|
27
|
-
expect(source).toContain('import { encodePath } from "../src/path";');
|
|
28
|
-
expect(source).toContain(`["encodePath('/a b')", () => encodePath('/a b')],`);
|
|
29
|
-
expect(source).toContain('writeFileSync(process.env.RIGOUR_DIFF_OUT as string, JSON.stringify(out));');
|
|
30
|
-
expect(source).not.toContain('expect(');
|
|
31
|
-
expect(testFileSource('jest', 'f', './f', ['f(1)'])).not.toContain("from 'vitest'");
|
|
32
|
-
});
|
|
33
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,58 +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, describe, expect, it } from 'vitest';
|
|
6
|
-
import { runDiffTests } from './run.js';
|
|
7
|
-
// The workspace root's node_modules, where the vitest binary is installed.
|
|
8
|
-
const WORKSPACE_MODULES = path.resolve(__dirname, '../../../../../node_modules');
|
|
9
|
-
let repo;
|
|
10
|
-
afterEach(() => { if (repo)
|
|
11
|
-
fs.rmSync(repo, { recursive: true, force: true }); repo = undefined; });
|
|
12
|
-
const BEFORE = "export function encodePath(p: string): string {\n return p.replace(/ /g, '%20').replace(/#/g, '%23');\n}\n";
|
|
13
|
-
const AFTER = "export function encodePath(p: string): string {\n return p.replace(/#/g, '%23');\n}\n";
|
|
14
|
-
function repoWithChange() {
|
|
15
|
-
repo = fs.mkdtempSync(path.join(os.tmpdir(), 'diff-tests-'));
|
|
16
|
-
const git = (...args) => execFileSync('git', args, { cwd: repo });
|
|
17
|
-
git('init', '-q');
|
|
18
|
-
git('config', 'user.email', 't@example.com');
|
|
19
|
-
git('config', 'user.name', 't');
|
|
20
|
-
git('config', 'commit.gpgsign', 'false');
|
|
21
|
-
fs.writeFileSync(path.join(repo, 'package.json'), JSON.stringify({ name: 'paths', type: 'module', devDependencies: { vitest: '*' } }));
|
|
22
|
-
fs.writeFileSync(path.join(repo, '.gitignore'), 'node_modules\n');
|
|
23
|
-
fs.mkdirSync(path.join(repo, 'src'));
|
|
24
|
-
fs.writeFileSync(path.join(repo, 'src/encode.ts'), BEFORE);
|
|
25
|
-
git('add', '-A');
|
|
26
|
-
git('commit', '-qm', 'init');
|
|
27
|
-
fs.symlinkSync(WORKSPACE_MODULES, path.join(repo, 'node_modules'), process.platform === 'win32' ? 'junction' : 'dir');
|
|
28
|
-
fs.writeFileSync(path.join(repo, 'src/encode.ts'), AFTER);
|
|
29
|
-
return repo;
|
|
30
|
-
}
|
|
31
|
-
function provider(calls, intended = false) {
|
|
32
|
-
return {
|
|
33
|
-
name: 'fake', isAvailable: async () => true, setup: async () => { }, dispose: () => { },
|
|
34
|
-
analyze: async (prompt) => prompt.includes('"intended"') ? JSON.stringify({ intended }) : JSON.stringify({ calls }),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
describe('runDiffTests', () => {
|
|
38
|
-
it('reports a call whose outcome changed between the base and the change', async () => {
|
|
39
|
-
const cwd = repoWithChange();
|
|
40
|
-
const changes = await runDiffTests({
|
|
41
|
-
cwd, baseRef: 'HEAD', focusLines: { 'src/encode.ts': [2] }, inference: {},
|
|
42
|
-
provider: provider(["encodePath('/a b')", "encodePath('/x#y')", "process.exit(1)"]),
|
|
43
|
-
});
|
|
44
|
-
expect(changes).toEqual([{
|
|
45
|
-
file: 'src/encode.ts', line: 2, name: 'encodePath', call: "encodePath('/a b')",
|
|
46
|
-
before: 'returns "/a%20b"', after: 'returns "/a b"',
|
|
47
|
-
}]);
|
|
48
|
-
expect(fs.readdirSync(path.join(cwd, 'src'))).toEqual(['encode.ts']);
|
|
49
|
-
}, 60_000);
|
|
50
|
-
it('drops a change the PR description says is intended', async () => {
|
|
51
|
-
const cwd = repoWithChange();
|
|
52
|
-
const changes = await runDiffTests({
|
|
53
|
-
cwd, baseRef: 'HEAD', focusLines: { 'src/encode.ts': [2] }, inference: {}, prBody: 'Stop encoding spaces.',
|
|
54
|
-
provider: provider(["encodePath('/a b')"], true),
|
|
55
|
-
});
|
|
56
|
-
expect(changes).toEqual([]);
|
|
57
|
-
}, 60_000);
|
|
58
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|