tau-coding-agent 0.1.2 → 0.1.4

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 (35) hide show
  1. package/README.md +22 -21
  2. package/extensions/answer.ts +4 -4
  3. package/extensions/branch-term.ts +3 -3
  4. package/extensions/btw.ts +4 -4
  5. package/extensions/ghostty.ts +1 -1
  6. package/extensions/git-diff-stats.ts +1 -1
  7. package/extensions/git-pr-status.ts +1 -1
  8. package/extensions/insights.ts +3 -3
  9. package/extensions/interlude.ts +1 -1
  10. package/extensions/loop.ts +3 -3
  11. package/extensions/memory.ts +8 -2
  12. package/extensions/notify.ts +1 -1
  13. package/extensions/openai-fast.ts +1 -1
  14. package/extensions/openai-verbosity.ts +1 -1
  15. package/extensions/review.ts +111 -38
  16. package/extensions/sandbox/index.ts +84 -8
  17. package/extensions/tool-display-mode.ts +544 -0
  18. package/extensions/usage/anthropic.ts +1 -1
  19. package/extensions/usage/github-copilot.ts +1 -1
  20. package/extensions/usage/index.ts +3 -3
  21. package/extensions/usage/minimax.ts +1 -1
  22. package/extensions/usage/openai-codex.ts +1 -1
  23. package/extensions/usage/openrouter.ts +1 -1
  24. package/extensions/usage/providers.ts +0 -2
  25. package/extensions/usage/types.ts +1 -2
  26. package/extensions/usage/zai.ts +1 -1
  27. package/extensions/websearch/index.ts +3 -3
  28. package/extensions/websearch/providers/anthropic.pi.ts +1 -1
  29. package/extensions/websearch/providers/gemini.pi.ts +1 -1
  30. package/extensions/websearch/providers/openai-codex.pi.ts +1 -1
  31. package/extensions/websearch/providers/pi-model.shared.ts +2 -2
  32. package/extensions/worktree.ts +3 -3
  33. package/package.json +20 -7
  34. package/skills/oracle/scripts/oracle +0 -1
  35. package/extensions/usage/google-gemini-cli.ts +0 -232
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # tau-coding-agent
2
2
 
3
- Coding-focused Tau package for [Pi](https://pi.dev).
3
+ Coding agent built on top of [Pi](https://pi.dev).
4
4
 
5
5
  Tau turns Pi's minimal core into an opinionated, polished coding-agent setup with web search, code review workflows, sandboxing, local memory, usage reporting, and a curated skill set for day-to-day software work.
6
6
 
@@ -18,26 +18,27 @@ pi install -l npm:tau-coding-agent
18
18
 
19
19
  ## Extensions
20
20
 
21
- | Extension | Command | Description |
22
- | ------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------- |
23
- | `answer` | `/answer` | Extract and interactively answer agent questions. |
24
- | `branch-term` | `/branch` | Open a new terminal on the current session's git branch. |
25
- | `btw` | `/btw` | Run a one-off side request with read-only tools and no context persistence. |
26
- | `ghostty` | — | Ghostty tab title enhancements while the agent is working, waiting, or idle. |
27
- | `git-diff-stats` | — | Status bar diff stats for local changes in the current repo. |
28
- | `git-pr-status` | — | Status bar PR number and link for the current branch. |
29
- | `insights` | `/insights` | Analyze Pi sessions and suggest reusable instructions, templates, skills, and extensions. |
30
- | `interlude` | `alt+x` | Stash the current message draft, send one interlude message, then restore the draft. |
31
- | `loop` | `/loop` | Repeat a prompt until the agent signals success. |
32
- | `memory` | `/memory` | Opt-in project-local memory for learning and continuity across sessions. |
33
- | `notify` | — | Terminal notification when the agent is waiting for input. |
34
- | `openai-fast` | `/fast` | Toggle priority service tier for supported OpenAI models. |
35
- | `openai-verbosity` | `/verbosity` | Set verbosity for supported OpenAI models. |
36
- | `review` | `/review`, `/triage` | Multi-focus review and PR feedback triage for PRs, branches, commits, and local changes, with integrated follow-up fixes. |
37
- | `sandbox` | `/sandbox` | OS-level sandboxing for bash commands with runtime overrides. |
38
- | `usage` | `/usage` | Historical provider usage breakdown with all-provider history and live quota snapshots. |
39
- | `websearch` | — | Web search via Gemini, OpenAI, or Claude, leveraging Pi or browser session credentials. |
40
- | `worktree` | `/worktree` | Create, list, and archive git worktrees, optionally opening them in a new terminal or tmux pane. |
21
+ | Extension | Command | Description |
22
+ | ------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------- |
23
+ | `answer` | `/answer` | Extract and interactively answer agent questions. |
24
+ | `branch-term` | `/branch` | Open a new terminal on the current session's git branch. |
25
+ | `btw` | `/btw` | Run a one-off side request with read-only tools and no context persistence. |
26
+ | `ghostty` | — | Ghostty tab title enhancements while the agent is working, waiting, or idle. |
27
+ | `git-diff-stats` | — | Status bar diff stats for local changes in the current repo. |
28
+ | `git-pr-status` | — | Status bar PR number and link for the current branch. |
29
+ | `insights` | `/insights` | Analyze Pi sessions and suggest reusable instructions, templates, skills, and extensions. |
30
+ | `interlude` | `alt+x` | Stash the current message draft, send one interlude message, then restore the draft. |
31
+ | `loop` | `/loop` | Repeat a prompt until the agent signals success. |
32
+ | `memory` | `/memory` | Opt-in project-local memory for learning and continuity across sessions. |
33
+ | `notify` | — | Terminal notification when the agent is waiting for input. |
34
+ | `openai-fast` | `/fast` | Toggle priority service tier for supported OpenAI models. |
35
+ | `openai-verbosity` | `/verbosity` | Set verbosity for supported OpenAI models. |
36
+ | `review` | `/review`, `/triage` | Multi-focus review and PR feedback triage for PRs, branches, commits, and local changes, with integrated follow-up fixes. |
37
+ | `sandbox` | `/sandbox` | OS-level sandboxing for bash commands with runtime overrides. |
38
+ | `tool-display-mode` | `ctrl+o` | Cycle tool output between Pi's default rendering, expanded output, and compact summaries. |
39
+ | `usage` | `/usage` | Historical provider usage breakdown with all-provider history and live quota snapshots. |
40
+ | `websearch` | — | Web search via Gemini, OpenAI, or Claude, leveraging Pi or browser session credentials. |
41
+ | `worktree` | `/worktree` | Create, list, and archive git worktrees, optionally opening them in a new terminal or tmux pane. |
41
42
 
42
43
  ## Skills
43
44
 
@@ -10,9 +10,9 @@
10
10
  * 4. Submits the compiled answers when done
11
11
  */
12
12
 
13
- import { complete, type Model, type Api, type UserMessage } from "@mariozechner/pi-ai";
14
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
15
- import { BorderedLoader } from "@mariozechner/pi-coding-agent";
13
+ import { complete, type Model, type Api, type UserMessage } from "@earendil-works/pi-ai";
14
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
15
+ import { BorderedLoader } from "@earendil-works/pi-coding-agent";
16
16
  import {
17
17
  type Component,
18
18
  Editor,
@@ -23,7 +23,7 @@ import {
23
23
  type TUI,
24
24
  visibleWidth,
25
25
  wrapTextWithAnsi,
26
- } from "@mariozechner/pi-tui";
26
+ } from "@earendil-works/pi-tui";
27
27
 
28
28
  const SYSTEM_PROMPT = `You extract items that need user input from assistant text:
29
29
 
@@ -2,14 +2,14 @@ import { spawn, spawnSync } from "node:child_process";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import * as fs from "node:fs";
4
4
  import * as path from "node:path";
5
- import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
5
+ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
6
6
  import {
7
7
  CURRENT_SESSION_VERSION,
8
8
  SessionManager,
9
9
  highlightCode,
10
10
  type SessionHeader,
11
- } from "@mariozechner/pi-coding-agent";
12
- import { Box, Text } from "@mariozechner/pi-tui";
11
+ } from "@earendil-works/pi-coding-agent";
12
+ import { Box, Text } from "@earendil-works/pi-tui";
13
13
 
14
14
  const TERMINAL_FLAG = "branch-term";
15
15
  const TMUX_LAYOUT_FLAG = "branch-tmux-layout";
package/extensions/btw.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { AgentMessage } from "@mariozechner/pi-agent-core";
2
- import type { AssistantMessage, ThinkingLevel } from "@mariozechner/pi-ai";
1
+ import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
+ import type { AssistantMessage, ThinkingLevel } from "@earendil-works/pi-ai";
3
3
  import {
4
4
  DefaultResourceLoader,
5
5
  SessionManager,
@@ -11,7 +11,7 @@ import {
11
11
  type ExtensionCommandContext,
12
12
  type ExtensionContext,
13
13
  type Theme,
14
- } from "@mariozechner/pi-coding-agent";
14
+ } from "@earendil-works/pi-coding-agent";
15
15
  import {
16
16
  Key,
17
17
  Markdown,
@@ -21,7 +21,7 @@ import {
21
21
  type TUI,
22
22
  visibleWidth,
23
23
  wrapTextWithAnsi,
24
- } from "@mariozechner/pi-tui";
24
+ } from "@earendil-works/pi-tui";
25
25
 
26
26
  const STATUS_KEY = "0-btw";
27
27
  const RESULT_MARKDOWN_THEME = getMarkdownTheme();
@@ -10,7 +10,7 @@
10
10
  * Pi now emits native OSC 9;4 progress indicators, so this extension only manages the title.
11
11
  */
12
12
 
13
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
13
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
14
14
  import path from "node:path";
15
15
 
16
16
  const STATUS_SPINNER_INTERVAL_MS = 80;
@@ -3,7 +3,7 @@ import {
3
3
  isWriteToolResult,
4
4
  type ExtensionAPI,
5
5
  type ExtensionContext,
6
- } from "@mariozechner/pi-coding-agent";
6
+ } from "@earendil-works/pi-coding-agent";
7
7
  import { spawn } from "node:child_process";
8
8
  import { existsSync } from "node:fs";
9
9
  import { copyFile, mkdtemp, rm } from "node:fs/promises";
@@ -2,7 +2,7 @@ import {
2
2
  isBashToolResult,
3
3
  type ExtensionAPI,
4
4
  type ExtensionContext,
5
- } from "@mariozechner/pi-coding-agent";
5
+ } from "@earendil-works/pi-coding-agent";
6
6
 
7
7
  const STATUS_KEY = "1-git-pr-status";
8
8
 
@@ -1,4 +1,4 @@
1
- import { complete, type Api, type Model, type UserMessage } from "@mariozechner/pi-ai";
1
+ import { complete, type Api, type Model, type UserMessage } from "@earendil-works/pi-ai";
2
2
  import {
3
3
  BorderedLoader,
4
4
  SessionManager,
@@ -13,7 +13,7 @@ import {
13
13
  type SessionEntry,
14
14
  type SessionHeader,
15
15
  type Theme,
16
- } from "@mariozechner/pi-coding-agent";
16
+ } from "@earendil-works/pi-coding-agent";
17
17
  import {
18
18
  Key,
19
19
  Markdown,
@@ -23,7 +23,7 @@ import {
23
23
  type TUI,
24
24
  visibleWidth,
25
25
  wrapTextWithAnsi,
26
- } from "@mariozechner/pi-tui";
26
+ } from "@earendil-works/pi-tui";
27
27
  import { createHash, randomBytes } from "node:crypto";
28
28
  import fs from "node:fs/promises";
29
29
  import path from "node:path";
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
4
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
5
5
 
6
6
  const DEFAULT_SHORTCUTS = ["alt+x"] as const;
7
7
  const STATUS_KEY = "interlude";
@@ -7,15 +7,15 @@
7
7
  */
8
8
 
9
9
  import { Type } from "typebox";
10
- import { complete, type Api, type Model, type UserMessage } from "@mariozechner/pi-ai";
10
+ import { complete, type Api, type Model, type UserMessage } from "@earendil-works/pi-ai";
11
11
  import {
12
12
  compact,
13
13
  defineTool,
14
14
  DynamicBorder,
15
15
  type ExtensionAPI,
16
16
  type ExtensionContext,
17
- } from "@mariozechner/pi-coding-agent";
18
- import { Container, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui";
17
+ } from "@earendil-works/pi-coding-agent";
18
+ import { Container, type SelectItem, SelectList, Text } from "@earendil-works/pi-tui";
19
19
 
20
20
  const LOOP_PRESETS = [
21
21
  { value: "tests", label: "Until tests pass", description: "" },
@@ -1,11 +1,17 @@
1
- import { complete, StringEnum, type Api, type Model, type UserMessage } from "@mariozechner/pi-ai";
1
+ import {
2
+ complete,
3
+ StringEnum,
4
+ type Api,
5
+ type Model,
6
+ type UserMessage,
7
+ } from "@earendil-works/pi-ai";
2
8
  import {
3
9
  defineTool,
4
10
  withFileMutationQueue,
5
11
  type ExtensionAPI,
6
12
  type ExtensionCommandContext,
7
13
  type ExtensionContext,
8
- } from "@mariozechner/pi-coding-agent";
14
+ } from "@earendil-works/pi-coding-agent";
9
15
  import { Type } from "typebox";
10
16
  import fs from "node:fs/promises";
11
17
  import path from "node:path";
@@ -10,7 +10,7 @@
10
10
  * - Windows toast: Windows Terminal (WSL)
11
11
  */
12
12
 
13
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
13
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
14
14
 
15
15
  const REVIEW_EVENT_START = "review:start";
16
16
  const REVIEW_EVENT_END = "review:end";
@@ -1,7 +1,7 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
4
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
5
5
 
6
6
  const STATUS_KEY = "openai-fast";
7
7
  const SUPPORTED_APIS = new Set([
@@ -1,7 +1,7 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
4
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
5
5
 
6
6
  const STATUS_KEY = "openai-verbosity";
7
7
  const SUPPORTED_APIS = new Set([
@@ -10,6 +10,7 @@
10
10
  * when that message is a matching review payload, even if stale. Otherwise it
11
11
  * runs a fresh review first. If that fresh review becomes stale while running,
12
12
  * /fix shows the findings and waits for an explicit rerun before applying fixes.
13
+ * context=... guides the fix pass without invalidating reusable review payloads.
13
14
  *
14
15
  * Key behavior:
15
16
  * - /review is findings-only (no direct edits).
@@ -37,9 +38,9 @@ import type {
37
38
  ExtensionAPI,
38
39
  ExtensionCommandContext,
39
40
  ExtensionContext,
40
- } from "@mariozechner/pi-coding-agent";
41
- import { supportsXhigh, type Api, type Model } from "@mariozechner/pi-ai";
42
- import { matchesKey } from "@mariozechner/pi-tui";
41
+ } from "@earendil-works/pi-coding-agent";
42
+ import { getSupportedThinkingLevels, type Api, type Model } from "@earendil-works/pi-ai";
43
+ import { matchesKey } from "@earendil-works/pi-tui";
43
44
  import { spawn, type ChildProcess } from "node:child_process";
44
45
  import { createHash } from "node:crypto";
45
46
  import path from "node:path";
@@ -173,7 +174,7 @@ const REVIEW_FOCUSES: Record<FocusName, FocusDefinition> = {
173
174
  },
174
175
  };
175
176
 
176
- const REVIEW_ADDITIONAL_CONTEXT_SECTION_PROMPT = `Additional context from user:
177
+ const ADDITIONAL_CONTEXT_SECTION_PROMPT = `Additional context from user:
177
178
  {ADDITIONAL_CONTEXT}
178
179
  `;
179
180
 
@@ -245,7 +246,7 @@ Output formatting requirements:
245
246
  - Do not include the pipe character in any cell text (including inside backticks). Avoid regex alternation patterns like (a|b); rewrite checks without pipes and separate multiple checks with semicolons.
246
247
  - Decision values must be exactly fixed or skipped.
247
248
 
248
- Review findings:
249
+ {FIX_ADDITIONAL_CONTEXT_SECTION}Review findings:
249
250
 
250
251
  {REVIEW_FINDINGS_JSON}
251
252
 
@@ -430,10 +431,16 @@ type ParsedRequest = {
430
431
  target: ReviewTarget;
431
432
  mode: ReviewRequestMode;
432
433
  models: string[];
433
- rawArgs: string;
434
+ targetExplicit: boolean;
434
435
  additionalContext?: string;
435
436
  };
436
437
 
438
+ type RequestSignaturePayload = {
439
+ target: unknown;
440
+ models: string[];
441
+ additionalContext?: string | null;
442
+ };
443
+
437
444
  type ReviewFingerprint = {
438
445
  headSha: string;
439
446
  branch: string;
@@ -878,7 +885,9 @@ Run findings-only code review in 4 parallel focuses (general, reuse, quality, ef
878
885
 
879
886
  ### Options
880
887
  - \`models=<a,b>\`: run all review focuses for each listed model.
881
- - \`context=<text>\`: add extra guidance to every review focus.
888
+ - \`context=<text>\`: add extra guidance.
889
+ - For \`/review\`, guides every review focus.
890
+ - For \`/fix\`, guides the fix pass; if \`/fix\` must run a fresh review, it guides review too.
882
891
  - For spaces, quote the value, e.g. \`context="security and backpressure"\`.
883
892
 
884
893
  ### Examples
@@ -891,6 +900,7 @@ Run findings-only code review in 4 parallel focuses (general, reuse, quality, ef
891
900
  - \`/triage https://github.com/owner/repo/pull/123\`
892
901
  - \`/fix\`
893
902
  - \`/fix help\`
903
+ - \`/fix context="do not test mocks"\`
894
904
  - \`/fix branch main models=sonnet\`
895
905
 
896
906
  ### /fix behavior
@@ -931,7 +941,12 @@ function parseKeyValueOption(
931
941
  function parseRequestArgs(args: string | undefined): ParsedRequest {
932
942
  const raw = args?.trim() ?? "";
933
943
  if (!raw) {
934
- return { target: { type: "auto" }, mode: "auto", models: [], rawArgs: raw };
944
+ return {
945
+ target: { type: "auto" },
946
+ mode: "auto",
947
+ models: [],
948
+ targetExplicit: false,
949
+ };
935
950
  }
936
951
 
937
952
  const tokens = tokenizeArgs(raw);
@@ -984,16 +999,16 @@ function parseRequestArgs(args: string | undefined): ParsedRequest {
984
999
  return "custom";
985
1000
  }
986
1001
  };
987
- const withMeta = (target: ReviewTarget): ParsedRequest => ({
1002
+ const withMeta = (target: ReviewTarget, targetExplicit: boolean): ParsedRequest => ({
988
1003
  target,
989
1004
  mode: toMode(target),
990
1005
  models,
991
- rawArgs: raw,
1006
+ targetExplicit,
992
1007
  additionalContext,
993
1008
  });
994
1009
 
995
1010
  if (modeTokens.length === 0) {
996
- return withMeta({ type: "auto" });
1011
+ return withMeta({ type: "auto" }, false);
997
1012
  }
998
1013
 
999
1014
  const mode = modeTokens[0].toLowerCase();
@@ -1004,7 +1019,7 @@ function parseRequestArgs(args: string | undefined): ParsedRequest {
1004
1019
  throw new Error(
1005
1020
  `${mode} mode does not accept positional args. Use models=... and/or context=...`,
1006
1021
  );
1007
- return withMeta({ type: mode });
1022
+ return withMeta({ type: mode }, true);
1008
1023
  }
1009
1024
 
1010
1025
  if (mode === "branch" || mode === "commit" || mode === "pr") {
@@ -1023,20 +1038,20 @@ function parseRequestArgs(args: string | undefined): ParsedRequest {
1023
1038
  );
1024
1039
  }
1025
1040
 
1026
- if (mode === "branch") return withMeta({ type: "branch", branch: rest[0] });
1027
- if (mode === "commit") return withMeta({ type: "commit", sha: rest[0] });
1028
- return withMeta({ type: "pr", ref: rest[0] });
1041
+ if (mode === "branch") return withMeta({ type: "branch", branch: rest[0] }, true);
1042
+ if (mode === "commit") return withMeta({ type: "commit", sha: rest[0] }, true);
1043
+ return withMeta({ type: "pr", ref: rest[0] }, true);
1029
1044
  }
1030
1045
  if (mode === "folder") {
1031
1046
  if (rest.length === 0)
1032
1047
  throw new Error("folder mode requires at least one path (e.g. /review folder src docs)");
1033
- return withMeta({ type: "folder", paths: rest.map((p) => p.trim()).filter(Boolean) });
1048
+ return withMeta({ type: "folder", paths: rest.map((p) => p.trim()).filter(Boolean) }, true);
1034
1049
  }
1035
1050
  if (mode === "custom") {
1036
1051
  const instructions = rest.join(" ").trim();
1037
1052
  if (!instructions)
1038
1053
  throw new Error('custom mode requires instructions (e.g. /review custom "focus on auth")');
1039
- return withMeta({ type: "custom", instructions });
1054
+ return withMeta({ type: "custom", instructions }, true);
1040
1055
  }
1041
1056
 
1042
1057
  throw new Error(
@@ -1044,7 +1059,14 @@ function parseRequestArgs(args: string | undefined): ParsedRequest {
1044
1059
  );
1045
1060
  }
1046
1061
 
1047
- function buildRequestSignature(request: ParsedRequest): string {
1062
+ function normalizeRequestModels(models: string[]): string[] {
1063
+ return Array.from(new Set(models)).sort((a, b) => a.localeCompare(b));
1064
+ }
1065
+
1066
+ function buildRequestSignaturePayload(
1067
+ request: ParsedRequest,
1068
+ options: { includeAdditionalContext?: boolean } = {},
1069
+ ): RequestSignaturePayload {
1048
1070
  const additionalContext = request.additionalContext?.trim();
1049
1071
  const target =
1050
1072
  request.target.type === "folder"
@@ -1062,15 +1084,55 @@ function buildRequestSignature(request: ParsedRequest): string {
1062
1084
  ? { type: "commit", sha: request.target.sha }
1063
1085
  : { type: request.target.type };
1064
1086
 
1065
- return JSON.stringify({
1087
+ const payload: RequestSignaturePayload = {
1066
1088
  target,
1067
- models: Array.from(new Set(request.models)).sort((a, b) => a.localeCompare(b)),
1068
- additionalContext: additionalContext && additionalContext.length > 0 ? additionalContext : null,
1069
- });
1089
+ models: normalizeRequestModels(request.models),
1090
+ };
1091
+ if (options.includeAdditionalContext ?? true) {
1092
+ payload.additionalContext =
1093
+ additionalContext && additionalContext.length > 0 ? additionalContext : null;
1094
+ }
1095
+ return payload;
1070
1096
  }
1071
1097
 
1072
- function reviewMatchesRequest(details: ReviewMessageDetails, request: ParsedRequest): boolean {
1073
- return details.request.signature === buildRequestSignature(request);
1098
+ function buildRequestSignature(request: ParsedRequest): string {
1099
+ return JSON.stringify(buildRequestSignaturePayload(request));
1100
+ }
1101
+
1102
+ function buildContextlessRequestSignature(request: ParsedRequest): string {
1103
+ return JSON.stringify(buildRequestSignaturePayload(request, { includeAdditionalContext: false }));
1104
+ }
1105
+
1106
+ function parseRequestSignaturePayload(signature: string): RequestSignaturePayload | null {
1107
+ try {
1108
+ const payload: unknown = JSON.parse(signature);
1109
+ if (!payload || typeof payload !== "object") return null;
1110
+ const { target, models } = payload as { target?: unknown; models?: unknown };
1111
+ if (target === undefined || !Array.isArray(models)) return null;
1112
+ const parsedModels = models.filter((model): model is string => typeof model === "string");
1113
+ if (parsedModels.length !== models.length) return null;
1114
+ return { target, models: normalizeRequestModels(parsedModels) };
1115
+ } catch {
1116
+ return null;
1117
+ }
1118
+ }
1119
+
1120
+ function buildContextlessStoredRequestSignature(signature: string): string | null {
1121
+ const payload = parseRequestSignaturePayload(signature);
1122
+ if (!payload) return null;
1123
+ return JSON.stringify({ target: payload.target, models: payload.models });
1124
+ }
1125
+
1126
+ function isOpenEndedFixRequest(request: ParsedRequest): boolean {
1127
+ return !request.targetExplicit && request.models.length === 0;
1128
+ }
1129
+
1130
+ function reviewMatchesFixRequest(details: ReviewMessageDetails, request: ParsedRequest): boolean {
1131
+ if (isOpenEndedFixRequest(request)) return true;
1132
+ return (
1133
+ buildContextlessStoredRequestSignature(details.request.signature) ===
1134
+ buildContextlessRequestSignature(request)
1135
+ );
1074
1136
  }
1075
1137
 
1076
1138
  // --- Git & fingerprinting ---
@@ -1794,17 +1856,19 @@ function buildScopeInstructions(scope: ResolvedScope): string {
1794
1856
  }
1795
1857
  }
1796
1858
 
1859
+ function buildAdditionalContextSection(additionalContext: string | undefined): string {
1860
+ const trimmed = additionalContext?.trim();
1861
+ if (!trimmed) return "";
1862
+ return ADDITIONAL_CONTEXT_SECTION_PROMPT.replace("{ADDITIONAL_CONTEXT}", () => trimmed);
1863
+ }
1864
+
1797
1865
  function buildFocusPrompt(
1798
1866
  focus: FocusName,
1799
1867
  scopeInstructions: string,
1800
1868
  projectGuidelines: string | null,
1801
1869
  additionalContext: string | undefined,
1802
1870
  ): string {
1803
- const additionalContextSection = additionalContext?.trim()
1804
- ? REVIEW_ADDITIONAL_CONTEXT_SECTION_PROMPT.replace("{ADDITIONAL_CONTEXT}", () =>
1805
- additionalContext.trim(),
1806
- )
1807
- : "";
1871
+ const additionalContextSection = buildAdditionalContextSection(additionalContext);
1808
1872
  const projectGuidelinesSection = projectGuidelines
1809
1873
  ? REVIEW_PROJECT_GUIDELINES_SECTION_PROMPT.replace(
1810
1874
  "{PROJECT_GUIDELINES}",
@@ -3225,7 +3289,7 @@ function createResolvedReviewProviderCandidateFromModel(
3225
3289
  return createResolvedReviewProviderCandidate({
3226
3290
  baseModelArg: `${model.provider}/${model.id}`,
3227
3291
  supportsThinking: model.reasoning,
3228
- supportsXhigh: supportsXhigh(model),
3292
+ supportsXhigh: getSupportedThinkingLevels(model).includes("xhigh"),
3229
3293
  });
3230
3294
  }
3231
3295
 
@@ -3353,7 +3417,9 @@ async function resolveModels(
3353
3417
  createResolvedReviewProviderCandidate({
3354
3418
  baseModelArg,
3355
3419
  supportsThinking: ctx.model?.reasoning,
3356
- supportsXhigh: ctx.model ? supportsXhigh(ctx.model) : undefined,
3420
+ supportsXhigh: ctx.model
3421
+ ? getSupportedThinkingLevels(ctx.model).includes("xhigh")
3422
+ : undefined,
3357
3423
  }),
3358
3424
  ],
3359
3425
  }),
@@ -4083,7 +4149,10 @@ async function runTriagePipeline(
4083
4149
 
4084
4150
  // --- Command handlers ---
4085
4151
 
4086
- function buildFixPrompt(reviewMessageDetails: ReviewMessageDetails): string {
4152
+ function buildFixPrompt(
4153
+ reviewMessageDetails: ReviewMessageDetails,
4154
+ additionalContext: string | undefined,
4155
+ ): string {
4087
4156
  const worklistPayload = JSON.stringify(
4088
4157
  {
4089
4158
  scope: reviewMessageDetails.scope,
@@ -4101,8 +4170,12 @@ function buildFixPrompt(reviewMessageDetails: ReviewMessageDetails): string {
4101
4170
  null,
4102
4171
  2,
4103
4172
  );
4173
+ const additionalContextSection = buildAdditionalContextSection(additionalContext);
4104
4174
 
4105
- return FIX_PROMPT.replace("{REVIEW_FINDINGS_JSON}", () => worklistPayload);
4175
+ return FIX_PROMPT.replace(
4176
+ "{FIX_ADDITIONAL_CONTEXT_SECTION}",
4177
+ () => additionalContextSection,
4178
+ ).replace("{REVIEW_FINDINGS_JSON}", () => worklistPayload);
4106
4179
  }
4107
4180
 
4108
4181
  function parseTriagePrRef(
@@ -4259,7 +4332,7 @@ export default function reviewExtension(pi: ExtensionAPI) {
4259
4332
 
4260
4333
  pi.registerCommand("fix", {
4261
4334
  description:
4262
- "Fix findings when the last message is a matching /review result. Otherwise, runs review first. If the last review is stale, /fix warns and continues; if a /fix-started review goes stale mid-run, /fix shows the findings and applies no fixes. Use /review help for modes/options.",
4335
+ "Fix findings when the last message is a matching /review result. Otherwise, runs review first. context=... guides the fix pass without forcing a fresh review. If the last review is stale, /fix warns and continues; if a /fix-started review goes stale mid-run, /fix shows the findings and applies no fixes. Use /review help for modes/options.",
4263
4336
  getArgumentCompletions: getReviewArgumentCompletions,
4264
4337
  handler: async (args, ctx) => {
4265
4338
  const request = parseCommandRequest(pi, args, ctx);
@@ -4273,9 +4346,9 @@ export default function reviewExtension(pi: ExtensionAPI) {
4273
4346
 
4274
4347
  try {
4275
4348
  let reviewDetails = getLastMessageReviewDetails(ctx);
4276
- const lastReviewMatchesRequest =
4277
- !request.rawArgs ||
4278
- (reviewDetails ? reviewMatchesRequest(reviewDetails, request) : false);
4349
+ const lastReviewMatchesRequest = reviewDetails
4350
+ ? reviewMatchesFixRequest(reviewDetails, request)
4351
+ : false;
4279
4352
  const shouldRunFreshReview = !reviewDetails || !lastReviewMatchesRequest;
4280
4353
 
4281
4354
  if (reviewDetails && !shouldRunFreshReview) {
@@ -4332,7 +4405,7 @@ export default function reviewExtension(pi: ExtensionAPI) {
4332
4405
  return;
4333
4406
  }
4334
4407
 
4335
- const fixPrompt = buildFixPrompt(reviewDetails);
4408
+ const fixPrompt = buildFixPrompt(reviewDetails, request.additionalContext);
4336
4409
  if (ctx.isIdle()) {
4337
4410
  pi.sendUserMessage(fixPrompt);
4338
4411
  } else {