review-council 1.4.0 → 1.8.0
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/README.md +90 -9
- package/dist/config/defaults.d.ts +23 -4
- package/dist/config/defaults.js +27 -7
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/harness.d.ts +26 -0
- package/dist/config/harness.js +150 -0
- package/dist/config/harness.js.map +1 -0
- package/dist/config/loader.js +33 -5
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +13 -0
- package/dist/config/schema.js +9 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/consensus/deduper.d.ts +11 -0
- package/dist/consensus/deduper.js +106 -1
- package/dist/consensus/deduper.js.map +1 -1
- package/dist/consensus/parser.d.ts +6 -0
- package/dist/consensus/parser.js +36 -10
- package/dist/consensus/parser.js.map +1 -1
- package/dist/consensus/types.d.ts +40 -1
- package/dist/consensus/voter.d.ts +6 -5
- package/dist/consensus/voter.js +66 -12
- package/dist/consensus/voter.js.map +1 -1
- package/dist/discuss.d.ts +53 -0
- package/dist/discuss.js +112 -0
- package/dist/discuss.js.map +1 -0
- package/dist/dispatch/adapter.d.ts +11 -0
- package/dist/dispatch/anthropic.d.ts +2 -1
- package/dist/dispatch/anthropic.js +72 -14
- package/dist/dispatch/anthropic.js.map +1 -1
- package/dist/dispatch/google.d.ts +2 -1
- package/dist/dispatch/google.js +82 -14
- package/dist/dispatch/google.js.map +1 -1
- package/dist/dispatch/merge.d.ts +5 -0
- package/dist/dispatch/merge.js +13 -0
- package/dist/dispatch/merge.js.map +1 -1
- package/dist/dispatch/openai-compat.d.ts +15 -3
- package/dist/dispatch/openai-compat.js +96 -22
- package/dist/dispatch/openai-compat.js.map +1 -1
- package/dist/dispatch/openai.d.ts +2 -1
- package/dist/dispatch/openai.js +66 -14
- package/dist/dispatch/openai.js.map +1 -1
- package/dist/dispatch/runner.d.ts +5 -1
- package/dist/dispatch/runner.js +21 -2
- package/dist/dispatch/runner.js.map +1 -1
- package/dist/dispatch/utils.d.ts +73 -2
- package/dist/dispatch/utils.js +110 -3
- package/dist/dispatch/utils.js.map +1 -1
- package/dist/index.js +369 -161
- package/dist/index.js.map +1 -1
- package/dist/output/github.js +22 -2
- package/dist/output/github.js.map +1 -1
- package/dist/output/markdown.js +126 -8
- package/dist/output/markdown.js.map +1 -1
- package/dist/output/terminal.js +23 -3
- package/dist/output/terminal.js.map +1 -1
- package/dist/prepare/prompt-builder.d.ts +5 -0
- package/dist/prepare/prompt-builder.js +8 -6
- package/dist/prepare/prompt-builder.js.map +1 -1
- package/dist/prompts/plan.d.ts +15 -0
- package/dist/prompts/plan.js +55 -0
- package/dist/prompts/plan.js.map +1 -0
- package/dist/resolver/git.d.ts +3 -0
- package/dist/resolver/git.js +41 -0
- package/dist/resolver/git.js.map +1 -0
- package/dist/resolver/plan.d.ts +8 -0
- package/dist/resolver/plan.js +44 -0
- package/dist/resolver/plan.js.map +1 -0
- package/dist/roles/dispatcher.d.ts +1 -1
- package/dist/roles/dispatcher.js +2 -0
- package/dist/roles/dispatcher.js.map +1 -1
- package/package.json +5 -4
- package/dist/help/environment.d.ts +0 -4
- package/dist/help/environment.js +0 -67
- package/dist/help/environment.js.map +0 -1
package/README.md
CHANGED
|
@@ -58,19 +58,22 @@ rcl roles show security-auditor
|
|
|
58
58
|
|
|
59
59
|
## CLI Reference
|
|
60
60
|
|
|
61
|
-
### `rcl review
|
|
61
|
+
### `rcl review [target]`
|
|
62
62
|
|
|
63
|
-
Review a PR
|
|
63
|
+
Review a PR, a local diff, or uncommitted work.
|
|
64
64
|
|
|
65
65
|
**Target formats:**
|
|
66
66
|
- `owner/repo#N` — GitHub PR number
|
|
67
67
|
- GitHub PR URL
|
|
68
68
|
- Path to a `.patch` or `.diff` file
|
|
69
|
+
- No target with `--staged` or `--working-tree` — review uncommitted changes in the current repository
|
|
69
70
|
|
|
70
71
|
**Options:**
|
|
71
72
|
|
|
72
73
|
| Flag | Description |
|
|
73
74
|
|------|-------------|
|
|
75
|
+
| `--staged` | Review staged changes (`git diff --cached`) |
|
|
76
|
+
| `--working-tree` | Review all uncommitted changes (`git diff HEAD`, staged + unstaged) |
|
|
74
77
|
| `--role <name>` | Use a single named role |
|
|
75
78
|
| `--roles <names>` | Comma-separated list of roles |
|
|
76
79
|
| `--reviewer <model:role>` | Explicit model:role pair (repeatable) |
|
|
@@ -85,7 +88,7 @@ Review a PR or local diff.
|
|
|
85
88
|
| `--ci` | Exit non-zero if critical/important findings exist |
|
|
86
89
|
| `--config <path>` | Path to a config file |
|
|
87
90
|
|
|
88
|
-
`--role`, `--roles`, and `--reviewer` are mutually exclusive.
|
|
91
|
+
`--role`, `--roles`, and `--reviewer` are mutually exclusive. So are a positional target, `--staged`, and `--working-tree` — pick exactly one review source. Untracked files are invisible to `git diff` and therefore not reviewed.
|
|
89
92
|
|
|
90
93
|
**Examples:**
|
|
91
94
|
|
|
@@ -100,8 +103,45 @@ rcl review ./feature.patch --role spec-compliance --spec SPEC.md --context src/
|
|
|
100
103
|
|
|
101
104
|
# Output JSON for downstream processing
|
|
102
105
|
rcl review owner/repo#99 --json > findings.json
|
|
106
|
+
|
|
107
|
+
# Review your uncommitted work before committing
|
|
108
|
+
rcl review --staged
|
|
109
|
+
rcl review --working-tree --roles security-auditor,bug-hunter
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### `rcl review-plan <file>`
|
|
115
|
+
|
|
116
|
+
Council-review an implementation plan document (PRD, BUILD_PLAN.md, design doc) **before any code exists** — the cheapest bugs to fix are the ones caught in the plan.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
rcl review-plan docs/plan.md
|
|
120
|
+
rcl review-plan docs/plan.md --focus risks # feasibility | completeness | risks | timeline
|
|
121
|
+
rcl review-plan docs/plan.md --spec PRD.md # also check the plan against a spec
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The plan flows through the normal pipeline — multi-model dispatch, dedup, consensus, agreement-tier report — with plan-adapted prompts. Finding line numbers refer to the plan document's own lines. Categories are reinterpreted for plans (`correctness` = infeasible/contradictory steps, `tests` = missing validation strategy, `best-practices` = process gaps like rollback/migration, …).
|
|
125
|
+
|
|
126
|
+
Default roles are a plan-suited subset (`general`, `architecture`, `edge-case-hunter`, plus `spec-compliance` when a spec is given); `--role`/`--roles`/`--reviewer` and config `roles` override as usual. Shares `--context`, `--models`, `--json`, `--json-file`, `--markdown`, and `--config` with `rcl review`. `--post` and `--ci` are not offered (no PR to post to; plan findings are judgment calls, not gates).
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### `rcl discuss`
|
|
131
|
+
|
|
132
|
+
Ask the models that flagged a finding a follow-up question — one round, reconstructed from a saved report. Useful when triaging: "is this actually exploitable given the sanitizer at line 40?" goes to the reviewers who raised it (especially valuable for **disputed** findings, where the report shows each model's position).
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
rcl review --staged --json-file report.json
|
|
136
|
+
rcl discuss --report report.json --finding f003 "Is this exploitable given the sanitizer at line 40?"
|
|
137
|
+
|
|
138
|
+
# Attach code as context, or ask different models
|
|
139
|
+
rcl discuss --report report.json --finding f003 --context src/auth.ts "Does the middleware at line 12 not already cover this?"
|
|
140
|
+
rcl discuss --report report.json --finding f003 --models anthropic/claude-fable-5 "Summarize the strongest counterargument."
|
|
103
141
|
```
|
|
104
142
|
|
|
143
|
+
Model-generated finding ids can collide; when `--finding <id>` is ambiguous the error lists `<id>:<n>` disambiguators. Findings in the below-threshold appendix are addressable too. Answers come back in parallel, respecting the configured `timeout`, `maxRetries`, and `reasoningEffort`. There is no session state: each `discuss` is one independent round built from the report file.
|
|
144
|
+
|
|
105
145
|
---
|
|
106
146
|
|
|
107
147
|
### `rcl roles`
|
|
@@ -123,6 +163,8 @@ models:
|
|
|
123
163
|
- anthropic/claude-opus-4-6
|
|
124
164
|
- openai/gpt-5.4
|
|
125
165
|
- google/gemini-2.5-pro
|
|
166
|
+
# Any model on https://openrouter.ai — keep the vendor segment after the prefix
|
|
167
|
+
- openrouter/moonshotai/kimi-k3
|
|
126
168
|
|
|
127
169
|
# Default roles to run
|
|
128
170
|
roles:
|
|
@@ -147,7 +189,7 @@ customRoles:
|
|
|
147
189
|
|
|
148
190
|
# Consensus and deduplication thresholds
|
|
149
191
|
thresholds:
|
|
150
|
-
minConsensusScore: 0.4 # 0–1; findings below this are
|
|
192
|
+
minConsensusScore: 0.4 # 0–1; findings below this are demoted to the appendix
|
|
151
193
|
minConfidence: 0.2
|
|
152
194
|
dedupeLineWindow: 5 # lines within which findings are merged
|
|
153
195
|
jaccardThreshold: 0.3 # weighted title+description similarity threshold for dedup
|
|
@@ -156,12 +198,19 @@ thresholds:
|
|
|
156
198
|
output:
|
|
157
199
|
markdown: true
|
|
158
200
|
markdownPath: review-report.md
|
|
201
|
+
belowThresholdAppendix: true # false drops below-threshold findings outright
|
|
159
202
|
|
|
160
203
|
# Concurrency and reliability
|
|
161
204
|
concurrency: 6
|
|
162
|
-
timeout:
|
|
205
|
+
timeout: 600000 # ms per model call (reasoning models need headroom on real diffs)
|
|
163
206
|
maxRetries: 3
|
|
164
207
|
|
|
208
|
+
# Reasoning budget for providers that support it (currently OpenRouter).
|
|
209
|
+
# low | medium | high — default medium. Unbounded reasoning makes these
|
|
210
|
+
# models spend the whole completion budget thinking before they answer;
|
|
211
|
+
# raise to 'high' for deeper review at the cost of latency and tokens.
|
|
212
|
+
reasoningEffort: medium
|
|
213
|
+
|
|
165
214
|
# Context files to attach to every review
|
|
166
215
|
context:
|
|
167
216
|
- ARCHITECTURE.md
|
|
@@ -182,12 +231,12 @@ Supported config file names: `.review-council.yml`, `.review-council.yaml`, `.re
|
|
|
182
231
|
|
|
183
232
|
When multiple models and roles review the same diff, their findings are:
|
|
184
233
|
|
|
185
|
-
1. **Deduplicated** — findings on the same file and overlapping line range are grouped by weighted title+description token similarity; findings in different categories can still merge, but need stronger similarity (models disagree on category boundaries constantly). Repeats within a single review are collapsed first. Findings that clearly reach opposite conclusions are kept as separate, disputed findings; subtler contradictions merge but are flagged as disputed.
|
|
234
|
+
1. **Deduplicated** — findings on the same file and overlapping line range are grouped by weighted title+description token similarity; findings in different categories can still merge, but need stronger similarity (models disagree on category boundaries constantly). Findings whose line ranges strictly overlap and that name the same issue concept (sql injection, IDOR, hardcoded secret, …) merge regardless of wording — models phrase the same issue too differently for token overlap alone. Repeats within a single review are collapsed first. Findings that clearly reach opposite conclusions are kept as separate, disputed findings; subtler contradictions merge but are flagged as disputed.
|
|
186
235
|
2. **Scored** — each group receives a consensus score based on three dimensions: reviewer diversity (how many distinct models and roles flagged it, saturating at half the fleet so large configurations aren't penalized), role relevance (whether a role specialised in that finding type confirmed it), and isolation (what fraction of relevant reviewers flagged it).
|
|
187
|
-
3. **Classified** — groups are assigned a confidence band (Very High → Minimal) and a final severity. Severity is the most common rating across reviewers; when reviewers disagree, high-confidence agreement elevates it, but only to a severity at least two reviewers independently assigned — a lone outlier rating is surfaced as a dispute instead.
|
|
188
|
-
4. **Filtered** — groups below `minConsensusScore` or `minConfidence` are dropped.
|
|
236
|
+
3. **Classified** — groups are assigned a confidence band (Very High → Minimal) and a final severity. Severity is the most common rating across reviewers; when reviewers disagree, high-confidence agreement elevates it, but only to a severity at least two reviewers independently assigned — a lone outlier rating is surfaced as a dispute instead. Each group also gets an **agreement tier** measured over distinct models — `unanimous` (every successful model), `majority` (at least half), `minority` (2+, under half), `single` (one model) — because roles share a model's blind spots, so model count is the evidence axis.
|
|
237
|
+
4. **Filtered** — groups below `minConsensusScore` or `minConfidence` are demoted (blocking severities are never dropped). Demoted findings land in a collapsed "worth checking" appendix at the bottom of the report and in the JSON `belowThresholdFindings` field — never in severity totals or CI gating. Set `output.belowThresholdAppendix: false` to drop them outright instead.
|
|
189
238
|
|
|
190
|
-
The
|
|
239
|
+
The report is organized by agreement tier — unanimous first, then majority, minority, **disputed** (reviewers reached materially different conclusions; rendered as per-model positions so you can judge), and single-model last. Within each tier, findings sort by severity. The tier structure is the point of a multi-model council: it tells you which findings are independently confirmed and where to spend your own judgment.
|
|
191
240
|
|
|
192
241
|
For the full algorithm, see [CONSENSUS_V2_SPEC.md](./CONSENSUS_V2_SPEC.md).
|
|
193
242
|
|
|
@@ -200,8 +249,40 @@ For the full algorithm, see [CONSENSUS_V2_SPEC.md](./CONSENSUS_V2_SPEC.md).
|
|
|
200
249
|
| `ANTHROPIC_API_KEY` | API key for Claude models |
|
|
201
250
|
| `OPENAI_API_KEY` | API key for OpenAI models |
|
|
202
251
|
| `GEMINI_API_KEY` | API key for Google Gemini models |
|
|
252
|
+
| `OPENROUTER_API_KEY` | API key for [OpenRouter](https://openrouter.ai) models (`openrouter/…` prefix) |
|
|
203
253
|
| `GITHUB_TOKEN` | GitHub personal access token (PR fetch and post) |
|
|
204
254
|
| `RCL_DEBUG` | Set to any value to print full error stack traces |
|
|
255
|
+
| `RCL_NO_HARNESS_KEYS` | Set to any value to disable Harness key distribution (below) |
|
|
256
|
+
|
|
257
|
+
The default model fleet includes OpenRouter-hosted models. If `OPENROUTER_API_KEY`
|
|
258
|
+
is not set, those entries are dropped from the defaults with a warning naming the
|
|
259
|
+
surviving fleet (models you configure explicitly still fail loudly instead). The
|
|
260
|
+
default secondary list is entirely OpenRouter-hosted, so without the key it is
|
|
261
|
+
empty and specialist roles are spread across the primary models only — set
|
|
262
|
+
`secondaryModels:` explicitly if you want a secondary tier without OpenRouter. Note that when the key is set,
|
|
263
|
+
default reviews send diff and context content to OpenRouter — an aggregator and
|
|
264
|
+
an additional data processor beyond the direct model providers — as well as to
|
|
265
|
+
Anthropic, OpenAI, and Google. Configure `models:` explicitly if that matters
|
|
266
|
+
for your repository.
|
|
267
|
+
|
|
268
|
+
### Key distribution via Harness
|
|
269
|
+
|
|
270
|
+
Repos that carry a committed `.harness-cli/config.json` (discovered git-style,
|
|
271
|
+
walking up from the working directory) can get their provider keys from a
|
|
272
|
+
[Harness](https://harness.infra.one) backend instead of every teammate managing
|
|
273
|
+
them by hand: run `harness login` once, and any provider key **missing from the
|
|
274
|
+
environment** is fetched from `GET /api/v1/model-keys` on the host that minted
|
|
275
|
+
the stored login token, and injected for the run.
|
|
276
|
+
|
|
277
|
+
- Environment variables always win — only missing keys are injected.
|
|
278
|
+
- The stored credential is only ever sent to the host it was minted for, never
|
|
279
|
+
to a URL named by the repo's own config (untrusted input in a cloned repo).
|
|
280
|
+
- Any failure — not logged in, offline, older backend without the endpoint —
|
|
281
|
+
falls back silently to the plain-environment behavior above. The fetch runs
|
|
282
|
+
under a 3-second timeout and keys are never written to disk or logs.
|
|
283
|
+
- Which providers the backend serves is server configuration
|
|
284
|
+
(`HARNESS_MODEL_KEYS` on the backend); `RCL_NO_HARNESS_KEYS` disables the
|
|
285
|
+
whole mechanism client-side.
|
|
205
286
|
|
|
206
287
|
---
|
|
207
288
|
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/** SOTA models — run the general role + participate in specialized round-robin */
|
|
2
|
-
export declare const DEFAULT_MODELS: readonly ["anthropic/claude-fable-5", "openai/gpt-5.
|
|
3
|
-
/**
|
|
4
|
-
|
|
2
|
+
export declare const DEFAULT_MODELS: readonly ["anthropic/claude-fable-5", "openai/gpt-5.6-sol", "google/gemini-3.6-flash", "openrouter/moonshotai/kimi-k3"];
|
|
3
|
+
/**
|
|
4
|
+
* Secondary models — specialized round-robin only, no general role.
|
|
5
|
+
* Chosen for training-lineage diversity: with the four SOTA models above,
|
|
6
|
+
* every default voter comes from a different lab, so cross-model consensus
|
|
7
|
+
* always reflects independent confirmation.
|
|
8
|
+
*/
|
|
9
|
+
export declare const DEFAULT_SECONDARY_MODELS: readonly ["openrouter/qwen/qwen3.8-max", "openrouter/deepseek/deepseek-v4-flash-0731", "openrouter/x-ai/grok-4.5"];
|
|
5
10
|
export declare const DEFAULT_THRESHOLDS: {
|
|
6
11
|
readonly minConsensusScore: 0.4;
|
|
7
12
|
readonly minConfidence: 0.2;
|
|
@@ -17,8 +22,22 @@ export declare const DEFAULT_THRESHOLDS: {
|
|
|
17
22
|
*/
|
|
18
23
|
readonly jaccardThreshold: 0.3;
|
|
19
24
|
};
|
|
20
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Reasoning-heavy models (kimi-k3, qwen3.8-max, deepseek-v4, grok-4.5)
|
|
27
|
+
* routinely need several minutes to review a real diff: at 120s the entire
|
|
28
|
+
* OpenRouter wing of the default fleet timed out, and at 300s most
|
|
29
|
+
* specialist runs still did (successful calls measured 217–291s, with no
|
|
30
|
+
* headroom). Dogfooded on this repo's own diffs.
|
|
31
|
+
*/
|
|
32
|
+
export declare const DEFAULT_TIMEOUT_MS = 600000;
|
|
21
33
|
export declare const DEFAULT_MAX_RETRIES = 3;
|
|
34
|
+
/**
|
|
35
|
+
* Reasoning budget for OpenRouter-hosted models. Unbounded, they spend the
|
|
36
|
+
* whole completion budget (and many minutes) thinking before emitting any
|
|
37
|
+
* findings; 'medium' is calibrated against this repo's own diffs, where it
|
|
38
|
+
* took the council from 10/17 to 17/17 completed reviews.
|
|
39
|
+
*/
|
|
40
|
+
export declare const DEFAULT_REASONING_EFFORT = "medium";
|
|
22
41
|
export declare const DEFAULT_CONCURRENCY = 6;
|
|
23
42
|
export declare const DEFAULT_SEVERITY_ORDER: readonly ["critical", "important", "minor", "nitpick"];
|
|
24
43
|
export declare const CONFIDENCE_THRESHOLDS: {
|
package/dist/config/defaults.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
/** SOTA models — run the general role + participate in specialized round-robin */
|
|
2
2
|
export const DEFAULT_MODELS = [
|
|
3
3
|
'anthropic/claude-fable-5',
|
|
4
|
-
'openai/gpt-5.
|
|
5
|
-
'google/gemini-3.
|
|
4
|
+
'openai/gpt-5.6-sol',
|
|
5
|
+
'google/gemini-3.6-flash',
|
|
6
|
+
'openrouter/moonshotai/kimi-k3',
|
|
6
7
|
];
|
|
7
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Secondary models — specialized round-robin only, no general role.
|
|
10
|
+
* Chosen for training-lineage diversity: with the four SOTA models above,
|
|
11
|
+
* every default voter comes from a different lab, so cross-model consensus
|
|
12
|
+
* always reflects independent confirmation.
|
|
13
|
+
*/
|
|
8
14
|
export const DEFAULT_SECONDARY_MODELS = [
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'
|
|
15
|
+
'openrouter/qwen/qwen3.8-max',
|
|
16
|
+
'openrouter/deepseek/deepseek-v4-flash-0731',
|
|
17
|
+
'openrouter/x-ai/grok-4.5',
|
|
12
18
|
];
|
|
13
19
|
export const DEFAULT_THRESHOLDS = {
|
|
14
20
|
minConsensusScore: 0.4,
|
|
@@ -25,8 +31,22 @@ export const DEFAULT_THRESHOLDS = {
|
|
|
25
31
|
*/
|
|
26
32
|
jaccardThreshold: 0.3,
|
|
27
33
|
};
|
|
28
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Reasoning-heavy models (kimi-k3, qwen3.8-max, deepseek-v4, grok-4.5)
|
|
36
|
+
* routinely need several minutes to review a real diff: at 120s the entire
|
|
37
|
+
* OpenRouter wing of the default fleet timed out, and at 300s most
|
|
38
|
+
* specialist runs still did (successful calls measured 217–291s, with no
|
|
39
|
+
* headroom). Dogfooded on this repo's own diffs.
|
|
40
|
+
*/
|
|
41
|
+
export const DEFAULT_TIMEOUT_MS = 600_000;
|
|
29
42
|
export const DEFAULT_MAX_RETRIES = 3;
|
|
43
|
+
/**
|
|
44
|
+
* Reasoning budget for OpenRouter-hosted models. Unbounded, they spend the
|
|
45
|
+
* whole completion budget (and many minutes) thinking before emitting any
|
|
46
|
+
* findings; 'medium' is calibrated against this repo's own diffs, where it
|
|
47
|
+
* took the council from 10/17 to 17/17 completed reviews.
|
|
48
|
+
*/
|
|
49
|
+
export const DEFAULT_REASONING_EFFORT = 'medium';
|
|
30
50
|
export const DEFAULT_CONCURRENCY = 6;
|
|
31
51
|
export const DEFAULT_SEVERITY_ORDER = [
|
|
32
52
|
'critical',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,0BAA0B;IAC1B,
|
|
1
|
+
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,0BAA0B;IAC1B,oBAAoB;IACpB,yBAAyB;IACzB,+BAA+B;CACvB,CAAC;AAEX;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,6BAA6B;IAC7B,4CAA4C;IAC5C,0BAA0B;CAClB,CAAC;AAEX,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,iBAAiB,EAAE,GAAG;IACtB,aAAa,EAAE,GAAG;IAClB,gBAAgB,EAAE,CAAC;IACnB;;;;;;;;OAQG;IACH,gBAAgB,EAAE,GAAG;CACb,CAAC;AAEX;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAC1C,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAErC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,QAAQ,CAAC;AACjD,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAErC,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,UAAU;IACV,WAAW;IACX,OAAO;IACP,SAAS;CACD,CAAC;AAEX,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,QAAQ,EAAE,GAAG;IACb,IAAI,EAAE,GAAG;IACT,MAAM,EAAE,GAAG;IACX,GAAG,EAAE,GAAG;CACA,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface HarnessKeyResult {
|
|
2
|
+
/** Providers whose keys were injected into the env. */
|
|
3
|
+
injected: string[];
|
|
4
|
+
/** One human-readable line worth showing, when there is something to say. */
|
|
5
|
+
note?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface HarnessKeyOptions {
|
|
8
|
+
cwd?: string;
|
|
9
|
+
/** Injectable for tests; defaults to process.env and is MUTATED on inject. */
|
|
10
|
+
env?: Record<string, string | undefined>;
|
|
11
|
+
credentialsPath?: string;
|
|
12
|
+
fetchImpl?: typeof fetch;
|
|
13
|
+
}
|
|
14
|
+
/** Walk up from `startDir` looking for `.harness-cli/config.json`. */
|
|
15
|
+
export declare function findHarnessRepoConfig(startDir: string): string | null;
|
|
16
|
+
/**
|
|
17
|
+
* The credential `harness login` stores. The format is frozen on the
|
|
18
|
+
* harness-cli side (exactly {url, token}) — see its core/credentials.ts.
|
|
19
|
+
*/
|
|
20
|
+
export declare function defaultCredentialsPath(env: Record<string, string | undefined>): string;
|
|
21
|
+
/**
|
|
22
|
+
* The one entry point: called before config loading (default-model
|
|
23
|
+
* degradation for a missing OPENROUTER_API_KEY happens there, so the key
|
|
24
|
+
* must exist earlier). No-op unless keys are missing AND the repo opted in.
|
|
25
|
+
*/
|
|
26
|
+
export declare function applyHarnessModelKeys(options?: HarnessKeyOptions): Promise<HarnessKeyResult>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { accessSync } from 'fs';
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { dirname, isAbsolute, join, parse } from 'path';
|
|
5
|
+
/**
|
|
6
|
+
* Key distribution via Harness. When the repo under review carries a
|
|
7
|
+
* committed `.harness-cli/config.json` (the "this repo belongs to the org"
|
|
8
|
+
* signal, discovered git-style by walking up from the working directory)
|
|
9
|
+
* and the user has run `harness login`, provider API keys missing from the
|
|
10
|
+
* environment are fetched from the Harness backend
|
|
11
|
+
* (GET /api/v1/model-keys) and injected into the process environment.
|
|
12
|
+
*
|
|
13
|
+
* Rules this module must never break:
|
|
14
|
+
* - The environment always wins: only keys absent from the env are injected.
|
|
15
|
+
* - The stored {url, token} credential pair is indivisible: the token is
|
|
16
|
+
* only ever sent to the host that minted it — NEVER to a URL named by the
|
|
17
|
+
* repo's own config, which is attacker-controlled input in a cloned repo.
|
|
18
|
+
* - Every failure degrades silently to today's behavior (missing keys warn
|
|
19
|
+
* or fail exactly as before); the fetch runs under a short timeout so an
|
|
20
|
+
* offline machine never hangs a review.
|
|
21
|
+
* - Keys land in the (injected) env only — never on disk, never in logs.
|
|
22
|
+
*/
|
|
23
|
+
/** Provider names the backend may serve, mapped to the env var rcl reads. */
|
|
24
|
+
const PROVIDER_ENV_VARS = {
|
|
25
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
26
|
+
openai: 'OPENAI_API_KEY',
|
|
27
|
+
google: 'GEMINI_API_KEY',
|
|
28
|
+
openrouter: 'OPENROUTER_API_KEY',
|
|
29
|
+
};
|
|
30
|
+
/** Long enough for a healthy backend, short enough to never stall a review. */
|
|
31
|
+
const FETCH_TIMEOUT_MS = 3_000;
|
|
32
|
+
/** Walk up from `startDir` looking for `.harness-cli/config.json`. */
|
|
33
|
+
export function findHarnessRepoConfig(startDir) {
|
|
34
|
+
let dir = startDir;
|
|
35
|
+
const { root } = parse(startDir);
|
|
36
|
+
for (;;) {
|
|
37
|
+
const candidate = join(dir, '.harness-cli', 'config.json');
|
|
38
|
+
try {
|
|
39
|
+
// Presence is the signal; the file's contents (team, url) are not
|
|
40
|
+
// trusted for anything here.
|
|
41
|
+
accessSync(candidate);
|
|
42
|
+
return candidate;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// keep walking
|
|
46
|
+
}
|
|
47
|
+
if (dir === root)
|
|
48
|
+
return null;
|
|
49
|
+
dir = dirname(dir);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The credential `harness login` stores. The format is frozen on the
|
|
54
|
+
* harness-cli side (exactly {url, token}) — see its core/credentials.ts.
|
|
55
|
+
*/
|
|
56
|
+
export function defaultCredentialsPath(env) {
|
|
57
|
+
const xdg = env['XDG_CONFIG_HOME'];
|
|
58
|
+
const base = xdg !== undefined && xdg !== '' && isAbsolute(xdg) ? xdg : join(homedir(), '.config');
|
|
59
|
+
return join(base, 'harness', 'credentials.json');
|
|
60
|
+
}
|
|
61
|
+
async function readStoredCredentials(path) {
|
|
62
|
+
try {
|
|
63
|
+
const parsed = JSON.parse(await readFile(path, 'utf-8'));
|
|
64
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
65
|
+
const { url, token } = parsed;
|
|
66
|
+
if (typeof url === 'string' && typeof token === 'string' && url !== '' && token !== '') {
|
|
67
|
+
return { url: url.replace(/\/+$/, ''), token };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Missing or malformed reads as "not logged in".
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
async function fetchModelKeys(baseUrl, token, fetchImpl) {
|
|
77
|
+
try {
|
|
78
|
+
const response = await fetchImpl(`${baseUrl}/api/v1/model-keys`, {
|
|
79
|
+
headers: { authorization: `Bearer ${token}` },
|
|
80
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
81
|
+
});
|
|
82
|
+
if (!response.ok)
|
|
83
|
+
return null;
|
|
84
|
+
const body = await response.json();
|
|
85
|
+
const keys = body?.data?.keys;
|
|
86
|
+
if (typeof keys !== 'object' || keys === null)
|
|
87
|
+
return null;
|
|
88
|
+
const out = {};
|
|
89
|
+
for (const [provider, key] of Object.entries(keys)) {
|
|
90
|
+
if (typeof key === 'string' && key !== '')
|
|
91
|
+
out[provider] = key;
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Timeout, offline, older backend without the endpoint — all read as
|
|
97
|
+
// "no keys from Harness".
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function missingProviders(env) {
|
|
102
|
+
const isSet = (name) => (env[name] ?? '').trim() !== '';
|
|
103
|
+
return Object.entries(PROVIDER_ENV_VARS)
|
|
104
|
+
.filter(([provider, envVar]) => {
|
|
105
|
+
// The Google adapter reads either variable; respect both.
|
|
106
|
+
if (provider === 'google' && isSet('GOOGLE_API_KEY'))
|
|
107
|
+
return false;
|
|
108
|
+
return !isSet(envVar);
|
|
109
|
+
})
|
|
110
|
+
.map(([provider]) => provider);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The one entry point: called before config loading (default-model
|
|
114
|
+
* degradation for a missing OPENROUTER_API_KEY happens there, so the key
|
|
115
|
+
* must exist earlier). No-op unless keys are missing AND the repo opted in.
|
|
116
|
+
*/
|
|
117
|
+
export async function applyHarnessModelKeys(options = {}) {
|
|
118
|
+
const env = options.env ?? process.env;
|
|
119
|
+
if ((env['RCL_NO_HARNESS_KEYS'] ?? '') !== '')
|
|
120
|
+
return { injected: [] };
|
|
121
|
+
const missing = missingProviders(env);
|
|
122
|
+
if (missing.length === 0)
|
|
123
|
+
return { injected: [] };
|
|
124
|
+
if (findHarnessRepoConfig(options.cwd ?? process.cwd()) === null)
|
|
125
|
+
return { injected: [] };
|
|
126
|
+
const credentials = await readStoredCredentials(options.credentialsPath ?? defaultCredentialsPath(env));
|
|
127
|
+
if (credentials === null) {
|
|
128
|
+
return {
|
|
129
|
+
injected: [],
|
|
130
|
+
note: `Missing ${missing.map((p) => PROVIDER_ENV_VARS[p]).join(', ')} — this repo is Harness-managed; run \`harness login\` and rcl will fetch the keys for you.`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const keys = await fetchModelKeys(credentials.url, credentials.token, options.fetchImpl ?? fetch);
|
|
134
|
+
if (keys === null) {
|
|
135
|
+
return { injected: [], note: `Could not fetch model keys from ${credentials.url} — continuing with the environment as-is.` };
|
|
136
|
+
}
|
|
137
|
+
const injected = [];
|
|
138
|
+
for (const provider of missing) {
|
|
139
|
+
const key = keys[provider];
|
|
140
|
+
if (key !== undefined) {
|
|
141
|
+
env[PROVIDER_ENV_VARS[provider]] = key;
|
|
142
|
+
injected.push(provider);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
injected,
|
|
147
|
+
note: injected.length > 0 ? `Using ${injected.join(', ')} key(s) from Harness (${credentials.url}).` : undefined,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=harness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/config/harness.ts"],"names":[],"mappings":"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"}
|
package/dist/config/loader.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cosmiconfig } from 'cosmiconfig';
|
|
2
2
|
import { ConfigSchema } from './schema.js';
|
|
3
|
-
import { DEFAULT_MODELS, DEFAULT_SECONDARY_MODELS, DEFAULT_THRESHOLDS, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_CONCURRENCY, } from './defaults.js';
|
|
3
|
+
import { DEFAULT_MODELS, DEFAULT_SECONDARY_MODELS, DEFAULT_THRESHOLDS, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_CONCURRENCY, DEFAULT_REASONING_EFFORT, } from './defaults.js';
|
|
4
4
|
/**
|
|
5
5
|
* A config file was found but could not be used. Never silently recover
|
|
6
6
|
* from this: falling back to defaults would send code to cloud providers
|
|
@@ -53,20 +53,47 @@ export async function loadConfig(configPath, searchFrom) {
|
|
|
53
53
|
}
|
|
54
54
|
return mergeWithDefaults(parsed.data);
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* The default fleet includes openrouter/ models. When OPENROUTER_API_KEY is
|
|
58
|
+
* absent those voters would fail on every run (the runner refuses to fall
|
|
59
|
+
* back to OPENAI_API_KEY), silently breaking upgrades from pre-OpenRouter
|
|
60
|
+
* versions whose users only hold the big-three keys. Defaults therefore
|
|
61
|
+
* degrade gracefully: openrouter/ entries are dropped with a warning.
|
|
62
|
+
* Models the user configured explicitly are never filtered — those still
|
|
63
|
+
* fail loudly per review.
|
|
64
|
+
*/
|
|
65
|
+
function dropOpenRouterDefaultsWithoutKey(models, listName) {
|
|
66
|
+
if (process.env['OPENROUTER_API_KEY']?.trim())
|
|
67
|
+
return [...models];
|
|
68
|
+
const kept = models.filter((m) => !m.startsWith('openrouter/'));
|
|
69
|
+
const dropped = models.length - kept.length;
|
|
70
|
+
if (dropped > 0) {
|
|
71
|
+
// Name the resulting fleet, not just the count: the default secondary
|
|
72
|
+
// list is entirely openrouter/, so without the key it degrades to
|
|
73
|
+
// empty and every specialist role falls back to the primary models.
|
|
74
|
+
const remaining = kept.length > 0 ? kept.join(', ') : '(none)';
|
|
75
|
+
console.warn(`OPENROUTER_API_KEY is not set — dropping ${dropped} openrouter/ model(s) from the default ${listName}; remaining: ${remaining}. Set the key to run the full default fleet.`);
|
|
76
|
+
}
|
|
77
|
+
return kept;
|
|
78
|
+
}
|
|
56
79
|
function buildDefaultConfig() {
|
|
57
80
|
return {
|
|
58
|
-
models:
|
|
59
|
-
secondaryModels:
|
|
81
|
+
models: dropOpenRouterDefaultsWithoutKey(DEFAULT_MODELS, 'models'),
|
|
82
|
+
secondaryModels: dropOpenRouterDefaultsWithoutKey(DEFAULT_SECONDARY_MODELS, 'secondary models'),
|
|
60
83
|
thresholds: { ...DEFAULT_THRESHOLDS },
|
|
61
84
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
62
85
|
maxRetries: DEFAULT_MAX_RETRIES,
|
|
63
86
|
concurrency: DEFAULT_CONCURRENCY,
|
|
87
|
+
reasoningEffort: DEFAULT_REASONING_EFFORT,
|
|
64
88
|
};
|
|
65
89
|
}
|
|
66
90
|
function mergeWithDefaults(config) {
|
|
67
91
|
return {
|
|
68
|
-
models: config.models ??
|
|
69
|
-
secondaryModels: config.secondaryModels ??
|
|
92
|
+
models: config.models ?? dropOpenRouterDefaultsWithoutKey(DEFAULT_MODELS, 'models'),
|
|
93
|
+
secondaryModels: config.secondaryModels ??
|
|
94
|
+
(config.models
|
|
95
|
+
? []
|
|
96
|
+
: dropOpenRouterDefaultsWithoutKey(DEFAULT_SECONDARY_MODELS, 'secondary models')),
|
|
70
97
|
roles: config.roles,
|
|
71
98
|
reviewers: config.reviewers,
|
|
72
99
|
customRoles: config.customRoles,
|
|
@@ -78,6 +105,7 @@ function mergeWithDefaults(config) {
|
|
|
78
105
|
timeout: config.timeout ?? DEFAULT_TIMEOUT_MS,
|
|
79
106
|
maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
80
107
|
concurrency: config.concurrency ?? DEFAULT_CONCURRENCY,
|
|
108
|
+
reasoningEffort: config.reasoningEffort ?? DEFAULT_REASONING_EFFORT,
|
|
81
109
|
githubToken: config.githubToken ?? process.env['GITHUB_TOKEN'],
|
|
82
110
|
context: config.context,
|
|
83
111
|
spec: config.spec,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAe,MAAM,aAAa,CAAC;AACxD,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAe,MAAM,aAAa,CAAC;AACxD,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,eAAe,CAAC;AAEvB;;;;GAIG;AACH,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED,yEAAyE;AACzE,mEAAmE;AACnE,wEAAwE;AACxE,mCAAmC;AACnC,MAAM,aAAa,GAAG;IACpB,qBAAqB;IACrB,sBAAsB;IACtB,sBAAsB;CACvB,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAmB,EAAE,UAAmB;IACvE,MAAM,GAAG,GAAG,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACxC,MAAM,QAAQ,GAAG,WAAW,CAAC,gBAAgB,EAAE;QAC7C,YAAY,EAAE,aAAa;QAC3B,yEAAyE;QACzE,sEAAsE;QACtE,wEAAwE;QACxE,+DAA+D;QAC/D,cAAc,EAAE,MAAM;KACvB,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,UAAU;YACjB,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC;YACjC,CAAC,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,UAAU,IAAI,mBAAmB,CAAC;QACjD,MAAM,IAAI,WAAW,CAAC,8BAA8B,MAAM,KAAK,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC;IAED,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QAC9B,OAAO,kBAAkB,EAAE,CAAC;IAC9B,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACrD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;aAC/B,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;aACzE,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,MAAM,IAAI,WAAW,CAAC,qBAAqB,MAAM,CAAC,QAAQ,MAAM,MAAM,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,gCAAgC,CACvC,MAAyB,EACzB,QAAgB;IAEhB,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,IAAI,EAAE;QAAE,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC;IAClE,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC;IAChE,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5C,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,sEAAsE;QACtE,kEAAkE;QAClE,oEAAoE;QACpE,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC/D,OAAO,CAAC,IAAI,CACV,4CAA4C,OAAO,0CAA0C,QAAQ,gBAAgB,SAAS,8CAA8C,CAC7K,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,kBAAkB;IACzB,OAAO;QACL,MAAM,EAAE,gCAAgC,CAAC,cAAc,EAAE,QAAQ,CAAC;QAClE,eAAe,EAAE,gCAAgC,CAAC,wBAAwB,EAAE,kBAAkB,CAAC;QAC/F,UAAU,EAAE,EAAE,GAAG,kBAAkB,EAAE;QACrC,OAAO,EAAE,kBAAkB;QAC3B,UAAU,EAAE,mBAAmB;QAC/B,WAAW,EAAE,mBAAmB;QAChC,eAAe,EAAE,wBAAwB;KAC1C,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,MAAc;IACvC,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,gCAAgC,CAAC,cAAc,EAAE,QAAQ,CAAC;QACnF,eAAe,EACb,MAAM,CAAC,eAAe;YACtB,CAAC,MAAM,CAAC,MAAM;gBACZ,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,gCAAgC,CAAC,wBAAwB,EAAE,kBAAkB,CAAC,CAAC;QACrF,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,UAAU,EAAE;YACV,GAAG,kBAAkB;YACrB,GAAG,MAAM,CAAC,UAAU;SACrB;QACD,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,kBAAkB;QAC7C,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,mBAAmB;QACpD,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,mBAAmB;QACtD,eAAe,EAAE,MAAM,CAAC,eAAe,IAAI,wBAAwB;QACnE,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;QAC9D,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC;AACJ,CAAC"}
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -35,7 +35,13 @@ export declare const OutputSchema: z.ZodObject<{
|
|
|
35
35
|
github: z.ZodOptional<z.ZodBoolean>;
|
|
36
36
|
jsonPath: z.ZodOptional<z.ZodString>;
|
|
37
37
|
markdownPath: z.ZodOptional<z.ZodString>;
|
|
38
|
+
belowThresholdAppendix: z.ZodOptional<z.ZodBoolean>;
|
|
38
39
|
}, z.core.$strip>;
|
|
40
|
+
export declare const ReasoningEffortSchema: z.ZodEnum<{
|
|
41
|
+
low: "low";
|
|
42
|
+
medium: "medium";
|
|
43
|
+
high: "high";
|
|
44
|
+
}>;
|
|
39
45
|
export declare const ConfigSchema: z.ZodObject<{
|
|
40
46
|
models: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
41
47
|
secondaryModels: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -63,10 +69,16 @@ export declare const ConfigSchema: z.ZodObject<{
|
|
|
63
69
|
github: z.ZodOptional<z.ZodBoolean>;
|
|
64
70
|
jsonPath: z.ZodOptional<z.ZodString>;
|
|
65
71
|
markdownPath: z.ZodOptional<z.ZodString>;
|
|
72
|
+
belowThresholdAppendix: z.ZodOptional<z.ZodBoolean>;
|
|
66
73
|
}, z.core.$strip>>;
|
|
67
74
|
timeout: z.ZodOptional<z.ZodNumber>;
|
|
68
75
|
maxRetries: z.ZodOptional<z.ZodNumber>;
|
|
69
76
|
concurrency: z.ZodOptional<z.ZodNumber>;
|
|
77
|
+
reasoningEffort: z.ZodOptional<z.ZodEnum<{
|
|
78
|
+
low: "low";
|
|
79
|
+
medium: "medium";
|
|
80
|
+
high: "high";
|
|
81
|
+
}>>;
|
|
70
82
|
githubToken: z.ZodOptional<z.ZodString>;
|
|
71
83
|
context: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
72
84
|
spec: z.ZodOptional<z.ZodString>;
|
|
@@ -77,4 +89,5 @@ export type ReviewerPair = z.infer<typeof ReviewerPairSchema>;
|
|
|
77
89
|
export type RoleConfig = z.infer<typeof RoleConfigSchema>;
|
|
78
90
|
export type Thresholds = z.infer<typeof ThresholdsSchema>;
|
|
79
91
|
export type Severity = z.infer<typeof SeveritySchema>;
|
|
92
|
+
export type ReasoningEffort = z.infer<typeof ReasoningEffortSchema>;
|
|
80
93
|
export type Category = z.infer<typeof CategorySchema>;
|
package/dist/config/schema.js
CHANGED
|
@@ -30,7 +30,14 @@ export const OutputSchema = z.object({
|
|
|
30
30
|
github: z.boolean().optional(),
|
|
31
31
|
jsonPath: z.string().optional(),
|
|
32
32
|
markdownPath: z.string().optional(),
|
|
33
|
+
/**
|
|
34
|
+
* Carry below-threshold findings into the report's demoted "worth
|
|
35
|
+
* checking" appendix (and the JSON `belowThresholdFindings` field).
|
|
36
|
+
* Default true; set false to drop them outright as before 1.6.0.
|
|
37
|
+
*/
|
|
38
|
+
belowThresholdAppendix: z.boolean().optional(),
|
|
33
39
|
});
|
|
40
|
+
export const ReasoningEffortSchema = z.enum(['low', 'medium', 'high']);
|
|
34
41
|
export const ConfigSchema = z.object({
|
|
35
42
|
models: z.array(z.string()).optional(),
|
|
36
43
|
secondaryModels: z.array(z.string()).optional(),
|
|
@@ -42,6 +49,8 @@ export const ConfigSchema = z.object({
|
|
|
42
49
|
timeout: z.number().positive().optional(),
|
|
43
50
|
maxRetries: z.number().int().nonnegative().optional(),
|
|
44
51
|
concurrency: z.number().int().positive().optional(),
|
|
52
|
+
/** Reasoning budget for providers that support it (currently OpenRouter). */
|
|
53
|
+
reasoningEffort: ReasoningEffortSchema.optional(),
|
|
45
54
|
githubToken: z.string().optional(),
|
|
46
55
|
context: z.array(z.string()).optional(),
|
|
47
56
|
spec: z.string().optional(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;AACpF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,IAAI,CAAC;IACnC,UAAU;IACV,aAAa;IACb,gBAAgB;IAChB,OAAO;IACP,YAAY;CACb,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;CACjB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACrC,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;CAC1D,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IACtD,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAClD,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACxD,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CACtD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAChC,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC5B,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAChC,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC9B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,5 +26,16 @@ export declare function combinedSimilarity(a: Finding, b: Finding): number;
|
|
|
26
26
|
* adds a warning.
|
|
27
27
|
*/
|
|
28
28
|
export declare function hasOpposingSentiment(a: Finding, b: Finding, specificOnly?: boolean): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Concept-level similarity: 0.8+ when both findings name the same issue
|
|
31
|
+
* concept AND their line ranges strictly overlap. Proximity within the
|
|
32
|
+
* dedup window is deliberately NOT enough — two different SQL injections a
|
|
33
|
+
* few lines apart share the concept but are different findings, and the
|
|
34
|
+
* fixture corpus shows genuine cross-model duplicates point at overlapping
|
|
35
|
+
* ranges. Benchmarked in the RCL-9 PR: fixture recall 0.70 → 1.00 at
|
|
36
|
+
* precision 1.00, no new false merges on adversarial same-concept pairs
|
|
37
|
+
* (the ungated code-council version added one).
|
|
38
|
+
*/
|
|
39
|
+
export declare function conceptSimilarity(a: Finding, b: Finding): number;
|
|
29
40
|
export declare function linesOverlap(a: Finding, b: Finding, window: number): boolean;
|
|
30
41
|
export declare function deduplicateFindings(reviews: ModelReview[], jaccardThreshold?: number, lineWindow?: number): DeduplicatedGroup[];
|