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.
Files changed (74) hide show
  1. package/README.md +90 -9
  2. package/dist/config/defaults.d.ts +23 -4
  3. package/dist/config/defaults.js +27 -7
  4. package/dist/config/defaults.js.map +1 -1
  5. package/dist/config/harness.d.ts +26 -0
  6. package/dist/config/harness.js +150 -0
  7. package/dist/config/harness.js.map +1 -0
  8. package/dist/config/loader.js +33 -5
  9. package/dist/config/loader.js.map +1 -1
  10. package/dist/config/schema.d.ts +13 -0
  11. package/dist/config/schema.js +9 -0
  12. package/dist/config/schema.js.map +1 -1
  13. package/dist/consensus/deduper.d.ts +11 -0
  14. package/dist/consensus/deduper.js +106 -1
  15. package/dist/consensus/deduper.js.map +1 -1
  16. package/dist/consensus/parser.d.ts +6 -0
  17. package/dist/consensus/parser.js +36 -10
  18. package/dist/consensus/parser.js.map +1 -1
  19. package/dist/consensus/types.d.ts +40 -1
  20. package/dist/consensus/voter.d.ts +6 -5
  21. package/dist/consensus/voter.js +66 -12
  22. package/dist/consensus/voter.js.map +1 -1
  23. package/dist/discuss.d.ts +53 -0
  24. package/dist/discuss.js +112 -0
  25. package/dist/discuss.js.map +1 -0
  26. package/dist/dispatch/adapter.d.ts +11 -0
  27. package/dist/dispatch/anthropic.d.ts +2 -1
  28. package/dist/dispatch/anthropic.js +72 -14
  29. package/dist/dispatch/anthropic.js.map +1 -1
  30. package/dist/dispatch/google.d.ts +2 -1
  31. package/dist/dispatch/google.js +82 -14
  32. package/dist/dispatch/google.js.map +1 -1
  33. package/dist/dispatch/merge.d.ts +5 -0
  34. package/dist/dispatch/merge.js +13 -0
  35. package/dist/dispatch/merge.js.map +1 -1
  36. package/dist/dispatch/openai-compat.d.ts +15 -3
  37. package/dist/dispatch/openai-compat.js +96 -22
  38. package/dist/dispatch/openai-compat.js.map +1 -1
  39. package/dist/dispatch/openai.d.ts +2 -1
  40. package/dist/dispatch/openai.js +66 -14
  41. package/dist/dispatch/openai.js.map +1 -1
  42. package/dist/dispatch/runner.d.ts +5 -1
  43. package/dist/dispatch/runner.js +21 -2
  44. package/dist/dispatch/runner.js.map +1 -1
  45. package/dist/dispatch/utils.d.ts +73 -2
  46. package/dist/dispatch/utils.js +110 -3
  47. package/dist/dispatch/utils.js.map +1 -1
  48. package/dist/index.js +369 -161
  49. package/dist/index.js.map +1 -1
  50. package/dist/output/github.js +22 -2
  51. package/dist/output/github.js.map +1 -1
  52. package/dist/output/markdown.js +126 -8
  53. package/dist/output/markdown.js.map +1 -1
  54. package/dist/output/terminal.js +23 -3
  55. package/dist/output/terminal.js.map +1 -1
  56. package/dist/prepare/prompt-builder.d.ts +5 -0
  57. package/dist/prepare/prompt-builder.js +8 -6
  58. package/dist/prepare/prompt-builder.js.map +1 -1
  59. package/dist/prompts/plan.d.ts +15 -0
  60. package/dist/prompts/plan.js +55 -0
  61. package/dist/prompts/plan.js.map +1 -0
  62. package/dist/resolver/git.d.ts +3 -0
  63. package/dist/resolver/git.js +41 -0
  64. package/dist/resolver/git.js.map +1 -0
  65. package/dist/resolver/plan.d.ts +8 -0
  66. package/dist/resolver/plan.js +44 -0
  67. package/dist/resolver/plan.js.map +1 -0
  68. package/dist/roles/dispatcher.d.ts +1 -1
  69. package/dist/roles/dispatcher.js +2 -0
  70. package/dist/roles/dispatcher.js.map +1 -1
  71. package/package.json +5 -4
  72. package/dist/help/environment.d.ts +0 -4
  73. package/dist/help/environment.js +0 -67
  74. 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 <target>`
61
+ ### `rcl review [target]`
62
62
 
63
- Review a PR or local diff.
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 dropped
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: 120000 # ms per model call
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 result is a ranked list of findings that rewards agreement across independent reviewers and penalises noise from a single model.
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.5", "google/gemini-3.5-flash"];
3
- /** Previous-gen models — specialized round-robin only, no general role */
4
- export declare const DEFAULT_SECONDARY_MODELS: readonly ["anthropic/claude-opus-4-8", "openai/gpt-5.4", "google/gemini-2.5-pro"];
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
- export declare const DEFAULT_TIMEOUT_MS = 120000;
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: {
@@ -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',
5
- 'google/gemini-3.5-flash',
4
+ 'openai/gpt-5.6-sol',
5
+ 'google/gemini-3.6-flash',
6
+ 'openrouter/moonshotai/kimi-k3',
6
7
  ];
7
- /** Previous-gen models — specialized round-robin only, no general role */
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
- 'anthropic/claude-opus-4-8',
10
- 'openai/gpt-5.4',
11
- 'google/gemini-2.5-pro',
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
- export const DEFAULT_TIMEOUT_MS = 120_000;
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,gBAAgB;IAChB,yBAAyB;CACjB,CAAC;AAEX,0EAA0E;AAC1E,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,2BAA2B;IAC3B,gBAAgB;IAChB,uBAAuB;CACf,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,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAC1C,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AACrC,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"}
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":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAExD;;;;;;;;;;;;;;;;;GAiBG;AAEH,6EAA6E;AAC7E,MAAM,iBAAiB,GAA2B;IAChD,SAAS,EAAE,mBAAmB;IAC9B,MAAM,EAAE,gBAAgB;IACxB,MAAM,EAAE,gBAAgB;IACxB,UAAU,EAAE,oBAAoB;CACjC,CAAC;AAEF,+EAA+E;AAC/E,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAiB/B,sEAAsE;AACtE,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,IAAI,GAAG,GAAG,QAAQ,CAAC;IACnB,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;IACjC,SAAS,CAAC;QACR,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,EAAE,aAAa,CAAC,CAAC;QAC3D,IAAI,CAAC;YACH,kEAAkE;YAClE,6BAA6B;YAC7B,UAAU,CAAC,SAAS,CAAC,CAAC;YACtB,OAAO,SAAS,CAAC;QACnB,CAAC;QAAC,MAAM,CAAC;YACP,eAAe;QACjB,CAAC;QACD,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC9B,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAuC;IAC5E,MAAM,GAAG,GAAG,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACnG,OAAO,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,kBAAkB,CAAC,CAAC;AACnD,CAAC;AAED,KAAK,UAAU,qBAAqB,CAAC,IAAY;IAC/C,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QAClE,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YAClD,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,MAAiC,CAAC;YACzD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,GAAG,KAAK,EAAE,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;gBACvF,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC;YACjD,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,iDAAiD;IACnD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,KAAK,UAAU,cAAc,CAC3B,OAAe,EACf,KAAa,EACb,SAAuB;IAEvB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,OAAO,oBAAoB,EAAE;YAC/D,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;YAC7C,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,gBAAgB,CAAC;SAC9C,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAC9B,MAAM,IAAI,GAAY,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAI,IAAsC,EAAE,IAAI,EAAE,IAAI,CAAC;QACjE,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC3D,MAAM,GAAG,GAA2B,EAAE,CAAC;QACvC,KAAK,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,EAAE;gBAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC;QACjE,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;QACrE,0BAA0B;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAuC;IAC/D,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;IAChE,OAAO,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC;SACrC,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE;QAC7B,0DAA0D;QAC1D,IAAI,QAAQ,KAAK,QAAQ,IAAI,KAAK,CAAC,gBAAgB,CAAC;YAAE,OAAO,KAAK,CAAC;QACnE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC,CAAC;SACD,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,UAA6B,EAAE;IACzE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IAEvC,IAAI,CAAC,GAAG,CAAC,qBAAqB,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAEvE,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAElD,IAAI,qBAAqB,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI;QAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAE1F,MAAM,WAAW,GAAG,MAAM,qBAAqB,CAC7C,OAAO,CAAC,eAAe,IAAI,sBAAsB,CAAC,GAAG,CAAC,CACvD,CAAC;IACF,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QACzB,OAAO;YACL,QAAQ,EAAE,EAAE;YACZ,IAAI,EAAE,WAAW,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,6FAA6F;SAClK,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,GAAG,EAAE,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC,CAAC;IAClG,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,mCAAmC,WAAW,CAAC,GAAG,2CAA2C,EAAE,CAAC;IAC/H,CAAC;IAED,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,QAAQ,IAAI,OAAO,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3B,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,GAAG,CAAC,iBAAiB,CAAC,QAAQ,CAAE,CAAC,GAAG,GAAG,CAAC;YACxC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,OAAO;QACL,QAAQ;QACR,IAAI,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,yBAAyB,WAAW,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS;KACjH,CAAC;AACJ,CAAC"}
@@ -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: [...DEFAULT_MODELS],
59
- secondaryModels: [...DEFAULT_SECONDARY_MODELS],
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 ?? [...DEFAULT_MODELS],
69
- secondaryModels: config.secondaryModels ?? (config.models ? [] : [...DEFAULT_SECONDARY_MODELS]),
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":"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"}
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"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"}
@@ -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>;
@@ -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":"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"}
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[];