@oh-my-pi/pi-coding-agent 16.0.5 → 16.0.6

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 (223) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/cli.js +1927 -1376
  3. package/dist/types/advisor/advise-tool.d.ts +22 -19
  4. package/dist/types/autoresearch/tools/init-experiment.d.ts +13 -17
  5. package/dist/types/autoresearch/tools/log-experiment.d.ts +17 -19
  6. package/dist/types/autoresearch/tools/run-experiment.d.ts +3 -4
  7. package/dist/types/autoresearch/tools/update-notes.d.ts +4 -5
  8. package/dist/types/cli/ttsr-cli.d.ts +39 -0
  9. package/dist/types/commands/ttsr.d.ts +57 -0
  10. package/dist/types/commit/agentic/tools/analyze-file.d.ts +4 -5
  11. package/dist/types/commit/agentic/tools/git-file-diff.d.ts +4 -5
  12. package/dist/types/commit/agentic/tools/git-hunk.d.ts +5 -6
  13. package/dist/types/commit/agentic/tools/git-overview.d.ts +4 -5
  14. package/dist/types/commit/agentic/tools/propose-changelog.d.ts +23 -24
  15. package/dist/types/commit/agentic/tools/propose-commit.d.ts +11 -32
  16. package/dist/types/commit/agentic/tools/recent-commits.d.ts +3 -4
  17. package/dist/types/commit/agentic/tools/schemas.d.ts +6 -27
  18. package/dist/types/commit/agentic/tools/split-commit.d.ts +28 -49
  19. package/dist/types/commit/changelog/generate.d.ts +12 -13
  20. package/dist/types/commit/shared-llm.d.ts +10 -37
  21. package/dist/types/config/config-file.d.ts +4 -4
  22. package/dist/types/config/keybindings.d.ts +5 -0
  23. package/dist/types/config/models-config-schema.d.ts +625 -990
  24. package/dist/types/config/models-config.d.ts +229 -217
  25. package/dist/types/config/settings-schema.d.ts +53 -23
  26. package/dist/types/edit/hashline/params.d.ts +7 -11
  27. package/dist/types/edit/index.d.ts +2 -1
  28. package/dist/types/edit/modes/apply-patch.d.ts +4 -5
  29. package/dist/types/edit/modes/patch.d.ts +15 -24
  30. package/dist/types/edit/modes/replace.d.ts +16 -17
  31. package/dist/types/eval/js/index.d.ts +1 -0
  32. package/dist/types/extensibility/custom-commands/types.d.ts +6 -3
  33. package/dist/types/extensibility/custom-tools/types.d.ts +8 -5
  34. package/dist/types/extensibility/extensions/types.d.ts +6 -3
  35. package/dist/types/extensibility/hooks/types.d.ts +7 -4
  36. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +13 -5
  37. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +17 -0
  38. package/dist/types/extensibility/typebox.d.ts +80 -58
  39. package/dist/types/goals/tools/goal-tool.d.ts +11 -24
  40. package/dist/types/index.d.ts +2 -0
  41. package/dist/types/lsp/index.d.ts +11 -26
  42. package/dist/types/lsp/types.d.ts +12 -28
  43. package/dist/types/mcp/client.d.ts +8 -0
  44. package/dist/types/modes/components/btw-panel.d.ts +1 -0
  45. package/dist/types/modes/components/custom-editor.d.ts +3 -1
  46. package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
  47. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  48. package/dist/types/modes/interactive-mode.d.ts +3 -0
  49. package/dist/types/modes/setup-wizard/index.d.ts +1 -0
  50. package/dist/types/modes/setup-wizard/startup-splash.d.ts +7 -0
  51. package/dist/types/modes/theme/theme.d.ts +1 -1
  52. package/dist/types/modes/types.d.ts +3 -0
  53. package/dist/types/sdk.d.ts +5 -0
  54. package/dist/types/session/agent-session.d.ts +4 -0
  55. package/dist/types/startup-splash.d.ts +12 -0
  56. package/dist/types/task/types.d.ts +47 -48
  57. package/dist/types/tools/ask.d.ts +26 -27
  58. package/dist/types/tools/ast-edit.d.ts +17 -17
  59. package/dist/types/tools/ast-grep.d.ts +12 -13
  60. package/dist/types/tools/bash.d.ts +20 -17
  61. package/dist/types/tools/browser.d.ts +46 -71
  62. package/dist/types/tools/checkpoint.d.ts +14 -15
  63. package/dist/types/tools/debug.d.ts +82 -145
  64. package/dist/types/tools/eval.d.ts +30 -40
  65. package/dist/types/tools/find.d.ts +17 -18
  66. package/dist/types/tools/gh.d.ts +49 -78
  67. package/dist/types/tools/image-gen.d.ts +20 -36
  68. package/dist/types/tools/inspect-image.d.ts +10 -11
  69. package/dist/types/tools/irc.d.ts +22 -33
  70. package/dist/types/tools/job.d.ts +11 -12
  71. package/dist/types/tools/learn.d.ts +21 -28
  72. package/dist/types/tools/manage-skill.d.ts +13 -22
  73. package/dist/types/tools/memory-edit.d.ts +15 -24
  74. package/dist/types/tools/memory-recall.d.ts +7 -8
  75. package/dist/types/tools/memory-reflect.d.ts +9 -10
  76. package/dist/types/tools/memory-retain.d.ts +13 -14
  77. package/dist/types/tools/read.d.ts +7 -8
  78. package/dist/types/tools/resolve.d.ts +11 -18
  79. package/dist/types/tools/review.d.ts +9 -15
  80. package/dist/types/tools/search-tool-bm25.d.ts +9 -10
  81. package/dist/types/tools/search.d.ts +16 -17
  82. package/dist/types/tools/ssh.d.ts +14 -15
  83. package/dist/types/tools/todo.d.ts +27 -43
  84. package/dist/types/tools/tts.d.ts +8 -9
  85. package/dist/types/tools/write.d.ts +9 -10
  86. package/dist/types/tui/index.d.ts +1 -0
  87. package/dist/types/tui/width-aware-text.d.ts +23 -0
  88. package/dist/types/utils/markit.d.ts +10 -1
  89. package/dist/types/web/search/index.d.ts +17 -28
  90. package/dist/types/web/search/providers/perplexity.d.ts +0 -2
  91. package/dist/types/web/search/types.d.ts +32 -26
  92. package/package.json +14 -13
  93. package/scripts/omp +1 -1
  94. package/src/advisor/__tests__/advisor.test.ts +44 -1
  95. package/src/advisor/advise-tool.ts +34 -11
  96. package/src/autoresearch/tools/init-experiment.ts +13 -16
  97. package/src/autoresearch/tools/log-experiment.ts +15 -18
  98. package/src/autoresearch/tools/run-experiment.ts +3 -3
  99. package/src/autoresearch/tools/update-notes.ts +4 -4
  100. package/src/cli/ttsr-cli.ts +995 -0
  101. package/src/cli-commands.ts +1 -0
  102. package/src/cli.ts +7 -1
  103. package/src/commands/ttsr.ts +125 -0
  104. package/src/commit/agentic/tools/analyze-file.ts +4 -4
  105. package/src/commit/agentic/tools/git-file-diff.ts +4 -4
  106. package/src/commit/agentic/tools/git-hunk.ts +7 -5
  107. package/src/commit/agentic/tools/git-overview.ts +4 -4
  108. package/src/commit/agentic/tools/propose-changelog.ts +18 -15
  109. package/src/commit/agentic/tools/propose-commit.ts +6 -6
  110. package/src/commit/agentic/tools/recent-commits.ts +3 -3
  111. package/src/commit/agentic/tools/schemas.ts +8 -20
  112. package/src/commit/agentic/tools/split-commit.ts +19 -23
  113. package/src/commit/analysis/summary.ts +7 -5
  114. package/src/commit/changelog/generate.ts +15 -11
  115. package/src/commit/shared-llm.ts +17 -24
  116. package/src/config/config-file.ts +13 -15
  117. package/src/config/keybindings.ts +6 -0
  118. package/src/config/models-config-schema.ts +206 -179
  119. package/src/config/settings-schema.ts +34 -0
  120. package/src/discovery/builtin-rules/index.ts +2 -0
  121. package/src/discovery/builtin-rules/ts-import-type.md +2 -2
  122. package/src/discovery/builtin-rules/ts-no-any.md +11 -2
  123. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +55 -0
  124. package/src/edit/hashline/params.ts +12 -11
  125. package/src/edit/index.ts +5 -4
  126. package/src/edit/modes/apply-patch.ts +4 -4
  127. package/src/edit/modes/patch.ts +15 -18
  128. package/src/edit/modes/replace.ts +13 -17
  129. package/src/edit/renderer.ts +0 -1
  130. package/src/eval/agent-bridge.ts +11 -13
  131. package/src/eval/completion-bridge.ts +25 -17
  132. package/src/eval/js/context-manager.ts +17 -2
  133. package/src/eval/js/index.ts +1 -1
  134. package/src/eval/py/executor.ts +2 -2
  135. package/src/extensibility/custom-commands/loader.ts +5 -3
  136. package/src/extensibility/custom-commands/types.ts +6 -3
  137. package/src/extensibility/custom-tools/loader.ts +4 -2
  138. package/src/extensibility/custom-tools/types.ts +8 -5
  139. package/src/extensibility/extensions/loader.ts +4 -2
  140. package/src/extensibility/extensions/types.ts +6 -3
  141. package/src/extensibility/hooks/loader.ts +5 -2
  142. package/src/extensibility/hooks/types.ts +7 -4
  143. package/src/extensibility/legacy-pi-ai-shim.ts +42 -5
  144. package/src/extensibility/legacy-pi-coding-agent-shim.ts +113 -0
  145. package/src/extensibility/plugins/legacy-pi-compat.ts +13 -13
  146. package/src/extensibility/tool-proxy.ts +4 -1
  147. package/src/extensibility/typebox.ts +778 -251
  148. package/src/goals/guided-setup.ts +12 -3
  149. package/src/goals/tools/goal-tool.ts +6 -6
  150. package/src/index.ts +2 -0
  151. package/src/internal-urls/docs-index.generated.ts +11 -9
  152. package/src/lsp/types.ts +13 -27
  153. package/src/main.ts +19 -18
  154. package/src/mcp/client.ts +38 -13
  155. package/src/mcp/render.ts +102 -89
  156. package/src/modes/components/agent-hub.ts +11 -4
  157. package/src/modes/components/btw-panel.ts +5 -1
  158. package/src/modes/components/custom-editor.ts +18 -0
  159. package/src/modes/components/status-line/component.ts +8 -1
  160. package/src/modes/components/tool-execution.ts +17 -10
  161. package/src/modes/controllers/btw-controller.ts +69 -1
  162. package/src/modes/controllers/input-controller.ts +29 -0
  163. package/src/modes/interactive-mode.ts +38 -8
  164. package/src/modes/setup-wizard/index.ts +1 -0
  165. package/src/modes/setup-wizard/scenes/sign-in.ts +77 -5
  166. package/src/modes/setup-wizard/startup-splash.ts +107 -0
  167. package/src/modes/theme/theme.ts +133 -143
  168. package/src/modes/types.ts +3 -0
  169. package/src/modes/utils/context-usage.ts +9 -5
  170. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  171. package/src/prompts/system/system-prompt.md +1 -0
  172. package/src/sdk.ts +21 -4
  173. package/src/session/agent-session.ts +160 -33
  174. package/src/session/session-history-format.ts +11 -2
  175. package/src/session/snapcompact-inline.ts +1 -1
  176. package/src/slash-commands/builtin-registry.ts +4 -11
  177. package/src/startup-splash.ts +19 -0
  178. package/src/task/executor.ts +11 -6
  179. package/src/task/types.ts +44 -41
  180. package/src/tool-discovery/tool-index.ts +17 -4
  181. package/src/tools/ask.ts +14 -14
  182. package/src/tools/ast-edit.ts +17 -14
  183. package/src/tools/ast-grep.ts +10 -9
  184. package/src/tools/bash.ts +15 -10
  185. package/src/tools/browser/launch.ts +13 -0
  186. package/src/tools/browser.ts +26 -32
  187. package/src/tools/checkpoint.ts +7 -7
  188. package/src/tools/debug.ts +72 -69
  189. package/src/tools/eval.ts +18 -19
  190. package/src/tools/find.ts +20 -13
  191. package/src/tools/gh.ts +29 -49
  192. package/src/tools/image-gen.ts +27 -32
  193. package/src/tools/inspect-image.ts +8 -9
  194. package/src/tools/irc.ts +12 -12
  195. package/src/tools/job.ts +6 -6
  196. package/src/tools/learn.ts +11 -14
  197. package/src/tools/manage-skill.ts +19 -23
  198. package/src/tools/memory-edit.ts +8 -8
  199. package/src/tools/memory-recall.ts +4 -4
  200. package/src/tools/memory-reflect.ts +5 -5
  201. package/src/tools/memory-retain.ts +9 -11
  202. package/src/tools/puppeteer/02_stealth_hairline.txt +1 -1
  203. package/src/tools/puppeteer/04_stealth_iframe.txt +4 -4
  204. package/src/tools/puppeteer/05_stealth_webgl.txt +1 -1
  205. package/src/tools/puppeteer/10_stealth_plugins.txt +6 -4
  206. package/src/tools/puppeteer/12_stealth_codecs.txt +2 -2
  207. package/src/tools/puppeteer/13_stealth_worker.txt +1 -1
  208. package/src/tools/read.ts +169 -13
  209. package/src/tools/report-tool-issue.ts +6 -6
  210. package/src/tools/resolve.ts +6 -6
  211. package/src/tools/review.ts +10 -12
  212. package/src/tools/search-tool-bm25.ts +5 -5
  213. package/src/tools/search.ts +20 -29
  214. package/src/tools/ssh.ts +8 -8
  215. package/src/tools/todo.ts +16 -19
  216. package/src/tools/tts.ts +16 -15
  217. package/src/tools/write.ts +5 -5
  218. package/src/tui/index.ts +1 -0
  219. package/src/tui/width-aware-text.ts +58 -0
  220. package/src/utils/markit.ts +17 -2
  221. package/src/web/search/index.ts +9 -9
  222. package/src/web/search/providers/perplexity.ts +373 -126
  223. package/src/web/search/types.ts +28 -48
@@ -39,6 +39,7 @@ export const commands: CommandEntry[] = [
39
39
  { name: "usage", load: () => import("./commands/usage").then(m => m.default) },
40
40
  { name: "tiny-models", load: () => import("./commands/tiny-models").then(m => m.default) },
41
41
  { name: "token", load: () => import("./commands/token").then(m => m.default) },
42
+ { name: "ttsr", load: () => import("./commands/ttsr").then(m => m.default) },
42
43
  { name: "worktree", load: () => import("./commands/worktree").then(m => m.default), aliases: ["wt"] },
43
44
  { name: "search", load: () => import("./commands/web-search").then(m => m.default), aliases: ["q"] },
44
45
  ];
package/src/cli.ts CHANGED
@@ -275,7 +275,13 @@ export async function runCli(argv: string[]): Promise<void> {
275
275
  // Declare this module as the worker-host entry now that the active profile
276
276
  // is resolved. The worker-host module is side-effect-free; importing
277
277
  // `@oh-my-pi/pi-utils/env` here would snapshot the wrong agent `.env`.
278
- declareWorkerHostEntry();
278
+ // Gated on `import.meta.main`: only the real CLI process entry is a valid
279
+ // worker host. Worker-thread re-entry already returned above at the
280
+ // `__omp_worker_` dispatch, and importers (`runCli` in profile-CLI tests,
281
+ // SDK embedding) have `import.meta.main === false` — declaring there would
282
+ // poison `workerHostEntry()` for the whole test process, forcing eval/stats/
283
+ // browser workers onto the same-realm inline fallback.
284
+ if (import.meta.main) declareWorkerHostEntry();
279
285
 
280
286
  if (resolvedArgv[0] === "--smoke-test") {
281
287
  await runSmokeTest();
@@ -0,0 +1,125 @@
1
+ import { existsSync } from "node:fs";
2
+ import * as path from "node:path";
3
+ /**
4
+ * `omp ttsr` — inspect and test Time-Traveling Stream Rules.
5
+ *
6
+ * `omp ttsr test` feeds a snippet (inline, --file, or stdin) through the real
7
+ * TTSR matching pipeline and reports which rules would trigger. `omp ttsr list`
8
+ * shows every TTSR-registered rule the current project/user config would load.
9
+ */
10
+ import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
11
+ import {
12
+ runTtsrCommand,
13
+ TTSR_ACTIONS,
14
+ TTSR_SOURCES,
15
+ type TtsrCommandArgs,
16
+ type TtsrScanArgs,
17
+ type TtsrTestArgs,
18
+ } from "../cli/ttsr-cli";
19
+ import type { TtsrMatchSource } from "../export/ttsr";
20
+
21
+ export default class Ttsr extends Command {
22
+ static description = "Inspect and test Time-Traveling Stream Rules (TTSR)";
23
+
24
+ static args = {
25
+ action: Args.string({
26
+ description: "TTSR action",
27
+ required: false,
28
+ options: TTSR_ACTIONS,
29
+ }),
30
+ snippet: Args.string({
31
+ description: "Inline snippet text to test (ttsr test) or directory to scan (ttsr scan)",
32
+ required: false,
33
+ }),
34
+ };
35
+
36
+ static flags = {
37
+ file: Flags.string({ description: "Snippet file path, or - for stdin (ttsr test)" }),
38
+ rule: Flags.string({
39
+ char: "r",
40
+ description: "Rule markdown file to test in isolation (skips project rule loading)",
41
+ }),
42
+ source: Flags.string({
43
+ description: "Match source: text, thinking, or tool (inferred from --file when omitted)",
44
+ options: TTSR_SOURCES,
45
+ }),
46
+ tool: Flags.string({
47
+ description: "Tool name when source is tool (e.g. edit, write); defaults to edit",
48
+ }),
49
+ path: Flags.string({
50
+ char: "p",
51
+ description: "Candidate file path for scope/glob matching and AST language inference",
52
+ }),
53
+ verbose: Flags.boolean({ char: "v", description: "Show every evaluated rule, not just triggered ones" }),
54
+ json: Flags.boolean({ description: "Output JSON" }),
55
+ "no-gitignore": Flags.boolean({ description: "Include files excluded by .gitignore (ttsr scan)" }),
56
+ "max-bytes": Flags.integer({
57
+ description: "Maximum file size to scan in bytes; 0 disables the limit (ttsr scan)",
58
+ }),
59
+ };
60
+
61
+ static examples = [
62
+ "omp ttsr list",
63
+ "omp ttsr test 'const x: any = 1'",
64
+ "omp ttsr test src/foo.ts",
65
+ "omp ttsr test --file src/foo.ts",
66
+ "omp ttsr test --file src/foo.ts --source text",
67
+ "omp ttsr test --rule .omp/rules/no-any.md --source tool --path src/foo.ts 'const x: any = 1'",
68
+ "echo 'Box::leak(&mut v)' | omp ttsr test --file - --path src/lib.rs",
69
+ "omp ttsr test --source tool --tool edit --path src/foo.ts 'const x: any = 1'",
70
+ "omp ttsr scan",
71
+ "omp ttsr scan src/",
72
+ "omp ttsr scan -r .omp/rules/no-any.md src/",
73
+ ];
74
+
75
+ async run(): Promise<void> {
76
+ const { args, flags } = await this.parse(Ttsr);
77
+ const action = (args.action ?? "list") as (typeof TTSR_ACTIONS)[number];
78
+
79
+ // A positional that resolves to an existing file is a snippet file, not
80
+ // inline text — so `omp ttsr test src/foo.ts` works without --file.
81
+ // --file always wins over the positional.
82
+ let file = flags.file;
83
+ let snippet = args.snippet;
84
+ if (action === "test" && snippet && !file) {
85
+ const resolved = path.resolve(snippet);
86
+ if (existsSync(resolved)) {
87
+ file = resolved;
88
+ snippet = undefined;
89
+ }
90
+ }
91
+
92
+ const test: TtsrTestArgs | undefined =
93
+ action === "test"
94
+ ? {
95
+ snippet,
96
+ file,
97
+ rule: flags.rule,
98
+ source: flags.source as TtsrMatchSource | undefined,
99
+ tool: flags.tool,
100
+ filePath: flags.path,
101
+ verbose: flags.verbose,
102
+ }
103
+ : undefined;
104
+
105
+ const scan: TtsrScanArgs | undefined =
106
+ action === "scan"
107
+ ? {
108
+ directory: args.snippet,
109
+ rule: flags.rule,
110
+ gitignore: !flags["no-gitignore"],
111
+ maxBytes: flags["max-bytes"],
112
+ verbose: flags.verbose,
113
+ }
114
+ : undefined;
115
+
116
+ const cmd: TtsrCommandArgs = {
117
+ action,
118
+ test,
119
+ scan,
120
+ json: flags.json,
121
+ };
122
+
123
+ await runTtsrCommand(cmd);
124
+ }
125
+ }
@@ -1,5 +1,5 @@
1
1
  import { prompt } from "@oh-my-pi/pi-utils";
2
- import { z } from "zod/v4";
2
+ import { type } from "arktype";
3
3
  import analyzeFilePrompt from "../../../commit/agentic/prompts/analyze-file.md" with { type: "text" };
4
4
  import type { CommitAgentState } from "../../../commit/agentic/state";
5
5
  import type { NumstatEntry } from "../../../commit/types";
@@ -12,9 +12,9 @@ import type { TaskParams } from "../../../task/types";
12
12
  import type { ToolSession } from "../../../tools";
13
13
  import { getFilePriority } from "./git-file-diff";
14
14
 
15
- const analyzeFileSchema = z.object({
16
- files: z.array(z.string().describe("file path")).min(1),
17
- goal: z.string().describe("analysis focus").optional(),
15
+ const analyzeFileSchema = type({
16
+ files: type("string").describe("file path").array().atLeastLength(1),
17
+ "goal?": type("string").describe("analysis focus"),
18
18
  });
19
19
 
20
20
  const analyzeFileOutputSchema = {
@@ -1,4 +1,4 @@
1
- import { z } from "zod/v4";
1
+ import { type } from "arktype";
2
2
  import type { CommitAgentState } from "../../../commit/agentic/state";
3
3
  import type { CustomTool } from "../../../extensibility/custom-tools/types";
4
4
  import * as git from "../../../utils/git";
@@ -131,9 +131,9 @@ function processDiffs(files: string[], diffs: Map<string, string>): { result: st
131
131
  return { result: parts.join("\n\n"), truncatedFiles };
132
132
  }
133
133
 
134
- const gitFileDiffSchema = z.object({
135
- files: z.array(z.string().describe("file to diff")).min(1).max(10),
136
- staged: z.boolean().describe("use staged changes (default true)").optional(),
134
+ const gitFileDiffSchema = type({
135
+ files: type("string").describe("file to diff").array().atLeastLength(1).atMostLength(10),
136
+ "staged?": type("boolean").describe("use staged changes (default true)"),
137
137
  });
138
138
 
139
139
  export function createGitFileDiffTool(cwd: string, state: CommitAgentState): CustomTool<typeof gitFileDiffSchema> {
@@ -1,12 +1,14 @@
1
- import { z } from "zod/v4";
1
+ import { type } from "arktype";
2
2
  import type { DiffHunk, FileHunks } from "../../../commit/types";
3
3
  import type { CustomTool } from "../../../extensibility/custom-tools/types";
4
4
  import * as git from "../../../utils/git";
5
5
 
6
- const gitHunkSchema = z.object({
7
- file: z.string().describe("file path"),
8
- hunks: z.array(z.number().describe("1-based hunk index")).min(1).optional(),
9
- staged: z.boolean().describe("use staged changes (default true)").optional(),
6
+ const hunkIndexType = type("number").describe("1-based hunk index");
7
+
8
+ const gitHunkSchema = type({
9
+ file: type("string").describe("file path"),
10
+ "hunks?": hunkIndexType.array().atLeastLength(1),
11
+ "staged?": type("boolean").describe("use staged changes (default true)"),
10
12
  });
11
13
 
12
14
  function selectHunks(fileHunks: FileHunks, requested?: number[]): DiffHunk[] {
@@ -1,4 +1,4 @@
1
- import { z } from "zod/v4";
1
+ import { type } from "arktype";
2
2
  import type { CommitAgentState, GitOverviewSnapshot } from "../../../commit/agentic/state";
3
3
  import { extractScopeCandidates } from "../../../commit/analysis/scope";
4
4
  import type { CustomTool } from "../../../extensibility/custom-tools/types";
@@ -42,9 +42,9 @@ function filterExcludedFiles(files: string[]): { filtered: string[]; excluded: s
42
42
  return { filtered, excluded };
43
43
  }
44
44
 
45
- const gitOverviewSchema = z.object({
46
- staged: z.boolean().describe("use staged changes (default true)").optional(),
47
- include_untracked: z.boolean().describe("include untracked when unstaged").optional(),
45
+ const gitOverviewSchema = type({
46
+ "staged?": type("boolean").describe("use staged changes (default true)"),
47
+ "include_untracked?": type("boolean").describe("include untracked when unstaged"),
48
48
  });
49
49
 
50
50
  export function createGitOverviewTool(cwd: string, state: CommitAgentState): CustomTool<typeof gitOverviewSchema> {
@@ -1,27 +1,30 @@
1
- import { z } from "zod/v4";
1
+ import { type } from "arktype";
2
2
  import type { CommitAgentState } from "../../../commit/agentic/state";
3
3
  import { CHANGELOG_CATEGORIES, type ChangelogCategory } from "../../../commit/types";
4
4
  import type { CustomTool } from "../../../extensibility/custom-tools/types";
5
5
 
6
- const changelogEntryProperties = CHANGELOG_CATEGORIES.reduce<Record<ChangelogCategory, z.ZodType>>(
7
- (acc, category) => {
8
- acc[category] = z.array(z.string()).optional();
9
- return acc;
10
- },
11
- {} as Record<ChangelogCategory, z.ZodType>,
12
- );
6
+ const changelogCategoryProperties = {
7
+ "Breaking Changes?": "string[]",
8
+ "Added?": "string[]",
9
+ "Changed?": "string[]",
10
+ "Deprecated?": "string[]",
11
+ "Removed?": "string[]",
12
+ "Fixed?": "string[]",
13
+ "Security?": "string[]",
14
+ } as const;
13
15
 
14
- const changelogEntriesSchema = z.object(changelogEntryProperties);
15
- const changelogDeletionsSchema = z.object(changelogEntryProperties).describe("entries to remove");
16
+ const changelogEntriesSchema = type({
17
+ ...changelogCategoryProperties,
18
+ });
16
19
 
17
- const changelogEntrySchema = z.object({
18
- path: z.string(),
20
+ const changelogEntrySchema = type({
21
+ path: "string",
19
22
  entries: changelogEntriesSchema,
20
- deletions: changelogDeletionsSchema.optional(),
23
+ "deletions?": changelogEntriesSchema.describe("entries to remove"),
21
24
  });
22
25
 
23
- const proposeChangelogSchema = z.object({
24
- entries: z.array(changelogEntrySchema),
26
+ const proposeChangelogSchema = type({
27
+ entries: changelogEntrySchema.array(),
25
28
  });
26
29
 
27
30
  interface ChangelogResponse {
@@ -1,4 +1,4 @@
1
- import { z } from "zod/v4";
1
+ import { type } from "arktype";
2
2
  import type { CommitAgentState } from "../../../commit/agentic/state";
3
3
  import {
4
4
  capDetails,
@@ -15,12 +15,12 @@ import type { CustomTool } from "../../../extensibility/custom-tools/types";
15
15
  import * as git from "../../../utils/git";
16
16
  import { commitTypeSchema, detailSchema } from "./schemas.js";
17
17
 
18
- const proposeCommitSchema = z.object({
18
+ const proposeCommitSchema = type({
19
19
  type: commitTypeSchema,
20
- scope: z.union([z.string(), z.null()]),
21
- summary: z.string(),
22
- details: z.array(detailSchema),
23
- issue_refs: z.array(z.string()),
20
+ scope: type("string").or("null"),
21
+ summary: "string",
22
+ details: detailSchema.array(),
23
+ issue_refs: "string[]",
24
24
  });
25
25
 
26
26
  interface ProposalResponse {
@@ -1,9 +1,9 @@
1
- import { z } from "zod/v4";
1
+ import { type } from "arktype";
2
2
  import type { CustomTool } from "../../../extensibility/custom-tools/types";
3
3
  import * as git from "../../../utils/git";
4
4
 
5
- const recentCommitsSchema = z.object({
6
- count: z.number().min(1).max(50).describe("commit count").optional(),
5
+ const recentCommitsSchema = type({
6
+ "count?": type("1 <= number <= 50").describe("commit count"),
7
7
  });
8
8
 
9
9
  interface RecentCommitStats {
@@ -1,23 +1,11 @@
1
- import { z } from "zod/v4";
1
+ import { type } from "arktype";
2
2
 
3
- export const commitTypeSchema = z.enum([
4
- "feat",
5
- "fix",
6
- "refactor",
7
- "perf",
8
- "docs",
9
- "test",
10
- "build",
11
- "ci",
12
- "chore",
13
- "style",
14
- "revert",
15
- ] as const);
3
+ export const commitTypeSchema = type(
4
+ "'feat' | 'fix' | 'refactor' | 'perf' | 'docs' | 'test' | 'build' | 'ci' | 'chore' | 'style' | 'revert'",
5
+ );
16
6
 
17
- export const detailSchema = z.object({
18
- text: z.string(),
19
- changelog_category: z
20
- .enum(["Added", "Changed", "Fixed", "Deprecated", "Removed", "Security", "Breaking Changes"])
21
- .optional(),
22
- user_visible: z.boolean().optional(),
7
+ export const detailSchema = type({
8
+ text: "string",
9
+ "changelog_category?": "'Added' | 'Changed' | 'Fixed' | 'Deprecated' | 'Removed' | 'Security' | 'Breaking Changes'",
10
+ "user_visible?": "boolean",
23
11
  });
@@ -1,4 +1,4 @@
1
- import { z } from "zod/v4";
1
+ import { type } from "arktype";
2
2
  import type { CommitAgentState, SplitCommitGroup, SplitCommitPlan } from "../../../commit/agentic/state";
3
3
  import { computeDependencyOrder } from "../../../commit/agentic/topo-sort";
4
4
  import {
@@ -15,32 +15,28 @@ import type { CustomTool } from "../../../extensibility/custom-tools/types";
15
15
  import * as git from "../../../utils/git";
16
16
  import { commitTypeSchema, detailSchema } from "./schemas.js";
17
17
 
18
- const hunkSelectorSchema = z.discriminatedUnion("type", [
19
- z.object({ type: z.literal("all") }),
20
- z.object({ type: z.literal("indices"), indices: z.array(z.number()).min(1) }),
21
- z.object({ type: z.literal("lines"), start: z.number(), end: z.number() }),
22
- ]);
18
+ const hunkSelectorSchema = type({ type: "'all'" })
19
+ .or({ type: "'indices'", indices: "number[]" })
20
+ .or({ type: "'lines'", start: "number", end: "number" });
23
21
 
24
- const fileChangeSchema = z.object({
25
- path: z.string(),
22
+ const fileChangeSchema = type({
23
+ path: "string",
26
24
  hunks: hunkSelectorSchema,
27
25
  });
28
26
 
29
- const splitCommitSchema = z.object({
30
- commits: z
31
- .array(
32
- z.object({
33
- changes: z.array(fileChangeSchema).min(1),
34
- type: commitTypeSchema,
35
- scope: z.union([z.string(), z.null()]),
36
- summary: z.string(),
37
- details: z.array(detailSchema).optional(),
38
- issue_refs: z.array(z.string()).optional(),
39
- rationale: z.string().optional(),
40
- dependencies: z.array(z.number()).optional(),
41
- }),
42
- )
43
- .min(2),
27
+ const commitItemSchema = type({
28
+ changes: fileChangeSchema.array(),
29
+ type: commitTypeSchema,
30
+ scope: type("string").or("null"),
31
+ summary: "string",
32
+ "details?": detailSchema.array(),
33
+ "issue_refs?": "string[]",
34
+ "rationale?": "string",
35
+ "dependencies?": "number[]",
36
+ });
37
+
38
+ const splitCommitSchema = type({
39
+ commits: commitItemSchema.array(),
44
40
  });
45
41
 
46
42
  interface SplitCommitResponse {
@@ -2,19 +2,21 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import type { Api, ApiKey, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
3
3
  import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
4
4
  import { prompt } from "@oh-my-pi/pi-utils";
5
- import { z } from "zod/v4";
5
+ import { type } from "arktype";
6
6
  import summarySystemPrompt from "../../commit/prompts/summary-system.md" with { type: "text" };
7
7
  import summaryUserPrompt from "../../commit/prompts/summary-user.md" with { type: "text" };
8
8
  import type { CommitSummary } from "../../commit/types";
9
9
  import { toReasoningEffort } from "../../thinking";
10
10
  import { extractTextContent, extractToolCall } from "../utils";
11
11
 
12
+ const SummaryToolSchema = type({
13
+ summary: "string",
14
+ });
15
+
12
16
  const SummaryTool = {
13
17
  name: "create_commit_summary",
14
18
  description: "Generate the summary line for a conventional commit message.",
15
- parameters: z.object({
16
- summary: z.string(),
17
- }),
19
+ parameters: SummaryToolSchema,
18
20
  };
19
21
 
20
22
  export interface SummaryInput {
@@ -83,7 +85,7 @@ function renderSummaryPrompt({
83
85
  function parseSummaryFromResponse(message: AssistantMessage, commitType: string, scope: string | null): CommitSummary {
84
86
  const toolCall = extractToolCall(message, "create_commit_summary");
85
87
  if (toolCall) {
86
- const parsed = validateToolCall([SummaryTool], toolCall) as z.infer<(typeof SummaryTool)["parameters"]>;
88
+ const parsed = validateToolCall([SummaryTool], toolCall) as (typeof SummaryToolSchema)["infer"];
87
89
  return { summary: stripTypePrefix(parsed.summary, commitType, scope) };
88
90
  }
89
91
  const text = extractTextContent(message);
@@ -2,25 +2,29 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import type { Api, ApiKey, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
3
3
  import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
4
4
  import { prompt } from "@oh-my-pi/pi-utils";
5
- import { z } from "zod/v4";
5
+ import { type } from "arktype";
6
6
  import changelogSystemPrompt from "../../commit/prompts/changelog-system.md" with { type: "text" };
7
7
  import changelogUserPrompt from "../../commit/prompts/changelog-user.md" with { type: "text" };
8
- import { CHANGELOG_CATEGORIES, type ChangelogCategory, type ChangelogGenerationResult } from "../../commit/types";
8
+ import type { ChangelogGenerationResult } from "../../commit/types";
9
9
  import { toReasoningEffort } from "../../thinking";
10
10
  import { extractTextContent, extractToolCall, parseJsonPayload } from "../utils";
11
11
 
12
- const changelogEntryShape = Object.fromEntries(
13
- CHANGELOG_CATEGORIES.map(c => [c, z.array(z.string()).optional()] as const),
14
- ) as Record<ChangelogCategory, z.ZodOptional<z.ZodArray<z.ZodString>>>;
15
-
16
- const changelogEntriesSchema = z.object(changelogEntryShape);
12
+ // Build the changelog entry schema with arktype
13
+ // Each category maps to an optional array of strings
14
+ const changelogEntriesSchema = type({
15
+ "Breaking Changes?": "string[]",
16
+ "Added?": "string[]",
17
+ "Changed?": "string[]",
18
+ "Deprecated?": "string[]",
19
+ "Removed?": "string[]",
20
+ "Fixed?": "string[]",
21
+ "Security?": "string[]",
22
+ });
17
23
 
18
24
  export const changelogTool = {
19
25
  name: "create_changelog_entries",
20
26
  description: "Generate changelog entries grouped by Keep a Changelog categories.",
21
- parameters: z.object({
22
- entries: changelogEntriesSchema,
23
- }),
27
+ parameters: type({ entries: changelogEntriesSchema }),
24
28
  };
25
29
 
26
30
  export interface ChangelogPromptInput {
@@ -68,7 +72,7 @@ export async function generateChangelogEntries({
68
72
  function parseChangelogResponse(message: AssistantMessage): ChangelogGenerationResult {
69
73
  const toolCall = extractToolCall(message, "create_changelog_entries");
70
74
  if (toolCall) {
71
- const parsed = validateToolCall([changelogTool], toolCall) as z.infer<(typeof changelogTool)["parameters"]>;
75
+ const parsed = validateToolCall([changelogTool], toolCall) as typeof changelogTool.parameters.infer;
72
76
  return { entries: parsed.entries ?? {} };
73
77
  }
74
78
 
@@ -1,36 +1,29 @@
1
1
  import type { AssistantMessage } from "@oh-my-pi/pi-ai";
2
- import { validateToolCall } from "@oh-my-pi/pi-ai";
3
- import { z } from "zod/v4";
2
+ import { type as t, validateToolCall } from "@oh-my-pi/pi-ai";
4
3
  import type { ChangelogCategory, ConventionalAnalysis } from "./types";
5
4
  import { extractTextContent, extractToolCall, normalizeAnalysis, parseJsonPayload } from "./utils";
6
5
 
7
- const changelogCategoryLiteral = z.enum([
8
- "Added",
9
- "Changed",
10
- "Fixed",
11
- "Deprecated",
12
- "Removed",
13
- "Security",
14
- "Breaking Changes",
15
- ]);
6
+ const changelogCategoryLiteral = t(
7
+ "'Added' | 'Changed' | 'Fixed' | 'Deprecated' | 'Removed' | 'Security' | 'Breaking Changes'",
8
+ );
16
9
 
17
10
  /**
18
- * Shared Zod schema for the `create_conventional_analysis` tool used by
11
+ * Shared arktype schema for the `create_conventional_analysis` tool used by
19
12
  * both the single-pass analysis call and the map-reduce reduce phase. Schemas
20
13
  * are identical across phases — only the surrounding tool `description`
21
14
  * differs to reflect the input the phase is summarizing.
22
15
  */
23
- export const conventionalAnalysisParameters = z.object({
24
- type: z.enum(["feat", "fix", "refactor", "docs", "test", "chore", "style", "perf", "build", "ci", "revert"]),
25
- scope: z.union([z.string(), z.null()]),
26
- details: z.array(
27
- z.object({
28
- text: z.string(),
29
- changelog_category: changelogCategoryLiteral.optional(),
30
- user_visible: z.boolean().optional(),
31
- }),
32
- ),
33
- issue_refs: z.array(z.string()),
16
+ const detailItem = t({
17
+ text: "string",
18
+ "changelog_category?": changelogCategoryLiteral,
19
+ "user_visible?": "boolean",
20
+ });
21
+
22
+ export const conventionalAnalysisParameters = t({
23
+ type: "'feat' | 'fix' | 'refactor' | 'docs' | 'test' | 'chore' | 'style' | 'perf' | 'build' | 'ci' | 'revert'",
24
+ scope: t("string").or("null"),
25
+ details: detailItem.array(),
26
+ issue_refs: "string[]",
34
27
  });
35
28
 
36
29
  export interface ConventionalAnalysisTool {
@@ -68,7 +61,7 @@ export function parseConventionalAnalysisResponse(
68
61
  ): ConventionalAnalysis {
69
62
  const toolCall = extractToolCall(message, tool.name);
70
63
  if (toolCall) {
71
- const parsed = validateToolCall([tool], toolCall) as z.infer<typeof conventionalAnalysisParameters>;
64
+ const parsed = validateToolCall([tool], toolCall) as any;
72
65
  return normalizeAnalysis(parsed);
73
66
  }
74
67
  const text = extractTextContent(message);
@@ -1,8 +1,8 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { getAgentDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
4
+ import type { Type } from "arktype";
4
5
  import { JSONC, YAML } from "bun";
5
- import type { ZodType } from "zod/v4";
6
6
 
7
7
  /** Minimal subset of the AJV ConfigSchemaError shape this module actually relies on. */
8
8
  interface ConfigSchemaError {
@@ -55,7 +55,7 @@ function migrateJsonToYml(jsonPath: string, ymlPath: string) {
55
55
 
56
56
  export interface IConfigFile<T> {
57
57
  readonly id: string;
58
- readonly schema: ZodType<T>;
58
+ readonly schema: Type;
59
59
  path?(): string;
60
60
  load(): T | null;
61
61
  invalidate?(): void;
@@ -129,7 +129,7 @@ export class ConfigFile<T> implements IConfigFile<T> {
129
129
 
130
130
  constructor(
131
131
  readonly id: string,
132
- readonly schema: ZodType<T>,
132
+ readonly schema: Type,
133
133
  configPath: string = path.join(getAgentDir(), `${id}.yml`),
134
134
  ) {
135
135
  this.#basePath = configPath;
@@ -193,10 +193,10 @@ export class ConfigFile<T> implements IConfigFile<T> {
193
193
  }
194
194
 
195
195
  createDefault(): T {
196
- const parsed = this.schema.safeParse({});
197
- if (parsed.success) return parsed.data;
198
- const fallback = this.schema.safeParse(undefined);
199
- if (fallback.success) return fallback.data;
196
+ const parsed = this.schema({});
197
+ if (!(parsed instanceof Error)) return parsed as T;
198
+ const fallback = this.schema(undefined);
199
+ if (!(fallback instanceof Error)) return fallback as T;
200
200
  throw new ConfigError(this.id, undefined, {
201
201
  err: new Error("Schema produced no default value"),
202
202
  stage: "createDefault",
@@ -219,19 +219,17 @@ export class ConfigFile<T> implements IConfigFile<T> {
219
219
  throw new Error(`Invalid config file path: ${this.#basePath}`);
220
220
  }
221
221
 
222
- const checked = this.schema.safeParse(parsed);
223
- if (!checked.success) {
222
+ const checked = this.schema(parsed);
223
+ if (checked instanceof Error) {
224
224
  const schemaErrors: ConfigSchemaError[] = [];
225
- for (const issue of checked.error.issues) {
226
- const instancePath = issue.path.length === 0 ? "" : `/${issue.path.map(String).join("/")}`;
227
- schemaErrors.push({ instancePath, message: issue.message });
228
- if (schemaErrors.length >= 50) break;
229
- }
225
+ // arktype errors are Error instances with a message property
226
+ // Extract the error message as a single schema error
227
+ schemaErrors.push({ instancePath: "root", message: checked.message });
230
228
  const error = new ConfigError(this.id, schemaErrors);
231
229
  logger.warn("Failed to parse config file", { path: this.path(), error });
232
230
  return this.#storeCache({ error, status: "error" });
233
231
  }
234
- const value = checked.data;
232
+ const value = checked as T;
235
233
  try {
236
234
  this.#auxValidate?.(value);
237
235
  } catch (error) {
@@ -31,6 +31,7 @@ interface AppKeybindings {
31
31
  "app.tools.expand": true;
32
32
  "app.editor.external": true;
33
33
  "app.message.followUp": true;
34
+ "app.retry": true;
34
35
  "app.message.dequeue": true;
35
36
  "app.clipboard.pasteImage": true;
36
37
  "app.clipboard.pasteTextRaw": true;
@@ -131,6 +132,10 @@ export const KEYBINDINGS = {
131
132
  defaultKeys: ["ctrl+q", "ctrl+enter"],
132
133
  description: "Send follow-up message",
133
134
  },
135
+ "app.retry": {
136
+ defaultKeys: "alt+r",
137
+ description: "Retry last failed assistant turn",
138
+ },
134
139
  "app.message.dequeue": {
135
140
  defaultKeys: "alt+up",
136
141
  description: "Dequeue message",
@@ -238,6 +243,7 @@ const KEYBINDING_NAME_MIGRATIONS = {
238
243
  toggleThinking: "app.thinking.toggle",
239
244
  externalEditor: "app.editor.external",
240
245
  followUp: "app.message.followUp",
246
+ retry: "app.retry",
241
247
  dequeue: "app.message.dequeue",
242
248
  pasteImage: "app.clipboard.pasteImage",
243
249
  pasteTextRaw: "app.clipboard.pasteTextRaw",