@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
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type LessonMode } from '../review-learning/team-lessons.js';
|
|
2
|
+
/** The most items a briefing gives: past about ten, a briefing is a wall nobody reads. */
|
|
3
|
+
export declare const BRIEFING_MAX_ITEMS = 10;
|
|
4
|
+
/** The most items a briefing for one file gives, the first time an agent edits it: a word in passing, not a wall. */
|
|
5
|
+
export declare const FILE_BRIEFING_MAX_ITEMS = 3;
|
|
6
|
+
export interface BriefingItem {
|
|
7
|
+
kind: 'rule' | 'lesson' | 'settled';
|
|
8
|
+
/** What to do, in the team's words (a rule's text, a lesson's rule). */
|
|
9
|
+
text: string;
|
|
10
|
+
/** Where it came from: the rules file, or the pull requests the lesson was learned and proven on. */
|
|
11
|
+
cite: string;
|
|
12
|
+
/** A rule the team worded as a requirement: a break blocks at review. */
|
|
13
|
+
requirement?: boolean;
|
|
14
|
+
id: string;
|
|
15
|
+
}
|
|
16
|
+
export interface Briefing {
|
|
17
|
+
task?: string;
|
|
18
|
+
goal: string;
|
|
19
|
+
files: string[];
|
|
20
|
+
items: BriefingItem[];
|
|
21
|
+
}
|
|
22
|
+
export interface BriefingInput {
|
|
23
|
+
/** What the task is for: the agent's first prompt, a ticket's summary, the pull request's title. The branch name when absent. */
|
|
24
|
+
goal?: string;
|
|
25
|
+
/** The files the task will touch, when known; otherwise the branch's own changes and the files the goal names. */
|
|
26
|
+
files?: string[];
|
|
27
|
+
lessons?: LessonMode;
|
|
28
|
+
limit?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The briefing for a task in this checkout: requirement rules first (a break of one blocks later), then the team's
|
|
32
|
+
* verified lessons, then the points the team settled against, then guidance rules; `limit` items at most.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildBriefing(cwd: string, input?: BriefingInput): Briefing;
|
|
35
|
+
/** The briefing as an agent reads it: short, numbered, each item with where it came from. Empty when there is nothing to say. */
|
|
36
|
+
export declare function briefingText(briefing: Briefing): string;
|
|
37
|
+
/**
|
|
38
|
+
* The briefing for one file, the first time an agent edits it: the requirement rules that name it (or its folder), the
|
|
39
|
+
* lessons the team learned on it, and the points the team settled against on it; at most three. At session start the
|
|
40
|
+
* task's files are often unknown; the first edit of a file is when they are, and when a briefing can be specific.
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildFileBriefing(cwd: string, file: string, input?: {
|
|
43
|
+
lessons?: LessonMode;
|
|
44
|
+
limit?: number;
|
|
45
|
+
}): Briefing;
|
|
46
|
+
/** A file's briefing as an agent reads it, just before it edits that file. Empty when there is nothing to say. */
|
|
47
|
+
export declare function fileBriefingText(briefing: Briefing): string;
|
|
48
|
+
/** Builds a file's briefing and records it on the task's thread, with the file. */
|
|
49
|
+
export declare function briefFile(cwd: string, file: string, input?: {
|
|
50
|
+
lessons?: LessonMode;
|
|
51
|
+
limit?: number;
|
|
52
|
+
session?: string;
|
|
53
|
+
agent?: string;
|
|
54
|
+
}): Briefing;
|
|
55
|
+
/** Builds the briefing and records it on the task's thread (what was briefed, by id, so a later review can be read against it). */
|
|
56
|
+
export declare function briefTask(cwd: string, input: BriefingInput & {
|
|
57
|
+
session?: string;
|
|
58
|
+
agent?: string;
|
|
59
|
+
}): Briefing;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The briefing: before an agent writes, what a senior on this team would tell it about the task. The repository's own
|
|
3
|
+
* rules and the team's verified lessons for the files the task will likely touch, and the points the team settled
|
|
4
|
+
* against (so the agent does not re-raise or re-do them), at most `limit` items, each cited to where it came from.
|
|
5
|
+
*
|
|
6
|
+
* Nothing here is new knowledge: the same rules and lessons the reviewer checks a change against, selected by the same
|
|
7
|
+
* matchers, before the code exists instead of after. It is deterministic (no model call), local, and it never blocks.
|
|
8
|
+
*/
|
|
9
|
+
import { spawnSync } from 'child_process';
|
|
10
|
+
import { rulesForDiff } from '../review-learning/repo-rules.js';
|
|
11
|
+
import { describeLesson, lessonsForDiff, lessonView, rejectedForDiff } from '../review-learning/team-lessons.js';
|
|
12
|
+
import { appendTaskEvent, taskOf } from '../task/thread.js';
|
|
13
|
+
/** The most items a briefing gives: past about ten, a briefing is a wall nobody reads. */
|
|
14
|
+
export const BRIEFING_MAX_ITEMS = 10;
|
|
15
|
+
/** The most items a briefing for one file gives, the first time an agent edits it: a word in passing, not a wall. */
|
|
16
|
+
export const FILE_BRIEFING_MAX_ITEMS = 3;
|
|
17
|
+
/** The most files a briefing reads the task's likely reach from. */
|
|
18
|
+
const LIKELY_FILES = 20;
|
|
19
|
+
/**
|
|
20
|
+
* The briefing for a task in this checkout: requirement rules first (a break of one blocks later), then the team's
|
|
21
|
+
* verified lessons, then the points the team settled against, then guidance rules; `limit` items at most.
|
|
22
|
+
*/
|
|
23
|
+
export function buildBriefing(cwd, input = {}) {
|
|
24
|
+
const task = taskOf(cwd);
|
|
25
|
+
const goal = (input.goal?.trim() || (task ? task.branch.replace(/[/_-]+/g, ' ') : '')).slice(0, 2000);
|
|
26
|
+
const files = (input.files?.length ? input.files : likelyFiles(cwd, goal)).slice(0, LIKELY_FILES);
|
|
27
|
+
const limit = Math.max(0, Math.min(input.limit ?? BRIEFING_MAX_ITEMS, BRIEFING_MAX_ITEMS));
|
|
28
|
+
// The rule and lesson matchers read a change: the task's files and the goal's words stand in for the code to come.
|
|
29
|
+
const shape = shapeOf(files, goal);
|
|
30
|
+
// Only rules that name a path or identifier of the task: the reviewer can judge a rule against code; a briefing cannot.
|
|
31
|
+
const rules = rulesForDiff(cwd, shape, true, limit, true);
|
|
32
|
+
const lessons = lessonsForDiff(cwd, shape, input.lessons ?? 'verified', 5, limit, 3).filter(l => l.state === 'verified' || input.lessons === 'all');
|
|
33
|
+
const settled = rejectedForDiff(cwd, shape);
|
|
34
|
+
const cited = (prs) => (prs.length ? `learned in PR ${prs.map(p => `#${p}`).join(', ')}` : 'the team\'s decision');
|
|
35
|
+
const items = [
|
|
36
|
+
...rules.filter(r => r.requirement).map(r => ({ kind: 'rule', text: r.text, cite: ruleCite(r.source, r.scope), requirement: true, id: `rule:${r.id}` })),
|
|
37
|
+
...lessons.map(l => {
|
|
38
|
+
const view = lessonView(l);
|
|
39
|
+
return { kind: 'lesson', text: describeLesson({ ...view, prs: [] }), cite: cited(view.prs), id: `lesson:${l.id}` };
|
|
40
|
+
}),
|
|
41
|
+
...settled.map(l => ({ kind: 'settled', text: `settled against, do not do or raise it: ${lessonView(l).text}`, cite: cited(lessonView(l).prs), id: `settled:${l.id}` })),
|
|
42
|
+
...rules.filter(r => !r.requirement).map(r => ({ kind: 'rule', text: r.text, cite: ruleCite(r.source, r.scope), id: `rule:${r.id}` })),
|
|
43
|
+
];
|
|
44
|
+
return { ...(task ? { task: task.key } : {}), goal, files, items: items.slice(0, limit) };
|
|
45
|
+
}
|
|
46
|
+
/** The briefing as an agent reads it: short, numbered, each item with where it came from. Empty when there is nothing to say. */
|
|
47
|
+
export function briefingText(briefing) {
|
|
48
|
+
if (briefing.items.length === 0)
|
|
49
|
+
return '';
|
|
50
|
+
const lines = briefing.items.map((item, i) => `${i + 1}. ${item.requirement ? '[must] ' : item.kind === 'settled' ? '[settled] ' : ''}${item.text} (${item.cite})`);
|
|
51
|
+
return [`Rigour briefing${briefing.task ? ` for ${briefing.task}` : ''}: how this team builds the code this task will likely touch. Follow these; a [must] broken in your change blocks at review.`, ...lines].join('\n');
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The briefing for one file, the first time an agent edits it: the requirement rules that name it (or its folder), the
|
|
55
|
+
* lessons the team learned on it, and the points the team settled against on it; at most three. At session start the
|
|
56
|
+
* task's files are often unknown; the first edit of a file is when they are, and when a briefing can be specific.
|
|
57
|
+
*/
|
|
58
|
+
export function buildFileBriefing(cwd, file, input = {}) {
|
|
59
|
+
const task = taskOf(cwd);
|
|
60
|
+
const limit = Math.max(0, Math.min(input.limit ?? FILE_BRIEFING_MAX_ITEMS, FILE_BRIEFING_MAX_ITEMS));
|
|
61
|
+
const shape = shapeOf([file], '');
|
|
62
|
+
const mode = input.lessons ?? 'verified';
|
|
63
|
+
const rules = rulesForDiff(cwd, shape, true, BRIEFING_MAX_ITEMS, true).filter(r => r.requirement);
|
|
64
|
+
const lessons = lessonsForDiff(cwd, shape, mode, 0, BRIEFING_MAX_ITEMS, BRIEFING_MAX_ITEMS).filter(l => l.file === file && (l.state === 'verified' || mode === 'all'));
|
|
65
|
+
const settled = rejectedForDiff(cwd, shape).filter(l => l.file === file);
|
|
66
|
+
const cited = (prs) => (prs.length ? `learned in PR ${prs.map(p => `#${p}`).join(', ')}` : 'the team\'s decision');
|
|
67
|
+
const items = [
|
|
68
|
+
...rules.map(r => ({ kind: 'rule', text: r.text, cite: ruleCite(r.source, r.scope), requirement: true, id: `rule:${r.id}` })),
|
|
69
|
+
...lessons.map(l => ({ kind: 'lesson', text: lessonView(l).text, cite: cited(lessonView(l).prs), id: `lesson:${l.id}` })),
|
|
70
|
+
...settled.map(l => ({ kind: 'settled', text: `settled against, do not do or raise it: ${lessonView(l).text}`, cite: cited(lessonView(l).prs), id: `settled:${l.id}` })),
|
|
71
|
+
];
|
|
72
|
+
return { ...(task ? { task: task.key } : {}), goal: '', files: [file], items: items.slice(0, limit) };
|
|
73
|
+
}
|
|
74
|
+
/** A file's briefing as an agent reads it, just before it edits that file. Empty when there is nothing to say. */
|
|
75
|
+
export function fileBriefingText(briefing) {
|
|
76
|
+
if (briefing.items.length === 0)
|
|
77
|
+
return '';
|
|
78
|
+
const lines = briefing.items.map((item, i) => `${i + 1}. ${item.requirement ? '[must] ' : item.kind === 'settled' ? '[settled] ' : ''}${item.text} (${item.cite})`);
|
|
79
|
+
return [`Rigour, before you edit ${briefing.files[0]}: what this team asks of this file.`, ...lines].join('\n');
|
|
80
|
+
}
|
|
81
|
+
/** Builds a file's briefing and records it on the task's thread, with the file. */
|
|
82
|
+
export function briefFile(cwd, file, input = {}) {
|
|
83
|
+
const briefing = buildFileBriefing(cwd, file, input);
|
|
84
|
+
appendTaskEvent(cwd, { kind: 'brief', file, ...(input.session ? { session: input.session } : {}), ...(input.agent ? { agent: input.agent } : {}), items: briefing.items.length, ids: briefing.items.map(i => i.id), files: [file] });
|
|
85
|
+
return briefing;
|
|
86
|
+
}
|
|
87
|
+
/** Builds the briefing and records it on the task's thread (what was briefed, by id, so a later review can be read against it). */
|
|
88
|
+
export function briefTask(cwd, input) {
|
|
89
|
+
const briefing = buildBriefing(cwd, input);
|
|
90
|
+
appendTaskEvent(cwd, { kind: 'brief', ...(input.session ? { session: input.session } : {}), ...(input.agent ? { agent: input.agent } : {}), items: briefing.items.length, ids: briefing.items.map(i => i.id), files: briefing.files });
|
|
91
|
+
return briefing;
|
|
92
|
+
}
|
|
93
|
+
function ruleCite(source, scope) {
|
|
94
|
+
return scope ? `${source}, for ${scope}` : source;
|
|
95
|
+
}
|
|
96
|
+
/** A stand-in change for the matchers: each file as touched, the goal's words as the added line. */
|
|
97
|
+
function shapeOf(files, goal) {
|
|
98
|
+
const words = goal.replace(/\s+/g, ' ');
|
|
99
|
+
const touched = files.length ? files : ['.'];
|
|
100
|
+
return touched.map(f => `diff --git a/${f} b/${f}\n--- a/${f}\n+++ b/${f}\n@@ -0,0 +1,1 @@\n+${words}\n`).join('');
|
|
101
|
+
}
|
|
102
|
+
/** The files a task will likely touch: what the branch already changed, then tracked files whose path names a word of the goal. */
|
|
103
|
+
function likelyFiles(cwd, goal) {
|
|
104
|
+
const files = [];
|
|
105
|
+
const main = ['origin/main', 'main', 'origin/master', 'master'].find(ref => git(cwd, ['rev-parse', '--verify', '-q', ref]) !== undefined);
|
|
106
|
+
const base = main ? git(cwd, ['merge-base', 'HEAD', main]) : undefined;
|
|
107
|
+
if (base)
|
|
108
|
+
files.push(...(git(cwd, ['diff', '--name-only', `${base}...HEAD`]) ?? '').split('\n').filter(Boolean));
|
|
109
|
+
const words = [...new Set(goal.toLowerCase().match(/[a-z][a-z0-9]{3,}/g) ?? [])].filter(w => !STOP.has(w));
|
|
110
|
+
if (words.length) {
|
|
111
|
+
const tracked = (git(cwd, ['ls-files']) ?? '').split('\n').filter(Boolean);
|
|
112
|
+
for (const file of tracked) {
|
|
113
|
+
if (files.length >= LIKELY_FILES)
|
|
114
|
+
break;
|
|
115
|
+
const name = file.toLowerCase();
|
|
116
|
+
if (!files.includes(file) && words.some(w => name.includes(w)))
|
|
117
|
+
files.push(file);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return files;
|
|
121
|
+
}
|
|
122
|
+
const STOP = new Set(['this', 'that', 'with', 'from', 'into', 'when', 'then', 'than', 'have', 'make', 'should', 'would', 'could', 'please', 'there', 'their', 'what', 'which', 'about', 'feat', 'test', 'tests', 'code', 'file', 'files', 'change', 'update']);
|
|
123
|
+
function git(cwd, args) {
|
|
124
|
+
const result = spawnSync('git', args, { cwd, encoding: 'utf8', timeout: 5000, maxBuffer: 16 * 1024 * 1024 });
|
|
125
|
+
return result.status === 0 ? result.stdout.trim() : undefined;
|
|
126
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -119,3 +119,5 @@ export type { SemanticFinding } from './semantic/types.js';
|
|
|
119
119
|
export type { ModelUsage } from './storage/index.js';
|
|
120
120
|
export type { CheckpointMetric } from './storage/index.js';
|
|
121
121
|
export type { CursorUsageSyncOptions } from './services/cursor-usage-client.js';
|
|
122
|
+
export { appendTaskEvent, readThread, taskOf, threadText, threadsDir, THREADS_DIR, type TaskEvent, type TaskEventKind, type ThreadEvent } from './task/thread.js';
|
|
123
|
+
export { briefFile, briefingText, briefTask, buildBriefing, buildFileBriefing, fileBriefingText, BRIEFING_MAX_ITEMS, FILE_BRIEFING_MAX_ITEMS, type Briefing, type BriefingInput, type BriefingItem } from './brief/briefing.js';
|
package/dist/index.js
CHANGED
|
@@ -101,3 +101,5 @@ export { learnFromFix, learnFromFileChange } from './semantic/learn/learn.js';
|
|
|
101
101
|
export { extractFixTrees } from './semantic/learn/git-trees.js';
|
|
102
102
|
export { saveLearnedRule, loadLearnedRules, LEARNED_RULES_DIR } from './semantic/learn/store.js';
|
|
103
103
|
export { recordContextEvent, recordModelUsage, recordCheckpointMetric, getContextEvents, getModelUsages, getCheckpointMetrics, } from './storage/index.js';
|
|
104
|
+
export { appendTaskEvent, readThread, taskOf, threadText, threadsDir, THREADS_DIR } from './task/thread.js';
|
|
105
|
+
export { briefFile, briefingText, briefTask, buildBriefing, buildFileBriefing, fileBriefingText, BRIEFING_MAX_ITEMS, FILE_BRIEFING_MAX_ITEMS } from './brief/briefing.js';
|
|
@@ -107,7 +107,7 @@ export class CloudProvider {
|
|
|
107
107
|
const response = await this.client.messages.create({
|
|
108
108
|
model: this.modelName,
|
|
109
109
|
max_tokens: options?.maxTokens || 4096,
|
|
110
|
-
|
|
110
|
+
...claudeSampling(this.modelName, options?.temperature ?? 0.1),
|
|
111
111
|
messages: cacheFirstPrompt(toAnthropicMessages(messages)),
|
|
112
112
|
...(tools.length ? { tools: tools.map(t => ({ name: t.name, description: t.description, input_schema: t.parameters })) } : {}),
|
|
113
113
|
...(tools.length && options?.toolChoice === 'none' ? { tool_choice: { type: 'none' } } : {}),
|
|
@@ -157,7 +157,7 @@ export class CloudProvider {
|
|
|
157
157
|
const response = await this.client.messages.create({
|
|
158
158
|
model: this.modelName,
|
|
159
159
|
max_tokens: options?.maxTokens || 2048,
|
|
160
|
-
|
|
160
|
+
...claudeSampling(this.modelName, options?.temperature ?? 0.1),
|
|
161
161
|
messages: [
|
|
162
162
|
{ role: 'user', content: prompt }
|
|
163
163
|
],
|
|
@@ -203,6 +203,16 @@ export class CloudProvider {
|
|
|
203
203
|
this.client = null;
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Claude models that take no sampling parameters: Anthropic's model guide lists temperature, top_p and top_k as removed
|
|
208
|
+
* (a 400) on Fable, Mythos, Opus 5.5, Opus 5, Opus 4.8 and 4.7 and Sonnet 5, and non-default values as a 400 on Sonnet 5.5
|
|
209
|
+
* and Haiku 5.5; OpenRouter's model list marks temperature unsupported on them too. Older Claude models still take it.
|
|
210
|
+
*/
|
|
211
|
+
const CLAUDE_WITHOUT_SAMPLING = /claude-(?:fable|mythos|opus-5|opus-4-[78]|sonnet-5|haiku-5)/;
|
|
212
|
+
/** The temperature to send a Claude model: none to one that rejects sampling parameters. */
|
|
213
|
+
function claudeSampling(model, temperature) {
|
|
214
|
+
return CLAUDE_WITHOUT_SAMPLING.test(model) ? {} : { temperature };
|
|
215
|
+
}
|
|
206
216
|
/** The per-call timeout, honoured by both SDKs (their default is minutes, with retries). */
|
|
207
217
|
function requestOptions(options) {
|
|
208
218
|
return options?.timeout ? { timeout: options.timeout } : {};
|
|
@@ -11,11 +11,14 @@ export interface PrRounds {
|
|
|
11
11
|
export declare function roundsForPr(cwd: string, pr: number, config: Config, exec?: Exec, options?: {
|
|
12
12
|
approvedHead?: boolean;
|
|
13
13
|
}): Promise<PrRounds>;
|
|
14
|
-
/** The last `n` merged pull requests, newest merge first. */
|
|
15
|
-
export declare function mergedPrs(cwd: string, n: number, config: Config, exec?: Exec): Promise<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
/** The last `n` merged pull requests, newest merge first; `incomplete` when the listing could not prove it has them all. */
|
|
15
|
+
export declare function mergedPrs(cwd: string, n: number, config: Config, exec?: Exec): Promise<{
|
|
16
|
+
prs: Array<{
|
|
17
|
+
number: number;
|
|
18
|
+
mergedAt: string;
|
|
19
|
+
}>;
|
|
20
|
+
incomplete: boolean;
|
|
21
|
+
}>;
|
|
19
22
|
export declare function scaffoldLedger(cwd: string, pr: number, config: Config, exec?: Exec): Promise<{
|
|
20
23
|
file: string;
|
|
21
24
|
rounds: LedgerRound[];
|
|
@@ -46,17 +46,35 @@ export async function roundsForPr(cwd, pr, config, exec = defaultExec, options =
|
|
|
46
46
|
throw new Error(`pull request ${pr} has no review by a person yet`);
|
|
47
47
|
return { rounds, ...(approved ? { approved } : {}) };
|
|
48
48
|
}
|
|
49
|
-
/**
|
|
49
|
+
/** How many merged pull requests to list per one wanted, first: gh applies its limit before any sort of ours. */
|
|
50
|
+
const MERGED_OVERFETCH = 4;
|
|
51
|
+
/** The most merged pull requests listed per one wanted before Rigour stops and says the list may be incomplete. */
|
|
52
|
+
const MERGED_OVERFETCH_MAX = 32;
|
|
53
|
+
/** The last `n` merged pull requests, newest merge first; `incomplete` when the listing could not prove it has them all. */
|
|
50
54
|
export async function mergedPrs(cwd, n, config, exec = defaultExec) {
|
|
51
55
|
const env = await githubEnv(cwd, config.review?.github_account ?? process.env.RIGOUR_GITHUB_ACCOUNT, exec);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
// Listed by last update and sorted by merge date here. A merged pull request is updated at or after its merge, so once the
|
|
57
|
+
// oldest update listed is no later than the Nth merge kept, nothing unlisted can be among the last N (its merge is no later
|
|
58
|
+
// than its update, which is no later than that). Until then the listing doubles: bots that touch old pull requests after
|
|
59
|
+
// merge (backports, labels, stale comments) can crowd a window.
|
|
60
|
+
for (let limit = n * MERGED_OVERFETCH;; limit *= 2) {
|
|
61
|
+
const list = await exec('gh', ['pr', 'list', '--state', 'merged', '--limit', String(limit), '--search', 'sort:updated-desc', '--json', 'number,mergedAt,updatedAt'], { cwd, timeoutMs: GH_TIMEOUT_MS, env });
|
|
62
|
+
if (list.exitCode !== 0)
|
|
63
|
+
throw new Error(`could not list merged pull requests: ${list.stderr.trim() || 'is gh signed in?'}`);
|
|
64
|
+
let listed;
|
|
65
|
+
try {
|
|
66
|
+
listed = JSON.parse(list.stdout);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
throw new Error('could not read the list of merged pull requests');
|
|
70
|
+
}
|
|
71
|
+
const prs = [...listed].sort((a, b) => (a.mergedAt < b.mergedAt ? 1 : -1)).slice(0, n).map(({ number, mergedAt }) => ({ number, mergedAt }));
|
|
72
|
+
const oldestUpdate = listed.reduce((min, p) => (p.updatedAt < min ? p.updatedAt : min), listed[0]?.updatedAt ?? '');
|
|
73
|
+
const complete = listed.length < limit || (prs.length === n && oldestUpdate <= prs[n - 1].mergedAt);
|
|
74
|
+
if (complete)
|
|
75
|
+
return { prs, incomplete: false };
|
|
76
|
+
if (limit >= n * MERGED_OVERFETCH_MAX)
|
|
77
|
+
return { prs, incomplete: true };
|
|
60
78
|
}
|
|
61
79
|
}
|
|
62
80
|
/** Lines either side of an inline comment that a finding for the same point may land on. */
|
|
@@ -17,7 +17,9 @@ const GIT_TIMEOUT_MS = 5 * 60_000;
|
|
|
17
17
|
export async function backtestLast(cwd, config, options) {
|
|
18
18
|
const exec = options.exec ?? defaultExec;
|
|
19
19
|
const progress = options.progress ?? (() => undefined);
|
|
20
|
-
const prs = await mergedPrs(cwd, options.last, config, exec);
|
|
20
|
+
const { prs, incomplete } = await mergedPrs(cwd, options.last, config, exec);
|
|
21
|
+
if (incomplete)
|
|
22
|
+
progress(`backtest: warning: the listing could not prove these are the last ${options.last} merged pull requests (many old pull requests were updated after merge); the result may miss recent ones`);
|
|
21
23
|
const rounds = [];
|
|
22
24
|
const skipped = [];
|
|
23
25
|
for (const pr of prs) {
|
|
@@ -13,6 +13,10 @@ export interface Adapter {
|
|
|
13
13
|
answer(stdout: string): {
|
|
14
14
|
text: string;
|
|
15
15
|
} & Spend;
|
|
16
|
+
/** Variables the judge runs with, on top of what it inherits: what keeps a person's own instructions out of it. */
|
|
17
|
+
env?: Record<string, string>;
|
|
18
|
+
/** What this judge still reads from outside the repository on this machine (a person's own config), or undefined: said on the record, never hidden. */
|
|
19
|
+
outsideRepo?(home: string, version: string | undefined): string | undefined;
|
|
16
20
|
}
|
|
17
21
|
/** What a run used: dollars when the CLI reports them (Claude Code), tokens otherwise (Codex reports only tokens). */
|
|
18
22
|
export interface Spend {
|
|
@@ -11,6 +11,8 @@ import os from 'os';
|
|
|
11
11
|
import path from 'path';
|
|
12
12
|
import { defaultExec } from './exec.js';
|
|
13
13
|
const n = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
|
|
14
|
+
/** The Claude Code version the memory switches were verified in: below it, a judge may still load a person's CLAUDE.md. */
|
|
15
|
+
const CLAUDE_MEMORY_ISOLATION = '2.1.285';
|
|
14
16
|
const READ_ONLY_TOOLS = ['Read', 'Grep', 'Glob', 'Bash(git diff:*)', 'Bash(git show:*)', 'Bash(git log:*)', 'Bash(git grep:*)'];
|
|
15
17
|
export const ADAPTERS = {
|
|
16
18
|
claude: {
|
|
@@ -28,12 +30,26 @@ export const ADAPTERS = {
|
|
|
28
30
|
'--allowedTools', ...READ_ONLY_TOOLS,
|
|
29
31
|
'--disallowedTools', 'Edit', 'Write', 'NotebookEdit', 'Bash(git push:*)', 'Bash(git commit:*)',
|
|
30
32
|
],
|
|
33
|
+
// No memory file loads itself: not ~/.claude/CLAUDE.md, not one in a folder above the repository, not the repository's
|
|
34
|
+
// own (the judge reads the repository's rules as files, as Rigour's prompt tells every judge to), and no auto-memory.
|
|
35
|
+
// Claude Code's own safe mode uses the same switch. What the judge knows is the repository and what Rigour gives it.
|
|
36
|
+
env: { CLAUDE_CODE_DISABLE_CLAUDE_MDS: '1', CLAUDE_CODE_DISABLE_AUTO_MEMORY: '1' },
|
|
37
|
+
// An older Claude Code ignores those switches without a word and loads the memory files anyway: below the version
|
|
38
|
+
// they were verified in, or when the version cannot be read, the record says the isolation is unverified.
|
|
39
|
+
outsideRepo: (_home, version) => {
|
|
40
|
+
const found = /(\d+)\.(\d+)\.(\d+)/.exec(version ?? '');
|
|
41
|
+
const at = found ? found.slice(1).map(Number) : undefined;
|
|
42
|
+
const floor = CLAUDE_MEMORY_ISOLATION.split('.').map(Number);
|
|
43
|
+
const below = !at || at[0] !== floor[0] ? !at || at[0] < floor[0] : at[1] !== floor[1] ? at[1] < floor[1] : at[2] < floor[2];
|
|
44
|
+
return below ? `claude ${found?.[0] ?? '(version unknown)'}: memory isolation unverified (needs ${CLAUDE_MEMORY_ISOLATION} or later)` : undefined;
|
|
45
|
+
},
|
|
31
46
|
answer: stdout => claudeAnswer(stdout),
|
|
32
47
|
},
|
|
33
48
|
cursor: {
|
|
34
49
|
vendor: 'cursor',
|
|
35
50
|
binary: 'cursor-agent',
|
|
36
51
|
// Ask mode is read-only and cannot run git: every input the prompt names is a file.
|
|
52
|
+
outsideRepo: () => 'cursor also reads your own Cursor rules and settings',
|
|
37
53
|
args: (prompt, model) => ['-p', '--print', '--output-format', 'json', '--trust', '--mode', 'ask', '--model', model ?? 'auto', prompt],
|
|
38
54
|
answer: stdout => {
|
|
39
55
|
try {
|
|
@@ -48,6 +64,13 @@ export const ADAPTERS = {
|
|
|
48
64
|
codex: {
|
|
49
65
|
vendor: 'openai',
|
|
50
66
|
binary: 'codex',
|
|
67
|
+
// Codex has no switch that skips a person's ~/.codex/config.toml and ~/.codex/AGENTS.md and keeps their login, so the
|
|
68
|
+
// record says when those exist rather than claiming a judge that only knows the repository.
|
|
69
|
+
outsideRepo: home => {
|
|
70
|
+
const dir = process.env.CODEX_HOME?.trim() || path.join(home, '.codex');
|
|
71
|
+
const read = ['config.toml', 'AGENTS.md'].filter(name => fs.existsSync(path.join(dir, name)));
|
|
72
|
+
return read.length ? `codex also reads ${read.map(name => path.join(dir, name)).join(' and ')}` : undefined;
|
|
73
|
+
},
|
|
51
74
|
args: (prompt, model, options) => ['exec', '--sandbox', 'read-only', '--json', ...(model ? ['--model', model] : []), '-c', `model_reasoning_effort=${options?.reasoning ?? 'high'}`, prompt],
|
|
52
75
|
// `codex exec --json` streams events; the last text-bearing one carries the answer.
|
|
53
76
|
// Warnings arrive as `error` items with a `message`, not `text`, so they are never taken for the answer.
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type Exec } from './exec.js';
|
|
2
|
-
import type { Approval } from './verdict.js';
|
|
2
|
+
import type { Approval, LabelledPoint } from './verdict.js';
|
|
3
3
|
export interface PullRequest {
|
|
4
4
|
number: number;
|
|
5
5
|
state: 'open' | 'closed' | 'merged';
|
|
6
6
|
draft: boolean;
|
|
7
7
|
author: string;
|
|
8
8
|
body: string;
|
|
9
|
+
title?: string;
|
|
9
10
|
}
|
|
10
11
|
export interface HumanReviews {
|
|
11
12
|
/** As the reviewer reads them, oldest first, inline comments after; `none` when there are none. */
|
|
@@ -15,6 +16,8 @@ export interface HumanReviews {
|
|
|
15
16
|
count: number;
|
|
16
17
|
/** Each person's approval, in time order: every point that person raised before it is settled. */
|
|
17
18
|
approvals: Approval[];
|
|
19
|
+
/** The points each review put under a severity heading of its own ("Blocking", "Should fix", "Nits"). */
|
|
20
|
+
labels: LabelledPoint[];
|
|
18
21
|
/** `<login>, <date> (<n> reviews)` of the latest, for the report. */
|
|
19
22
|
label?: string;
|
|
20
23
|
}
|
|
@@ -13,7 +13,7 @@ import { GH_TIMEOUT_MS, parseJsonArrays } from './exec.js';
|
|
|
13
13
|
export function ghFor(cwd, exec, env) {
|
|
14
14
|
return args => exec('gh', args, { cwd, timeoutMs: GH_TIMEOUT_MS, env });
|
|
15
15
|
}
|
|
16
|
-
const PR_FIELDS = 'number,state,isDraft,author,body';
|
|
16
|
+
const PR_FIELDS = 'number,state,isDraft,author,body,title';
|
|
17
17
|
/**
|
|
18
18
|
* The description as it read at `at` (a backtest's review time): GitHub keeps every version in
|
|
19
19
|
* `userContentEdits`, the first being the text at creation. Undefined when it cannot be read, so a
|
|
@@ -67,7 +67,7 @@ async function viewPullRequest(gh, selector) {
|
|
|
67
67
|
const parsed = JSON.parse(result.stdout);
|
|
68
68
|
if (!Number.isInteger(parsed.number))
|
|
69
69
|
return { error: `gh returned no pull request number for ${selector}` };
|
|
70
|
-
return { pr: { number: parsed.number, state: String(parsed.state ?? '').toLowerCase(), draft: !!parsed.isDraft, author: String(parsed.author?.login ?? ''), body: String(parsed.body ?? '') } };
|
|
70
|
+
return { pr: { number: parsed.number, state: String(parsed.state ?? '').toLowerCase(), draft: !!parsed.isDraft, author: String(parsed.author?.login ?? ''), body: String(parsed.body ?? ''), ...(parsed.title ? { title: String(parsed.title) } : {}) } };
|
|
71
71
|
}
|
|
72
72
|
catch {
|
|
73
73
|
return { error: `gh returned something other than a pull request for ${selector}: ${result.stdout.slice(0, 120)}` };
|
|
@@ -104,10 +104,44 @@ export async function humanReviews(gh, pr, reviewsBefore) {
|
|
|
104
104
|
key: [...rounds.map((r) => `${r.id},${r.submitted_at},${r.commit_id},${r.state}`), ...inline.map((c) => `${c.id},${c.updated_at}`)].join('|'),
|
|
105
105
|
count,
|
|
106
106
|
approvals,
|
|
107
|
+
labels: rounds.filter(worded).flatMap((r) => severityLabels(String(r.body), String(r.user.login), String(r.submitted_at))),
|
|
107
108
|
...(latest ? { label: `${latest.user.login}, ${latest.submitted_at} (${count} review${count === 1 ? '' : 's'})` } : {}),
|
|
108
109
|
},
|
|
109
110
|
};
|
|
110
111
|
}
|
|
112
|
+
/** The severity a heading names: "Blocking", "Should fix", "Nits" and their usual spellings, alone on the line. */
|
|
113
|
+
function headingSeverity(line) {
|
|
114
|
+
const bare = line.replace(/[#*_:`>]/g, ' ').replace(/\(\d+\)/g, ' ').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
115
|
+
if (!bare || bare.split(' ').length > 3)
|
|
116
|
+
return undefined;
|
|
117
|
+
if (/^(blocking|blockers?|must fix|must-fix)$/.test(bare))
|
|
118
|
+
return 'blocking';
|
|
119
|
+
if (/^(should fix|should-fix|should)$/.test(bare))
|
|
120
|
+
return 'should-fix';
|
|
121
|
+
if (/^(nits?|non blocking|non-blocking|minor|optional)$/.test(bare))
|
|
122
|
+
return 'non-blocking';
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
/** Each bullet or numbered line under a severity heading of the review, with that heading's severity. */
|
|
126
|
+
function severityLabels(body, login, at) {
|
|
127
|
+
const out = [];
|
|
128
|
+
let severity;
|
|
129
|
+
for (const line of body.split('\n')) {
|
|
130
|
+
const heading = headingSeverity(line);
|
|
131
|
+
if (heading) {
|
|
132
|
+
severity = heading;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (/^\s{0,3}#{1,6}\s/.test(line))
|
|
136
|
+
severity = undefined; // another heading ends the section
|
|
137
|
+
// A bullet, a numbered line, or a numbered line set in bold or underline ("**1. The column is NOT NULL.**"), which is
|
|
138
|
+
// how many reviewers title a point before its evidence.
|
|
139
|
+
const item = /^\s*(?:\*\*|__)?\s*(?:[-*+]|\d+[.)])\s+(.+?)\s*(?:\*\*|__)?\s*$/.exec(line);
|
|
140
|
+
if (severity && item)
|
|
141
|
+
out.push({ login, at, severity, text: item[1].replace(/\*\*|__/g, '').trim() });
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
111
145
|
/** The repository's rules as the reviewer must read them. */
|
|
112
146
|
export function rulesText(cwd) {
|
|
113
147
|
return ['AGENTS.md', 'CLAUDE.md'].map(name => {
|
|
@@ -5,12 +5,14 @@ export interface ReviewRecord {
|
|
|
5
5
|
base: string;
|
|
6
6
|
scope: 'full' | 'delta';
|
|
7
7
|
at: string;
|
|
8
|
+
/** `outside_repo`: what the judge also read from the machine's own config (a person's instructions); absent when it read only the repository and Rigour's inputs. */
|
|
8
9
|
judges: Array<{
|
|
9
10
|
reviewer: string;
|
|
10
11
|
version?: string;
|
|
11
12
|
model?: string;
|
|
12
13
|
cost_usd?: number;
|
|
13
14
|
turns?: number;
|
|
15
|
+
outside_repo?: string;
|
|
14
16
|
}>;
|
|
15
17
|
/** Checked by Rigour against the checkout. */
|
|
16
18
|
verified: {
|
|
@@ -28,10 +30,13 @@ export interface ReviewRecord {
|
|
|
28
30
|
served: number;
|
|
29
31
|
applied: number;
|
|
30
32
|
};
|
|
33
|
+
/** `labelled`: points that took the review's own severity heading; `relabelled`: of those, the ones the judge had read otherwise. */
|
|
31
34
|
prior_points: {
|
|
32
35
|
open: number;
|
|
33
36
|
resolved: number;
|
|
34
37
|
answer_in_reply: number;
|
|
38
|
+
labelled?: number;
|
|
39
|
+
relabelled?: number;
|
|
35
40
|
};
|
|
36
41
|
unverified: number;
|
|
37
42
|
notes: number;
|
|
@@ -21,7 +21,7 @@ export function buildRecord(input) {
|
|
|
21
21
|
should_fix: input.accounted.advisory,
|
|
22
22
|
rules: { served: rules.length, followed: rules.filter(r => r.status === 'followed').length, broken: rules.filter(r => r.status === 'broken').length, not_applicable: rules.filter(r => r.status === 'not-applicable').length },
|
|
23
23
|
lessons: { served: input.lessonsServed, applied: lessons.filter(l => l.applies === true).length },
|
|
24
|
-
prior_points: { open: input.accounted.open.filter(i => i.kind === 'prior').length, resolved: input.accounted.resolved.length, answer_in_reply: input.accounted.answerInReply.length },
|
|
24
|
+
prior_points: { open: input.accounted.open.filter(i => i.kind === 'prior').length, resolved: input.accounted.resolved.length, answer_in_reply: input.accounted.answerInReply.length, ...(input.accounted.labels?.served ? { labelled: input.accounted.labels.taken, relabelled: input.accounted.labels.disagreed } : {}) },
|
|
25
25
|
unverified: input.accounted.unverified.length,
|
|
26
26
|
notes: input.accounted.notes.length,
|
|
27
27
|
disputed: input.accounted.disputed.length,
|
|
@@ -54,7 +54,7 @@ function canonical(value) {
|
|
|
54
54
|
export function recordLines(r, shouldFixShown = 5) {
|
|
55
55
|
const where = (i) => `${i.file ? `\`${i.file}${i.line ? `:${i.line}` : ''}\` ` : ''}${i.issue}${i.locations?.length ? ` (also ${i.locations.map(l => `\`${l.file}${l.line ? `:${l.line}` : ''}\``).join(', ')})` : ''}`;
|
|
56
56
|
const v = r.verified;
|
|
57
|
-
const lines = [`**Review record** · ${v.blocking.length} blocking · ${v.should_fix.length} should-fix · rules ${v.rules.followed} followed, ${v.rules.broken} broken, ${v.rules.not_applicable} not applicable of ${v.rules.served} · lessons ${v.lessons.applied} of ${v.lessons.served} apply · prior points ${v.prior_points.open} open, ${v.prior_points.resolved} resolved`];
|
|
57
|
+
const lines = [`**Review record** · ${v.blocking.length} blocking · ${v.should_fix.length} should-fix · rules ${v.rules.followed} followed, ${v.rules.broken} broken, ${v.rules.not_applicable} not applicable of ${v.rules.served} · lessons ${v.lessons.applied} of ${v.lessons.served} apply · prior points ${v.prior_points.open} open, ${v.prior_points.resolved} resolved${v.prior_points.labelled !== undefined ? `, ${v.prior_points.labelled} by the review's own label (${v.prior_points.relabelled} relabelled)` : ''}`];
|
|
58
58
|
for (const i of v.blocking)
|
|
59
59
|
lines.push(`- **Blocking** ${where(i)}`);
|
|
60
60
|
for (const i of v.should_fix.slice(0, shouldFixShown))
|
|
@@ -64,6 +64,6 @@ export function recordLines(r, shouldFixShown = 5) {
|
|
|
64
64
|
const folded = [[v.notes, 'working note'], [v.disputed, 'disputed'], [v.unverified, 'unverified'], [r.people.dismissed, 'dismissed']].filter(([n]) => n > 0);
|
|
65
65
|
if (folded.length)
|
|
66
66
|
lines.push(`Also seen, never blocking: ${folded.map(([n, w]) => `${n} ${w}${n === 1 || w === 'disputed' || w === 'unverified' || w === 'dismissed' ? '' : 's'}`).join(', ')}.`);
|
|
67
|
-
lines.push(`Judged by ${r.judges.map(j => `${j.reviewer}${j.version ? ` ${j.version}` : ''}${j.model ? ` (${j.model})` : ''}${typeof j.cost_usd === 'number' ? ` $${j.cost_usd.toFixed(2)}` : ''}`).join(', ') || 'no judge'} on \`${r.head.slice(0, 9)}\` against \`${r.base.slice(0, 9)}\` (${r.scope}); ${r.reported.human_reviews} human review(s) seen. Integrity \`${r.integrity.slice(0, 16)}\`.`);
|
|
67
|
+
lines.push(`Judged by ${r.judges.map(j => `${j.reviewer}${j.version ? ` ${j.version}` : ''}${j.model ? ` (${j.model})` : ''}${typeof j.cost_usd === 'number' ? ` $${j.cost_usd.toFixed(2)}` : ''}${j.outside_repo ? ` [${j.outside_repo}]` : ''}`).join(', ') || 'no judge'} on \`${r.head.slice(0, 9)}\` against \`${r.base.slice(0, 9)}\` (${r.scope}); ${r.reported.human_reviews} human review(s) seen. Integrity \`${r.integrity.slice(0, 16)}\`.`);
|
|
68
68
|
return lines;
|
|
69
69
|
}
|
|
@@ -27,6 +27,14 @@ export interface PriorChecks {
|
|
|
27
27
|
approvals: Approval[];
|
|
28
28
|
inCheckout: (text: string) => string | undefined;
|
|
29
29
|
changed?: ChangedLines;
|
|
30
|
+
labels?: LabelledPoint[];
|
|
31
|
+
}
|
|
32
|
+
/** A point under a severity heading the reviewer wrote ("Blocking", "Should fix", "Nits"): the reviewer's own label. */
|
|
33
|
+
export interface LabelledPoint {
|
|
34
|
+
login: string;
|
|
35
|
+
at: string;
|
|
36
|
+
severity: NonNullable<PriorPoint['severity']>;
|
|
37
|
+
text: string;
|
|
30
38
|
}
|
|
31
39
|
/** The lines a change touched, per file: every line added, and the place of every deletion, in the new file's numbering. */
|
|
32
40
|
export type ChangedLines = Map<string, Set<number>>;
|
|
@@ -252,6 +260,12 @@ export interface Accounting {
|
|
|
252
260
|
notes: OpenItem[];
|
|
253
261
|
/** Should-fixes the judge could show (a verified quote): worth a person's time, never a block. */
|
|
254
262
|
advisory: OpenItem[];
|
|
263
|
+
/** The reviews' own severity labels: how many there were, how many prior points took one, and how many the judge read otherwise. */
|
|
264
|
+
labels?: {
|
|
265
|
+
served: number;
|
|
266
|
+
taken: number;
|
|
267
|
+
disagreed: number;
|
|
268
|
+
};
|
|
255
269
|
}
|
|
256
270
|
/** Everything the reviewers reported blocks; in delta mode, previous open items carry unless resolved with evidence. */
|
|
257
271
|
export declare function account(verdict: Verdict, previousOpen: OpenItem[] | undefined, verify: Verify, prior?: PriorChecks): Accounting;
|