@lcv-ideas-software/cross-review 4.0.1 → 4.0.3

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 (37) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/README.md +6 -0
  3. package/SECURITY.md +1 -1
  4. package/dist/scripts/runtime-default-smoke.js.map +1 -1
  5. package/dist/scripts/smoke.js +30 -6
  6. package/dist/scripts/smoke.js.map +1 -1
  7. package/dist/src/core/caller-tokens.js +1 -1
  8. package/dist/src/core/caller-tokens.js.map +1 -1
  9. package/dist/src/core/config.d.ts +1 -1
  10. package/dist/src/core/config.js +3 -3
  11. package/dist/src/core/config.js.map +1 -1
  12. package/dist/src/core/orchestrator.d.ts +1 -1
  13. package/dist/src/core/orchestrator.js +7 -7
  14. package/dist/src/core/orchestrator.js.map +1 -1
  15. package/dist/src/core/relator-lottery.js.map +1 -1
  16. package/dist/src/core/session-store.d.ts +1 -1
  17. package/dist/src/core/session-store.js +2 -2
  18. package/dist/src/core/session-store.js.map +1 -1
  19. package/dist/src/mcp/server.js +2 -2
  20. package/dist/src/mcp/server.js.map +1 -1
  21. package/dist/src/observability/logger.d.ts +1 -1
  22. package/dist/src/observability/logger.js.map +1 -1
  23. package/dist/src/peers/anthropic.js.map +1 -1
  24. package/dist/src/peers/base.js.map +1 -1
  25. package/dist/src/peers/deepseek.js.map +1 -1
  26. package/dist/src/peers/gemini.js.map +1 -1
  27. package/dist/src/peers/grok.js +1 -1
  28. package/dist/src/peers/grok.js.map +1 -1
  29. package/dist/src/peers/model-selection.js +6 -6
  30. package/dist/src/peers/model-selection.js.map +1 -1
  31. package/dist/src/peers/openai.js +1 -1
  32. package/dist/src/peers/openai.js.map +1 -1
  33. package/dist/src/peers/perplexity.js +0 -4
  34. package/dist/src/peers/perplexity.js.map +1 -1
  35. package/docs/architecture.md +6 -2
  36. package/docs/model-selection.md +20 -11
  37. package/package.json +6 -2
package/CHANGELOG.md CHANGED
@@ -7,6 +7,117 @@ standard `v00.00.00`; npm package versions remain SemVer.
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [v04.00.03] — 2026-05-15
11
+
12
+ **Patch — biome integration to satisfy the 4-gate quality directive
13
+ (operator 2026-05-15: eslint + biome + prettier + cross-review).** The
14
+ repo had eslint + prettier covering the static gates but lacked biome.
15
+ This release adds biome at parity with the 8 other workspace apps that
16
+ already use it (admin-app, astrologo-frontend, calculadora-app,
17
+ mainsite-frontend, mainsite-worker, mtasts-motor, oraculo-financeiro,
18
+ sponsor-motor).
19
+
20
+ ### Added
21
+
22
+ - `@biomejs/biome` (^2.4.0) devDep + `biome.json` config matching the
23
+ prettier conventions already in use (lineWidth 100, indent space 2,
24
+ double quotes, trailing commas all, semicolons always). Linter
25
+ enabled with `recommended` rules.
26
+ - `npm run biome` (check-only) + `npm run biome:write` (with --write
27
+ auto-fix) scripts scoped to `src/` and `scripts/`.
28
+ - `npm run check` aggregate script that runs all 4 statics:
29
+ `format:check && lint && biome && typecheck`. Single-command gate
30
+ for local + CI use.
31
+
32
+ ### Updated
33
+
34
+ - `.github/workflows/ci.yml` adds an explicit `Biome (lint + format)`
35
+ step between `Lint (eslint)` and `Typecheck` for granular per-gate
36
+ visibility in CI logs.
37
+ - `.github/workflows/publish.yml` adds a `Pre-publish gate (format +
38
+ lint + biome + typecheck)` step calling `npm run check` before the
39
+ existing `npm test` verify step. Defense-in-depth: the publish
40
+ runner re-verifies the 4 statics independent of the CI workflow.
41
+
42
+ ### Fixed (biome --write --unsafe applied)
43
+
44
+ - `scripts/smoke.ts`: 5 `lint/style/useTemplate` (prefer template
45
+ literals) + 1 `lint/correctness/noEmptyCharacterClassInRegex`
46
+ (the regex `[^]*?` flagged as negated-empty class → replaced with
47
+ `[\s\S]*?` which is semantically identical and lint-clean).
48
+ - `src/peers/perplexity.ts`: 4 `lint/complexity/noUselessSwitchCase`
49
+ (collapsed cases that fell through to the same return).
50
+ - `src/core/caller-tokens.ts`: 1 `lint/complexity/useOptionalChain`.
51
+ - `src/observability/logger.ts`: 1
52
+ `lint/correctness/noUnusedPrivateClassMembers`.
53
+ - 15 files received import-reorder auto-fixes (mostly grouping
54
+ `import type` vs `import` and sort order). Zero behavioral
55
+ change — typecheck + smoke + npm test all green post-fix.
56
+
57
+ ## [v04.00.02] — 2026-05-15
58
+
59
+ **Patch — Codex second-pass audit close-out (6 findings).** v4.0.1 closed 8
60
+ findings from the first Codex parecer; this v4.0.2 closes 6 additional
61
+ items the second parecer flagged. None affects runtime semantics.
62
+
63
+ ### Fixed
64
+
65
+ - **AUDIT-1 (MEDIUM) — lockfile version drift.** v4.0.1 bumped
66
+ `package.json` to `4.0.1` but the lockfile root `.version` and
67
+ `.packages[""].version` stayed at `4.0.0` (the `npm install` ran
68
+ during v4.0.0 captured deps, then the version bump didn't trigger a
69
+ re-resolve). Full reinstall this release brings the lockfile back to
70
+ the current `package.json` version. **Plus: new anti-drift smoke
71
+ marker `package_version_consistency_test`** asserts the four
72
+ equalities `pkg.name === pkg-lock.name`, `pkg-lock.name ===
73
+ "@lcv-ideas-software/cross-review"`, `pkg.version === pkg-lock.version`,
74
+ `pkg.version === pkg-lock.packages[""].version`. Any future version
75
+ bump that forgets the `npm install` re-resolve fails smoke loudly
76
+ instead of slipping through to publish.
77
+ - **AUDIT-2 (MEDIUM) — model-selection wording aligned with no-fallback
78
+ policy.** Both `docs/model-selection.md` and `src/peers/model-selection.ts`
79
+ were still describing "automatic model selection", "priority list",
80
+ and "documented fallback" — terms from the pre-v3.7.2 multi-model era.
81
+ The runtime has used canonical pins (one model per peer, no auto-chain)
82
+ since v3.7.2 (operator directive "sem fallback é sem fallback").
83
+ Updated docs + the `selected/candidates/reason` messages in
84
+ `selectFromCandidates`, `overrideSelection`, and the no-API-key
85
+ path to use canonical-pin language consistently. The `confidence`
86
+ classification logic is unchanged.
87
+ - **AUDIT-3 (LOW) — SECURITY.md stub wording.** The doc said
88
+ `CROSS_REVIEW_STUB=1` alone is "ignored"; the code at
89
+ `src/peers/registry.ts:36-44` actually throws an explicit error
90
+ referencing the missing confirmation flag. Updated wording to
91
+ "rejected fail-fast" to match runtime behavior.
92
+ - **AUDIT-4 (LOW) — README MCP tools list completed.** The README
93
+ Section listing the available tools had 22 entries; the runtime
94
+ exposes 28. Added the 6 missing tools:
95
+ `session_evidence_checklist_update`,
96
+ `session_evidence_judge_pass`,
97
+ `session_evidence_judge_consensus_pass`,
98
+ `session_judgment_precision_report`,
99
+ `contest_verdict`,
100
+ `regenerate_caller_tokens`.
101
+ - **AUDIT-5 (LOW) — `docs/architecture.md` rename event.** The "Stable
102
+ Rename" section said the rename to `cross-review` happened at
103
+ `2.1.0`; the actual event is v4.0.0 on 2026-05-15 (this rename ship).
104
+ Rewrote the section to record v4.0.0 as the rename event and note
105
+ that prior names live only in dated changelog and memory.
106
+ - **AUDIT-6 (MEDIUM) — StepSecurity post-rename detections triaged.**
107
+ Two new org-level suppression rules created:
108
+ (a) `Source-Code-Overwritten` for `*/dist/*` in
109
+ `.github/workflows/publish.yml` under the `Pre-publish gate (test +
110
+ metadata)` job for repo `cross-review` (the prior cross-review-v2
111
+ rules no longer match after rename); (b)
112
+ `Action-Uses-Commit-From-Non-Default-Branch` for
113
+ `github/codeql-action*` (GitHub's release-branch model — tagged
114
+ versions cut from `releases/vN` not from default branch are
115
+ intended/audited by GitHub Security; rejecting would force less
116
+ secure tag-based usage). Plus 20 existing detections (19
117
+ Source-Code-Overwritten dist/\* + 1 codeql-action SHA) suppressed
118
+ per-detection via `update_detection_status` with rule-id citations
119
+ in the suppress reason.
120
+
10
121
  ## [v04.00.01] — 2026-05-15
11
122
 
12
123
  **Patch — close-out of post-v4.0.0 audit (eight surfaces left stale by the
package/README.md CHANGED
@@ -193,7 +193,13 @@ these environment variables before running real sessions (example):
193
193
  - `session_report`
194
194
  - `session_check_convergence`
195
195
  - `session_attach_evidence`
196
+ - `session_evidence_checklist_update`
197
+ - `session_evidence_judge_pass`
198
+ - `session_evidence_judge_consensus_pass`
199
+ - `session_judgment_precision_report`
200
+ - `contest_verdict`
196
201
  - `escalate_to_operator`
202
+ - `regenerate_caller_tokens`
197
203
  - `session_sweep`
198
204
  - `session_finalize`
199
205
 
package/SECURITY.md CHANGED
@@ -37,7 +37,7 @@ This repository employs:
37
37
 
38
38
  - **Multi-host concurrency.** Running two MCP host instances of `cross-review` against the same `CROSS_REVIEW_DATA_DIR` is **not supported**. The per-session lock has TTL + PID-liveness fallbacks that close the common cases but leave a narrow TOCTOU window when two hosts contend for the same session. If you need multi-host operation, point each instance at a distinct `CROSS_REVIEW_DATA_DIR` (introduced in v2.0; tilde expansion since v2.4.0) or share one host across all clients.
39
39
  - **API keys in memory.** `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `DEEPSEEK_API_KEY` are loaded into `AppConfig.api_keys` at boot. The persistence layer redacts secrets via [`redact()`](./src/security/redact.ts) before any meta.json/event log write, but the in-memory object is not opaque — do not log `config` directly. Stack traces from SDK errors are passed through `safeErrorMessage()` which redacts known key shapes.
40
- - **Stub adapters.** `CROSS_REVIEW_STUB=1` alone is **ignored** since v2.4.0. To activate stubs, also set `NODE_ENV=test` OR `CROSS_REVIEW_STUB_CONFIRMED=1`. This double-confirmation prevents a stray dotenv variable from invalidating a cross-review used as a pre-commit gate.
40
+ - **Stub adapters.** `CROSS_REVIEW_STUB=1` alone is **rejected fail-fast** since v2.4.0 — boot throws an explicit error referencing the missing confirmation flag, rather than silently demoting to real adapters. To deliberately activate stubs, set BOTH `CROSS_REVIEW_STUB=1` AND one of `NODE_ENV=test` OR `CROSS_REVIEW_STUB_CONFIRMED=1`. The double-confirmation prevents a stray dotenv variable from invalidating a cross-review used as a pre-commit gate.
41
41
  - **Dashboard HTTP.** The dashboard binds only to `127.0.0.1`. There is no authentication or rate-limit; same-machine processes can read all session metadata, costs and report markdown. Do not expose the dashboard port over a network without an authenticating reverse proxy.
42
42
  - **Untrusted callers.** The MCP `tools/list` schemas enforce per-field caps (`maxLength`, `pattern`) since v2.4.0 to defend against memory-exhaustion attempts via oversized `task`/`draft`/`prompt`. The trust boundary still assumes a cooperative caller — do not expose the stdio transport over a network socket without an authenticating proxy.
43
43
  - **Untrusted peers.** Peer streaming responses are capped at 16 MiB per call (`STREAM_TEXT_MAX_BYTES` since v2.4.0). The structured `<cross_review_status>` payload is rejected as malformed when it exceeds 64 KiB before `JSON.parse` runs.
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-default-smoke.js","sourceRoot":"","sources":["../../scripts/runtime-default-smoke.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime-default-smoke.js","sourceRoot":"","sources":["../../scripts/runtime-default-smoke.ts"],"names":[],"mappings":"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"}
@@ -3842,26 +3842,26 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
3842
3842
  assert.equal(stripPerplexityThinkingBlock(cleanJson), cleanJson, "clean JSON without <think> blocks must be returned unchanged");
3843
3843
  // Scenario B: single `<think>` block followed by JSON — strip block,
3844
3844
  // trim whitespace, return JSON.
3845
- const singleThink = "<think>\nLet me reason about this carefully.\nThe evidence shows...\n</think>\n\n" + cleanJson;
3845
+ const singleThink = `<think>\nLet me reason about this carefully.\nThe evidence shows...\n</think>\n\n${cleanJson}`;
3846
3846
  assert.equal(stripPerplexityThinkingBlock(singleThink), cleanJson, "single <think> block must be stripped; trailing JSON preserved");
3847
3847
  // Scenario C: multiple `<think>` blocks (rare but legal) — every
3848
3848
  // occurrence must be removed.
3849
- const multipleThinks = "<think>first thought</think>\nintermediate text\n<think>second thought</think>\n" + cleanJson;
3849
+ const multipleThinks = `<think>first thought</think>\nintermediate text\n<think>second thought</think>\n${cleanJson}`;
3850
3850
  const strippedMultiple = stripPerplexityThinkingBlock(multipleThinks);
3851
3851
  assert.ok(!/<think/i.test(strippedMultiple), "multiple <think> blocks must all be stripped");
3852
3852
  assert.ok(strippedMultiple.includes(cleanJson), "multi-think strip must preserve trailing JSON payload");
3853
3853
  // Scenario D: `<think>` block spanning multiple lines with arbitrary
3854
3854
  // whitespace and nested-looking content (no actual nesting since
3855
3855
  // Perplexity never emits nested reasoning blocks).
3856
- const multilineThink = "<think>\n Line 1\n Line 2 with <b>html</b>\n Line 3\n</think>\n" + cleanJson;
3856
+ const multilineThink = `<think>\n Line 1\n Line 2 with <b>html</b>\n Line 3\n</think>\n${cleanJson}`;
3857
3857
  assert.equal(stripPerplexityThinkingBlock(multilineThink), cleanJson, "multi-line <think> with arbitrary indentation must strip cleanly");
3858
3858
  // Scenario E: `<think>` with attribute-like content (`<think foo="bar">`)
3859
3859
  // — regex must allow attribute fragment before close `>`.
3860
- const attributedThink = '<think foo="bar">reasoning</think>\n' + cleanJson;
3860
+ const attributedThink = `<think foo="bar">reasoning</think>\n${cleanJson}`;
3861
3861
  assert.equal(stripPerplexityThinkingBlock(attributedThink), cleanJson, "<think> tag with attribute fragment must still strip");
3862
3862
  // Scenario F: empty `<think></think>` (degenerate case) — strip + trim
3863
3863
  // still yields the trailing JSON.
3864
- const emptyThink = "<think></think>\n" + cleanJson;
3864
+ const emptyThink = `<think></think>\n${cleanJson}`;
3865
3865
  assert.equal(stripPerplexityThinkingBlock(emptyThink), cleanJson, "empty <think></think> must strip cleanly");
3866
3866
  // Scenario G: only `<think>` block, no trailing JSON — output is empty
3867
3867
  // string after trim (caller's downstream parser then triggers
@@ -6015,7 +6015,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
6015
6015
  // 5 sites must use `.max(PEERS.length)`.
6016
6016
  const maxPeersLen = serverSrcA.match(/\.max\(PEERS\.length\)/g) ?? [];
6017
6017
  assert.ok(maxPeersLen.length >= 5, `v3.7.0 / AUDIT-3: expected >=5 .max(PEERS.length) sites (4 peers panels + judge_peers), found ${maxPeersLen.length}`);
6018
- assert.ok(!/\.min\(1\)\s*\n?\s*[^]*?\.max\(5\)\s*\n?\s*\.default\(\[\.\.\.PEERS\]/.test(serverSrcA), "v3.7.0 / AUDIT-3: the peers schema must NOT keep the stale `.max(5)` against a 6-element PEERS default");
6018
+ assert.ok(!/\.min\(1\)\s*\n?\s*[\s\S]*?\.max\(5\)\s*\n?\s*\.default\(\[\.\.\.PEERS\]/.test(serverSrcA), "v3.7.0 / AUDIT-3: the peers schema must NOT keep the stale `.max(5)` against a 6-element PEERS default");
6019
6019
  assert.ok(!/judge_peers: z\.array\(PeerSchema\)\.min\(2\)\.max\(5\)/.test(serverSrcA), "v3.7.0 / AUDIT-3: judge_peers must NOT keep the stale `.max(5)`");
6020
6020
  // AUDIT-4.
6021
6021
  assert.ok(/PEERS\.filter\(\(peer\) => runtime\.config\.peer_enabled\[peer\]\)/.test(serverSrcA), "v3.7.0 / AUDIT-4: server_info.financial_controls must compute readiness over the enabled peer subset");
@@ -6120,6 +6120,30 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
6120
6120
  assert.ok(/pruneCorruptSessions\(minAgeMs: number\): \{ scanned: number; removed: number; kept: number \}/.test(storeSrcB1), "v3.7.5 / B1: store.pruneCorruptSessions must declare the {scanned, removed, kept} shape");
6121
6121
  console.log("[smoke] session_sweep_prune_corrupt_test: PASS");
6122
6122
  }
6123
+ // v4.0.2 (AUDIT-1, Codex audit close-out 2026-05-15): anti-drift check
6124
+ // that `package.json.version` === `package-lock.json.version` ===
6125
+ // `package-lock.json.packages[""].version`. The v4.0.1 ship bumped
6126
+ // package.json without re-running `npm install`, so the lockfile root
6127
+ // versions stayed at `4.0.0` while package.json said `4.0.1`. Codex's
6128
+ // audit caught it. This marker pins the consistency invariant so any
6129
+ // future bump that forgets to regenerate the lockfile fails smoke
6130
+ // loudly instead of slipping through to publish.
6131
+ {
6132
+ const pjPath = path.join(process.cwd(), "package.json");
6133
+ const plPath = path.join(process.cwd(), "package-lock.json");
6134
+ const pj = JSON.parse(fs.readFileSync(pjPath, "utf8"));
6135
+ const pl = JSON.parse(fs.readFileSync(plPath, "utf8"));
6136
+ const pjVer = pj.version;
6137
+ const plRootVer = pl.version;
6138
+ const plPackagesRootVer = pl.packages?.[""]?.version;
6139
+ assert.equal(plRootVer, pjVer, `v4.0.2 / AUDIT-1: package-lock.json root .version (${plRootVer}) must equal package.json .version (${pjVer}); run \`npm install\` after bumping the version.`);
6140
+ assert.equal(plPackagesRootVer, pjVer, `v4.0.2 / AUDIT-1: package-lock.json packages[""].version (${plPackagesRootVer}) must equal package.json .version (${pjVer}); run \`npm install\` after bumping the version.`);
6141
+ // Also verify package.json `name` matches the new canonical name; if
6142
+ // the rename ever needs to happen again, this fails fast.
6143
+ assert.equal(pj.name, "@lcv-ideas-software/cross-review", `v4.0.2 / AUDIT-1: package.json .name must be "@lcv-ideas-software/cross-review" (post-v4.0.0 rename); got "${pj.name}".`);
6144
+ assert.equal(pl.name, "@lcv-ideas-software/cross-review", `v4.0.2 / AUDIT-1: package-lock.json .name must be "@lcv-ideas-software/cross-review"; got "${pl.name}".`);
6145
+ console.log("[smoke] package_version_consistency_test: PASS");
6146
+ }
6123
6147
  // v2.6.1 NOTE: smoke coverage for `peer.fallback.budget_blocked` and
6124
6148
  // `peer.moderation_recovery.budget_blocked` is intentionally NOT
6125
6149
  // included. These two gates use the same arithmetic shape as preflight