@lcv-ideas-software/cross-review 4.0.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 (122) hide show
  1. package/CHANGELOG.md +2568 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +26 -0
  4. package/README.md +208 -0
  5. package/SECURITY.md +52 -0
  6. package/dist/scripts/api-streaming-smoke.d.ts +1 -0
  7. package/dist/scripts/api-streaming-smoke.js +78 -0
  8. package/dist/scripts/api-streaming-smoke.js.map +1 -0
  9. package/dist/scripts/runtime-default-smoke.d.ts +1 -0
  10. package/dist/scripts/runtime-default-smoke.js +88 -0
  11. package/dist/scripts/runtime-default-smoke.js.map +1 -0
  12. package/dist/scripts/runtime-smoke.d.ts +1 -0
  13. package/dist/scripts/runtime-smoke.js +148 -0
  14. package/dist/scripts/runtime-smoke.js.map +1 -0
  15. package/dist/scripts/smoke.d.ts +1 -0
  16. package/dist/scripts/smoke.js +6156 -0
  17. package/dist/scripts/smoke.js.map +1 -0
  18. package/dist/src/core/cache-manifest.d.ts +22 -0
  19. package/dist/src/core/cache-manifest.js +133 -0
  20. package/dist/src/core/cache-manifest.js.map +1 -0
  21. package/dist/src/core/caller-tokens.d.ts +32 -0
  22. package/dist/src/core/caller-tokens.js +240 -0
  23. package/dist/src/core/caller-tokens.js.map +1 -0
  24. package/dist/src/core/config.d.ts +9 -0
  25. package/dist/src/core/config.js +643 -0
  26. package/dist/src/core/config.js.map +1 -0
  27. package/dist/src/core/convergence.d.ts +5 -0
  28. package/dist/src/core/convergence.js +186 -0
  29. package/dist/src/core/convergence.js.map +1 -0
  30. package/dist/src/core/cost.d.ts +59 -0
  31. package/dist/src/core/cost.js +359 -0
  32. package/dist/src/core/cost.js.map +1 -0
  33. package/dist/src/core/file-config.d.ts +316 -0
  34. package/dist/src/core/file-config.js +490 -0
  35. package/dist/src/core/file-config.js.map +1 -0
  36. package/dist/src/core/orchestrator.d.ts +199 -0
  37. package/dist/src/core/orchestrator.js +3430 -0
  38. package/dist/src/core/orchestrator.js.map +1 -0
  39. package/dist/src/core/prompt-parts.d.ts +58 -0
  40. package/dist/src/core/prompt-parts.js +122 -0
  41. package/dist/src/core/prompt-parts.js.map +1 -0
  42. package/dist/src/core/relator-lottery.d.ts +23 -0
  43. package/dist/src/core/relator-lottery.js +112 -0
  44. package/dist/src/core/relator-lottery.js.map +1 -0
  45. package/dist/src/core/reports.d.ts +2 -0
  46. package/dist/src/core/reports.js +82 -0
  47. package/dist/src/core/reports.js.map +1 -0
  48. package/dist/src/core/session-store.d.ts +149 -0
  49. package/dist/src/core/session-store.js +1923 -0
  50. package/dist/src/core/session-store.js.map +1 -0
  51. package/dist/src/core/status.d.ts +61 -0
  52. package/dist/src/core/status.js +249 -0
  53. package/dist/src/core/status.js.map +1 -0
  54. package/dist/src/core/timeouts.d.ts +2 -0
  55. package/dist/src/core/timeouts.js +3 -0
  56. package/dist/src/core/timeouts.js.map +1 -0
  57. package/dist/src/core/types.d.ts +604 -0
  58. package/dist/src/core/types.js +36 -0
  59. package/dist/src/core/types.js.map +1 -0
  60. package/dist/src/dashboard/server.d.ts +2 -0
  61. package/dist/src/dashboard/server.js +339 -0
  62. package/dist/src/dashboard/server.js.map +1 -0
  63. package/dist/src/mcp/server.d.ts +54 -0
  64. package/dist/src/mcp/server.js +1584 -0
  65. package/dist/src/mcp/server.js.map +1 -0
  66. package/dist/src/observability/logger.d.ts +9 -0
  67. package/dist/src/observability/logger.js +24 -0
  68. package/dist/src/observability/logger.js.map +1 -0
  69. package/dist/src/peers/anthropic.d.ts +14 -0
  70. package/dist/src/peers/anthropic.js +290 -0
  71. package/dist/src/peers/anthropic.js.map +1 -0
  72. package/dist/src/peers/base.d.ts +72 -0
  73. package/dist/src/peers/base.js +416 -0
  74. package/dist/src/peers/base.js.map +1 -0
  75. package/dist/src/peers/deepseek.d.ts +12 -0
  76. package/dist/src/peers/deepseek.js +246 -0
  77. package/dist/src/peers/deepseek.js.map +1 -0
  78. package/dist/src/peers/errors.d.ts +2 -0
  79. package/dist/src/peers/errors.js +185 -0
  80. package/dist/src/peers/errors.js.map +1 -0
  81. package/dist/src/peers/gemini.d.ts +13 -0
  82. package/dist/src/peers/gemini.js +215 -0
  83. package/dist/src/peers/gemini.js.map +1 -0
  84. package/dist/src/peers/grok.d.ts +17 -0
  85. package/dist/src/peers/grok.js +346 -0
  86. package/dist/src/peers/grok.js.map +1 -0
  87. package/dist/src/peers/model-selection.d.ts +4 -0
  88. package/dist/src/peers/model-selection.js +260 -0
  89. package/dist/src/peers/model-selection.js.map +1 -0
  90. package/dist/src/peers/openai.d.ts +14 -0
  91. package/dist/src/peers/openai.js +299 -0
  92. package/dist/src/peers/openai.js.map +1 -0
  93. package/dist/src/peers/perplexity.d.ts +18 -0
  94. package/dist/src/peers/perplexity.js +375 -0
  95. package/dist/src/peers/perplexity.js.map +1 -0
  96. package/dist/src/peers/registry.d.ts +3 -0
  97. package/dist/src/peers/registry.js +77 -0
  98. package/dist/src/peers/registry.js.map +1 -0
  99. package/dist/src/peers/retry.d.ts +2 -0
  100. package/dist/src/peers/retry.js +36 -0
  101. package/dist/src/peers/retry.js.map +1 -0
  102. package/dist/src/peers/stub.d.ts +13 -0
  103. package/dist/src/peers/stub.js +344 -0
  104. package/dist/src/peers/stub.js.map +1 -0
  105. package/dist/src/peers/text.d.ts +18 -0
  106. package/dist/src/peers/text.js +39 -0
  107. package/dist/src/peers/text.js.map +1 -0
  108. package/dist/src/security/redact.d.ts +2 -0
  109. package/dist/src/security/redact.js +128 -0
  110. package/dist/src/security/redact.js.map +1 -0
  111. package/docs/api-keys.md +34 -0
  112. package/docs/architecture.md +118 -0
  113. package/docs/caching.md +135 -0
  114. package/docs/costs.md +40 -0
  115. package/docs/evidence-preflight.md +88 -0
  116. package/docs/github-security-baseline.md +32 -0
  117. package/docs/model-selection.md +105 -0
  118. package/docs/reports/cross-review-v2-api-capability-smoke-2026-04-30.md +354 -0
  119. package/docs/reports/cross-review-v2-format-recovery-findings-2026-04-28.md +223 -0
  120. package/docs/reports/cross-review-v2-official-provider-docs-refresh-2026-05-05.md +60 -0
  121. package/docs/reports/cross-review-v2-token-streaming-smoke-2026-04-30.md +119 -0
  122. package/package.json +88 -0
@@ -0,0 +1,344 @@
1
+ import { BasePeerAdapter } from "./base.js";
2
+ const PROVIDERS = {
3
+ codex: "stub-openai",
4
+ claude: "stub-anthropic",
5
+ gemini: "stub-google",
6
+ deepseek: "stub-deepseek",
7
+ // v2.14.0: stub provider id for the Grok adapter so smoke runs
8
+ // without the real xAI API key.
9
+ grok: "stub-xai",
10
+ // v3.0.0: stub provider id for the Perplexity adapter so smoke runs
11
+ // without the real Sonar API key.
12
+ perplexity: "stub-perplexity",
13
+ };
14
+ // v2.5.0 fix (Codex audit P1, 2026-05-03): stub adapters must NEVER attribute
15
+ // real currency to a session. Pre-v2.5.0, the stub passed prompt/text
16
+ // character counts as `usage.input_tokens`/`usage.output_tokens`, which
17
+ // `estimateCost` then multiplied by the configured cost-rate-per-million,
18
+ // producing tens of dollars of phantom spend in `meta.json` and
19
+ // `totals.cost.total_cost`. The fix overrides the cost field on every
20
+ // stub-emitted PeerResult / GenerationResult to a canonical zero-cost
21
+ // estimate tagged `source: "stub"` so downstream FinOps tooling can both
22
+ // (a) ignore the row and (b) audit that no paid provider call ever ran.
23
+ // The token usage shape stays intact so smoke tests that check
24
+ // `usage.total_tokens > 0` continue to pass.
25
+ //
26
+ // Test-only escape hatch: `CROSS_REVIEW_STUB_FORCE_REAL_COST=1` lets
27
+ // the smoke suite exercise budget_exceeded enforcement (which is
28
+ // arithmetically driven by `cost.total_cost`). It MUST NOT be set in
29
+ // any production-like environment; the env-confirmation gate that
30
+ // `CROSS_REVIEW_STUB` already enforces is the upstream guard.
31
+ function shouldForceRealStubCost() {
32
+ return process.env.CROSS_REVIEW_STUB_FORCE_REAL_COST === "1";
33
+ }
34
+ function stubZeroCost() {
35
+ return {
36
+ currency: "USD",
37
+ input_cost: 0,
38
+ output_cost: 0,
39
+ total_cost: 0,
40
+ estimated: false,
41
+ source: "stub",
42
+ };
43
+ }
44
+ export class StubAdapter extends BasePeerAdapter {
45
+ id;
46
+ provider;
47
+ model;
48
+ constructor(config, id, modelOverride) {
49
+ super(config);
50
+ this.id = id;
51
+ this.provider = PROVIDERS[id];
52
+ this.model = modelOverride ?? `stub-${id}`;
53
+ }
54
+ streamStubText(context, phase, text) {
55
+ if (!this.shouldStreamTokens(context))
56
+ return;
57
+ const tokenStream = this.createTokenEventBuffer(context, phase, "stub.chunk");
58
+ for (const delta of text.match(/.{1,32}/gs) ?? []) {
59
+ tokenStream.append(delta);
60
+ }
61
+ tokenStream.complete(text.length);
62
+ }
63
+ async probe() {
64
+ return {
65
+ peer: this.id,
66
+ provider: this.provider,
67
+ model: this.model,
68
+ available: true,
69
+ auth_present: true,
70
+ latency_ms: 0,
71
+ model_selection: this.config.model_selection[this.id],
72
+ message: "Stub enabled by CROSS_REVIEW_STUB=1.",
73
+ };
74
+ }
75
+ async call(prompt, context) {
76
+ context.emit({
77
+ type: "peer.call.started",
78
+ session_id: context.session_id,
79
+ round: context.round,
80
+ peer: this.id,
81
+ message: "stub review",
82
+ });
83
+ if (context.signal?.aborted) {
84
+ throw new Error("AbortError: stub call cancelled");
85
+ }
86
+ if (prompt.includes("FORCE_MODERATION_FAIL_UNRECOVERABLE")) {
87
+ throw new Error("Invalid prompt: prompt flagged by moderation policy.");
88
+ }
89
+ if (prompt.includes("FORCE_MODERATION_FAIL") &&
90
+ !prompt.includes("Compact Moderation-Safe Review")) {
91
+ throw new Error("Invalid prompt: prompt flagged by moderation policy.");
92
+ }
93
+ if (prompt.includes("FORCE_NETWORK_FAIL") && !this.model.includes("fallback")) {
94
+ throw new Error("network fetch failed");
95
+ }
96
+ const text = prompt.includes("Cross Review - Decision Retry")
97
+ ? JSON.stringify({
98
+ status: "READY",
99
+ summary: "Stub completed a full decision retry after an empty response.",
100
+ confidence: "verified",
101
+ evidence_sources: [],
102
+ caller_requests: [],
103
+ follow_ups: [],
104
+ })
105
+ : prompt.includes("Cross Review - Format Recovery")
106
+ ? prompt.includes("FORCE_RECOVERY_FAIL")
107
+ ? "Still no machine-readable status."
108
+ : JSON.stringify({
109
+ status: "READY",
110
+ summary: "Stub recovered the previous unparseable response.",
111
+ confidence: "verified",
112
+ evidence_sources: [],
113
+ caller_requests: [],
114
+ follow_ups: [],
115
+ })
116
+ : prompt.includes("FORCE_BAD_FORMAT_UNRECOVERABLE")
117
+ ? "I am READY, but this intentionally lacks JSON. FORCE_RECOVERY_FAIL"
118
+ : prompt.includes("FORCE_EMPTY_REVIEW")
119
+ ? ""
120
+ : prompt.includes("FORCE_BAD_FORMAT")
121
+ ? "I am READY, but this intentionally lacks the required machine-readable status object."
122
+ : prompt.includes("FORCE_NOT_READY") || prompt.includes("FORCE_NEEDS_EVIDENCE")
123
+ ? JSON.stringify({
124
+ status: prompt.includes("FORCE_NEEDS_EVIDENCE")
125
+ ? "NEEDS_EVIDENCE"
126
+ : "NOT_READY",
127
+ summary: "Stub detected a test marker.",
128
+ confidence: "verified",
129
+ evidence_sources: [],
130
+ caller_requests: ["Remove the test marker."],
131
+ follow_ups: [],
132
+ })
133
+ : prompt.includes("FORCE_CANCEL_SLOW")
134
+ ? await new Promise((resolve, reject) => {
135
+ const timer = setTimeout(() => resolve(JSON.stringify({
136
+ status: "READY",
137
+ summary: "Stub completed after a cancellable delay.",
138
+ confidence: "verified",
139
+ evidence_sources: [],
140
+ caller_requests: [],
141
+ follow_ups: [],
142
+ })), 10_000);
143
+ context.signal?.addEventListener("abort", () => {
144
+ clearTimeout(timer);
145
+ reject(new Error("AbortError: stub call cancelled"));
146
+ }, { once: true });
147
+ })
148
+ : JSON.stringify({
149
+ status: "READY",
150
+ summary: "Stub approved the test round.",
151
+ confidence: "verified",
152
+ evidence_sources: [],
153
+ caller_requests: [],
154
+ follow_ups: [],
155
+ });
156
+ this.streamStubText(context, "review", text);
157
+ return {
158
+ ...this.resultFromText({
159
+ text,
160
+ raw: { stub: true },
161
+ usage: {
162
+ input_tokens: prompt.length,
163
+ output_tokens: text.length,
164
+ total_tokens: prompt.length + text.length,
165
+ },
166
+ started: Date.now(),
167
+ attempts: 1,
168
+ modelReported: process.env.CROSS_REVIEW_STUB_REPORTED_MODEL,
169
+ }),
170
+ ...(shouldForceRealStubCost() ? {} : { cost: stubZeroCost() }),
171
+ };
172
+ }
173
+ async generate(prompt, context) {
174
+ context.emit({
175
+ type: "peer.generate.started",
176
+ session_id: context.session_id,
177
+ round: context.round,
178
+ peer: this.id,
179
+ message: "stub generation",
180
+ });
181
+ // v2.5.0: propagate FORCE_* test markers from the input prompt into
182
+ // the generated draft. Pre-v2.5.0 the stub took a 1200-char slice of
183
+ // the prompt; once cross-review grew per-round prompt headers
184
+ // (review focus, session-start contract directives, etc.) the
185
+ // FORCE_* markers buried in `## Previous Version` fell out of the
186
+ // 1200-char window, breaking smoke tests that rely on multi-round
187
+ // marker continuity (e.g. budget-exceeded test driving claude with
188
+ // FORCE_NOT_READY across 3 rounds). The slice still drives the
189
+ // synthetic body for inspection; the marker preamble guarantees
190
+ // semantic continuity.
191
+ const FORCE_MARKERS = [
192
+ "FORCE_BAD_FORMAT_UNRECOVERABLE",
193
+ "FORCE_MODERATION_FAIL_UNRECOVERABLE",
194
+ "FORCE_BAD_FORMAT",
195
+ "FORCE_MODERATION_FAIL",
196
+ "FORCE_NETWORK_FAIL",
197
+ "FORCE_NEEDS_EVIDENCE",
198
+ "FORCE_NOT_READY",
199
+ "FORCE_RECOVERY_FAIL",
200
+ "FORCE_EMPTY_REVIEW",
201
+ "FORCE_CANCEL_SLOW",
202
+ // v2.13.0: drift simulation. Three shapes covered (resolution
203
+ // path: keyword-prefix → JSON-prefix → markdown-fenced JSON,
204
+ // matching ship-review R1 → R2 → R-fix2 evolution):
205
+ // FORCE_DRIFT → keyword-prefix drift (`NEEDS_EVIDENCE\n...`)
206
+ // FORCE_DRIFT_JSON → raw JSON drift (`{"status":"NEEDS_EVIDENCE",...}`)
207
+ // FORCE_DRIFT_MD → markdown-wrapped JSON drift (` ```json\n{...}\n``` `)
208
+ // FORCE_DRIFT_MD takes precedence over FORCE_DRIFT_JSON, which
209
+ // takes precedence over FORCE_DRIFT.
210
+ "FORCE_DRIFT_MD",
211
+ "FORCE_DRIFT_JSON",
212
+ "FORCE_DRIFT",
213
+ ];
214
+ const carriedMarkers = FORCE_MARKERS.filter((marker) => prompt.includes(marker));
215
+ let driftPrefix = [];
216
+ if (prompt.includes("FORCE_DRIFT_MD")) {
217
+ driftPrefix = [
218
+ "```json",
219
+ '{"status":"NEEDS_EVIDENCE","summary":"Stub-forced markdown-fenced JSON drift; orchestrator should detect this via PATTERN_STATUS_FIELD.","caller_requests":[]}',
220
+ "```",
221
+ "",
222
+ ];
223
+ }
224
+ else if (prompt.includes("FORCE_DRIFT_JSON")) {
225
+ driftPrefix = [
226
+ '{"status":"NEEDS_EVIDENCE","summary":"Stub-forced JSON-shape meta-review drift; orchestrator should detect this via PATTERN_STATUS_FIELD.","caller_requests":[]}',
227
+ "",
228
+ ];
229
+ }
230
+ else if (prompt.includes("FORCE_DRIFT")) {
231
+ driftPrefix = [
232
+ "NEEDS_EVIDENCE",
233
+ "",
234
+ "summary: Stub-forced meta-review drift; orchestrator should detect this and preserve prior draft.",
235
+ "",
236
+ ];
237
+ }
238
+ const text = [
239
+ ...driftPrefix,
240
+ ...(driftPrefix.length ? [] : ["# Test Draft", ""]),
241
+ "This text was generated by the stub only because CROSS_REVIEW_STUB=1 is active.",
242
+ "",
243
+ ...(carriedMarkers.length ? [carriedMarkers.join(" "), ""] : []),
244
+ prompt.slice(0, 1200),
245
+ ].join("\n");
246
+ this.streamStubText(context, "generation", text);
247
+ return {
248
+ ...this.generationFromText({
249
+ text,
250
+ raw: { stub: true },
251
+ usage: {
252
+ input_tokens: prompt.length,
253
+ output_tokens: text.length,
254
+ total_tokens: prompt.length + text.length,
255
+ },
256
+ started: Date.now(),
257
+ attempts: 1,
258
+ modelReported: process.env.CROSS_REVIEW_STUB_REPORTED_MODEL,
259
+ }),
260
+ ...(shouldForceRealStubCost() ? {} : { cost: stubZeroCost() }),
261
+ };
262
+ }
263
+ // v2.9.0: deterministic judge response driven by FORCE_JUDGE_* markers
264
+ // in the draft (NOT the prompt; the prompt is built by BasePeerAdapter
265
+ // and includes the ask too). Markers:
266
+ // FORCE_JUDGE_SATISFIED → satisfied=true, confidence=verified
267
+ // FORCE_JUDGE_INFERRED → satisfied=true, confidence=inferred (NOT promoted by runtime)
268
+ // FORCE_JUDGE_UNKNOWN → satisfied=false, confidence=unknown
269
+ // FORCE_JUDGE_PARSE_FAIL → invalid JSON returned (parser_warnings populated)
270
+ // default → satisfied=false, confidence=verified
271
+ async judgeEvidenceAsk(ask, draft, context) {
272
+ void ask;
273
+ context.emit({
274
+ type: "peer.judge.started",
275
+ session_id: context.session_id,
276
+ round: context.round,
277
+ peer: this.id,
278
+ message: "stub judge",
279
+ });
280
+ const started = Date.now();
281
+ let payload;
282
+ if (draft.includes("FORCE_JUDGE_PARSE_FAIL")) {
283
+ // Bypass the JSON parser by emitting plain prose.
284
+ const generation = {
285
+ peer: this.id,
286
+ provider: this.provider,
287
+ model: this.model,
288
+ text: "stub: this response intentionally lacks a JSON object",
289
+ raw: { stub: true },
290
+ usage: { input_tokens: ask.length, output_tokens: 60, total_tokens: ask.length + 60 },
291
+ cost: shouldForceRealStubCost() ? undefined : stubZeroCost(),
292
+ latency_ms: Date.now() - started,
293
+ attempts: 1,
294
+ };
295
+ return this.parseJudgeResponse(generation, draft.length);
296
+ }
297
+ if (draft.includes("FORCE_JUDGE_SATISFIED")) {
298
+ payload = {
299
+ satisfied: true,
300
+ confidence: "verified",
301
+ rationale: "Stub judge: draft contains FORCE_JUDGE_SATISFIED marker.",
302
+ };
303
+ }
304
+ else if (draft.includes("FORCE_JUDGE_INFERRED")) {
305
+ payload = {
306
+ satisfied: true,
307
+ confidence: "inferred",
308
+ rationale: "Stub judge: draft hints at satisfaction but evidence is indirect.",
309
+ };
310
+ }
311
+ else if (draft.includes("FORCE_JUDGE_UNKNOWN")) {
312
+ payload = {
313
+ satisfied: false,
314
+ confidence: "unknown",
315
+ rationale: "Stub judge: cannot determine whether the draft satisfies the ask.",
316
+ };
317
+ }
318
+ else {
319
+ payload = {
320
+ satisfied: false,
321
+ confidence: "verified",
322
+ rationale: "Stub judge default: no FORCE_JUDGE_* marker; treating as not satisfied.",
323
+ };
324
+ }
325
+ const text = JSON.stringify(payload);
326
+ const generation = {
327
+ peer: this.id,
328
+ provider: this.provider,
329
+ model: this.model,
330
+ text,
331
+ raw: { stub: true, payload },
332
+ usage: {
333
+ input_tokens: ask.length,
334
+ output_tokens: text.length,
335
+ total_tokens: ask.length + text.length,
336
+ },
337
+ cost: shouldForceRealStubCost() ? undefined : stubZeroCost(),
338
+ latency_ms: Date.now() - started,
339
+ attempts: 1,
340
+ };
341
+ return this.parseJudgeResponse(generation, draft.length);
342
+ }
343
+ }
344
+ //# sourceMappingURL=stub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stub.js","sourceRoot":"","sources":["../../../src/peers/stub.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export declare function compactJson(value: unknown): string;
2
+ export interface AnthropicParseResult {
3
+ text: string;
4
+ parser_warning?: string;
5
+ }
6
+ export declare function parseAnthropicContent(content: Array<{
7
+ type: string;
8
+ text?: string;
9
+ }>): AnthropicParseResult;
10
+ export declare function textFromAnthropicContent(content: Array<{
11
+ type: string;
12
+ text?: string;
13
+ }>): string;
14
+ export declare function textFromOpenAIResponse(response: {
15
+ output_text?: string;
16
+ output?: unknown;
17
+ }): string;
18
+ export declare function userPrompt(reviewPrompt: string): string;
@@ -0,0 +1,39 @@
1
+ export function compactJson(value) {
2
+ return JSON.stringify(value, null, 2);
3
+ }
4
+ const ANTHROPIC_THINKING_BLOCK_TYPES = new Set(["thinking", "redacted_thinking"]);
5
+ export function parseAnthropicContent(content) {
6
+ const textBlocks = content.filter((block) => block.type === "text" && typeof block.text === "string");
7
+ const text = textBlocks
8
+ .map((block) => block.text)
9
+ .filter(Boolean)
10
+ .join("\n")
11
+ .trim();
12
+ if (text === "" && content.length > 0) {
13
+ const hasThinking = content.some((block) => ANTHROPIC_THINKING_BLOCK_TYPES.has(block.type));
14
+ return {
15
+ text: "",
16
+ parser_warning: hasThinking
17
+ ? "anthropic_thinking_only_no_text_block"
18
+ : "anthropic_empty_text_blocks",
19
+ };
20
+ }
21
+ return { text };
22
+ }
23
+ // Thin backward-compatibility shim — discards the parser_warning. New code
24
+ // SHOULD call `parseAnthropicContent` directly so the warning can flow to
25
+ // `PeerResult.parser_warnings` / `GenerationResult.parser_warnings` and
26
+ // (for the relator-revision path) block promotion of an empty draft.
27
+ export function textFromAnthropicContent(content) {
28
+ return parseAnthropicContent(content).text;
29
+ }
30
+ export function textFromOpenAIResponse(response) {
31
+ if (typeof response.output_text === "string" && response.output_text.trim()) {
32
+ return response.output_text.trim();
33
+ }
34
+ return compactJson(response.output ?? response);
35
+ }
36
+ export function userPrompt(reviewPrompt) {
37
+ return reviewPrompt.trim();
38
+ }
39
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../../src/peers/text.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACxC,CAAC;AAkBD,MAAM,8BAA8B,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC,CAAC;AAElF,MAAM,UAAU,qBAAqB,CACnC,OAA+C;IAE/C,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAC/B,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CACnE,CAAC;IACF,MAAM,IAAI,GAAG,UAAU;SACpB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAc,CAAC;SACpC,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,IAAI,CAAC;SACV,IAAI,EAAE,CAAC;IACV,IAAI,IAAI,KAAK,EAAE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtC,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,8BAA8B,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5F,OAAO;YACL,IAAI,EAAE,EAAE;YACR,cAAc,EAAE,WAAW;gBACzB,CAAC,CAAC,uCAAuC;gBACzC,CAAC,CAAC,6BAA6B;SAClC,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,CAAC;AAClB,CAAC;AAED,2EAA2E;AAC3E,0EAA0E;AAC1E,wEAAwE;AACxE,qEAAqE;AACrE,MAAM,UAAU,wBAAwB,CAAC,OAA+C;IACtF,OAAO,qBAAqB,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,QAGtC;IACC,IAAI,OAAO,QAAQ,CAAC,WAAW,KAAK,QAAQ,IAAI,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC;QAC5E,OAAO,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;IACrC,CAAC;IACD,OAAO,WAAW,CAAC,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,YAAoB;IAC7C,OAAO,YAAY,CAAC,IAAI,EAAE,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function redact(value: string): string;
2
+ export declare function safeErrorMessage(error: unknown): string;
@@ -0,0 +1,128 @@
1
+ const SECRET_PATTERNS = [
2
+ /sk-[A-Za-z0-9_-]{20,}/g,
3
+ /sk-ant-[A-Za-z0-9_-]{20,}/g,
4
+ /AIza[A-Za-z0-9_-]{20,}/g,
5
+ /cfut_[A-Za-z0-9_-]{30,}/g,
6
+ /gh[pousr]_[A-Za-z0-9]{30,}/g,
7
+ /github_pat_[A-Za-z0-9_]{20,}/g,
8
+ /npm_[A-Za-z0-9]{30,}/g,
9
+ /re_[A-Za-z0-9_]{30,}/g,
10
+ /xox[baprs]-[A-Za-z0-9-]{20,}/g,
11
+ // v2.18.4 / Codex audit 2026-05-07 P1.2: xAI API keys have prefix
12
+ // `xai-` and were not previously covered. Logs and session payloads
13
+ // can persist provider error messages or environment dumps that
14
+ // include the key, so adding this pattern closes a credential leak
15
+ // surface at parity with sk-/sk-ant-/AIza/etc.
16
+ /xai-[A-Za-z0-9_-]{20,}/g,
17
+ /AKIA[A-Z0-9]{16}/g,
18
+ /Bearer\s+[A-Za-z0-9._-]{20,}/gi,
19
+ /[A-Za-z0-9_-]{32,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}/g,
20
+ // v2.4.0 / audit closure: env-style assignments. Catches `PASSWORD=value`
21
+ // / `API_KEY="value"` / `SECRET: value` / `Authorization: token` shapes
22
+ // that providers, smoke fixtures or stack traces sometimes echo back.
23
+ // The replacement preserves the key name so audit consumers see WHICH
24
+ // var was redacted, only the value is replaced. Mirrors the pattern in
25
+ // v1's `REDACTION_PATTERNS`.
26
+ // v2.25.1 (2026-05-11): exclude `\` from value char class. Without the
27
+ // exclusion the {6,} quantifier would consume the JSON-escape backslash
28
+ // in `token: write\"` (a peer-response string that survived round-1
29
+ // serialization), replace `write\` → `[REDACTED]`, and leave a bare `"`
30
+ // that closes the outer JSON string prematurely → corrupt meta.json.
31
+ // Empirically observed in 3 sessions today (be47a5b0, 77c47284, 7edf63e3)
32
+ // when the scorecard hotfix peer responses quoted `id-token: write` in
33
+ // backtick-fenced YAML excerpts. Excluding `\` keeps the regex from
34
+ // crossing JSON-escape boundaries.
35
+ /\b((?:password|passwd|api[_-]?key|secret|token|access[_-]?key|auth(?:orization)?|bearer|private[_-]?key)\s*[:=]\s*["']?)([^\s"',}\\]{6,})/gi,
36
+ ];
37
+ const PRIVATE_KEY_LABELS = [
38
+ "PRIVATE KEY",
39
+ "OPENSSH PRIVATE KEY",
40
+ "EC PRIVATE KEY",
41
+ "RSA PRIVATE KEY",
42
+ "DSA PRIVATE KEY",
43
+ ];
44
+ const PRIVATE_KEY_BEGIN_MARKERS = PRIVATE_KEY_LABELS.map((label) => `-----BEGIN ${label}-----`);
45
+ const PRIVATE_KEY_END_MARKERS = PRIVATE_KEY_LABELS.map((label) => `-----END ${label}-----`);
46
+ function findNextMarker(value, markers, fromIndex) {
47
+ let found;
48
+ for (const marker of markers) {
49
+ const index = value.indexOf(marker, fromIndex);
50
+ if (index !== -1 && (!found || index < found.index)) {
51
+ found = { index, marker };
52
+ }
53
+ }
54
+ return found;
55
+ }
56
+ function findNextPrivateKeyMarker(value, fromIndex) {
57
+ const begin = findNextMarker(value, PRIVATE_KEY_BEGIN_MARKERS, fromIndex);
58
+ const end = findNextMarker(value, PRIVATE_KEY_END_MARKERS, fromIndex);
59
+ if (!begin)
60
+ return end ? { ...end, side: "END" } : undefined;
61
+ if (!end)
62
+ return { ...begin, side: "BEGIN" };
63
+ return begin.index <= end.index ? { ...begin, side: "BEGIN" } : { ...end, side: "END" };
64
+ }
65
+ function redactPrivateKeyBlocks(value) {
66
+ let cursor = 0;
67
+ let parts;
68
+ while (cursor < value.length) {
69
+ const begin = findNextMarker(value, PRIVATE_KEY_BEGIN_MARKERS, cursor);
70
+ if (!begin)
71
+ break;
72
+ let depth = 1;
73
+ let scan = begin.index + begin.marker.length;
74
+ let close;
75
+ while (scan < value.length) {
76
+ const marker = findNextPrivateKeyMarker(value, scan);
77
+ if (!marker)
78
+ break;
79
+ scan = marker.index + marker.marker.length;
80
+ if (marker.side === "BEGIN") {
81
+ depth += 1;
82
+ continue;
83
+ }
84
+ depth -= 1;
85
+ if (depth === 0) {
86
+ close = marker;
87
+ break;
88
+ }
89
+ }
90
+ if (!close) {
91
+ break;
92
+ }
93
+ parts ??= [];
94
+ parts.push(value.slice(cursor, begin.index), "[REDACTED]");
95
+ cursor = close.index + close.marker.length;
96
+ }
97
+ if (!parts)
98
+ return value;
99
+ parts.push(value.slice(cursor));
100
+ return parts.join("");
101
+ }
102
+ export function redact(value) {
103
+ let output = redactPrivateKeyBlocks(value);
104
+ for (const re of SECRET_PATTERNS) {
105
+ // The env-style assignment pattern uses two capture groups so that
106
+ // the key name is preserved; the standalone-token patterns do not
107
+ // capture and we replace the whole match. We dispatch on the regex
108
+ // shape (`re.source.includes("(")`) but the safer signal is the
109
+ // number of groups we declared — both env-style and JWT use groups,
110
+ // but only the env-style declares two ((key)(value)). For the JWT
111
+ // pattern we still replace the whole match because there is no key
112
+ // half to preserve.
113
+ output = output.replace(re, (...args) => {
114
+ const groups = args.slice(1, -2).filter((g) => typeof g === "string");
115
+ if (groups.length >= 2) {
116
+ return `${groups[0]}[REDACTED]`;
117
+ }
118
+ return "[REDACTED]";
119
+ });
120
+ }
121
+ return output;
122
+ }
123
+ export function safeErrorMessage(error) {
124
+ if (error instanceof Error)
125
+ return redact(error.message);
126
+ return redact(String(error));
127
+ }
128
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../../../src/security/redact.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ # API Keys
2
+
3
+ All runtime credentials must come from Windows environment variables.
4
+
5
+ ## Required Variables
6
+
7
+ ```powershell
8
+ [Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "<OPENAI_API_KEY>", "User")
9
+ [Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", "<ANTHROPIC_API_KEY>", "User")
10
+ [Environment]::SetEnvironmentVariable("GEMINI_API_KEY", "<GEMINI_API_KEY>", "User")
11
+ [Environment]::SetEnvironmentVariable("DEEPSEEK_API_KEY", "<DEEPSEEK_API_KEY>", "User")
12
+ ```
13
+
14
+ Restart any terminal, editor, app or MCP host after changing these variables.
15
+
16
+ ## Optional Model Overrides
17
+
18
+ Use overrides only when you intentionally want to pin a model rather than use automatic best-model selection.
19
+
20
+ ```powershell
21
+ [Environment]::SetEnvironmentVariable("CROSS_REVIEW_OPENAI_MODEL", "gpt-5.5", "User")
22
+ [Environment]::SetEnvironmentVariable("CROSS_REVIEW_OPENAI_REASONING_EFFORT", "xhigh", "User")
23
+ [Environment]::SetEnvironmentVariable("CROSS_REVIEW_ANTHROPIC_MODEL", "claude-opus-4-7", "User")
24
+ [Environment]::SetEnvironmentVariable("CROSS_REVIEW_ANTHROPIC_REASONING_EFFORT", "xhigh", "User")
25
+ [Environment]::SetEnvironmentVariable("CROSS_REVIEW_GEMINI_MODEL", "gemini-2.5-pro", "User")
26
+ [Environment]::SetEnvironmentVariable("CROSS_REVIEW_DEEPSEEK_MODEL", "deepseek-v4-pro", "User")
27
+ [Environment]::SetEnvironmentVariable("CROSS_REVIEW_DEEPSEEK_REASONING_EFFORT", "max", "User")
28
+ ```
29
+
30
+ ## Safety
31
+
32
+ - Do not create `.env` files containing real secrets.
33
+ - Do not paste keys into prompts, issues, logs, screenshots or README files.
34
+ - If a key is accidentally committed, revoke it immediately and rotate it at the provider.