@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,76 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { buildEngineeringKnowledgeGraph } from './engineering-knowledge-graph.js';
|
|
3
|
-
describe('engineering knowledge graph', () => {
|
|
4
|
-
it('connects code, agent outcomes and cross-repository lessons', () => {
|
|
5
|
-
const graph = buildEngineeringKnowledgeGraph({
|
|
6
|
-
repository: { id: 'repo-a', name: 'payments' },
|
|
7
|
-
dependencyGraph: { version: 1, generatedAt: '', nodes: ['src/a.ts', 'src/b.ts'], edges: [{ from: 'src/a.ts', to: 'src/b.ts' }] },
|
|
8
|
-
events: [],
|
|
9
|
-
runs: [{ id: 'run-1', agentId: 'agent-1', startedAt: '', endedAt: '', status: 'success', eventCount: 2, files: ['src/a.ts'], tools: ['check'] }],
|
|
10
|
-
lessons: [{ id: 'lesson-1', repositoryId: 'repo-b', repositoryName: 'platform', visibility: 'team', state: 'promoted', kind: 'interaction', subject: 'Use bounded retries', evidence: {}, confidence: 0.9, source: 'mcp', createdAt: 1, updatedAt: 1 }],
|
|
11
|
-
patterns: [{ id: 'pattern-1', name: 'withTransaction', type: 'function', file: 'src/a.ts', usageCount: 8 }],
|
|
12
|
-
memories: [{ id: 'memory-1', label: 'migration-owner', source: 'project' }],
|
|
13
|
-
});
|
|
14
|
-
expect(graph.counts.repository).toBe(2);
|
|
15
|
-
expect(graph.nodes.some(node => node.type === 'outcome' && node.label === 'success')).toBe(true);
|
|
16
|
-
expect(graph.edges.some(edge => edge.type === 'imports')).toBe(true);
|
|
17
|
-
expect(graph.edges.some(edge => edge.type === 'produced')).toBe(true);
|
|
18
|
-
expect(graph.nodes.some(node => node.type === 'pattern')).toBe(true);
|
|
19
|
-
expect(graph.nodes.some(node => node.type === 'memory')).toBe(true);
|
|
20
|
-
});
|
|
21
|
-
it('represents prevented agent actions as policy evidence', () => {
|
|
22
|
-
const graph = buildEngineeringKnowledgeGraph({
|
|
23
|
-
repository: { id: 'repo-a', name: 'payments' },
|
|
24
|
-
events: [{ id: 'event-1', timestamp: '', type: 'tool_denied', agentId: 'agent-1', outcome: 'rejected', files: [], summary: 'write denied' }],
|
|
25
|
-
runs: [],
|
|
26
|
-
lessons: [],
|
|
27
|
-
});
|
|
28
|
-
expect(graph.nodes).toContainEqual(expect.objectContaining({ type: 'policy', state: 'active' }));
|
|
29
|
-
expect(graph.nodes).toContainEqual(expect.objectContaining({ type: 'outcome', state: 'verified' }));
|
|
30
|
-
expect(graph.edges.some(edge => edge.type === 'protects')).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
it('connects issued advice to its run and supporting knowledge', () => {
|
|
33
|
-
const event = {
|
|
34
|
-
id: 'event-1', requestId: 'run-1', timestamp: '2026-01-01T00:00:00Z', type: 'tool_response',
|
|
35
|
-
agentId: 'agent-1', outcome: 'success', files: ['src/a.ts'], summary: 'scope returned',
|
|
36
|
-
guidance: {
|
|
37
|
-
kind: 'context-scope',
|
|
38
|
-
recommendation: 'Read only src/a.ts.',
|
|
39
|
-
patternRefs: [{ id: 'pattern-1', label: 'withTransaction' }],
|
|
40
|
-
lessonRefs: [], memoryRefs: [], selectedFiles: ['src/a.ts'], excludedFileCount: 9, conflicts: 0,
|
|
41
|
-
},
|
|
42
|
-
telemetry: {
|
|
43
|
-
candidateTokens: 1200, returnedTokens: 200, candidateFiles: 10, returnedFiles: 1,
|
|
44
|
-
cacheStatus: 'exact-hit', classification: 'measured estimate',
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
const graph = buildEngineeringKnowledgeGraph({
|
|
48
|
-
repository: { id: 'repo-a', name: 'payments' },
|
|
49
|
-
dependencyGraph: { version: 1, generatedAt: '', nodes: ['src/a.ts'], edges: [] },
|
|
50
|
-
events: [event],
|
|
51
|
-
runs: [{ id: 'run-1', agentId: 'agent-1', startedAt: event.timestamp, endedAt: event.timestamp, status: 'success', eventCount: 1, files: ['src/a.ts'], tools: ['rigour_context_scope'] }],
|
|
52
|
-
lessons: [],
|
|
53
|
-
patterns: [{ id: 'pattern-1', name: 'withTransaction', type: 'function', file: 'src/a.ts' }],
|
|
54
|
-
});
|
|
55
|
-
expect(graph.nodes).toContainEqual(expect.objectContaining({ type: 'advice', evidence: expect.objectContaining({ avoidedTokens: 1000 }) }));
|
|
56
|
-
expect(graph.edges).toContainEqual(expect.objectContaining({ from: 'run:run-1', to: 'advice:run-1', type: 'guided' }));
|
|
57
|
-
expect(graph.edges).toContainEqual(expect.objectContaining({ from: 'pattern:pattern-1', to: 'advice:run-1', type: 'informed' }));
|
|
58
|
-
});
|
|
59
|
-
it('shows trusted gateway receipts, simulated denials and capability lineage', () => {
|
|
60
|
-
const graph = buildEngineeringKnowledgeGraph({
|
|
61
|
-
repository: { id: 'repo-a', name: 'payments' }, events: [], runs: [], lessons: [],
|
|
62
|
-
gateway: { mode: 'observe', agentId: 'agent-1', taskId: 'task-1', serverCount: 1, toolCount: 2, chainValid: true, receiptCount: 1 },
|
|
63
|
-
capabilities: [{ id: 'child', action: 'mcp.call', resource: 'mcp://github/get_issue', expiresAt: Date.now() + 1000, policyHash: 'hash', used: false, issuerId: 'owner', subjectId: 'agent-1', parentCapabilityId: 'parent' }],
|
|
64
|
-
receipts: [{
|
|
65
|
-
version: 1, id: 'receipt-1', repositoryId: 'repo-a', mode: 'observe', decision: 'allow', simulatedDecision: 'deny',
|
|
66
|
-
reason: 'would deny', policyHash: 'hash', outcome: 'forwarded', createdAt: '2026-01-01T00:00:00Z', digest: 'digest', signature: 'signature',
|
|
67
|
-
action: { version: 1, actorId: 'agent-1', taskId: 'task-1', channel: 'mcp', operation: 'github__get_issue', resource: 'mcp://github/get_issue', environment: 'local', sideEffect: 'read', reversibility: 'high', sensitivity: 'normal', blastRadius: 'single-resource' },
|
|
68
|
-
capabilityId: 'child',
|
|
69
|
-
}],
|
|
70
|
-
});
|
|
71
|
-
expect(graph.nodes).toContainEqual(expect.objectContaining({ type: 'gateway', evidence: expect.objectContaining({ chainValid: true }) }));
|
|
72
|
-
expect(graph.nodes).toContainEqual(expect.objectContaining({ type: 'action', state: 'would deny' }));
|
|
73
|
-
expect(graph.edges).toContainEqual(expect.objectContaining({ from: 'capability:child', to: 'capability:parent', type: 'delegated_from' }));
|
|
74
|
-
expect(graph.edges).toContainEqual(expect.objectContaining({ from: 'action:receipt-1', to: 'gateway:rigour-mcp', type: 'routes_through' }));
|
|
75
|
-
});
|
|
76
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { normalizeModelKey, getInputPricePerMillionUsd, computeWeightedInputPricePerMillion, estimateAvoidedContextCostUsd, estimateTokenCostUsd, FALLBACK_INPUT_PRICE_PER_MILLION_USD, } from './model-pricing.js';
|
|
3
|
-
describe('model-pricing', () => {
|
|
4
|
-
it('resolves Composer 2.5 Standard input pricing', () => {
|
|
5
|
-
expect(getInputPricePerMillionUsd('Composer 2.5 Standard')).toBe(0.5);
|
|
6
|
-
expect(normalizeModelKey('composer-2-5-standard')).toBe('composer-2.5-standard');
|
|
7
|
-
});
|
|
8
|
-
it('resolves Composer 2.5 Fast input pricing', () => {
|
|
9
|
-
expect(getInputPricePerMillionUsd('composer-2.5-fast')).toBe(3.0);
|
|
10
|
-
expect(normalizeModelKey('Composer 2.5 Fast')).toBe('composer-2.5-fast');
|
|
11
|
-
});
|
|
12
|
-
it('normalizes aliases with spaces and hyphens', () => {
|
|
13
|
-
expect(normalizeModelKey('Composer_2.5 FAST')).toBe('composer-2.5-fast');
|
|
14
|
-
expect(normalizeModelKey('composer 25 standard')).toBe('composer-2.5-standard');
|
|
15
|
-
});
|
|
16
|
-
it('uses conservative fallback for unknown models', () => {
|
|
17
|
-
expect(getInputPricePerMillionUsd('totally-unknown-model')).toBe(FALLBACK_INPUT_PRICE_PER_MILLION_USD);
|
|
18
|
-
});
|
|
19
|
-
it('computes mixed-model input-token-weighted pricing', () => {
|
|
20
|
-
const usages = [
|
|
21
|
-
{ source: 'cursor-admin-api', model: 'composer-2.5-standard', inputTokens: 1000, outputTokens: 0 },
|
|
22
|
-
{ source: 'cursor-admin-api', model: 'composer-2.5-fast', inputTokens: 3000, outputTokens: 0 },
|
|
23
|
-
];
|
|
24
|
-
const weighted = computeWeightedInputPricePerMillion(usages);
|
|
25
|
-
expect(weighted.inputPricePerMillionUsd).toBeCloseTo(2.375, 3);
|
|
26
|
-
expect(weighted.pricingBasis).toContain('mixed-model-weighted');
|
|
27
|
-
});
|
|
28
|
-
it('prefers observed cost when estimating actual spend is unnecessary', () => {
|
|
29
|
-
const observed = 4.25;
|
|
30
|
-
expect(observed).toBeGreaterThan(0);
|
|
31
|
-
});
|
|
32
|
-
it('estimates actual token cost when observed cost is absent', () => {
|
|
33
|
-
const estimated = estimateTokenCostUsd(1_000_000, 0, 'composer-2.5-standard');
|
|
34
|
-
expect(estimated).toBe(0.5);
|
|
35
|
-
});
|
|
36
|
-
it('estimates avoided context using input pricing only', () => {
|
|
37
|
-
const usages = [
|
|
38
|
-
{ source: 'cursor-admin-api', model: 'composer-2.5-standard', inputTokens: 100, outputTokens: 0 },
|
|
39
|
-
];
|
|
40
|
-
const { costUsd, pricing } = estimateAvoidedContextCostUsd(2_000_000, usages);
|
|
41
|
-
expect(pricing.inputPricePerMillionUsd).toBe(0.5);
|
|
42
|
-
expect(costUsd).toBe(1.0);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,37 +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 { observedSavings, recordAgentActivity, recordHookPayload, recordScopeOffer } from './observed-savings.js';
|
|
6
|
-
let cwd;
|
|
7
|
-
beforeEach(() => { cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'observed-savings-')); });
|
|
8
|
-
afterEach(() => { fs.rmSync(cwd, { recursive: true, force: true }); });
|
|
9
|
-
const T = 1_000_000;
|
|
10
|
-
describe('observedSavings', () => {
|
|
11
|
-
it('counts only scoped files the agent did not read afterwards, less the summary', () => {
|
|
12
|
-
recordScopeOffer(cwd, [{ path: 'src/a.ts', tokens: 3000 }, { path: 'src/b.ts', tokens: 2000 }], 400, T);
|
|
13
|
-
recordAgentActivity(cwd, 'Read', path.join(cwd, 'src/a.ts'), T + 60_000); // read anyway
|
|
14
|
-
recordAgentActivity(cwd, 'Edit', path.join(cwd, 'src/b.ts'), T + 90_000); // an edit is not a read
|
|
15
|
-
expect(observedSavings(cwd)).toEqual({ scopes: 1, trackedScopes: 1, offeredTokens: 5000, readBackTokens: 3000, avoidedTokens: 1600 });
|
|
16
|
-
});
|
|
17
|
-
it('claims nothing for a scope no hook observed, so a missing hook never reads as a saving', () => {
|
|
18
|
-
recordScopeOffer(cwd, [{ path: 'src/a.ts', tokens: 3000 }], 400, T);
|
|
19
|
-
expect(observedSavings(cwd)).toMatchObject({ scopes: 1, trackedScopes: 0, avoidedTokens: 0 });
|
|
20
|
-
});
|
|
21
|
-
it('ignores activity outside the observation window', () => {
|
|
22
|
-
recordScopeOffer(cwd, [{ path: 'src/a.ts', tokens: 3000 }], 400, T);
|
|
23
|
-
recordAgentActivity(cwd, 'Read', 'src/a.ts', T + 2 * 60 * 60 * 1000);
|
|
24
|
-
expect(observedSavings(cwd).trackedScopes).toBe(0);
|
|
25
|
-
});
|
|
26
|
-
it('never claims more than was offered when the summary outweighs unread files', () => {
|
|
27
|
-
recordScopeOffer(cwd, [{ path: 'src/a.ts', tokens: 100 }], 400, T);
|
|
28
|
-
recordAgentActivity(cwd, 'Grep', undefined, T + 1000);
|
|
29
|
-
expect(observedSavings(cwd).avoidedTokens).toBe(0);
|
|
30
|
-
});
|
|
31
|
-
it('reads a Claude Code PreToolUse payload: the tool, the file it reads, and its working directory', () => {
|
|
32
|
-
recordScopeOffer(cwd, [{ path: 'src/a.ts', tokens: 3000 }], 400, T);
|
|
33
|
-
recordHookPayload({ tool_name: 'Read', tool_input: { file_path: path.join(cwd, 'src/a.ts') }, cwd }, '/elsewhere', T + 1000);
|
|
34
|
-
recordHookPayload({ hello: 'not a tool call' }, cwd, T + 2000);
|
|
35
|
-
expect(observedSavings(cwd)).toMatchObject({ trackedScopes: 1, readBackTokens: 3000, avoidedTokens: 0 });
|
|
36
|
-
});
|
|
37
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as os from 'os';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
-
import { getScoreTrend } from './score-history.js';
|
|
6
|
-
describe('getScoreTrend', () => {
|
|
7
|
-
let testDir;
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'score-history-test-'));
|
|
10
|
-
fs.mkdirSync(path.join(testDir, '.rigour'));
|
|
11
|
-
});
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
14
|
-
});
|
|
15
|
-
function writeScores(scores) {
|
|
16
|
-
const entries = scores.map((score, index) => ({
|
|
17
|
-
timestamp: new Date(index * 1000).toISOString(),
|
|
18
|
-
status: 'FAIL',
|
|
19
|
-
score,
|
|
20
|
-
failureCount: 1,
|
|
21
|
-
severity_breakdown: {},
|
|
22
|
-
provenance_breakdown: {},
|
|
23
|
-
}));
|
|
24
|
-
fs.writeFileSync(path.join(testDir, '.rigour', 'score-history.jsonl'), entries.map(entry => JSON.stringify(entry)).join('\n') + '\n');
|
|
25
|
-
}
|
|
26
|
-
it('labels the displayed rising scores as improving despite a higher older window', () => {
|
|
27
|
-
writeScores([90, 92, 94, 96, 98, 34, 29, 29, 52, 59]);
|
|
28
|
-
expect(getScoreTrend(testDir)).toMatchObject({
|
|
29
|
-
direction: 'improving',
|
|
30
|
-
delta: -53.4,
|
|
31
|
-
visibleDelta: 25,
|
|
32
|
-
recentScores: [34, 29, 29, 52, 59],
|
|
33
|
-
recentAvg: 41,
|
|
34
|
-
previousAvg: 94,
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
it('labels 41 → 41 → 41 → 41 → 52 as improving', () => {
|
|
38
|
-
writeScores([41, 41, 41, 41, 52]);
|
|
39
|
-
expect(getScoreTrend(testDir)).toMatchObject({
|
|
40
|
-
direction: 'improving',
|
|
41
|
-
visibleDelta: 11,
|
|
42
|
-
recentScores: [41, 41, 41, 41, 52],
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
it('labels a falling displayed series as degrading', () => {
|
|
46
|
-
writeScores([80, 75, 70, 64, 59]);
|
|
47
|
-
expect(getScoreTrend(testDir)).toMatchObject({
|
|
48
|
-
direction: 'degrading',
|
|
49
|
-
delta: 0,
|
|
50
|
-
visibleDelta: -21,
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
it('treats small visible changes as stable', () => {
|
|
54
|
-
writeScores([50, 49, 52, 51, 53]);
|
|
55
|
-
expect(getScoreTrend(testDir)).toMatchObject({
|
|
56
|
-
direction: 'stable',
|
|
57
|
-
delta: 0,
|
|
58
|
-
visibleDelta: 3,
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
});
|
package/dist/smoke.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/smoke.test.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { GateRunner } from '../src/gates/runner.js';
|
|
3
|
-
describe('GateRunner Smoke Test', () => {
|
|
4
|
-
it('should initialize with empty config', async () => {
|
|
5
|
-
const config = {
|
|
6
|
-
version: 1,
|
|
7
|
-
commands: {},
|
|
8
|
-
gates: {
|
|
9
|
-
max_file_lines: 500,
|
|
10
|
-
forbid_todos: true,
|
|
11
|
-
forbid_fixme: true,
|
|
12
|
-
},
|
|
13
|
-
};
|
|
14
|
-
const runner = new GateRunner(config);
|
|
15
|
-
expect(runner).toBeDefined();
|
|
16
|
-
});
|
|
17
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,54 +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 { cleanContextCache, deadCacheRows } from './cache-cleanup.js';
|
|
6
|
-
import { openDatabase } from './db.js';
|
|
7
|
-
let dir;
|
|
8
|
-
let dbPath;
|
|
9
|
-
async function seed(rows) {
|
|
10
|
-
const db = (await openDatabase(dbPath));
|
|
11
|
-
// One transaction: row-at-a-time commits are slow enough on Windows to time the test out.
|
|
12
|
-
await db.exec('BEGIN');
|
|
13
|
-
for (const [key, type] of rows) {
|
|
14
|
-
await db.run(`INSERT INTO context_cache (cache_key, cache_type, repo, branch, dependency_fingerprint, payload_json, payload_tokens, created_at)
|
|
15
|
-
VALUES (?, ?, 'r', 'main', 'f', ?, 1, 0)`, key, type, JSON.stringify({ padding: 'x'.repeat(2000) }));
|
|
16
|
-
}
|
|
17
|
-
await db.exec('COMMIT');
|
|
18
|
-
await db.close();
|
|
19
|
-
}
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cache-cleanup-')); // a temporary database: never ~/.rigour
|
|
22
|
-
dbPath = path.join(dir, 'rigour.db');
|
|
23
|
-
});
|
|
24
|
-
// Windows keeps a just-closed SQLite file locked for a moment: retry the removal instead of failing.
|
|
25
|
-
afterEach(() => fs.rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
|
|
26
|
-
// Real SQLite files on disk: slower on Windows runners than the 5 s default allows.
|
|
27
|
-
describe('cleanContextCache', { timeout: 30_000 }, () => {
|
|
28
|
-
it('removes the unread layers, keeps the readable rows and the indexes, and shrinks the file', async () => {
|
|
29
|
-
await seed([
|
|
30
|
-
...Array.from({ length: 200 }, (_, i) => [`static:${i}`, 'static']),
|
|
31
|
-
...Array.from({ length: 200 }, (_, i) => [`component:${i}`, 'component']),
|
|
32
|
-
['semantic:a', 'semantic'], ['checkpoint:b', 'checkpoint'],
|
|
33
|
-
]);
|
|
34
|
-
expect(await deadCacheRows(dbPath)).toMatchObject({ rows: 400 });
|
|
35
|
-
const report = await cleanContextCache(dbPath, () => Number.MAX_SAFE_INTEGER);
|
|
36
|
-
expect(report).toMatchObject({ removed: 400, kept: 2, vacuumed: true });
|
|
37
|
-
expect(report.sizeAfter).toBeLessThan(report.sizeBefore);
|
|
38
|
-
const db = (await openDatabase(dbPath));
|
|
39
|
-
expect((await db.all('SELECT cache_key FROM context_cache ORDER BY cache_key')).map(r => r.cache_key)).toEqual(['checkpoint:b', 'semantic:a']);
|
|
40
|
-
expect((await db.all("SELECT name FROM sqlite_master WHERE tbl_name = 'context_cache' AND type = 'index'")).map(r => r.name)).toContain('idx_context_cache_type');
|
|
41
|
-
await db.close();
|
|
42
|
-
expect(await deadCacheRows(dbPath)).toMatchObject({ rows: 0 });
|
|
43
|
-
});
|
|
44
|
-
it('still removes the rows but leaves the file size alone when the disk lacks room to shrink it', async () => {
|
|
45
|
-
await seed([['static:1', 'static'], ['semantic:a', 'semantic']]);
|
|
46
|
-
const report = await cleanContextCache(dbPath, () => 0);
|
|
47
|
-
expect(report).toMatchObject({ removed: 1, kept: 1, vacuumed: false });
|
|
48
|
-
expect(report.vacuumSkipped).toContain('free to shrink the file');
|
|
49
|
-
});
|
|
50
|
-
it('does nothing when there is nothing to remove', async () => {
|
|
51
|
-
await seed([['semantic:a', 'semantic']]);
|
|
52
|
-
expect(await cleanContextCache(dbPath, () => 0)).toMatchObject({ removed: 0, vacuumed: false, vacuumSkipped: 'nothing to remove' });
|
|
53
|
-
});
|
|
54
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import fs from 'fs-extra';
|
|
5
|
-
vi.mock('./db.js', () => ({
|
|
6
|
-
isSQLiteAvailable: () => false,
|
|
7
|
-
openDatabase: async () => null,
|
|
8
|
-
DB_PATH: '/tmp/rigour-test.db',
|
|
9
|
-
RIGOUR_DIR: '/tmp/.rigour',
|
|
10
|
-
}));
|
|
11
|
-
import { recordContextEvent, recordModelUsage, setContextCacheRecord, getContextCacheRecord, recordCheckpointMetric, getContextEvents, getModelUsages, getCheckpointMetrics, } from './context-telemetry.js';
|
|
12
|
-
describe('context-telemetry storage', () => {
|
|
13
|
-
const testCwd = path.join(os.tmpdir(), `rigour-ctx-telemetry-${Date.now()}`);
|
|
14
|
-
beforeEach(async () => {
|
|
15
|
-
await fs.ensureDir(path.join(testCwd, '.rigour'));
|
|
16
|
-
});
|
|
17
|
-
afterEach(async () => {
|
|
18
|
-
await fs.remove(testCwd);
|
|
19
|
-
});
|
|
20
|
-
it('records and retrieves context events via JSON fallback', async () => {
|
|
21
|
-
const id = await recordContextEvent({
|
|
22
|
-
taskId: 'task-1',
|
|
23
|
-
toolName: 'rigour_recall',
|
|
24
|
-
cacheStatus: 'semantic-hit',
|
|
25
|
-
candidateTokens: 4000,
|
|
26
|
-
returnedTokens: 800,
|
|
27
|
-
deduplicatedTokens: 1200,
|
|
28
|
-
}, testCwd);
|
|
29
|
-
expect(id).toMatch(/^evt-/);
|
|
30
|
-
const events = await getContextEvents('task-1', testCwd);
|
|
31
|
-
expect(events).toHaveLength(1);
|
|
32
|
-
expect(events[0].toolName).toBe('rigour_recall');
|
|
33
|
-
expect(events[0].candidateTokens).toBe(4000);
|
|
34
|
-
expect(events[0].returnedTokens).toBe(800);
|
|
35
|
-
});
|
|
36
|
-
it('records and retrieves model usage via JSON fallback', async () => {
|
|
37
|
-
await recordModelUsage({
|
|
38
|
-
taskId: 'cursor-imported',
|
|
39
|
-
provider: 'cursor',
|
|
40
|
-
model: 'claude-3-5-sonnet',
|
|
41
|
-
inputTokens: 12000,
|
|
42
|
-
outputTokens: 1500,
|
|
43
|
-
observedCostUsd: 1.25,
|
|
44
|
-
source: 'cursor-dashboard-csv',
|
|
45
|
-
}, testCwd);
|
|
46
|
-
const usages = await getModelUsages(undefined, testCwd);
|
|
47
|
-
expect(usages).toHaveLength(1);
|
|
48
|
-
expect(usages[0].inputTokens).toBe(12000);
|
|
49
|
-
expect(usages[0].source).toBe('cursor-dashboard-csv');
|
|
50
|
-
});
|
|
51
|
-
it('stores cache records and increments hit count', async () => {
|
|
52
|
-
await setContextCacheRecord({
|
|
53
|
-
cacheKey: 'static:repo:main:file.ts:abc123:v1',
|
|
54
|
-
cacheType: 'static',
|
|
55
|
-
repo: 'repo',
|
|
56
|
-
branch: 'main',
|
|
57
|
-
dependencyFingerprint: 'dep-1',
|
|
58
|
-
payloadJson: JSON.stringify({ exports: ['foo'] }),
|
|
59
|
-
payloadTokens: 42,
|
|
60
|
-
}, testCwd);
|
|
61
|
-
const first = await getContextCacheRecord('static:repo:main:file.ts:abc123:v1', testCwd);
|
|
62
|
-
const second = await getContextCacheRecord('static:repo:main:file.ts:abc123:v1', testCwd);
|
|
63
|
-
expect(first?.payloadTokens).toBe(42);
|
|
64
|
-
expect(second?.hitCount).toBe(2);
|
|
65
|
-
});
|
|
66
|
-
it('records and retrieves checkpoint metrics via JSON fallback', async () => {
|
|
67
|
-
await recordCheckpointMetric({
|
|
68
|
-
checkpointId: 'checkpoint:CTP-1:agent-a:implementation',
|
|
69
|
-
taskId: 'CTP-1',
|
|
70
|
-
agentId: 'agent-a',
|
|
71
|
-
rawStateTokens: 50000,
|
|
72
|
-
checkpointTokens: 400,
|
|
73
|
-
replayTokensAvoided: 49600,
|
|
74
|
-
}, testCwd);
|
|
75
|
-
const metrics = await getCheckpointMetrics('CTP-1', testCwd);
|
|
76
|
-
expect(metrics).toHaveLength(1);
|
|
77
|
-
expect(metrics[0].rawStateTokens).toBe(50000);
|
|
78
|
-
expect(metrics[0].replayTokensAvoided).toBe(49600);
|
|
79
|
-
});
|
|
80
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/storage/db.test.js
DELETED
|
@@ -1,46 +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 { isSQLiteAvailable, openDatabase } from './db.js';
|
|
6
|
-
let dir;
|
|
7
|
-
let db;
|
|
8
|
-
beforeEach(async () => {
|
|
9
|
-
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'rigour-db-'));
|
|
10
|
-
db = (await openDatabase(path.join(dir, 'rigour.db')));
|
|
11
|
-
});
|
|
12
|
-
afterEach(async () => {
|
|
13
|
-
await db?.close();
|
|
14
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
15
|
-
});
|
|
16
|
-
describe('the database (node:sqlite)', () => {
|
|
17
|
-
it('is built in: no package to install', () => {
|
|
18
|
-
expect(isSQLiteAvailable()).toBe(true);
|
|
19
|
-
expect(db).not.toBeNull();
|
|
20
|
-
});
|
|
21
|
-
it('stores what callers pass the way sqlite3 did: undefined as NULL, booleans as 1 or 0, a Date as epoch ms', async () => {
|
|
22
|
-
await db.exec('CREATE TABLE t (a, b, c, d)');
|
|
23
|
-
const when = new Date('2026-01-02T03:04:05Z');
|
|
24
|
-
const written = await db.run('INSERT INTO t VALUES (?, ?, ?, ?)', undefined, true, false, when);
|
|
25
|
-
expect(written).toEqual({ changes: 1, lastID: 1 });
|
|
26
|
-
const row = await db.get('SELECT * FROM t');
|
|
27
|
-
expect(row).toEqual({ a: null, b: 1, c: 0, d: when.getTime() });
|
|
28
|
-
expect(Object.getPrototypeOf(row)).toBe(Object.prototype); // a plain object, as callers spread and compare
|
|
29
|
-
expect(await db.get('SELECT * FROM t WHERE a = 1')).toBeUndefined();
|
|
30
|
-
});
|
|
31
|
-
it('rolls a failed transaction back whole', async () => {
|
|
32
|
-
await db.exec('CREATE TABLE t (a UNIQUE)');
|
|
33
|
-
await expect(db.transaction(async (tx) => {
|
|
34
|
-
await tx.run('INSERT INTO t VALUES (?)', 1);
|
|
35
|
-
await tx.run('INSERT INTO t VALUES (?)', 1); // violates UNIQUE
|
|
36
|
-
})).rejects.toThrow(/UNIQUE/);
|
|
37
|
-
expect(await db.all('SELECT * FROM t')).toEqual([]);
|
|
38
|
-
});
|
|
39
|
-
it('opens an existing database again with its schema and data, in WAL mode', async () => {
|
|
40
|
-
await db.run("INSERT INTO meta (key, value) VALUES ('probe', 'kept')");
|
|
41
|
-
await db.close();
|
|
42
|
-
db = (await openDatabase(path.join(dir, 'rigour.db')));
|
|
43
|
-
expect(await db.get("SELECT value FROM meta WHERE key = 'probe'")).toEqual({ value: 'kept' });
|
|
44
|
-
expect(await db.get('PRAGMA journal_mode')).toEqual({ journal_mode: 'wal' });
|
|
45
|
-
});
|
|
46
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
/** An in-memory lessons table, enough for the reads and writes fix lessons make. */
|
|
3
|
-
const db = vi.hoisted(() => ({ rows: new Map(), reset() { this.rows = new Map(); } }));
|
|
4
|
-
vi.mock('./db.js', () => {
|
|
5
|
-
const store = {
|
|
6
|
-
run: async (sql, ...p) => {
|
|
7
|
-
if (sql.includes('INSERT INTO lessons'))
|
|
8
|
-
db.rows.set(p[0], { id: p[0], state: p[4], subject: p[5], evidence_json: p[6], confidence: p[7] });
|
|
9
|
-
if (sql.includes('UPDATE lessons')) {
|
|
10
|
-
const row = db.rows.get(p[4]);
|
|
11
|
-
if (!['promoted', 'rejected'].includes(row.state))
|
|
12
|
-
row.state = p[0];
|
|
13
|
-
Object.assign(row, { evidence_json: p[1], confidence: Math.max(row.confidence, p[2]) });
|
|
14
|
-
}
|
|
15
|
-
return { changes: 1, lastID: 0 };
|
|
16
|
-
},
|
|
17
|
-
get: async (sql, ...p) => (sql.includes("kind = 'fix'") ? [...db.rows.values()].find(r => r.subject.slice(0, p[1]) === p[2]) : undefined),
|
|
18
|
-
all: async () => [],
|
|
19
|
-
exec: async () => undefined,
|
|
20
|
-
close: async () => undefined,
|
|
21
|
-
};
|
|
22
|
-
return { openDatabase: async () => store };
|
|
23
|
-
});
|
|
24
|
-
vi.mock('./local-encryption.js', () => ({ encryptLocalPayload: async (v) => JSON.stringify(v), decryptLocalPayload: async (v) => JSON.parse(v) }));
|
|
25
|
-
vi.mock('./team-store.js', () => ({ loadTeamConfiguration: async () => null }));
|
|
26
|
-
const { recordFixLessons, fixLessonSubject } = await import('./fix-lessons.js');
|
|
27
|
-
const fix = (id, file) => ({
|
|
28
|
-
id, file, rule: 'semantic-bugs', title: 'Unbounded read without paging', details: 'Rows read without a limit.', before: 'a', after: 'b', resolvedAt: '2026-10-02T00:00:00Z',
|
|
29
|
-
});
|
|
30
|
-
const lessons = () => [...db.rows.values()];
|
|
31
|
-
beforeEach(() => db.reset());
|
|
32
|
-
describe('recordFixLessons', () => {
|
|
33
|
-
it('turns a fix into a candidate lesson named after the defect it removed', async () => {
|
|
34
|
-
await recordFixLessons(process.cwd(), [fix('f1', 'src/orders.ts')]);
|
|
35
|
-
expect(lessons().map(l => [l.subject, l.state])).toEqual([['Fixed before: Unbounded read without paging (semantic-bugs). Rows read without a limit.', 'candidate']]);
|
|
36
|
-
});
|
|
37
|
-
it('validates the lesson once fixes of that kind reach a second file, and counts the same fix once', async () => {
|
|
38
|
-
await recordFixLessons(process.cwd(), [fix('f1', 'src/orders.ts')]);
|
|
39
|
-
await recordFixLessons(process.cwd(), [fix('f1', 'src/orders.ts'), fix('f2', 'src/orders.ts')]);
|
|
40
|
-
expect(lessons()[0].state).toBe('candidate');
|
|
41
|
-
await recordFixLessons(process.cwd(), [fix('f3', 'src/invoices.ts')]);
|
|
42
|
-
expect(lessons()).toHaveLength(1);
|
|
43
|
-
expect(lessons()[0].state).toBe('validated');
|
|
44
|
-
expect(JSON.parse(lessons()[0].evidence_json).files).toEqual(['src/orders.ts', 'src/invoices.ts']);
|
|
45
|
-
});
|
|
46
|
-
it("never overrides a person's decision to promote or reject", async () => {
|
|
47
|
-
await recordFixLessons(process.cwd(), [fix('f1', 'src/orders.ts')]);
|
|
48
|
-
lessons()[0].state = 'rejected';
|
|
49
|
-
await recordFixLessons(process.cwd(), [fix('f2', 'src/invoices.ts')]);
|
|
50
|
-
expect(lessons()[0].state).toBe('rejected');
|
|
51
|
-
});
|
|
52
|
-
it('names a fix whose finding had no title after its rule', () => {
|
|
53
|
-
expect(fixLessonSubject({ rule: 'hallucinated-imports' })).toBe('Fixed before: hallucinated-imports (hallucinated-imports).');
|
|
54
|
-
});
|
|
55
|
-
it('groups fixes of one kind even when their details differ', async () => {
|
|
56
|
-
await recordFixLessons(process.cwd(), [fix('f1', 'src/orders.ts')]);
|
|
57
|
-
await recordFixLessons(process.cwd(), [{ ...fix('f2', 'src/invoices.ts'), details: 'Invoices read in full to count them.' }]);
|
|
58
|
-
expect(lessons()).toHaveLength(1);
|
|
59
|
-
expect(lessons()[0].state).toBe('validated');
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import os from 'os';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import { openDatabase } from './db.js';
|
|
6
|
-
import { decryptLocalPayload, encryptLocalPayload } from './local-encryption.js';
|
|
7
|
-
import { getRepositoryId } from './lessons.js';
|
|
8
|
-
import { queueLocalLessonsForTeam } from './team-import.js';
|
|
9
|
-
const roots = [];
|
|
10
|
-
afterEach(async () => {
|
|
11
|
-
vi.unstubAllEnvs();
|
|
12
|
-
await Promise.all(roots.splice(0).map((root) => fs.remove(root)));
|
|
13
|
-
});
|
|
14
|
-
describe('getRepositoryId', () => {
|
|
15
|
-
it('uses normalized origin identity instead of a basename', async () => {
|
|
16
|
-
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-repo-id-'));
|
|
17
|
-
roots.push(root);
|
|
18
|
-
await fs.ensureDir(path.join(root, '.git'));
|
|
19
|
-
await fs.writeFile(path.join(root, '.git', 'config'), '[remote "origin"]\nurl = git@github.com:Rigour-Labs/Rigour.git\n');
|
|
20
|
-
const first = await getRepositoryId(root);
|
|
21
|
-
const second = await getRepositoryId(path.join(root, '..', path.basename(root)));
|
|
22
|
-
expect(first).toBe(second);
|
|
23
|
-
expect(first).toHaveLength(64);
|
|
24
|
-
});
|
|
25
|
-
it('uses the shared origin identity from a linked worktree', async () => {
|
|
26
|
-
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-worktree-id-'));
|
|
27
|
-
roots.push(root);
|
|
28
|
-
const common = path.join(root, 'common');
|
|
29
|
-
const linkedGit = path.join(common, 'worktrees', 'feature');
|
|
30
|
-
const worktree = path.join(root, 'checkout');
|
|
31
|
-
await fs.ensureDir(linkedGit);
|
|
32
|
-
await fs.ensureDir(worktree);
|
|
33
|
-
await fs.writeFile(path.join(worktree, '.git'), `gitdir: ${linkedGit}\n`);
|
|
34
|
-
await fs.writeFile(path.join(linkedGit, 'commondir'), '../..\n');
|
|
35
|
-
await fs.writeFile(path.join(common, 'config'), '[remote "origin"]\nurl = https://github.com/rigour-labs/rigour.git\n');
|
|
36
|
-
const regular = path.join(root, 'regular');
|
|
37
|
-
await fs.ensureDir(path.join(regular, '.git'));
|
|
38
|
-
await fs.writeFile(path.join(regular, '.git', 'config'), '[remote "origin"]\nurl = git@github.com:Rigour-Labs/Rigour.git\n');
|
|
39
|
-
expect(await getRepositoryId(worktree)).toBe(await getRepositoryId(regular));
|
|
40
|
-
expect(await getRepositoryId(worktree)).toHaveLength(64);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
describe('local lesson team import', () => {
|
|
44
|
-
it('dry-runs, assigns legacy ownership, and queues each lesson version once', async () => {
|
|
45
|
-
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-team-import-'));
|
|
46
|
-
roots.push(root);
|
|
47
|
-
await fs.ensureDir(path.join(root, '.git'));
|
|
48
|
-
await fs.writeFile(path.join(root, '.git', 'config'), '[remote "origin"]\nurl = https://github.com/acme/service.git\n');
|
|
49
|
-
vi.stubEnv('RIGOUR_ORGANIZATION_ID', 'acme');
|
|
50
|
-
vi.stubEnv('RIGOUR_TEAM_ID', 'platform');
|
|
51
|
-
vi.stubEnv('RIGOUR_ACTOR_ID', 'alice');
|
|
52
|
-
vi.stubEnv('RIGOUR_TEAM_DATABASE_URL', 'postgresql://alice@127.0.0.1/rigour');
|
|
53
|
-
vi.stubEnv('RIGOUR_LOCAL_CACHE_KEY', Buffer.alloc(32, 7).toString('base64'));
|
|
54
|
-
const repositoryId = await getRepositoryId(root);
|
|
55
|
-
const databasePath = path.join(root, 'rigour.db');
|
|
56
|
-
const db = await openDatabase(databasePath);
|
|
57
|
-
expect(db).not.toBeNull();
|
|
58
|
-
const now = Date.now();
|
|
59
|
-
await db.run(`INSERT INTO lessons (
|
|
60
|
-
id, repository_id, actor_id, team_id, visibility, state, kind, subject,
|
|
61
|
-
evidence_json, confidence, source, created_at, updated_at
|
|
62
|
-
) VALUES (?, ?, NULL, NULL, 'personal', 'candidate', 'interaction', ?, ?, 0.3, 'mcp', ?, ?)`, 'lesson-legacy', repositoryId, 'Prefer scoped retrieval.', await encryptLocalPayload({ requestId: 'legacy-request' }), now, now);
|
|
63
|
-
await db.close();
|
|
64
|
-
const preview = await queueLocalLessonsForTeam([root], { databasePath, dryRun: true });
|
|
65
|
-
expect(preview).toMatchObject({ matched: 1, eligible: 1, wouldQueue: 1, queued: 0 });
|
|
66
|
-
const queued = await queueLocalLessonsForTeam([root], { databasePath });
|
|
67
|
-
expect(queued).toMatchObject({ matched: 1, eligible: 1, wouldQueue: 1, queued: 1 });
|
|
68
|
-
const existingOutbox = await openDatabase(databasePath);
|
|
69
|
-
await existingOutbox.run('UPDATE sync_outbox SET id = ? WHERE entity_id = ?', 'outbox-created-by-normal-learning', 'lesson-legacy');
|
|
70
|
-
await existingOutbox.close();
|
|
71
|
-
const repeated = await queueLocalLessonsForTeam([root], { databasePath });
|
|
72
|
-
expect(repeated).toMatchObject({ matched: 1, eligible: 1, wouldQueue: 0, queued: 0, alreadyQueued: 1 });
|
|
73
|
-
const verified = await openDatabase(databasePath);
|
|
74
|
-
const lesson = await verified.get('SELECT actor_id, team_id, visibility, state FROM lessons WHERE id = ?', 'lesson-legacy');
|
|
75
|
-
expect(lesson).toMatchObject({ actor_id: 'alice', team_id: 'platform', visibility: 'personal', state: 'candidate' });
|
|
76
|
-
const outbox = await verified.get('SELECT payload_json FROM sync_outbox WHERE entity_id = ?', 'lesson-legacy');
|
|
77
|
-
const payload = await decryptLocalPayload(outbox.payload_json);
|
|
78
|
-
expect(payload).toMatchObject({ id: 'lesson-legacy', actorId: 'alice', teamId: 'platform', visibility: 'personal', state: 'candidate' });
|
|
79
|
-
await verified.close();
|
|
80
|
-
});
|
|
81
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import os from 'os';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
-
import { loadOrCreateFileKey } from './local-encryption.js';
|
|
6
|
-
const temporaryDirectories = [];
|
|
7
|
-
afterEach(async () => {
|
|
8
|
-
await Promise.all(temporaryDirectories.splice(0).map(directory => fs.remove(directory)));
|
|
9
|
-
});
|
|
10
|
-
async function temporaryKeyPath() {
|
|
11
|
-
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-key-test-'));
|
|
12
|
-
temporaryDirectories.push(directory);
|
|
13
|
-
return path.join(directory, 'team-cache.key');
|
|
14
|
-
}
|
|
15
|
-
describe('local encryption key storage', () => {
|
|
16
|
-
it('returns one key when concurrent processes initialize the same path', async () => {
|
|
17
|
-
const keyPath = await temporaryKeyPath();
|
|
18
|
-
const keys = await Promise.all(Array.from({ length: 32 }, () => loadOrCreateFileKey(keyPath)));
|
|
19
|
-
expect(new Set(keys.map(key => key.toString('base64')))).toHaveLength(1);
|
|
20
|
-
expect(await fs.readFile(keyPath, 'utf8')).toBe(keys[0].toString('base64'));
|
|
21
|
-
});
|
|
22
|
-
it('rejects an invalid existing key without replacing it', async () => {
|
|
23
|
-
const keyPath = await temporaryKeyPath();
|
|
24
|
-
await fs.writeFile(keyPath, 'not-a-valid-key', 'utf8');
|
|
25
|
-
await expect(loadOrCreateFileKey(keyPath)).rejects.toThrow('must contain a base64-encoded 32-byte key');
|
|
26
|
-
expect(await fs.readFile(keyPath, 'utf8')).toBe('not-a-valid-key');
|
|
27
|
-
});
|
|
28
|
-
it('accepts an existing key without base64 padding for backward compatibility', async () => {
|
|
29
|
-
const keyPath = await temporaryKeyPath();
|
|
30
|
-
const encodedKey = Buffer.alloc(32, 7).toString('base64').replace(/=+$/, '');
|
|
31
|
-
await fs.writeFile(keyPath, encodedKey, 'utf8');
|
|
32
|
-
expect((await loadOrCreateFileKey(keyPath)).equals(Buffer.alloc(32, 7))).toBe(true);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|