@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,55 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import os from 'os';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
/** An in-memory stand-in for the local SQLite store: records writes, answers the two reads under test. */
|
|
6
|
-
const db = vi.hoisted(() => ({
|
|
7
|
-
writes: [],
|
|
8
|
-
patterns: [],
|
|
9
|
-
findings: [],
|
|
10
|
-
reset() { this.writes = []; this.patterns = []; this.findings = []; },
|
|
11
|
-
}));
|
|
12
|
-
vi.mock('./db.js', () => {
|
|
13
|
-
const store = {
|
|
14
|
-
run: async (sql) => { db.writes.push(sql.trim().split(/\s+/).slice(0, 3).join(' ')); return { changes: 1, lastID: 0 }; },
|
|
15
|
-
get: async () => undefined,
|
|
16
|
-
all: async (sql) => (sql.includes('FROM patterns') ? db.patterns : sql.includes('FROM findings') ? db.findings : []),
|
|
17
|
-
exec: async () => undefined,
|
|
18
|
-
transaction: async (work) => work(store),
|
|
19
|
-
close: async () => undefined,
|
|
20
|
-
};
|
|
21
|
-
return { openDatabase: async () => store };
|
|
22
|
-
});
|
|
23
|
-
const { checkLocalPatterns, persistAndReinforce } = await import('./local-memory.js');
|
|
24
|
-
let repo;
|
|
25
|
-
beforeEach(() => {
|
|
26
|
-
db.reset();
|
|
27
|
-
repo = fs.mkdtempSync(path.join(os.tmpdir(), 'local-memory-'));
|
|
28
|
-
fs.mkdirSync(path.join(repo, 'src'));
|
|
29
|
-
});
|
|
30
|
-
afterEach(() => fs.rmSync(repo, { recursive: true, force: true }));
|
|
31
|
-
const failure = (id, line) => ({ id, title: id, details: 'x', severity: 'medium', provenance: 'traditional', files: ['src/a.ts'], line });
|
|
32
|
-
describe('persistAndReinforce', () => {
|
|
33
|
-
it('reinforces each category once per scan, however many findings it has', async () => {
|
|
34
|
-
await persistAndReinforce(repo, { status: 'FAIL', failures: [failure('content-check', 1), failure('content-check', 2), failure('content-check', 3), failure('ast', 4)], stats: {} });
|
|
35
|
-
expect(db.writes.filter(w => w.startsWith('INSERT INTO patterns'))).toHaveLength(2);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
describe('checkLocalPatterns', () => {
|
|
39
|
-
const remembered = (scanTime) => ({ file: 'src/a.ts', line: 3, category: 'security', description: 'token logged', confidence: 0.9, scan_time: scanTime });
|
|
40
|
-
it('replays a remembered finding while its file is unchanged since that scan, once', async () => {
|
|
41
|
-
fs.writeFileSync(path.join(repo, 'src/a.ts'), 'log(token)\n');
|
|
42
|
-
const after = fs.statSync(path.join(repo, 'src/a.ts')).mtimeMs + 1000;
|
|
43
|
-
db.patterns = [{ pattern: 'security', strength: 0.9 }];
|
|
44
|
-
db.findings = [remembered(after), remembered(after)];
|
|
45
|
-
const findings = await checkLocalPatterns(repo, ['src/a.ts']);
|
|
46
|
-
expect(findings.map(f => f.description)).toEqual(['[Local Memory] token logged']);
|
|
47
|
-
});
|
|
48
|
-
it('never replays a finding for a file edited since, which may be the fix', async () => {
|
|
49
|
-
fs.writeFileSync(path.join(repo, 'src/a.ts'), 'log(redact(token))\n');
|
|
50
|
-
const before = fs.statSync(path.join(repo, 'src/a.ts')).mtimeMs - 1000;
|
|
51
|
-
db.patterns = [{ pattern: 'security', strength: 0.9 }];
|
|
52
|
-
db.findings = [remembered(before)];
|
|
53
|
-
expect(await checkLocalPatterns(repo, ['src/a.ts'])).toEqual([]);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
const db = vi.hoisted(() => ({ rows: [], outbox: 0, reset() { this.rows = []; this.outbox = 0; } }));
|
|
3
|
-
vi.mock('./db.js', () => {
|
|
4
|
-
const store = {
|
|
5
|
-
run: async (sql, ...params) => {
|
|
6
|
-
if (sql.includes('INSERT INTO lessons'))
|
|
7
|
-
db.rows.push({ id: params[0], visibility: 'team', state: 'candidate', kind: 'memory', subject: params[4] });
|
|
8
|
-
if (sql.includes('INSERT INTO sync_outbox'))
|
|
9
|
-
db.outbox++;
|
|
10
|
-
return { changes: 1, lastID: 0 };
|
|
11
|
-
},
|
|
12
|
-
get: async (sql, ...params) => (sql.includes("kind = 'memory'") ? db.rows.find(r => r.subject === params[1]) : undefined),
|
|
13
|
-
all: async () => [],
|
|
14
|
-
exec: async () => undefined,
|
|
15
|
-
close: async () => undefined,
|
|
16
|
-
};
|
|
17
|
-
return { openDatabase: async () => store };
|
|
18
|
-
});
|
|
19
|
-
vi.mock('./local-encryption.js', () => ({ encryptLocalPayload: async (v) => JSON.stringify(v), decryptLocalPayload: async (v) => JSON.parse(v) }));
|
|
20
|
-
vi.mock('./team-store.js', () => ({ loadTeamConfiguration: async () => null }));
|
|
21
|
-
const { shareMemoryLesson } = await import('./lessons.js');
|
|
22
|
-
beforeEach(() => db.reset());
|
|
23
|
-
describe('shareMemoryLesson', () => {
|
|
24
|
-
it('stores a team candidate of kind memory, once per memory', async () => {
|
|
25
|
-
const first = await shareMemoryLesson(process.cwd(), 'retries', 'Use withRetry from lib/net.');
|
|
26
|
-
const again = await shareMemoryLesson(process.cwd(), 'retries', 'Use withRetry from lib/net.');
|
|
27
|
-
expect(first).toBe(again);
|
|
28
|
-
expect(db.rows).toEqual([{ id: first, visibility: 'team', state: 'candidate', kind: 'memory', subject: 'retries: Use withRetry from lib/net.' }]);
|
|
29
|
-
});
|
|
30
|
-
it('queues nothing for sync until team mode is configured', async () => {
|
|
31
|
-
await shareMemoryLesson(process.cwd(), 'naming', 'Hooks are named useX.');
|
|
32
|
-
expect(db.outbox).toBe(0);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { diagnoseMissingMembership, explainTeamConnectionError } from './team-diagnostics.js';
|
|
3
|
-
import { TEAM_SCHEMA } from './team-schema.js';
|
|
4
|
-
function pool(rows) {
|
|
5
|
-
return {
|
|
6
|
-
query: async () => {
|
|
7
|
-
if (rows instanceof Error)
|
|
8
|
-
throw rows;
|
|
9
|
-
return { rows, rowCount: rows.length };
|
|
10
|
-
},
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
describe('explainTeamConnectionError', () => {
|
|
14
|
-
it('explains an untrusted certificate chain with the CA fix', () => {
|
|
15
|
-
const message = explainTeamConnectionError(Object.assign(new Error('self-signed certificate in certificate chain'), { code: 'SELF_SIGNED_CERT_IN_CHAIN' }));
|
|
16
|
-
expect(message).toContain('NODE_EXTRA_CA_CERTS');
|
|
17
|
-
expect(message).toContain('sslmode=verify-full');
|
|
18
|
-
});
|
|
19
|
-
it('points a missing schema at init-schema', () => {
|
|
20
|
-
expect(explainTeamConnectionError({ code: '42P01', message: 'relation "rigour.memberships" does not exist' })).toContain('rigour team init-schema');
|
|
21
|
-
});
|
|
22
|
-
it('explains a rejected login and an unreachable host', () => {
|
|
23
|
-
expect(explainTeamConnectionError({ code: '28P01', message: 'password authentication failed' })).toContain('rejected the login');
|
|
24
|
-
expect(explainTeamConnectionError({ code: 'ECONNREFUSED', message: 'connect ECONNREFUSED' })).toContain('Cannot reach');
|
|
25
|
-
});
|
|
26
|
-
it('passes other errors through', () => {
|
|
27
|
-
expect(explainTeamConnectionError(new Error('something else'))).toBe('something else');
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
describe('diagnoseMissingMembership', () => {
|
|
31
|
-
it('names row-level security without a policy as the cause', async () => {
|
|
32
|
-
const message = await diagnoseMissingMembership(pool([{ table_name: 'memberships' }, { table_name: 'meta' }]));
|
|
33
|
-
expect(message).toContain('rigour.memberships and rigour.meta');
|
|
34
|
-
expect(message).toContain('init-schema');
|
|
35
|
-
});
|
|
36
|
-
it('reports a missing membership when nothing hides it', async () => {
|
|
37
|
-
expect(await diagnoseMissingMembership(pool([]))).toContain('no membership');
|
|
38
|
-
});
|
|
39
|
-
it('falls back to the membership message when the catalog cannot be read', async () => {
|
|
40
|
-
expect(await diagnoseMissingMembership(pool(new Error('permission denied')))).toContain('no membership');
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
describe('team schema', () => {
|
|
44
|
-
it('protects every team table with row-level security and a read policy', () => {
|
|
45
|
-
for (const table of ['lessons', 'memberships', 'meta']) {
|
|
46
|
-
expect(TEAM_SCHEMA).toContain(`ALTER TABLE rigour.${table} ENABLE ROW LEVEL SECURITY;`);
|
|
47
|
-
}
|
|
48
|
-
expect(TEAM_SCHEMA).toContain('CREATE POLICY memberships_read_own ON rigour.memberships FOR SELECT USING (db_role = current_user);');
|
|
49
|
-
expect(TEAM_SCHEMA).toContain('CREATE POLICY meta_read ON rigour.meta FOR SELECT USING (true);');
|
|
50
|
-
});
|
|
51
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { repositoryAllowed, withheldReason } from './team-scope.js';
|
|
3
|
-
const scope = { organizationId: 'acme', repositories: ['github.com/acme/*', 'https://GitLab.com/Acme/api'] };
|
|
4
|
-
describe('team scope', () => {
|
|
5
|
-
it('sends lessons only from the repositories the team lists', () => {
|
|
6
|
-
expect(repositoryAllowed('https://github.com/acme/web', scope)).toBe(true);
|
|
7
|
-
expect(repositoryAllowed('https://gitlab.com/acme/api', scope)).toBe(true);
|
|
8
|
-
expect(repositoryAllowed('https://gitlab.com/acme/api-internal', scope)).toBe(false);
|
|
9
|
-
expect(repositoryAllowed('https://github.com/other-org/web', scope)).toBe(false);
|
|
10
|
-
expect(repositoryAllowed('https://github.com/acme-evil/web', scope)).toBe(false);
|
|
11
|
-
});
|
|
12
|
-
it('sends nothing when the team lists no repositories', () => {
|
|
13
|
-
expect(repositoryAllowed('https://github.com/acme/web', { organizationId: 'acme' })).toBe(false);
|
|
14
|
-
});
|
|
15
|
-
it('keeps personal lessons and unknown repositories on the machine, and says why', () => {
|
|
16
|
-
expect(withheldReason('personal', 'https://github.com/acme/web', scope)).toBe('personal lesson');
|
|
17
|
-
expect(withheldReason('personal', 'https://github.com/acme/web', { ...scope, syncPersonal: true })).toBeNull();
|
|
18
|
-
expect(withheldReason('team', undefined, scope)).toBe('repository unknown on this machine');
|
|
19
|
-
expect(withheldReason('team', 'https://github.com/other-org/web', scope)).toContain('github.com/other-org/web');
|
|
20
|
-
expect(withheldReason('team', 'https://github.com/acme/web', scope)).toBeNull();
|
|
21
|
-
});
|
|
22
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { validateTeamDatabaseUrl } from './team-store.js';
|
|
3
|
-
describe('team database configuration', () => {
|
|
4
|
-
it('requires TLS for remote PostgreSQL URLs', () => {
|
|
5
|
-
expect(() => validateTeamDatabaseUrl('postgresql://user:pass@db.example.com/rigour')).toThrow(/sslmode/);
|
|
6
|
-
expect(() => validateTeamDatabaseUrl('postgresql://user:pass@db.example.com/rigour?sslmode=verify-full')).not.toThrow();
|
|
7
|
-
});
|
|
8
|
-
it('rejects non-PostgreSQL URLs', () => {
|
|
9
|
-
expect(() => validateTeamDatabaseUrl('mysql://localhost/rigour')).toThrow(/postgres/);
|
|
10
|
-
});
|
|
11
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
/** The outbox and repositories a machine holds, and what reached the team database. */
|
|
3
|
-
const state = vi.hoisted(() => ({ outbox: [], repos: new Map(), sent: [], refuse: new Set(), meta: new Map(), remote: [], since: [] }));
|
|
4
|
-
vi.mock('./db.js', () => ({
|
|
5
|
-
openDatabase: async () => ({
|
|
6
|
-
all: async () => state.outbox.filter(o => o.synced_at == null),
|
|
7
|
-
get: async (sql, id) => sql.includes('FROM meta')
|
|
8
|
-
? (state.meta.has(id) ? { value: state.meta.get(id) } : undefined)
|
|
9
|
-
: (state.repos.has(id) ? { canonical_uri: state.repos.get(id) } : undefined),
|
|
10
|
-
run: async (sql, ...p) => {
|
|
11
|
-
if (sql.startsWith('INSERT INTO meta'))
|
|
12
|
-
state.meta.set(p[0], p[1]);
|
|
13
|
-
if (sql.startsWith('UPDATE sync_outbox SET synced_at')) {
|
|
14
|
-
const id = p.at(-1);
|
|
15
|
-
Object.assign(state.outbox.find(o => o.id === id) ?? {}, { synced_at: p[0], last_error: p.length === 3 ? p[1] : null });
|
|
16
|
-
}
|
|
17
|
-
return { changes: 1 };
|
|
18
|
-
},
|
|
19
|
-
close: async () => undefined,
|
|
20
|
-
}),
|
|
21
|
-
}));
|
|
22
|
-
vi.mock('./local-encryption.js', () => ({ encryptLocalPayload: async (v) => JSON.stringify(v), decryptLocalPayload: async (v) => JSON.parse(v) }));
|
|
23
|
-
vi.mock('pg', () => ({
|
|
24
|
-
Pool: class {
|
|
25
|
-
async query(sql, params) {
|
|
26
|
-
if (sql.includes('INSERT INTO rigour.lessons')) {
|
|
27
|
-
if (state.refuse.has(params[0]))
|
|
28
|
-
throw Object.assign(new Error('new row violates row-level security policy for table "lessons"'), { code: '42501' });
|
|
29
|
-
state.sent.push(params[0]);
|
|
30
|
-
return { rowCount: 1, rows: [] };
|
|
31
|
-
}
|
|
32
|
-
if (sql.includes('FROM rigour.lessons')) {
|
|
33
|
-
state.since.push(params[3]);
|
|
34
|
-
return { rowCount: 0, rows: state.remote.filter(row => row.updated_at > params[3]) };
|
|
35
|
-
}
|
|
36
|
-
return { rowCount: 1, rows: [] };
|
|
37
|
-
}
|
|
38
|
-
async end() { }
|
|
39
|
-
},
|
|
40
|
-
}));
|
|
41
|
-
const { syncTeamOutbox } = await import('./team-store.js');
|
|
42
|
-
const queue = (id, repositoryId, visibility) => state.outbox.push({ id: `outbox-${id}`, payload_json: JSON.stringify({ id, repositoryId, visibility, evidence: {} }), synced_at: null });
|
|
43
|
-
const TEAM_ENV = {
|
|
44
|
-
RIGOUR_ORGANIZATION_ID: 'acme', RIGOUR_TEAM_ID: 'web', RIGOUR_ACTOR_ID: 'dev-1',
|
|
45
|
-
RIGOUR_TEAM_DATABASE_URL: 'postgresql://localhost/rigour', RIGOUR_TEAM_REPOSITORIES: 'github.com/acme/*',
|
|
46
|
-
};
|
|
47
|
-
const saved = { ...process.env };
|
|
48
|
-
beforeEach(() => {
|
|
49
|
-
Object.assign(process.env, TEAM_ENV);
|
|
50
|
-
delete process.env.RIGOUR_TEAM_SYNC_PERSONAL;
|
|
51
|
-
state.outbox = [];
|
|
52
|
-
state.sent = [];
|
|
53
|
-
state.refuse = new Set();
|
|
54
|
-
state.meta = new Map();
|
|
55
|
-
state.remote = [];
|
|
56
|
-
state.since = [];
|
|
57
|
-
state.repos = new Map([['r-team', 'https://github.com/acme/web'], ['r-other', 'https://github.com/other-org/app']]);
|
|
58
|
-
queue('l-team', 'r-team', 'team');
|
|
59
|
-
queue('l-other', 'r-other', 'team');
|
|
60
|
-
queue('l-personal', 'r-team', 'personal');
|
|
61
|
-
queue('l-unknown', 'r-gone', 'team');
|
|
62
|
-
});
|
|
63
|
-
afterEach(() => { process.env = { ...saved }; });
|
|
64
|
-
describe('team sync scope', () => {
|
|
65
|
-
it("sends only the team's own shared lessons and says what it kept back", async () => {
|
|
66
|
-
expect(await syncTeamOutbox({ dryRun: true })).toMatchObject({ pending: 1, withheld: 3 });
|
|
67
|
-
expect(state.sent).toEqual([]);
|
|
68
|
-
const result = await syncTeamOutbox();
|
|
69
|
-
expect(result).toMatchObject({ synced: 1, withheld: 3 });
|
|
70
|
-
expect(state.sent).toEqual(['l-team']);
|
|
71
|
-
const reasons = Object.fromEntries(state.outbox.map(o => [o.id, o.last_error]));
|
|
72
|
-
expect(reasons['outbox-l-other']).toContain('github.com/other-org/app');
|
|
73
|
-
expect(reasons['outbox-l-personal']).toContain('personal lesson');
|
|
74
|
-
expect(reasons['outbox-l-unknown']).toContain('repository unknown');
|
|
75
|
-
expect(await syncTeamOutbox({ dryRun: true })).toMatchObject({ pending: 0, withheld: 0 }); // never retried
|
|
76
|
-
});
|
|
77
|
-
it('sets aside a lesson the database refuses and sends the rest of the queue', async () => {
|
|
78
|
-
state.outbox = [];
|
|
79
|
-
queue('l-refused', 'r-team', 'team'); // queued first: it must not hold back what comes after
|
|
80
|
-
queue('l-team', 'r-team', 'team');
|
|
81
|
-
state.refuse.add('l-refused');
|
|
82
|
-
expect(await syncTeamOutbox()).toMatchObject({ synced: 1 });
|
|
83
|
-
expect(state.sent).toEqual(['l-team']);
|
|
84
|
-
expect(state.outbox.find(o => o.id === 'outbox-l-refused').last_error).toContain('refused by the team database: new row violates row-level security');
|
|
85
|
-
expect(await syncTeamOutbox({ dryRun: true })).toMatchObject({ pending: 0 }); // not tried again
|
|
86
|
-
});
|
|
87
|
-
it('pulls only what changed since the last pull, re-reading a margin for slow clocks', async () => {
|
|
88
|
-
const lesson = (id, updated) => ({ id, repository_id: 'r-team', actor_id: 'dev-2', team_id: 'web', visibility: 'team', state: 'promoted', kind: 'k', subject: 's', evidence_json: {}, confidence: 1, source: 'x', supersedes_id: null, created_at: updated, updated_at: updated });
|
|
89
|
-
const HOUR = 3_600_000;
|
|
90
|
-
state.remote = [lesson('a', 10 * HOUR), lesson('b', 20 * HOUR)];
|
|
91
|
-
expect(await syncTeamOutbox()).toMatchObject({ pulled: 2 });
|
|
92
|
-
expect(state.since).toEqual([0]); // the first pull reads everything
|
|
93
|
-
expect(await syncTeamOutbox()).toMatchObject({ pulled: 1 }); // only b, inside the 15-minute margin
|
|
94
|
-
expect(state.since[1]).toBe(20 * HOUR - 15 * 60_000);
|
|
95
|
-
});
|
|
96
|
-
it('sends nothing when the team lists no repositories', async () => {
|
|
97
|
-
delete process.env.RIGOUR_TEAM_REPOSITORIES;
|
|
98
|
-
expect(await syncTeamOutbox({ dryRun: true })).toMatchObject({ pending: 0, withheld: 4 });
|
|
99
|
-
});
|
|
100
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { getTeamSemanticHealth, upsertTeamLessonEmbedding } from './team-vector-store.js';
|
|
3
|
-
const config = {
|
|
4
|
-
organizationId: 'acme',
|
|
5
|
-
teamId: 'platform',
|
|
6
|
-
actorId: 'alice',
|
|
7
|
-
databaseUrl: 'postgresql://localhost/rigour',
|
|
8
|
-
semantic: {
|
|
9
|
-
provider: 'pgvector',
|
|
10
|
-
model: 'Xenova/all-MiniLM-L6-v2',
|
|
11
|
-
dimensions: 384,
|
|
12
|
-
},
|
|
13
|
-
};
|
|
14
|
-
describe('pgvector team health', () => {
|
|
15
|
-
it('reports extension, model, and embedding coverage', async () => {
|
|
16
|
-
const pool = {
|
|
17
|
-
query: vi.fn(async () => ({ rows: [{
|
|
18
|
-
extversion: '0.8.1',
|
|
19
|
-
table_ready: true,
|
|
20
|
-
indexed_lessons: 12,
|
|
21
|
-
missing_lessons: 3,
|
|
22
|
-
}] })),
|
|
23
|
-
end: async () => undefined,
|
|
24
|
-
};
|
|
25
|
-
await expect(getTeamSemanticHealth(pool, config)).resolves.toEqual({
|
|
26
|
-
provider: 'pgvector',
|
|
27
|
-
status: 'ready',
|
|
28
|
-
extensionVersion: '0.8.1',
|
|
29
|
-
model: 'Xenova/all-MiniLM-L6-v2',
|
|
30
|
-
dimensions: 384,
|
|
31
|
-
indexedLessons: 12,
|
|
32
|
-
missingLessons: 3,
|
|
33
|
-
});
|
|
34
|
-
expect(pool.query).toHaveBeenCalledWith(expect.stringContaining("lesson.state IN ('validated', 'promoted')"), ['acme', 'platform', 'Xenova/all-MiniLM-L6-v2', 'alice']);
|
|
35
|
-
});
|
|
36
|
-
it('fails health checks when vector storage is incomplete', async () => {
|
|
37
|
-
const pool = {
|
|
38
|
-
query: async () => ({ rows: [{ extversion: null, table_ready: false }] }),
|
|
39
|
-
end: async () => undefined,
|
|
40
|
-
};
|
|
41
|
-
await expect(getTeamSemanticHealth(pool, config)).rejects.toThrow(/vector extension/);
|
|
42
|
-
});
|
|
43
|
-
it('does not embed unvalidated candidate lessons', async () => {
|
|
44
|
-
const pool = { query: vi.fn(), end: async () => undefined };
|
|
45
|
-
await expect(upsertTeamLessonEmbedding(pool, config, {
|
|
46
|
-
id: 'candidate-1',
|
|
47
|
-
repositoryId: 'repo-1',
|
|
48
|
-
actorId: 'alice',
|
|
49
|
-
kind: 'interaction',
|
|
50
|
-
subject: 'Unverified suggestion',
|
|
51
|
-
source: 'mcp',
|
|
52
|
-
state: 'candidate',
|
|
53
|
-
})).resolves.toBe(false);
|
|
54
|
-
expect(pool.query).not.toHaveBeenCalled();
|
|
55
|
-
});
|
|
56
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import os from 'os';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { afterAll, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
/** Every open goes to a scratch database, never ~/.rigour/rigour.db. */
|
|
6
|
-
const dbFile = vi.hoisted(() => require('path').join(require('os').tmpdir(), `rigour-telemetry-${process.pid}.db`));
|
|
7
|
-
vi.mock('./db.js', async (importOriginal) => {
|
|
8
|
-
const real = await importOriginal();
|
|
9
|
-
return { ...real, openDatabase: (p) => real.openDatabase(p ?? dbFile) };
|
|
10
|
-
});
|
|
11
|
-
const { openDatabase, isSQLiteAvailable } = await import('./db.js');
|
|
12
|
-
const { getCheckpointMetrics, getContextEvents, recordCheckpointMetric, recordContextEvent } = await import('./context-telemetry.js');
|
|
13
|
-
const repoA = fs.mkdtempSync(path.join(os.tmpdir(), 'telemetry-a-'));
|
|
14
|
-
const repoB = fs.mkdtempSync(path.join(os.tmpdir(), 'telemetry-b-'));
|
|
15
|
-
afterAll(() => {
|
|
16
|
-
for (const p of [dbFile, `${dbFile}-wal`, `${dbFile}-shm`])
|
|
17
|
-
fs.rmSync(p, { force: true });
|
|
18
|
-
for (const dir of [repoA, repoB])
|
|
19
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
20
|
-
});
|
|
21
|
-
describe.runIf(isSQLiteAvailable())('telemetry scoped to its repository', () => {
|
|
22
|
-
it('migrates idempotently when two processes open the database at once', async () => {
|
|
23
|
-
const [a, b] = await Promise.all([openDatabase(), openDatabase()]);
|
|
24
|
-
const columns = (await a.all('PRAGMA table_info(checkpoint_metrics)')).map((c) => c.name);
|
|
25
|
-
expect(columns).toContain('repository_id');
|
|
26
|
-
await a.close();
|
|
27
|
-
await b.close();
|
|
28
|
-
});
|
|
29
|
-
it("shows a repository its own checkpoints and context events, never another's", async () => {
|
|
30
|
-
await recordCheckpointMetric({ checkpointId: 'cp-a', taskId: 't', agentId: 'agent', checkpointTokens: 10, rawStateTokens: 0, replayTokensAvoided: 0 }, repoA);
|
|
31
|
-
await recordCheckpointMetric({ checkpointId: 'cp-b', taskId: 't', agentId: 'agent', checkpointTokens: 10, rawStateTokens: 0, replayTokensAvoided: 0 }, repoB);
|
|
32
|
-
await recordContextEvent({ toolName: 'rigour_recall', cacheStatus: 'miss', candidateTokens: 0, returnedTokens: 0 }, repoA);
|
|
33
|
-
expect((await getCheckpointMetrics(undefined, repoA)).map(m => m.checkpointId)).toEqual(['cp-a']);
|
|
34
|
-
expect((await getCheckpointMetrics(undefined, repoB)).map(m => m.checkpointId)).toEqual(['cp-b']);
|
|
35
|
-
expect(await getContextEvents(undefined, repoB)).toEqual([]);
|
|
36
|
-
expect((await getContextEvents(undefined, repoA)).map(e => e.toolName)).toEqual(['rigour_recall']);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import os from 'os';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import { countUsage, durationBucket, flushDailyUsage, isTelemetryEnabled, readTelemetryState, setTelemetryEnabled, shouldAskTelemetry, trackUsage } from './telemetry.js';
|
|
6
|
-
let home;
|
|
7
|
-
const env = { RIGOUR_MIXPANEL_TOKEN: 'tok' };
|
|
8
|
-
beforeEach(() => { home = fs.mkdtempSync(path.join(os.tmpdir(), 'telemetry-')); });
|
|
9
|
-
afterEach(() => { fs.rmSync(home, { recursive: true, force: true }); });
|
|
10
|
-
describe('telemetry consent', () => {
|
|
11
|
-
it('sends nothing until the person says yes, and the environment always wins', () => {
|
|
12
|
-
expect(isTelemetryEnabled({ env, home })).toBe(false);
|
|
13
|
-
expect(shouldAskTelemetry(true, { env, home })).toBe(true);
|
|
14
|
-
setTelemetryEnabled(true, home);
|
|
15
|
-
expect(isTelemetryEnabled({ env, home })).toBe(true);
|
|
16
|
-
expect(shouldAskTelemetry(true, { env, home })).toBe(false); // asked once
|
|
17
|
-
expect(isTelemetryEnabled({ env: { ...env, DO_NOT_TRACK: '1' }, home })).toBe(false);
|
|
18
|
-
expect(isTelemetryEnabled({ env: { ...env, RIGOUR_TELEMETRY: '0' }, home })).toBe(false);
|
|
19
|
-
expect(isTelemetryEnabled({ env: { ...env, CI: 'true' }, home })).toBe(false);
|
|
20
|
-
expect(isTelemetryEnabled({ env: {}, home })).toBe(false); // a build without a token never sends
|
|
21
|
-
});
|
|
22
|
-
it('never asks in CI or without a terminal, and keeps one random install id', () => {
|
|
23
|
-
expect(shouldAskTelemetry(false, { env, home })).toBe(false);
|
|
24
|
-
expect(shouldAskTelemetry(true, { env: { ...env, GITHUB_ACTIONS: 'true' }, home })).toBe(false);
|
|
25
|
-
setTelemetryEnabled(true, home);
|
|
26
|
-
for (const ci of ['JENKINS_URL', 'TF_BUILD', 'TEAMCITY_VERSION']) {
|
|
27
|
-
expect(isTelemetryEnabled({ env: { ...env, [ci]: 'x' }, home })).toBe(false); // a build agent's stored yes is not a CI opt-in
|
|
28
|
-
expect(isTelemetryEnabled({ env: { ...env, [ci]: 'x', RIGOUR_TELEMETRY: '1' }, home })).toBe(true);
|
|
29
|
-
}
|
|
30
|
-
const first = setTelemetryEnabled(false, home).installId;
|
|
31
|
-
expect(readTelemetryState(home).installId).toBe(first);
|
|
32
|
-
expect(first).toMatch(/^[0-9a-f-]{36}$/);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
describe('telemetry sending', () => {
|
|
36
|
-
it('sends one event with the install id and no IP, and swallows a failing network', async () => {
|
|
37
|
-
setTelemetryEnabled(true, home);
|
|
38
|
-
const fetch = vi.fn().mockResolvedValue({ ok: true });
|
|
39
|
-
await trackUsage('review_completed', { status: 'PASS', findings_by_gate: { 'semantic-bugs': 1 } }, { env, home, fetch, version: '6.5.0' });
|
|
40
|
-
const [url, init] = fetch.mock.calls[0];
|
|
41
|
-
expect(url).toBe('https://api.mixpanel.com/track?ip=0');
|
|
42
|
-
const [event] = JSON.parse(init.body);
|
|
43
|
-
expect(event.event).toBe('review_completed');
|
|
44
|
-
expect(event.properties).toMatchObject({ token: 'tok', distinct_id: readTelemetryState(home).installId, version: '6.5.0', status: 'PASS' });
|
|
45
|
-
expect(JSON.stringify(event)).not.toContain(home);
|
|
46
|
-
await expect(trackUsage('x', {}, { env, home, fetch: vi.fn().mockRejectedValue(new Error('offline')) })).resolves.toBeUndefined();
|
|
47
|
-
});
|
|
48
|
-
it('counts hot-path activity locally and sends it as one daily event', async () => {
|
|
49
|
-
setTelemetryEnabled(true, home);
|
|
50
|
-
const fetch = vi.fn().mockResolvedValue({ ok: true });
|
|
51
|
-
const day = 24 * 60 * 60 * 1000;
|
|
52
|
-
countUsage('hook_check', 1, { env, home, now: 0 });
|
|
53
|
-
countUsage('hook_check', 1, { env, home, now: 1000 });
|
|
54
|
-
countUsage('mcp:rigour_review', 1, { env, home, now: 2000 });
|
|
55
|
-
expect(await flushDailyUsage({ env, home, fetch, now: 5000 })).toBe(false); // not a day yet
|
|
56
|
-
expect(await flushDailyUsage({ env, home, fetch, now: day + 1 })).toBe(true);
|
|
57
|
-
const [event] = JSON.parse(fetch.mock.calls[0][1].body);
|
|
58
|
-
expect(event).toMatchObject({ event: 'daily_usage', properties: { hook_check: 2, 'mcp:rigour_review': 1 } });
|
|
59
|
-
expect(await flushDailyUsage({ env, home, fetch, now: 2 * day + 2 })).toBe(false); // counters reset
|
|
60
|
-
});
|
|
61
|
-
it('buckets durations so timing never identifies a run', () => {
|
|
62
|
-
expect([500, 3000, 20000, 60000, 600000].map(durationBucket)).toEqual(['<1s', '1-5s', '5-30s', '30s-2m', '>2m']);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types/index.test.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import yaml from 'yaml';
|
|
3
|
-
import { ConfigSchema } from './index.js';
|
|
4
|
-
describe('rigour.yml', () => {
|
|
5
|
-
it('still loads a file that sets the settings no check ever read, and keeps everything else', () => {
|
|
6
|
-
const old = yaml.parse(`
|
|
7
|
-
version: 1
|
|
8
|
-
planned: [Layer boundaries]
|
|
9
|
-
hooks: { enabled: true, tools: [claude], fast_gates: [file-size], timeout_ms: 5000, block_on_failure: true, dlp: false, require_review_ack: true }
|
|
10
|
-
gates:
|
|
11
|
-
max_file_lines: 300
|
|
12
|
-
forbid_paths: [secret/]
|
|
13
|
-
ast: { complexity: 7, max_nesting: 3, max_function_lines: 40 }
|
|
14
|
-
adaptive: { enabled: true, forced_tier: enterprise }
|
|
15
|
-
input_validation: { enabled: true, min_secret_length: 12 }
|
|
16
|
-
deep: { enabled: true, provider: claude, api_key: sk-test, model_name: x, threads: 8, temperature: 0.2 }
|
|
17
|
-
test_quality: { check_snapshot_abuse: false, ignore_patterns: [x], max_mocks_per_test: 3 }
|
|
18
|
-
`);
|
|
19
|
-
const config = ConfigSchema.parse(old);
|
|
20
|
-
expect(config.gates.max_file_lines).toBe(300);
|
|
21
|
-
expect(config.gates.ast.complexity).toBe(7);
|
|
22
|
-
expect(config.gates.deep.temperature).toBe(0.2);
|
|
23
|
-
expect(config.gates.test_quality.max_mocks_per_test).toBe(3);
|
|
24
|
-
expect(config.hooks.require_review_ack).toBe(true);
|
|
25
|
-
// What nothing read is dropped, so no code can start depending on it by accident.
|
|
26
|
-
expect(config).not.toHaveProperty('version');
|
|
27
|
-
expect(config).not.toHaveProperty('planned');
|
|
28
|
-
expect(config.gates).not.toHaveProperty('adaptive');
|
|
29
|
-
expect(config.gates).not.toHaveProperty('input_validation');
|
|
30
|
-
expect(config.gates.deep).not.toHaveProperty('api_key');
|
|
31
|
-
expect(config.hooks).not.toHaveProperty('block_on_failure');
|
|
32
|
-
});
|
|
33
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { splitCommand } from './command-line.js';
|
|
3
|
-
describe('splitCommand', () => {
|
|
4
|
-
it('splits on spaces and keeps a quoted argument whole, without its quotes', () => {
|
|
5
|
-
expect(splitCommand('npm run lint')).toEqual({ bin: 'npm', args: ['run', 'lint'] });
|
|
6
|
-
expect(splitCommand(`vitest run "src/a b.test.ts" --reporter 'dot'`)).toEqual({ bin: 'vitest', args: ['run', 'src/a b.test.ts', '--reporter', 'dot'] });
|
|
7
|
-
});
|
|
8
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { parseDiff, removedByFile } from './diff.js';
|
|
3
|
-
const DIFF = `diff --git a/src/a.ts b/src/a.ts
|
|
4
|
-
--- a/src/a.ts
|
|
5
|
-
+++ b/src/a.ts
|
|
6
|
-
@@ -1,5 +1,4 @@
|
|
7
|
-
export function load(id: string) {
|
|
8
|
-
- if (!id) return null;
|
|
9
|
-
- audit(id);
|
|
10
|
-
+ audit(id ?? '');
|
|
11
|
-
return fetch(id);
|
|
12
|
-
}
|
|
13
|
-
@@ -20,2 +19,1 @@
|
|
14
|
-
-const legacy = true;
|
|
15
|
-
export const x = 1;
|
|
16
|
-
`;
|
|
17
|
-
describe('removedByFile', () => {
|
|
18
|
-
it('groups removed lines into blocks anchored at the new-side line', () => {
|
|
19
|
-
expect(removedByFile(DIFF)).toEqual({
|
|
20
|
-
'src/a.ts': [
|
|
21
|
-
{ line: 2, text: [' if (!id) return null;', ' audit(id);'] },
|
|
22
|
-
{ line: 19, text: ['const legacy = true;'] },
|
|
23
|
-
],
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
it('leaves added-line parsing unchanged', () => {
|
|
27
|
-
expect([...parseDiff(DIFF)['src/a.ts']]).toEqual([2]);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/utils/diff.test.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { changedLinesByFile, parseDiff } from './diff.js';
|
|
3
|
-
const lines = (m) => changedLinesByFile(m);
|
|
4
|
-
describe('parseDiff', () => {
|
|
5
|
-
it('maps added lines to new-side line numbers', () => {
|
|
6
|
-
const diff = [
|
|
7
|
-
'diff --git a/src/a.ts b/src/a.ts',
|
|
8
|
-
'--- a/src/a.ts',
|
|
9
|
-
'+++ b/src/a.ts',
|
|
10
|
-
'@@ -1,3 +1,4 @@',
|
|
11
|
-
' const a = 1;',
|
|
12
|
-
'-const b = 2;',
|
|
13
|
-
'+const b = 3;',
|
|
14
|
-
'+const c = 4;',
|
|
15
|
-
' export { a };',
|
|
16
|
-
].join('\n');
|
|
17
|
-
expect(lines(parseDiff(diff))).toEqual({ 'src/a.ts': [2, 3] });
|
|
18
|
-
});
|
|
19
|
-
it('handles several files and hunks', () => {
|
|
20
|
-
const diff = [
|
|
21
|
-
'+++ b/a.ts', '@@ -10,2 +10,3 @@', ' x', '+y', ' z',
|
|
22
|
-
'+++ b/b.ts', '@@ -1 +1 @@', '-old', '+new',
|
|
23
|
-
].join('\n');
|
|
24
|
-
expect(lines(parseDiff(diff))).toEqual({ 'a.ts': [11], 'b.ts': [1] });
|
|
25
|
-
});
|
|
26
|
-
it('does not mistake content lines starting with +++ or --- for headers', () => {
|
|
27
|
-
const diff = ['+++ b/a.ts', '@@ -1,2 +1,3 @@', ' let i = 0;', '+++i;', '---j;', ' done();'].join('\n');
|
|
28
|
-
expect(lines(parseDiff(diff))).toEqual({ 'a.ts': [2] });
|
|
29
|
-
});
|
|
30
|
-
it('does not count "No newline at end of file" as a line', () => {
|
|
31
|
-
const diff = ['+++ b/a.ts', '@@ -1,1 +1,2 @@', ' a', '\', '+b'].join('\n');
|
|
32
|
-
expect(lines(parseDiff(diff))).toEqual({ 'a.ts': [2] });
|
|
33
|
-
});
|
|
34
|
-
it('ignores deleted files', () => {
|
|
35
|
-
const diff = ['diff --git a/gone.ts b/gone.ts', '--- a/gone.ts', '+++ /dev/null', '@@ -1,2 +0,0 @@', '-a', '-b'].join('\n');
|
|
36
|
-
expect(parseDiff(diff)).toEqual({});
|
|
37
|
-
});
|
|
38
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|