@oh-my-pi/pi-coding-agent 15.5.15 → 15.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/dist/types/capability/rule-buckets.d.ts +30 -0
  3. package/dist/types/capability/rule.d.ts +7 -0
  4. package/dist/types/cli/classify-install-target.d.ts +0 -10
  5. package/dist/types/cli/completion-gen.d.ts +80 -0
  6. package/dist/types/cli/initial-message.d.ts +1 -1
  7. package/dist/types/cli/tiny-models-cli.d.ts +9 -0
  8. package/dist/types/commands/complete.d.ts +6 -0
  9. package/dist/types/commands/completions.d.ts +13 -0
  10. package/dist/types/commands/setup.d.ts +10 -1
  11. package/dist/types/commands/tiny-models.d.ts +22 -0
  12. package/dist/types/commit/analysis/conventional.d.ts +1 -1
  13. package/dist/types/commit/analysis/summary.d.ts +1 -1
  14. package/dist/types/commit/changelog/generate.d.ts +1 -1
  15. package/dist/types/commit/changelog/index.d.ts +2 -2
  16. package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
  17. package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
  18. package/dist/types/config/model-id-affixes.d.ts +10 -0
  19. package/dist/types/config/settings-schema.d.ts +402 -17
  20. package/dist/types/discovery/builtin-defaults.d.ts +1 -0
  21. package/dist/types/discovery/builtin-rules/index.d.ts +7 -0
  22. package/dist/types/discovery/helpers.d.ts +1 -1
  23. package/dist/types/discovery/index.d.ts +1 -0
  24. package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
  25. package/dist/types/edit/hashline/block-resolver.d.ts +9 -0
  26. package/dist/types/edit/hashline/index.d.ts +1 -0
  27. package/dist/types/eval/js/shared/rewrite-imports.d.ts +16 -1
  28. package/dist/types/eval/py/kernel.d.ts +3 -0
  29. package/dist/types/eval/py/runtime.d.ts +11 -1
  30. package/dist/types/export/html/template.generated.d.ts +1 -1
  31. package/dist/types/internal-urls/agent-protocol.d.ts +2 -1
  32. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -1
  33. package/dist/types/internal-urls/local-protocol.d.ts +2 -1
  34. package/dist/types/internal-urls/memory-protocol.d.ts +2 -1
  35. package/dist/types/internal-urls/omp-protocol.d.ts +2 -1
  36. package/dist/types/internal-urls/router.d.ts +8 -1
  37. package/dist/types/internal-urls/rule-protocol.d.ts +2 -1
  38. package/dist/types/internal-urls/skill-protocol.d.ts +2 -1
  39. package/dist/types/internal-urls/types.d.ts +26 -0
  40. package/dist/types/main.d.ts +1 -0
  41. package/dist/types/memory-backend/index.d.ts +1 -0
  42. package/dist/types/memory-backend/resolve.d.ts +2 -1
  43. package/dist/types/memory-backend/types.d.ts +7 -1
  44. package/dist/types/mnemosyne/backend.d.ts +4 -0
  45. package/dist/types/mnemosyne/config.d.ts +29 -0
  46. package/dist/types/mnemosyne/index.d.ts +3 -0
  47. package/dist/types/mnemosyne/state.d.ts +72 -0
  48. package/dist/types/modes/components/custom-editor.d.ts +2 -3
  49. package/dist/types/modes/components/hook-selector.d.ts +27 -0
  50. package/dist/types/modes/components/index.d.ts +2 -0
  51. package/dist/types/modes/components/segment-track.d.ts +22 -0
  52. package/dist/types/modes/components/status-line/context-thresholds.d.ts +6 -0
  53. package/dist/types/modes/components/tiny-title-download-progress.d.ts +11 -0
  54. package/dist/types/modes/components/welcome.d.ts +22 -0
  55. package/dist/types/modes/controllers/extension-ui-controller.d.ts +4 -1
  56. package/dist/types/modes/gradient-highlight.d.ts +23 -0
  57. package/dist/types/modes/interactive-mode.d.ts +7 -4
  58. package/dist/types/modes/internal-url-autocomplete.d.ts +43 -0
  59. package/dist/types/modes/orchestrate.d.ts +10 -0
  60. package/dist/types/modes/setup-wizard/index.d.ts +16 -0
  61. package/dist/types/modes/setup-wizard/scenes/glyph.d.ts +2 -0
  62. package/dist/types/modes/setup-wizard/scenes/outro.d.ts +2 -0
  63. package/dist/types/modes/setup-wizard/scenes/providers.d.ts +2 -0
  64. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +19 -0
  65. package/dist/types/modes/setup-wizard/scenes/splash.d.ts +11 -0
  66. package/dist/types/modes/setup-wizard/scenes/theme.d.ts +2 -0
  67. package/dist/types/modes/setup-wizard/scenes/types.d.ts +43 -0
  68. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +19 -0
  69. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +14 -0
  70. package/dist/types/modes/theme/defaults/index.d.ts +8406 -8406
  71. package/dist/types/modes/theme/shimmer.d.ts +2 -0
  72. package/dist/types/modes/theme/theme.d.ts +11 -0
  73. package/dist/types/modes/types.d.ts +5 -1
  74. package/dist/types/modes/ultrathink.d.ts +3 -3
  75. package/dist/types/modes/utils/keybinding-matchers.d.ts +5 -0
  76. package/dist/types/sdk.d.ts +3 -0
  77. package/dist/types/session/agent-session.d.ts +33 -0
  78. package/dist/types/system-prompt.d.ts +2 -0
  79. package/dist/types/task/executor.d.ts +2 -0
  80. package/dist/types/task/render.d.ts +5 -1
  81. package/dist/types/tiny/device.d.ts +78 -0
  82. package/dist/types/tiny/dtype.d.ts +85 -0
  83. package/dist/types/tiny/models.d.ts +185 -0
  84. package/dist/types/tiny/text.d.ts +19 -0
  85. package/dist/types/tiny/title-client.d.ts +32 -0
  86. package/dist/types/tiny/title-protocol.d.ts +74 -0
  87. package/dist/types/tiny/worker.d.ts +2 -0
  88. package/dist/types/tools/bash.d.ts +3 -2
  89. package/dist/types/tools/eval.d.ts +1 -1
  90. package/dist/types/tools/index.d.ts +7 -4
  91. package/dist/types/tools/memory-edit.d.ts +40 -0
  92. package/dist/types/tools/{hindsight-recall.d.ts → memory-recall.d.ts} +6 -6
  93. package/dist/types/tools/{hindsight-reflect.d.ts → memory-reflect.d.ts} +6 -6
  94. package/dist/types/tools/memory-render.d.ts +60 -0
  95. package/dist/types/tools/{hindsight-retain.d.ts → memory-retain.d.ts} +6 -6
  96. package/dist/types/tools/todo-write.d.ts +8 -0
  97. package/dist/types/tools/tool-result.d.ts +2 -0
  98. package/dist/types/tui/code-cell.d.ts +2 -0
  99. package/dist/types/tui/output-block.d.ts +17 -0
  100. package/dist/types/utils/title-generator.d.ts +3 -0
  101. package/package.json +18 -14
  102. package/scripts/build-binary.ts +1 -0
  103. package/src/capability/rule-buckets.ts +64 -0
  104. package/src/capability/rule.ts +8 -0
  105. package/src/cli/completion-gen.ts +550 -0
  106. package/src/cli/setup-cli.ts +5 -3
  107. package/src/cli/tiny-models-cli.ts +127 -0
  108. package/src/cli-commands.ts +3 -0
  109. package/src/cli.ts +9 -15
  110. package/src/commands/complete.ts +66 -0
  111. package/src/commands/completions.ts +60 -0
  112. package/src/commands/setup.ts +29 -4
  113. package/src/commands/tiny-models.ts +36 -0
  114. package/src/config/model-equivalence.ts +43 -2
  115. package/src/config/model-id-affixes.ts +64 -0
  116. package/src/config/model-registry.ts +84 -10
  117. package/src/config/settings-schema.ts +275 -15
  118. package/src/discovery/builtin-defaults.ts +39 -0
  119. package/src/discovery/builtin-rules/index.ts +48 -0
  120. package/src/discovery/builtin-rules/rs-box-leak.md +48 -0
  121. package/src/discovery/builtin-rules/rs-future-prelude.md +23 -0
  122. package/src/discovery/builtin-rules/rs-lazylock.md +51 -0
  123. package/src/discovery/builtin-rules/rs-match-ergonomics.md +67 -0
  124. package/src/discovery/builtin-rules/rs-parking-lot.md +44 -0
  125. package/src/discovery/builtin-rules/rs-result-type.md +19 -0
  126. package/src/discovery/builtin-rules/ts-bare-catch.md +38 -0
  127. package/src/discovery/builtin-rules/ts-import-type.md +42 -0
  128. package/src/discovery/builtin-rules/ts-no-any.md +56 -0
  129. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +39 -0
  130. package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
  131. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +50 -0
  132. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +65 -0
  133. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  134. package/src/discovery/index.ts +1 -0
  135. package/src/edit/hashline/block-resolver.ts +14 -0
  136. package/src/edit/hashline/diff.ts +9 -8
  137. package/src/edit/hashline/execute.ts +2 -1
  138. package/src/edit/hashline/index.ts +1 -0
  139. package/src/eval/__tests__/shared-executors.test.ts +36 -0
  140. package/src/eval/js/shared/local-module-loader.ts +13 -1
  141. package/src/eval/js/shared/rewrite-imports.ts +31 -26
  142. package/src/eval/py/kernel.ts +37 -15
  143. package/src/eval/py/runtime.ts +57 -28
  144. package/src/export/html/template.generated.ts +1 -1
  145. package/src/export/html/template.js +0 -12
  146. package/src/export/ttsr.ts +2 -0
  147. package/src/internal-urls/agent-protocol.ts +18 -1
  148. package/src/internal-urls/artifact-protocol.ts +19 -1
  149. package/src/internal-urls/docs-index.generated.ts +8 -7
  150. package/src/internal-urls/local-protocol.ts +14 -1
  151. package/src/internal-urls/memory-protocol.ts +6 -1
  152. package/src/internal-urls/omp-protocol.ts +5 -1
  153. package/src/internal-urls/router.ts +20 -1
  154. package/src/internal-urls/rule-protocol.ts +8 -1
  155. package/src/internal-urls/skill-protocol.ts +8 -1
  156. package/src/internal-urls/types.ts +27 -0
  157. package/src/lsp/render.ts +1 -1
  158. package/src/main.ts +18 -1
  159. package/src/mcp/oauth-flow.ts +2 -2
  160. package/src/memory-backend/index.ts +1 -0
  161. package/src/memory-backend/resolve.ts +4 -1
  162. package/src/memory-backend/types.ts +8 -1
  163. package/src/mnemosyne/backend.ts +374 -0
  164. package/src/mnemosyne/config.ts +160 -0
  165. package/src/mnemosyne/index.ts +3 -0
  166. package/src/mnemosyne/state.ts +548 -0
  167. package/src/modes/acp/acp-agent.ts +11 -6
  168. package/src/modes/components/agent-dashboard.ts +4 -4
  169. package/src/modes/components/custom-editor.ts +3 -2
  170. package/src/modes/components/diff.ts +2 -2
  171. package/src/modes/components/extensions/extension-list.ts +3 -2
  172. package/src/modes/components/footer.ts +5 -6
  173. package/src/modes/components/history-search.ts +3 -3
  174. package/src/modes/components/hook-selector.ts +92 -8
  175. package/src/modes/components/index.ts +2 -0
  176. package/src/modes/components/mcp-add-wizard.ts +3 -3
  177. package/src/modes/components/model-selector.ts +5 -4
  178. package/src/modes/components/oauth-selector.ts +3 -3
  179. package/src/modes/components/segment-track.ts +52 -0
  180. package/src/modes/components/session-observer-overlay.ts +19 -13
  181. package/src/modes/components/session-selector.ts +3 -3
  182. package/src/modes/components/settings-defs.ts +7 -0
  183. package/src/modes/components/status-line/context-thresholds.ts +11 -0
  184. package/src/modes/components/status-line/segments.ts +2 -2
  185. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  186. package/src/modes/components/tips.txt +13 -0
  187. package/src/modes/components/tool-execution.ts +72 -4
  188. package/src/modes/components/tree-selector.ts +3 -3
  189. package/src/modes/components/user-message-selector.ts +3 -3
  190. package/src/modes/components/welcome.ts +102 -43
  191. package/src/modes/controllers/command-controller.ts +16 -1
  192. package/src/modes/controllers/extension-ui-controller.ts +3 -1
  193. package/src/modes/controllers/input-controller.ts +69 -21
  194. package/src/modes/gradient-highlight.ts +70 -0
  195. package/src/modes/interactive-mode.ts +75 -114
  196. package/src/modes/internal-url-autocomplete.ts +143 -0
  197. package/src/modes/orchestrate.ts +36 -0
  198. package/src/modes/prompt-action-autocomplete.ts +12 -0
  199. package/src/modes/setup-wizard/index.ts +88 -0
  200. package/src/modes/setup-wizard/scenes/glyph.ts +96 -0
  201. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  202. package/src/modes/setup-wizard/scenes/providers.ts +69 -0
  203. package/src/modes/setup-wizard/scenes/sign-in.ts +193 -0
  204. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  205. package/src/modes/setup-wizard/scenes/theme.ts +299 -0
  206. package/src/modes/setup-wizard/scenes/types.ts +48 -0
  207. package/src/modes/setup-wizard/scenes/web-search.ts +128 -0
  208. package/src/modes/setup-wizard/wizard-overlay.ts +275 -0
  209. package/src/modes/theme/shimmer.ts +5 -0
  210. package/src/modes/theme/theme.ts +44 -20
  211. package/src/modes/types.ts +6 -1
  212. package/src/modes/ultrathink.ts +9 -53
  213. package/src/modes/utils/keybinding-matchers.ts +11 -0
  214. package/src/prompts/system/memory-consolidation-system.md +8 -0
  215. package/src/prompts/system/memory-extraction-system.md +26 -0
  216. package/src/prompts/{commands/orchestrate.md → system/orchestrate-notice.md} +6 -17
  217. package/src/prompts/system/system-prompt.md +2 -0
  218. package/src/prompts/system/tiny-title-system.md +8 -0
  219. package/src/prompts/tools/memory-edit.md +8 -0
  220. package/src/prompts/tools/read.md +4 -0
  221. package/src/prompts/tools/task.md +4 -7
  222. package/src/sdk.ts +13 -21
  223. package/src/session/agent-session.ts +128 -44
  224. package/src/slash-commands/builtin-registry.ts +18 -1
  225. package/src/system-prompt.ts +4 -0
  226. package/src/task/commands.ts +1 -5
  227. package/src/task/executor.ts +8 -0
  228. package/src/task/index.ts +2 -0
  229. package/src/task/render.ts +69 -26
  230. package/src/tiny/device.ts +117 -0
  231. package/src/tiny/dtype.ts +101 -0
  232. package/src/tiny/models.ts +218 -0
  233. package/src/tiny/text.ts +54 -0
  234. package/src/tiny/title-client.ts +395 -0
  235. package/src/tiny/title-protocol.ts +51 -0
  236. package/src/tiny/worker.ts +587 -0
  237. package/src/tools/bash.ts +74 -29
  238. package/src/tools/browser/tab-worker.ts +1 -1
  239. package/src/tools/eval.ts +9 -4
  240. package/src/tools/index.ts +17 -22
  241. package/src/tools/memory-edit.ts +59 -0
  242. package/src/tools/memory-recall.ts +100 -0
  243. package/src/tools/memory-reflect.ts +88 -0
  244. package/src/tools/memory-render.ts +185 -0
  245. package/src/tools/memory-retain.ts +91 -0
  246. package/src/tools/read.ts +1 -0
  247. package/src/tools/renderers.ts +4 -2
  248. package/src/tools/todo-write.ts +128 -29
  249. package/src/tools/tool-result.ts +8 -0
  250. package/src/tui/code-cell.ts +6 -1
  251. package/src/tui/output-block.ts +199 -38
  252. package/src/utils/title-generator.ts +115 -13
  253. package/dist/types/tools/recipe/index.d.ts +0 -46
  254. package/dist/types/tools/recipe/render.d.ts +0 -36
  255. package/dist/types/tools/recipe/runner.d.ts +0 -60
  256. package/dist/types/tools/recipe/runners/cargo.d.ts +0 -16
  257. package/dist/types/tools/recipe/runners/index.d.ts +0 -2
  258. package/dist/types/tools/recipe/runners/just.d.ts +0 -2
  259. package/dist/types/tools/recipe/runners/make.d.ts +0 -2
  260. package/dist/types/tools/recipe/runners/pkg.d.ts +0 -2
  261. package/dist/types/tools/recipe/runners/task.d.ts +0 -2
  262. package/src/prompts/tools/recipe.md +0 -16
  263. package/src/tools/hindsight-recall.ts +0 -69
  264. package/src/tools/hindsight-reflect.ts +0 -58
  265. package/src/tools/hindsight-retain.ts +0 -57
  266. package/src/tools/recipe/index.ts +0 -81
  267. package/src/tools/recipe/render.ts +0 -19
  268. package/src/tools/recipe/runner.ts +0 -219
  269. package/src/tools/recipe/runners/cargo.ts +0 -131
  270. package/src/tools/recipe/runners/index.ts +0 -8
  271. package/src/tools/recipe/runners/just.ts +0 -73
  272. package/src/tools/recipe/runners/make.ts +0 -101
  273. package/src/tools/recipe/runners/pkg.ts +0 -167
  274. package/src/tools/recipe/runners/task.ts +0 -72
@@ -0,0 +1,19 @@
1
+ ---
2
+ description: Result type aliases must include a defaulted error type parameter
3
+ condition: "type\\s+Result<[A-Za-z_]\\w*>\\s*="
4
+ scope: "tool:edit(*.rs), tool:write(*.rs)"
5
+ ---
6
+
7
+ `Result` aliases must expose the error type as a defaulted parameter.
8
+
9
+ ```rust
10
+ pub type Result<T, E = anyhow::Error> = std::result::Result<T, E>;
11
+ ```
12
+
13
+ Never write:
14
+
15
+ ```rust
16
+ type Result<T> = std::result::Result<T, anyhow::Error>;
17
+ ```
18
+
19
+ The default keeps common call sites short while preserving escape hatches for precise errors.
@@ -0,0 +1,38 @@
1
+ ---
2
+ description: Use bare `catch {` when the error binding is unused
3
+ condition: "catch \\(_"
4
+ scope: "tool:edit(*.ts), tool:edit(*.tsx), tool:write(*.ts), tool:write(*.tsx)"
5
+ ---
6
+
7
+ Use bare `catch {}` when the caught value is unused. An underscore-prefixed binding adds noise and still allocates a local name.
8
+
9
+ ## Replace
10
+
11
+ ```typescript
12
+ // Bad
13
+ try {
14
+ await loadConfig();
15
+ } catch (_err) {
16
+ return null;
17
+ }
18
+
19
+ // Good
20
+ try {
21
+ await loadConfig();
22
+ } catch {
23
+ return null;
24
+ }
25
+ ```
26
+
27
+ ## Keep a real name when used
28
+
29
+ ```typescript
30
+ try {
31
+ await saveConfig();
32
+ } catch (err) {
33
+ logger.error("save failed", { err });
34
+ throw err;
35
+ }
36
+ ```
37
+
38
+ Unused error? Bare `catch`. Used error? Name it for what it carries.
@@ -0,0 +1,42 @@
1
+ ---
2
+ description: "Use `import type`, not `import('pkg').Type` in type positions"
3
+ condition: "import\\("
4
+ scope: "tool:edit(*.ts), tool:edit(*.tsx), tool:write(*.ts), tool:write(*.tsx)"
5
+ ---
6
+
7
+ Use top-level `import type` declarations for type-only dependencies. NEVER write `import("pkg").Type` inside source annotations.
8
+
9
+ ## Why
10
+
11
+ - Top-level imports expose dependencies immediately.
12
+ - Import sorting and deduplication can manage them.
13
+ - Signatures stay readable and reviewable.
14
+ - Re-exports do not inherit noisy inline paths.
15
+
16
+ ## Avoid
17
+
18
+ ```typescript
19
+ // Bad — inline imports hide dependencies in signatures.
20
+ function run(client: import("some-sdk").Client, input: import("zod/v4").infer<Schema>): Promise<Output>;
21
+
22
+ // Bad — annotations become path dumps.
23
+ const options: import("some-sdk/config").ClientOptions = { ... };
24
+ ```
25
+
26
+ ## Use
27
+
28
+ ```typescript
29
+ import type { Client } from "some-sdk";
30
+ import type { ClientOptions } from "some-sdk/config";
31
+ import type { infer as Infer } from "zod/v4";
32
+
33
+ function run(client: Client, input: Infer<Schema>): Promise<Output>;
34
+ const options: ClientOptions = { ... };
35
+ ```
36
+
37
+ ## Exceptions
38
+
39
+ - Ambient `.d.ts` globals that must not become modules.
40
+ - Generated files whose generator owns import management.
41
+
42
+ In normal `.ts` / `.tsx` source, use `import type`.
@@ -0,0 +1,56 @@
1
+ ---
2
+ description: "Never use `any` in TypeScript annotations or assertions — use `unknown`, generics, or the actual type"
3
+ condition: ": any|as any"
4
+ scope: "tool:edit(*.ts), tool:edit(*.tsx), tool:write(*.ts), tool:write(*.tsx)"
5
+ ---
6
+
7
+ Never use `: any` or `as any`. They disable type checking exactly where the boundary needs precision.
8
+
9
+ ## Use instead
10
+
11
+ - `unknown` for unvalidated input.
12
+ - A domain type when the shape is known.
13
+ - A generic when the caller supplies the shape.
14
+ - A type guard when runtime checks establish shape.
15
+ - `satisfies` for object literals that must match a contract.
16
+
17
+ ## Parameters and returns
18
+
19
+ ```typescript
20
+ // Bad
21
+ function readId(value: any): any {
22
+ return value.id;
23
+ }
24
+
25
+ // Good — validate unknown input.
26
+ function readId(value: unknown): string | undefined {
27
+ if (value && typeof value === "object" && "id" in value) {
28
+ const candidate = (value as { id: unknown }).id;
29
+ return typeof candidate === "string" ? candidate : undefined;
30
+ }
31
+ }
32
+ ```
33
+
34
+ ## Assertions
35
+
36
+ ```typescript
37
+ // Bad
38
+ const root = document.getElementById("root") as any;
39
+ root.innerText = "ready";
40
+
41
+ // Good
42
+ const root = document.getElementById("root") as HTMLElement | null;
43
+ root?.innerText = "ready";
44
+ ```
45
+
46
+ ## Object literals
47
+
48
+ ```typescript
49
+ // Bad
50
+ const config = { port: 3000 } as any as ServerConfig;
51
+
52
+ // Good
53
+ const config = { port: 3000 } satisfies ServerConfig;
54
+ ```
55
+
56
+ If a library boundary truly requires an unchecked cast, use `as unknown as T` with a short reason. Never leave a bare `any`.
@@ -0,0 +1,39 @@
1
+ ---
2
+ description: "Do not use `await import()` — use static imports unless dynamic loading is unavoidable"
3
+ condition: "await import\\("
4
+ scope: "tool:edit(*.ts), tool:edit(*.tsx), tool:write(*.ts), tool:write(*.tsx)"
5
+ ---
6
+
7
+ Use static imports for modules known at author time. Reach for `await import()` only when the module specifier is genuinely runtime-selected.
8
+
9
+ ## Why
10
+
11
+ - Static imports fail during build, not under load.
12
+ - Bundlers, type checkers, and tree shakers see them.
13
+ - The dependency graph remains reviewable.
14
+ - Consumers keep precise module types without casts.
15
+
16
+ ## Avoid
17
+
18
+ ```typescript
19
+ // Bad — the module path is a literal.
20
+ const { createClient } = await import("some-sdk");
21
+
22
+ // Bad — dynamic import followed by a shape assertion.
23
+ const mod = (await import("./known-module")) as { run?: unknown };
24
+ ```
25
+
26
+ ## Use
27
+
28
+ ```typescript
29
+ import { createClient } from "some-sdk";
30
+ import { run } from "./known-module";
31
+ ```
32
+
33
+ ## Exceptions
34
+
35
+ - Plugin loading from a runtime registry.
36
+ - Platform-specific modules that do not exist everywhere.
37
+ - Test cases that intentionally exercise module loading boundaries.
38
+
39
+ Exception? Add a short comment naming why static import cannot work.
@@ -0,0 +1,45 @@
1
+ ---
2
+ description: "Do not use `ReturnType<typeof fn>` — name the type explicitly"
3
+ condition: "ReturnType<"
4
+ scope: "tool:edit(*.ts), tool:edit(*.tsx), tool:write(*.ts), tool:write(*.tsx)"
5
+ ---
6
+
7
+ Do not publish contracts through `ReturnType<typeof fn>`. Name the type at the module that owns the value and import that name at consumers.
8
+
9
+ ## Why
10
+
11
+ - Named types document the contract directly.
12
+ - Consumers stop coupling to implementation helpers.
13
+ - JSDoc and changelog notes attach to the exported type.
14
+ - Type errors point at the intended API boundary.
15
+
16
+ ## Avoid
17
+
18
+ ```typescript
19
+ // Bad — opaque and coupled to implementation names.
20
+ type Config = Awaited<ReturnType<typeof loadConfig>>;
21
+ type Message = ReturnType<typeof buildMessage>["message"];
22
+ let service: ReturnType<typeof createService> | undefined;
23
+ ```
24
+
25
+ ## Use
26
+
27
+ ```typescript
28
+ // In the module that owns the function:
29
+ export interface LoadedConfig {
30
+ path: string;
31
+ values: Record<string, unknown>;
32
+ }
33
+
34
+ export function loadConfig(path: string): Promise<LoadedConfig> { ... }
35
+
36
+ // At the consumer:
37
+ import type { LoadedConfig } from "./config";
38
+ ```
39
+
40
+ ## Exceptions
41
+
42
+ - Timer handles: `ReturnType<typeof setTimeout>` / `setInterval`.
43
+ - Generic type utilities where the function is a type parameter.
44
+
45
+ Concrete function? Export a concrete type.
@@ -0,0 +1,50 @@
1
+ ---
2
+ description: "Do not extract 1-2 line functions that only wrap an expression — inline them"
3
+ condition: "\\{\\s*return [^;{}\\n]+;?\\s*\\}|\\b(?:const|let|var)\\s+[\\w$]+\\s*=\\s*(\\([^)]*\\)|[a-zA-Z_$][\\w$]*)\\s*=>\\s*[^{\\n]+$"
4
+ scope: "tool:edit(*.ts), tool:edit(*.tsx), tool:write(*.ts), tool:write(*.tsx)"
5
+ interruptMode: never
6
+ ---
7
+
8
+ Do not extract a function whose whole body is one expression or one `return`. Inline it unless the name creates a durable contract.
9
+
10
+ ## Why
11
+
12
+ - One-line wrappers hide no real behavior.
13
+ - Readers must jump to verify trivial code.
14
+ - The signature freezes a shape too early.
15
+ - Search and type flow work better with inline expressions.
16
+
17
+ ## Avoid
18
+
19
+ ```typescript
20
+ // Bad — pure rename, no behavior added.
21
+ function isEmpty(value: string): boolean {
22
+ return value.length === 0;
23
+ }
24
+
25
+ const getDisplayName = (user: User) => user.profile.displayName;
26
+
27
+ function double(value: number) {
28
+ return value * 2;
29
+ }
30
+
31
+ if (isEmpty(name)) { ... }
32
+ ```
33
+
34
+ ## Use
35
+
36
+ ```typescript
37
+ if (name.length === 0) { ... }
38
+ const displayName = user.profile.displayName;
39
+ const doubled = value * 2;
40
+ ```
41
+
42
+ ## Allowed tiny functions
43
+
44
+ - Three or more call sites need lockstep behavior.
45
+ - Exported name represents a stable domain concept.
46
+ - Callback identity matters.
47
+ - Type guard preserves narrowing.
48
+ - Public API, test seam, or DI boundary needs indirection.
49
+
50
+ If none apply, inline it.
@@ -0,0 +1,65 @@
1
+ ---
2
+ description: Use Promise.withResolvers() instead of new Promise() constructor
3
+ condition: "new Promise\\("
4
+ scope: "tool:edit(*.ts), tool:edit(*.tsx), tool:write(*.ts), tool:write(*.tsx)"
5
+ ---
6
+
7
+ Use `Promise.withResolvers()` instead of `new Promise((resolve, reject) => ...)`. It keeps control flow linear and exposes typed resolver functions without callback nesting.
8
+
9
+ ## Basic operation
10
+
11
+ ```typescript
12
+ // Bad
13
+ function delay(ms: number): Promise<void> {
14
+ return new Promise(resolve => {
15
+ setTimeout(resolve, ms);
16
+ });
17
+ }
18
+
19
+ // Good
20
+ function delay(ms: number): Promise<void> {
21
+ const { promise, resolve } = Promise.withResolvers<void>();
22
+ setTimeout(resolve, ms);
23
+ return promise;
24
+ }
25
+ ```
26
+
27
+ ## Event-based completion
28
+
29
+ ```typescript
30
+ // Bad
31
+ function waitForEvent(emitter: EventEmitter, event: string): Promise<unknown> {
32
+ return new Promise((resolve, reject) => {
33
+ emitter.once(event, resolve);
34
+ emitter.once("error", reject);
35
+ });
36
+ }
37
+
38
+ // Good
39
+ function waitForEvent(emitter: EventEmitter, event: string): Promise<unknown> {
40
+ const { promise, resolve, reject } = Promise.withResolvers<unknown>();
41
+ emitter.once(event, resolve);
42
+ emitter.once("error", reject);
43
+ return promise;
44
+ }
45
+ ```
46
+
47
+ ## Stored resolver
48
+
49
+ ```typescript
50
+ class Gate {
51
+ #promise: Promise<void>;
52
+ #resolve: () => void;
53
+
54
+ constructor() {
55
+ const { promise, resolve } = Promise.withResolvers<void>();
56
+ this.#promise = promise;
57
+ this.#resolve = resolve;
58
+ }
59
+
60
+ open(): void { this.#resolve(); }
61
+ wait(): Promise<void> { return this.#promise; }
62
+ }
63
+ ```
64
+
65
+ Use the constructor only when an API specifically requires the executor form.
@@ -0,0 +1,28 @@
1
+ ---
2
+ description: Prefer Record<K, V> for small static literals; use Set/Map for anything dynamic
3
+ condition: "\\bnew\\s+(Set|Map)\\b"
4
+ scope: "tool:edit(**/*.{ts,tsx}), tool:write(**/*.{ts,tsx})"
5
+ interruptMode: never
6
+ ---
7
+
8
+ Use `Record<K, V>` / `Record<K, true>` for small, static string-keyed lookup tables.
9
+
10
+ Use `Set` / `Map` when keys are dynamic, non-string, inserted or deleted at runtime, or when code needs `.size`, `.clear()`, stable insertion order, or iterator APIs.
11
+
12
+ ```typescript
13
+ // Static literal → Record
14
+ const LABEL_BY_KIND: Record<string, string> = {
15
+ text: "Text",
16
+ json: "JSON",
17
+ binary: "Binary",
18
+ };
19
+
20
+ // Dynamic membership → Set
21
+ const seen = new Set<string>();
22
+ for (const item of items) {
23
+ if (seen.has(item.id)) continue;
24
+ seen.add(item.id);
25
+ }
26
+ ```
27
+
28
+ Small fixed table? `Record`. Runtime collection? `Set` / `Map`.
@@ -22,6 +22,7 @@ import "../capability/tool";
22
22
  // Import providers (each registers itself on import)
23
23
  import "./agents-md";
24
24
  import "./builtin";
25
+ import "./builtin-defaults";
25
26
  import "./claude";
26
27
  import "./claude-plugins";
27
28
  import "./cline";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Tree-sitter-backed {@link BlockResolver} for the hashline `replace block N:`
3
+ * operator. Bridges the pure hashline seam to the native `blockRangeAt`
4
+ * primitive in `@oh-my-pi/pi-natives`, which infers the language from the file
5
+ * path and returns the 1-indexed line span of the syntactic block beginning on
6
+ * the requested line (or `null` when none can be resolved).
7
+ */
8
+ import type { BlockResolver } from "@oh-my-pi/hashline";
9
+ import { blockRangeAt } from "@oh-my-pi/pi-natives";
10
+
11
+ export const nativeBlockResolver: BlockResolver = ({ path, text, line }) => {
12
+ const range = blockRangeAt({ code: text, path, line });
13
+ return range ? { start: range.startLine, end: range.endLine } : null;
14
+ };
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import {
13
13
  Patch as HashlinePatch,
14
+ missingSnapshotTagMessage,
14
15
  normalizeToLF,
15
16
  type Patch,
16
17
  type PatchSection,
@@ -21,6 +22,7 @@ import {
21
22
  import { resolveToCwd } from "../../tools/path-utils";
22
23
  import { generateDiffString } from "../diff";
23
24
  import { readEditFileText } from "../read-file";
25
+ import { nativeBlockResolver } from "./block-resolver";
24
26
 
25
27
  export interface HashlineDiffOptions {
26
28
  /**
@@ -40,10 +42,6 @@ async function readSectionText(absolutePath: string, sectionPath: string): Promi
40
42
  }
41
43
  }
42
44
 
43
- function hasAnchorScoped(section: PatchSection): boolean {
44
- return section.hasAnchorScopedEdit;
45
- }
46
-
47
45
  function snapshotMatchesCurrent(snapshot: Snapshot, currentText: string): boolean {
48
46
  return snapshot.text === currentText;
49
47
  }
@@ -54,9 +52,10 @@ function validateSectionHash(
54
52
  snapshots: SnapshotStore,
55
53
  ): string | null {
56
54
  if (section.fileHash === undefined) {
57
- return hasAnchorScoped(section)
58
- ? `Missing hashline snapshot tag for anchored edit to ${section.path}; use \`¶${section.path}#tag\` from your latest read.`
59
- : null;
55
+ // The snapshot tag is mandatory on every section — head/tail inserts
56
+ // included to keep this preview path in lockstep with the apply path
57
+ // (`Patcher.prepare`), which rejects tagless sections unconditionally.
58
+ return missingSnapshotTagMessage(section.path);
60
59
  }
61
60
  const snapshot = snapshots.byHash(absolutePath, section.fileHash);
62
61
  if (snapshot && snapshotMatchesCurrent(snapshot, text)) return null;
@@ -76,7 +75,9 @@ export async function computeHashlineSectionDiff(
76
75
  const normalized = normalizeToLF(content);
77
76
  const hashError = validateSectionHash(section, absolutePath, normalized, snapshots);
78
77
  if (hashError) return { error: hashError };
79
- const result = options.streaming ? section.applyPartialTo(normalized) : section.applyTo(normalized);
78
+ const result = options.streaming
79
+ ? section.applyPartialTo(normalized, nativeBlockResolver)
80
+ : section.applyTo(normalized, nativeBlockResolver);
80
81
  if (normalized === result.text) return { error: `No changes would be made to ${section.path}.` };
81
82
  return generateDiffString(normalized, result.text);
82
83
  } catch (err) {
@@ -25,6 +25,7 @@ import { outputMeta } from "../../tools/output-meta";
25
25
  import { generateDiffString } from "../diff";
26
26
  import { getFileSnapshotStore } from "../file-snapshot-store";
27
27
  import type { EditToolDetails, EditToolPerFileResult, LspBatchRequest } from "../renderer";
28
+ import { nativeBlockResolver } from "./block-resolver";
28
29
  import { HashlineFilesystem } from "./filesystem";
29
30
  import { type HashlineParams, hashlineEditParamsSchema } from "./params";
30
31
 
@@ -133,7 +134,7 @@ export async function executeHashlineSingle(
133
134
  batchRequest: options.batchRequest,
134
135
  });
135
136
  const snapshots = getFileSnapshotStore(options.session);
136
- const patcher = new Patcher({ fs, snapshots });
137
+ const patcher = new Patcher({ fs, snapshots, blockResolver: nativeBlockResolver });
137
138
 
138
139
  // Single-section fast path: prepare, commit, render.
139
140
  if (patch.sections.length === 1) {
@@ -1,3 +1,4 @@
1
+ export * from "./block-resolver";
1
2
  export * from "./diff";
2
3
  export * from "./execute";
3
4
  export * from "./filesystem";
@@ -492,6 +492,42 @@ display({"label": "A"})`,
492
492
  expect(reloaded.output.trim()).toBe("2");
493
493
  });
494
494
 
495
+ it("loads TypeScript type-only imports in cells and local modules", async () => {
496
+ using tempDir = TempDir.createSync("@omp-eval-js-type-imports-");
497
+ const sessionFile = path.join(tempDir.path(), "session.jsonl");
498
+ const sessionId = `js-type-imports:${crypto.randomUUID()}`;
499
+ const session = createToolSession(tempDir.path(), sessionFile);
500
+ const typesPath = path.join(tempDir.path(), "types.ts");
501
+ const valuesPath = path.join(tempDir.path(), "values.ts");
502
+ const entryPath = path.join(tempDir.path(), "entry.ts");
503
+ const typesSpec = JSON.stringify(typesPath);
504
+ const entrySpec = JSON.stringify(entryPath);
505
+ await Bun.write(typesPath, "export interface TypeOnly { value: number }\n");
506
+ await Bun.write(valuesPath, "export interface InlineOnly { value: number }\nexport const imported = 41;\n");
507
+ await Bun.write(
508
+ entryPath,
509
+ [
510
+ 'import type { TypeOnly } from "./types.ts";',
511
+ 'import { type InlineOnly, imported } from "./values.ts";',
512
+ "export const typeOnly = 1;",
513
+ "export const inlineType = imported;",
514
+ "",
515
+ ].join("\n"),
516
+ );
517
+
518
+ const result = await executeJs(
519
+ `import type { TypeOnly } from ${typesSpec};\nconst mod = await import(${entrySpec});\nreturn mod.typeOnly + mod.inlineType;`,
520
+ {
521
+ sessionId,
522
+ session,
523
+ sessionFile,
524
+ },
525
+ );
526
+
527
+ expect(result.exitCode).toBe(0);
528
+ expect(result.output.trim()).toBe("42");
529
+ });
530
+
495
531
  it("refreshes the Python tool proxy when bridge env appears after kernel warm-up", async () => {
496
532
  using tempDir = TempDir.createSync("@omp-eval-py-tool-proxy-");
497
533
  const sessionFile = path.join(tempDir.path(), "session.jsonl");
@@ -88,7 +88,10 @@ export class LocalModuleLoader {
88
88
 
89
89
  async #buildLocalModule(modulePath: string): Promise<LocalModuleEntry> {
90
90
  const rawSource = fs.readFileSync(modulePath, "utf8");
91
- const stripped = stripTypeScriptSyntax(rawSource);
91
+ const stripped = stripTypeScriptSyntax(rawSource, {
92
+ force: isTypeScriptModulePath(modulePath),
93
+ loader: stripLoaderForPath(modulePath),
94
+ });
92
95
  const moduleDir = path.dirname(modulePath);
93
96
  const localDeps = new Set<string>();
94
97
  for (const specifier of collectModuleSourceSpecifiers(stripped)) {
@@ -251,6 +254,15 @@ function isLocalPathSpecifier(source: string): boolean {
251
254
  );
252
255
  }
253
256
 
257
+ function isTypeScriptModulePath(modulePath: string): boolean {
258
+ const ext = path.extname(modulePath);
259
+ return ext === ".ts" || ext === ".tsx" || ext === ".mts";
260
+ }
261
+
262
+ function stripLoaderForPath(modulePath: string): "ts" | "tsx" {
263
+ return path.extname(modulePath) === ".tsx" ? "tsx" : "ts";
264
+ }
265
+
254
266
  function isManagedLocalModulePath(target: string): boolean {
255
267
  return (
256
268
  path.isAbsolute(target) &&
@@ -75,6 +75,7 @@ function parseProgram(code: string): { program: { body: ReadonlyArray<BabelProgr
75
75
  allowSuperOutsideMethod: true,
76
76
  allowUndeclaredExports: true,
77
77
  errorRecovery: true,
78
+ plugins: ["typescript"],
78
79
  }) as unknown as { program: { body: ReadonlyArray<BabelProgramNode> } };
79
80
  } catch {
80
81
  return null;
@@ -178,8 +179,7 @@ export function rewriteImports(code: string): string {
178
179
  if (node.type !== "CallExpression") return;
179
180
  const call = node as unknown as { callee?: { type?: string; start?: number; end?: number } };
180
181
  const callee = call.callee;
181
- if (!callee || callee.type !== "Import" || typeof callee.start !== "number" || typeof callee.end !== "number")
182
- return;
182
+ if (callee?.type !== "Import" || typeof callee.start !== "number" || typeof callee.end !== "number") return;
183
183
  edits.push({ start: callee.start, end: callee.end, text: "__omp_import__" });
184
184
  });
185
185
 
@@ -252,12 +252,7 @@ export function rewriteDynamicImports(code: string, callee = "__omp_import__"):
252
252
  if (node.type !== "CallExpression") return;
253
253
  const call = node as unknown as { callee?: { type?: string; start?: number; end?: number } };
254
254
  const callCallee = call.callee;
255
- if (
256
- !callCallee ||
257
- callCallee.type !== "Import" ||
258
- typeof callCallee.start !== "number" ||
259
- typeof callCallee.end !== "number"
260
- ) {
255
+ if (callCallee?.type !== "Import" || typeof callCallee.start !== "number" || typeof callCallee.end !== "number") {
261
256
  return;
262
257
  }
263
258
  edits.push({ start: callCallee.start, end: callCallee.end, text: callee });
@@ -453,38 +448,48 @@ function requiresAsyncWrapper(code: string): boolean {
453
448
  }
454
449
 
455
450
  /**
456
- * Strip TypeScript syntax (type annotations, `interface`, `as`, `satisfies`, generics in
457
- * call expressions, etc.) before the import/lexical rewriters parse the code. We use Bun's
458
- * native transpiler in `ts` loader mode — fast, no JSX transforms, preserves `import`/
459
- * `export` declarations so the downstream Babel rewrites keep working.
451
+ * Strip TypeScript syntax (type annotations, type-only imports/exports, `interface`, `as`,
452
+ * `satisfies`, generics in call expressions, etc.) before the import/lexical rewriters parse
453
+ * the code. Bun's native transpiler preserves `import`/`export` declarations, so downstream
454
+ * Babel rewrites still control module resolution.
460
455
  *
461
- * Skipped when the code parses as plain JavaScript already (Babel can accept it), so the
462
- * common case avoids an extra transpile pass. We detect "looks like TS" with a cheap regex
463
- * before invoking the transpiler.
456
+ * Eval cells use a cheap "looks like TS" heuristic to avoid transpiling ordinary JS. Known
457
+ * TypeScript modules pass `force` because a file can contain TS-only module syntax such as
458
+ * `import type` without any value-level type annotations.
464
459
  */
465
- function stripTypeScript(code: string): string {
466
- if (!LOOKS_LIKE_TS.test(code)) return code;
460
+ type TypeScriptStripLoader = "ts" | "tsx";
461
+
462
+ const TS_TRANSPILER = new Bun.Transpiler({ loader: "ts" });
463
+ const TSX_TRANSPILER = new Bun.Transpiler({ loader: "tsx" });
464
+
465
+ function stripTypeScript(code: string, options: { force?: boolean; loader?: TypeScriptStripLoader } = {}): string {
466
+ if (!options.force && !LOOKS_LIKE_TS.test(code)) return code;
467
467
  try {
468
- return new Bun.Transpiler({ loader: "ts" }).transformSync(code);
468
+ const transpiler = options.loader === "tsx" ? TSX_TRANSPILER : TS_TRANSPILER;
469
+ return transpiler.transformSync(code);
469
470
  } catch {
470
471
  // Transpiler failed (e.g. unrecoverable syntax). Hand the original source back so the
471
472
  // downstream rewriter / VM surfaces the real error to the user.
472
473
  return code;
473
474
  }
474
475
  }
475
- export function stripTypeScriptSyntax(code: string): string {
476
- return stripTypeScript(code);
476
+ export function stripTypeScriptSyntax(
477
+ code: string,
478
+ options: { force?: boolean; loader?: TypeScriptStripLoader } = {},
479
+ ): string {
480
+ return stripTypeScript(code, options);
477
481
  }
478
482
 
479
- // Heuristic: any of the obvious TS-only tokens. Plain JS using `as` only inside strings
480
- // won't match because we require a leading word boundary plus a colon/keyword neighbor.
483
+ // Heuristic: obvious TS-only tokens, including type-only module syntax. Plain JS using `as`
484
+ // only inside strings won't match because we require a leading word boundary plus a
485
+ // colon/keyword neighbor.
481
486
  const LOOKS_LIKE_TS =
482
- /(?:\binterface\s+\w|\btype\s+\w+\s*=|\b(?:as|satisfies)\s+(?:[A-Z]|\bconst\b)|:\s*(?:string|number|boolean|any|unknown|void|never|object|[A-Z]\w*)\b|<\s*[A-Z]\w*\s*[,>])/;
487
+ /(?:\bimport\s+type\b|\bexport\s+type\b|\b(?:import|export)\s*\{[^}\n]*\btype\s+\w|\binterface\s+\w|\btype\s+\w+\s*=|\b(?:as|satisfies)\s+(?:[A-Z]|\bconst\b)|:\s*(?:string|number|boolean|any|unknown|void|never|object|[A-Z]\w*)\b|<\s*[A-Z]\w*\s*[,>])/;
483
488
 
484
489
  export function wrapCode(code: string): { source: string; asyncWrapped: boolean; finalExpressionReturned: boolean } {
485
- const stripped = stripTypeScript(code);
486
- const finalExpression = returnFinalExpression(stripped);
487
- const importsRewritten = rewriteImports(finalExpression.source);
490
+ const finalExpression = returnFinalExpression(code);
491
+ const stripped = stripTypeScript(finalExpression.source);
492
+ const importsRewritten = rewriteImports(stripped);
488
493
  const needsAsyncWrapper = requiresAsyncWrapper(importsRewritten);
489
494
  const rewritten = {
490
495
  source: demoteTopLevelLexicals(importsRewritten, { publishGlobals: needsAsyncWrapper }),