@ask-llm/plugin 0.15.0 → 0.16.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/.claude-plugin/plugin.json +1 -1
  2. package/.cursor-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +979 -0
  4. package/README.md +2 -0
  5. package/agents/brainstorm-coordinator.md +1 -1
  6. package/agents/gemini-reviewer.md +1 -1
  7. package/dist/antigravity-run.js +0 -0
  8. package/dist/brainstorm-run.js +0 -0
  9. package/dist/codex-run.js +0 -0
  10. package/dist/grok-run.js +0 -0
  11. package/dist/ollama-run.js +0 -0
  12. package/dist/run.js +0 -0
  13. package/package.json +14 -14
  14. package/pi/extensions/provider-tools.ts +1 -1
  15. package/scripts/benchmark/README.md +114 -0
  16. package/scripts/benchmark/fixtures/README.md +29 -0
  17. package/scripts/codex-pair-debounce-worker.mjs +0 -0
  18. package/scripts/codex-pair-log.mjs +4 -13
  19. package/scripts/codex-pair-prompt-drain.mjs +1 -1
  20. package/scripts/codex-pair-session.mjs +2 -2
  21. package/scripts/codex-pair-stop-gate.mjs +8 -8
  22. package/scripts/codex-pair-watch.mjs +20 -39
  23. package/skills/gemini-review/SKILL.md +1 -1
  24. package/scripts/lib/broker-lifecycle.mjs +0 -575
  25. package/scripts/lib/broker-rpc.mjs +0 -203
  26. package/scripts/lib/broker-transport.mjs +0 -407
  27. package/scripts/lib/broker.mjs +0 -537
  28. package/scripts/lib/debounce-state.mjs +0 -208
  29. package/scripts/lib/parser.d.mts +0 -12
  30. package/scripts/lib/parser.mjs +0 -229
  31. package/scripts/lib/process.mjs +0 -56
  32. package/scripts/lib/prompt.d.mts +0 -8
  33. package/scripts/lib/prompt.mjs +0 -41
  34. package/scripts/lib/session-registry.mjs +0 -162
  35. package/scripts/lib/state.d.mts +0 -58
  36. package/scripts/lib/state.mjs +0 -733
  37. package/scripts/lib/stop-gate.mjs +0 -134
package/README.md CHANGED
@@ -61,6 +61,8 @@ pi list
61
61
 
62
62
  Pi discovers the portable skills as `/skill:<name>` commands and registers native `ask-codex`, `ask-gemini`, `ask-grok`, `ask-ollama`, `ask-antigravity`, model-neutral `ask-cursor-agent`, and bounded concurrent `ask-multi` tools. Pi intentionally has no built-in MCP client; do not configure these as MCP servers in Pi. `fable-review` and `grok-pair` are excluded from Pi discovery; Grok pairing currently has Claude/Cursor adapters, while Pi retains its dedicated Codex pairing lifecycle.
63
63
 
64
+ The plugin's `ask-gemini-run`, Gemini reviewer agent/skill, and Pi `ask-gemini` tool all delegate to the canonical Gemini executor: `gemini-3.1-pro-preview` remains primary and quota errors fall back to `gemini-3.8-flash` unless `ASK_GEMINI_FALLBACK_MODEL` overrides it.
65
+
64
66
  For codex-pair, create `.codex-pair/context.md`, ensure Pi trusts the project, then run interactive `/codex-pair` to grant user-owned canonical-project consent. The marker alone never authorizes data transfer/cost. Revoke with `/codex-pair revoke`. Pi findings are non-blocking; blocking Stop-gate and one-shot print parity are not available.
65
67
 
66
68
  ```bash
@@ -231,7 +231,7 @@ node "${CLAUDE_PLUGIN_ROOT}/dist/grok-run.js" "$(cat "$workdir/prompt.md")" > "$
231
231
  pid_grok=$!
232
232
 
233
233
  # Only include this line if ollama was requested:
234
- ollama run "${ASK_OLLAMA_MODEL:-qwen3.6:27b}" < "$workdir/prompt.md" > "$workdir/ollama.out" 2> "$workdir/ollama.err" &
234
+ ollama run "${ASK_OLLAMA_MODEL:-qwen3.8:27b}" < "$workdir/prompt.md" > "$workdir/ollama.out" 2> "$workdir/ollama.err" &
235
235
  pid_ollama=$!
236
236
 
237
237
  # Wait for each by PID so we capture per-provider exit codes independently.
@@ -14,7 +14,7 @@ tools:
14
14
  <!-- PORTABLE-CONTRACT:START -->
15
15
  ## Portable contract
16
16
 
17
- Review only the supplied changes and context. Ask Gemini for concrete correctness, security, and regression concerns; validate every candidate against source; require file/line evidence and reproduction for behavior claims; omit style-only or speculative findings; report provider failures explicitly.
17
+ Review only the supplied changes and context. Ask Gemini for concrete correctness, security, and regression concerns; validate every candidate against source; require file/line evidence and reproduction for behavior claims; omit style-only or speculative findings; report provider failures explicitly. Preserve the canonical `gemini-3.1-pro-preview` → `gemini-3.8-flash` quota fallback and disclose when fallback occurs.
18
18
  <!-- PORTABLE-CONTRACT:END -->
19
19
 
20
20
  <!-- HOST-ADAPTER:CLAUDE-CODE:START -->
File without changes
File without changes
package/dist/codex-run.js CHANGED
File without changes
package/dist/grok-run.js CHANGED
File without changes
File without changes
package/dist/run.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ask-llm/plugin",
3
- "version": "0.15.0",
3
+ "version": "0.16.3",
4
4
  "description": "Ask LLM review, comparison, brainstorming, image, verification, and pairing workflows for Claude Code, Cursor Agent, and Pi",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -83,12 +83,12 @@
83
83
  ]
84
84
  },
85
85
  "dependencies": {
86
- "@ask-llm/antigravity-mcp": "^0.7.2",
87
- "@ask-llm/codex-mcp": "^0.7.5",
88
- "@ask-llm/gemini-mcp": "^1.7.2",
89
- "@ask-llm/grok-mcp": "^0.1.2",
90
- "@ask-llm/mcp": "^0.8.0",
91
- "@ask-llm/ollama-mcp": "^0.5.7"
86
+ "@ask-llm/antigravity-mcp": "^0.7.5",
87
+ "@ask-llm/codex-mcp": "^0.7.8",
88
+ "@ask-llm/gemini-mcp": "^1.9.1",
89
+ "@ask-llm/grok-mcp": "^0.1.5",
90
+ "@ask-llm/mcp": "^0.9.3",
91
+ "@ask-llm/ollama-mcp": "^0.6.2"
92
92
  },
93
93
  "peerDependencies": {
94
94
  "@earendil-works/pi-ai": "*",
@@ -99,15 +99,15 @@
99
99
  "node": ">=20.0.0"
100
100
  },
101
101
  "devDependencies": {
102
- "@biomejs/biome": "^2.4.4",
103
- "@earendil-works/pi-ai": "^0.83.0",
104
- "@earendil-works/pi-coding-agent": "^0.83.0",
105
- "@types/node": "^22.19.13",
106
- "typebox": "^1.3.7",
102
+ "@biomejs/biome": "^2.5.10",
103
+ "@earendil-works/pi-ai": "^0.84.2",
104
+ "@earendil-works/pi-coding-agent": "^0.84.2",
105
+ "@types/node": "^22.20.1",
106
+ "typebox": "1.3.7",
107
107
  "typescript": "^7.0.2",
108
- "vitest": "^4.1.10"
108
+ "vitest": "^4.1.11"
109
109
  },
110
110
  "publishConfig": {
111
111
  "access": "public"
112
112
  }
113
- }
113
+ }
@@ -203,7 +203,7 @@ export function registerProviderTools(pi: ExtensionAPI): void {
203
203
  name: "ask-gemini",
204
204
  label: "Ask Gemini",
205
205
  description:
206
- "Consult Gemini through Ask LLM's canonical executor, including its quota fallback, validation, sessions, and structured response. Output is bounded to Pi's 50KB/2000-line limits.",
206
+ "Consult Gemini through Ask LLM's canonical executor (`gemini-3.1-pro-preview` → `gemini-3.8-flash` on quota), including validation, sessions, and structured response. Output is bounded to Pi's 50KB/2000-line limits.",
207
207
  parameters: geminiSchema,
208
208
  provider: "gemini",
209
209
  });
@@ -0,0 +1,114 @@
1
+ # codex-pair prompt A/B benchmark (ADR-100)
2
+
3
+ Empirical harness for validating prompt-template changes. Built initially
4
+ to test ADR-099 (Karpathy baseline principles), but reusable for any
5
+ future prompt change that wants empirical justification before shipping.
6
+
7
+ ## What this measures
8
+
9
+ For each fixture, the driver:
10
+
11
+ 1. Renders the review prompt twice — once against the **pre-baseline**
12
+ template (current production prompt without the Karpathy block) and
13
+ once against the **baseline** template (ADR-099's version with the
14
+ block).
15
+ 2. Invokes `codex exec --json` with each rendered prompt against the
16
+ same fixture file + context.
17
+ 3. Parses the verdict JSON and matches findings against the fixture's
18
+ `probes.json` ground truth.
19
+ 4. Aggregates **recall** (true positives caught / total ground-truth
20
+ probes) and **extra findings** (findings not matching any probe —
21
+ either real concerns the probes didn't anticipate, or noise).
22
+
23
+ The headline output is a markdown report with a per-fixture caught /
24
+ missed / extra breakdown and an aggregate recall delta.
25
+
26
+ ## Decision rule
27
+
28
+ Ship the prompt change to `prompts/review.txt` permanently if:
29
+
30
+ - **Recall delta ≥ +10 percentage points** (the baseline catches a
31
+ meaningfully larger fraction of ground-truth probes than the
32
+ pre-baseline prompt does)
33
+ - **Extra-finding delta ≤ +1 per fixture on average** (the baseline
34
+ doesn't add overwhelming noise)
35
+
36
+ If recall regresses or noise jumps, the ADR-099 rollback path applies
37
+ (two file edits + one test-assertion update; documented in ADR-099's
38
+ Consequences section).
39
+
40
+ ## Usage
41
+
42
+ ```bash
43
+ # Full run (all fixtures, both arms)
44
+ node packages/claude-plugin/scripts/benchmark/prompt-ab.mjs \
45
+ --model gpt-5.5 \
46
+ --out benchmark-report.md
47
+
48
+ # Single-fixture quick check
49
+ node packages/claude-plugin/scripts/benchmark/prompt-ab.mjs \
50
+ --fixtures 01-overcomplication \
51
+ --out single-report.md
52
+
53
+ # Cheaper model for sanity
54
+ node packages/claude-plugin/scripts/benchmark/prompt-ab.mjs \
55
+ --model gpt-5.4-mini \
56
+ --timeout-ms 60000
57
+ ```
58
+
59
+ ## Cost
60
+
61
+ - Per fixture per arm: one `codex exec` review (~$0.04–0.07 at current
62
+ gpt-5.5 pricing)
63
+ - Four fixtures × two arms = ~$0.40 per full run
64
+ - Variance is high (codex sometimes returns longer responses); budget
65
+ $1 per careful run to absorb retries
66
+
67
+ ## Output interpretation
68
+
69
+ The report has three layers:
70
+
71
+ 1. **Aggregate table** — recall percentages per arm, plus a one-line
72
+ recall-delta and extra-finding-delta verdict line that mechanically
73
+ checks against the decision rule.
74
+ 2. **Per-fixture sections** — for each fixture and each arm, the list
75
+ of probes caught (✅), missed (❌), and extra findings (⚠️ — these
76
+ require manual judgment whether they're real concerns or noise).
77
+ 3. **Token usage** — the per-fixture input/output token counts. Useful
78
+ for cost analysis if the prompt grows further.
79
+
80
+ ## Limitations
81
+
82
+ - **Keyword-based probe matching is crude.** A real finding that uses
83
+ different vocabulary than the probe's `keywords` array will be
84
+ flagged as missed. Tune keywords as needed when you see this happen.
85
+ Future enhancement: LLM-judge mode that asks gemini "did this finding
86
+ match this probe?" — costs another ~$0.01 per evaluation but better
87
+ signal.
88
+ - **Four fixtures is small.** Sample size is sufficient to see large
89
+ effects but won't reveal subtle improvements. Add more fixtures as
90
+ you encounter representative bug patterns in the wild — see
91
+ `fixtures/README.md` for the contribution shape.
92
+ - **Codex is non-deterministic.** Run the benchmark twice and you'll
93
+ see modest variance per fixture. Wide swings (>30% recall delta on a
94
+ single fixture) suggest the fixture is borderline and should be
95
+ refined rather than accepted as signal.
96
+ - **The fixtures are not the ADR-077 four-task benchmark.** ADR-077's
97
+ fixtures live on `experiment/codex-pair-poc` and exercise *bug
98
+ detection* (float-money precision etc.). These ADR-100 fixtures
99
+ exercise *prompt-rule detection* (does the Karpathy baseline change
100
+ what codex flags?). Different questions; both empirical.
101
+
102
+ ## Adding fixtures
103
+
104
+ See `fixtures/README.md`. Three files per fixture: `code.ts`,
105
+ `context.md`, `probes.json`. The driver auto-discovers any directory
106
+ under `fixtures/` containing all three.
107
+
108
+ ## Related ADRs
109
+
110
+ - **ADR-099** — codex-pair Karpathy baseline principles in review prompt
111
+ - **ADR-077** — original codex-pair POC + four-task benchmark
112
+ - **ADR-089** — externalized prompt template + golden fixture invariant
113
+ - **ADR-100** — this benchmark harness (the document you should read for
114
+ methodology rationale)
@@ -0,0 +1,29 @@
1
+ # Benchmark fixtures (ADR-100)
2
+
3
+ Each fixture is a self-contained scenario crafted to exercise one of
4
+ the Karpathy-baseline rules from ADR-099. Each directory contains:
5
+
6
+ - **`code.ts`** — the file content sent to codex for review. Starts
7
+ with a comment block restating the task that was supposedly given to
8
+ Claude, so codex has the same context a real edit would have.
9
+ - **`context.md`** — marker-file content sent as `projectContext` in
10
+ the rendered prompt.
11
+ - **`probes.json`** — the ground-truth expectations: `should_flag`
12
+ entries are findings codex SHOULD raise (true positives); `should_not_flag`
13
+ entries are things codex should NOT raise (negative probes for noise).
14
+
15
+ ## Fixture index
16
+
17
+ | Fixture | Tests | Why this exercises the rule |
18
+ |---|---|---|
19
+ | `01-overcomplication` | Simplicity | Implements `getFullName` via a single-use interface, factory class, runtime null checks against TypeScript types, and unused configurability params — every kind of "code beyond what was asked" |
20
+ | `02-drive-by-refactor` | Surgical scope | Bug fix to `parsePagination` correctly applied, but the diff also includes an unrelated function rename, a reformatted constants block, and a history comment — all changes that should be in a separate PR |
21
+ | `03-orphan-imports` | Surgical scope | Removes `formatV1` correctly, but leaves `decodeBase64` and `logger` imports orphaned (no longer used by anything in the file) |
22
+ | `04-hidden-assumption` | Hidden assumptions | Implements `firstAvailableSlot` correctly for an already-sorted input, but the task asked for the "earliest" slot — depends on an unstated sort-order invariant. Should either sort internally or document the requirement. |
23
+
24
+ ## Adding a new fixture
25
+
26
+ Create a new directory under `fixtures/` with the three files. The
27
+ driver auto-discovers any directory containing all three. Naming
28
+ convention: `NN-short-slug/` where `NN` is a two-digit sequence and
29
+ `slug` describes the rule being exercised.
File without changes
@@ -19,7 +19,7 @@ import { readFileSync } from "node:fs";
19
19
  import { access } from "node:fs/promises";
20
20
  import { homedir } from "node:os";
21
21
  import { dirname, join, resolve } from "node:path";
22
- import { CONTEXT_FILENAME, logPath as resolveLogPath, PAIR_ROOT_DIR } from "./lib/state.mjs";
22
+ import { CONTEXT_FILENAME, PAIR_ROOT_DIR, logPath as resolveLogPath } from "./lib/state.mjs";
23
23
 
24
24
  const MARKER_FILE = join(PAIR_ROOT_DIR, CONTEXT_FILENAME);
25
25
 
@@ -171,17 +171,10 @@ function showSummary(entries) {
171
171
  }
172
172
  if (e.fellBack) fallbackCount++;
173
173
  if (e.verdict === "cached") cachedCount++;
174
- if (
175
- e.verdict === "none" ||
176
- e.verdict === "concerns" ||
177
- e.verdict === "cached"
178
- ) {
174
+ if (e.verdict === "none" || e.verdict === "concerns" || e.verdict === "cached") {
179
175
  runCount++;
180
176
  }
181
- if (
182
- e.durationMs != null &&
183
- (e.verdict === "none" || e.verdict === "concerns")
184
- ) {
177
+ if (e.durationMs != null && (e.verdict === "none" || e.verdict === "concerns")) {
185
178
  durationSum += e.durationMs;
186
179
  durationCount++;
187
180
  }
@@ -240,9 +233,7 @@ async function main() {
240
233
  }
241
234
  const markerDir = await findMarkerUp(process.cwd());
242
235
  if (!markerDir) {
243
- process.stderr.write(
244
- `codex-pair-log: no .codex-pair/context.md marker found in cwd or parents\n`,
245
- );
236
+ process.stderr.write(`codex-pair-log: no .codex-pair/context.md marker found in cwd or parents\n`);
246
237
  process.exit(1);
247
238
  }
248
239
  const logPath = resolveLogPath(markerDir);
@@ -13,9 +13,9 @@
13
13
  import { access } from "node:fs/promises";
14
14
  import { homedir } from "node:os";
15
15
  import { dirname, join, resolve } from "node:path";
16
- import { CONTEXT_FILENAME, PAIR_ROOT_DIR } from "./lib/state.mjs";
17
16
  import { drainPending, joinPendingForSurface } from "./lib/debounce-state.mjs";
18
17
  import { collectSessionMarkers } from "./lib/session-registry.mjs";
18
+ import { CONTEXT_FILENAME, PAIR_ROOT_DIR } from "./lib/state.mjs";
19
19
 
20
20
  const MARKER_FILE = join(PAIR_ROOT_DIR, CONTEXT_FILENAME);
21
21
 
@@ -13,16 +13,16 @@ import { homedir } from "node:os";
13
13
  import { dirname, join, resolve } from "node:path";
14
14
  import { bootstrapBroker, clearStaleBrokerState, teardownBroker } from "./lib/broker-lifecycle.mjs";
15
15
  import { clearAllDebounceState } from "./lib/debounce-state.mjs";
16
+ import { clearSession } from "./lib/session-registry.mjs";
16
17
  import {
17
18
  appendLog,
18
- clearAutoPause,
19
19
  CONTEXT_FILENAME,
20
+ clearAutoPause,
20
21
  PAIR_ROOT_DIR,
21
22
  readPauseInfo,
22
23
  readPluginVersion,
23
24
  resolveAutoResume,
24
25
  } from "./lib/state.mjs";
25
- import { clearSession } from "./lib/session-registry.mjs";
26
26
 
27
27
  const MARKER_FILE = join(PAIR_ROOT_DIR, CONTEXT_FILENAME);
28
28
 
@@ -7,17 +7,18 @@
7
7
  // findMarkerUp is duplicated by design (zero-workspace-imports; see prompt-drain).
8
8
 
9
9
  import { execFileSync } from "node:child_process";
10
- import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
10
+ import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
11
11
  import { homedir } from "node:os";
12
12
  import { dirname, join, resolve } from "node:path";
13
13
  import { debounceRoot, drainPending, joinPendingForSurface, reviewingRoot } from "./lib/debounce-state.mjs";
14
+ import { collectSessionMarkers } from "./lib/session-registry.mjs";
14
15
  import {
15
16
  CONTEXT_FILENAME,
16
- INFLIGHT_TTL_MIN_MS,
17
- PAIR_ROOT_DIR,
18
17
  contextPath,
18
+ INFLIGHT_TTL_MIN_MS,
19
19
  inflightRoot,
20
20
  logPath,
21
+ PAIR_ROOT_DIR,
21
22
  readAcks,
22
23
  } from "./lib/state.mjs";
23
24
  import {
@@ -28,7 +29,6 @@ import {
28
29
  parseGitPorcelain,
29
30
  selectLatestEntries,
30
31
  } from "./lib/stop-gate.mjs";
31
- import { collectSessionMarkers } from "./lib/session-registry.mjs";
32
32
 
33
33
  const MARKER_FILE = join(PAIR_ROOT_DIR, CONTEXT_FILENAME);
34
34
 
@@ -131,9 +131,7 @@ function readInFlightInputs(markerDir) {
131
131
  function inflightFreshMs(markerDir) {
132
132
  const fmTimeout = Number(readMarkerScalar(markerDir, "timeoutMs"));
133
133
  const timeout =
134
- Number.isFinite(fmTimeout) && fmTimeout > 0
135
- ? fmTimeout
136
- : Number(process.env.ASK_CODEX_TIMEOUT_MS ?? 800_000);
134
+ Number.isFinite(fmTimeout) && fmTimeout > 0 ? fmTimeout : Number(process.env.ASK_CODEX_TIMEOUT_MS ?? 800_000);
137
135
  return Math.max(timeout, INFLIGHT_TTL_MIN_MS) + 60_000;
138
136
  }
139
137
 
@@ -266,6 +264,8 @@ async function main() {
266
264
  }
267
265
 
268
266
  main().catch((err) => {
269
- process.stderr.write(`[codex-pair] WARNING: stop-gate failed (${err?.message ?? err}). Allowing turn end — HIGH findings may remain.\n`);
267
+ process.stderr.write(
268
+ `[codex-pair] WARNING: stop-gate failed (${err?.message ?? err}). Allowing turn end — HIGH findings may remain.\n`,
269
+ );
270
270
  process.exit(0);
271
271
  });
@@ -17,12 +17,11 @@
17
17
  // invocation is inlined; semantics mirror `codexExecutor.ts` deliberately.
18
18
 
19
19
  import { spawn } from "node:child_process";
20
- import { access, readFile } from "node:fs/promises";
21
20
  import { readFileSync } from "node:fs";
22
- import { dirname, join, resolve } from "node:path";
21
+ import { access, readFile } from "node:fs/promises";
23
22
  import { homedir } from "node:os";
23
+ import { dirname, join, resolve } from "node:path";
24
24
  import { fileURLToPath } from "node:url";
25
- import { IS_WINDOWS, terminateProcessTree } from "./lib/process.mjs";
26
25
  // M4: broker integration. Importing initializeBroker + isBrokerEnabled +
27
26
  // submitReview from broker.mjs transitively pulls in broker-transport,
28
27
  // broker-rpc, broker-lifecycle. ESM-static cost is paid on every hook
@@ -30,15 +29,14 @@ import { IS_WINDOWS, terminateProcessTree } from "./lib/process.mjs";
30
29
  // isn't set, so the per-edit fast path is unaffected.
31
30
  import { initializeBroker, isBrokerEnabled, readBrokerState, submitReview } from "./lib/broker.mjs";
32
31
  import {
33
- DEFAULT_DEBOUNCE_MS,
34
- DEFAULT_DEBOUNCE_MAX_MS,
35
32
  bumpEditRecord,
33
+ DEFAULT_DEBOUNCE_MAX_MS,
34
+ DEFAULT_DEBOUNCE_MS,
36
35
  drainPending,
37
36
  joinPendingForSurface,
38
37
  markReviewed,
39
38
  sweepStaleDebounce,
40
39
  } from "./lib/debounce-state.mjs";
41
- import { buildReviewPrompt } from "./lib/prompt.mjs";
42
40
  import {
43
41
  buildVerdictMessage,
44
42
  DEFAULT_SURFACE_THRESHOLD,
@@ -48,20 +46,23 @@ import {
48
46
  VALID_THRESHOLDS,
49
47
  VERDICT_PREFIXES,
50
48
  } from "./lib/parser.mjs";
49
+ import { IS_WINDOWS, terminateProcessTree } from "./lib/process.mjs";
50
+ import { buildReviewPrompt } from "./lib/prompt.mjs";
51
+ import { registerMarker } from "./lib/session-registry.mjs";
51
52
  import {
52
- appendLog,
53
53
  AUTOPAUSE_FAILURE_THRESHOLD,
54
+ appendLog,
55
+ CONTEXT_FILENAME,
54
56
  clearAutoPause,
55
57
  clearReviewFailures,
56
58
  computeCacheKey,
57
- CONTEXT_FILENAME,
58
59
  contextPath,
59
60
  getBlockingFromShard,
60
61
  getCachedConcerns,
61
62
  hashConcernBody,
63
+ INFLIGHT_TTL_MIN_MS,
62
64
  ignorePath,
63
65
  includePath,
64
- INFLIGHT_TTL_MIN_MS,
65
66
  logPath,
66
67
  PAIR_ROOT_DIR,
67
68
  readPauseInfo,
@@ -74,7 +75,6 @@ import {
74
75
  updateRepetitions,
75
76
  writeAutoPause,
76
77
  } from "./lib/state.mjs";
77
- import { registerMarker } from "./lib/session-registry.mjs";
78
78
 
79
79
  const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
80
80
  const DEFAULTS_PATH = join(SCRIPT_DIR, "..", "codex-pair-defaults.json");
@@ -293,10 +293,7 @@ function parseFrontmatter(content) {
293
293
  valueRaw = valueRaw.slice(0, inlineComment.index);
294
294
  }
295
295
  let value = valueRaw.trim();
296
- if (
297
- (value.startsWith('"') && value.endsWith('"')) ||
298
- (value.startsWith("'") && value.endsWith("'"))
299
- ) {
296
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
300
297
  value = value.slice(1, -1);
301
298
  }
302
299
  if (value === "true") frontmatter[key] = true;
@@ -505,23 +502,13 @@ function resolveConfig(frontmatter) {
505
502
  return {
506
503
  model: typeof fm.model === "string" && fm.model.length > 0 ? fm.model : DEFAULT_MODEL,
507
504
  fallbackModel:
508
- typeof fm.fallbackModel === "string" && fm.fallbackModel.length > 0
509
- ? fm.fallbackModel
510
- : FALLBACK_MODEL,
511
- timeoutMs:
512
- typeof fm.timeoutMs === "number" && fm.timeoutMs > 0 ? fm.timeoutMs : DEFAULT_TIMEOUT_MS,
513
- maxFileBytes:
514
- typeof fm.maxFileBytes === "number" && fm.maxFileBytes > 0
515
- ? fm.maxFileBytes
516
- : MAX_FILE_BYTES,
505
+ typeof fm.fallbackModel === "string" && fm.fallbackModel.length > 0 ? fm.fallbackModel : FALLBACK_MODEL,
506
+ timeoutMs: typeof fm.timeoutMs === "number" && fm.timeoutMs > 0 ? fm.timeoutMs : DEFAULT_TIMEOUT_MS,
507
+ maxFileBytes: typeof fm.maxFileBytes === "number" && fm.maxFileBytes > 0 ? fm.maxFileBytes : MAX_FILE_BYTES,
517
508
  surfaceThreshold:
518
- surfaceCandidate && VALID_THRESHOLDS.has(surfaceCandidate)
519
- ? surfaceCandidate
520
- : DEFAULT_SURFACE_THRESHOLD,
521
- debounceMs:
522
- typeof fm.debounceMs === "number" && fm.debounceMs >= 0 ? fm.debounceMs : DEBOUNCE_MS,
523
- debounceMaxMs:
524
- typeof fm.debounceMaxMs === "number" && fm.debounceMaxMs > 0 ? fm.debounceMaxMs : DEBOUNCE_MAX_MS,
509
+ surfaceCandidate && VALID_THRESHOLDS.has(surfaceCandidate) ? surfaceCandidate : DEFAULT_SURFACE_THRESHOLD,
510
+ debounceMs: typeof fm.debounceMs === "number" && fm.debounceMs >= 0 ? fm.debounceMs : DEBOUNCE_MS,
511
+ debounceMaxMs: typeof fm.debounceMaxMs === "number" && fm.debounceMaxMs > 0 ? fm.debounceMaxMs : DEBOUNCE_MAX_MS,
525
512
  };
526
513
  }
527
514
 
@@ -729,9 +716,7 @@ function spawnCodex({ prompt, model, timeoutMs }) {
729
716
  setTimeout(() => {
730
717
  terminateProcessTree(child, "SIGKILL");
731
718
  }, 5000);
732
- rejectCall(
733
- taggedError(`codex exec timed out after ${Math.round(timeoutMs / 1000)}s`, "timeout"),
734
- );
719
+ rejectCall(taggedError(`codex exec timed out after ${Math.round(timeoutMs / 1000)}s`, "timeout"));
735
720
  }, timeoutMs);
736
721
 
737
722
  child.on("error", (err) => {
@@ -757,9 +742,7 @@ function spawnCodex({ prompt, model, timeoutMs }) {
757
742
  // tail (often just the stdin banner) — #176.
758
743
  const errorEvents = extractJsonlErrorEvents(stdout);
759
744
  const reason =
760
- errorEvents.length > 0
761
- ? errorEvents[errorEvents.length - 1]
762
- : stderrTail(stderr) || `codex exit ${code}`;
745
+ errorEvents.length > 0 ? errorEvents[errorEvents.length - 1] : stderrTail(stderr) || `codex exit ${code}`;
763
746
  rejectCall(taggedError(reason, "error"));
764
747
  }
765
748
  });
@@ -1209,9 +1192,7 @@ async function main() {
1209
1192
  verdict: "skipped",
1210
1193
  reason: `unreadable: ${err.message}`,
1211
1194
  });
1212
- await emitSystemMessage(
1213
- `codex-pair ${VERDICT_PREFIXES.skipped}: ${filePath} — unreadable (${err.message})`,
1214
- );
1195
+ await emitSystemMessage(`codex-pair ${VERDICT_PREFIXES.skipped}: ${filePath} — unreadable (${err.message})`);
1215
1196
  process.exit(0);
1216
1197
  }
1217
1198
 
@@ -6,7 +6,7 @@ description: Get a second opinion from Gemini on your current code changes. Anal
6
6
  <!-- PORTABLE-CONTRACT:START -->
7
7
  ## Portable contract
8
8
 
9
- Gather the relevant staged, unstaged, and untracked code changes; build a bounded context brief; request a Gemini review; verify each reported finding against source; and return only prioritized, source-supported findings. Preserve provider fallback, timeout, and explicit failure disclosure.
9
+ Gather the relevant staged, unstaged, and untracked code changes; build a bounded context brief; request a Gemini review; verify each reported finding against source; and return only prioritized, source-supported findings. Preserve the canonical `gemini-3.1-pro-preview` → `gemini-3.8-flash` quota fallback, timeout, and explicit failure disclosure.
10
10
  <!-- PORTABLE-CONTRACT:END -->
11
11
 
12
12
  ## Host adapters