kimaki 0.26.0 → 0.28.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 (302) hide show
  1. package/dist/agent-model.e2e.test.js +250 -164
  2. package/dist/anthropic-account-identity.js +9 -1
  3. package/dist/anthropic-auth-plugin.js +10 -13
  4. package/dist/anthropic-auth-plugin.test.js +17 -0
  5. package/dist/anthropic-auth-state.js +1 -0
  6. package/dist/bash-tool-schema-plugin.js +32 -0
  7. package/dist/bash-tool-schema-plugin.test.js +49 -0
  8. package/dist/bin.js +73 -19
  9. package/dist/cli-commands/maintenance.js +46 -7
  10. package/dist/cli-commands/project.js +11 -27
  11. package/dist/cli-commands/send.js +30 -1
  12. package/dist/cli-commands/session.js +322 -90
  13. package/dist/cli-commands/task.js +34 -15
  14. package/dist/cli-parsing.test.js +83 -4
  15. package/dist/cli-runner.js +44 -8
  16. package/dist/cli-runner.test.js +27 -0
  17. package/dist/cli-send-thread.e2e.test.js +9 -9
  18. package/dist/cli.js +32 -0
  19. package/dist/commands/abort.js +5 -1
  20. package/dist/commands/action-buttons.js +2 -1
  21. package/dist/commands/add-project.js +4 -7
  22. package/dist/commands/agent.js +164 -32
  23. package/dist/commands/ask-question.js +23 -33
  24. package/dist/commands/ask-question.test.js +43 -1
  25. package/dist/commands/btw.js +28 -6
  26. package/dist/commands/file-upload.js +2 -1
  27. package/dist/commands/fork-subagent.js +2 -4
  28. package/dist/commands/fork.js +4 -4
  29. package/dist/commands/last-sessions.js +17 -10
  30. package/dist/commands/login.js +6 -0
  31. package/dist/commands/merge-worktree.js +47 -14
  32. package/dist/commands/model-variant.js +8 -1
  33. package/dist/commands/model.js +16 -9
  34. package/dist/commands/multioauth.js +1 -0
  35. package/dist/commands/new-worktree.js +170 -102
  36. package/dist/commands/queue.js +3 -2
  37. package/dist/commands/resume.js +4 -4
  38. package/dist/commands/session.js +78 -63
  39. package/dist/commands/user-command.js +6 -8
  40. package/dist/commands/worktrees.js +5 -13
  41. package/dist/config.js +16 -1
  42. package/dist/cpu-profiler.js +211 -0
  43. package/dist/cpu-profiler.test.js +160 -0
  44. package/dist/database.js +152 -3
  45. package/dist/db.js +33 -0
  46. package/dist/db.test.js +267 -1
  47. package/dist/default-channel-provisioning.test.js +148 -0
  48. package/dist/discord-bot.js +517 -488
  49. package/dist/discord-command-registration.js +25 -10
  50. package/dist/discord-command-registration.test.js +17 -0
  51. package/dist/discord-utils.js +97 -5
  52. package/dist/discord-utils.test.js +151 -1
  53. package/dist/errors.js +10 -0
  54. package/dist/exec-async.js +17 -5
  55. package/dist/external-directory-permissions.e2e.test.js +11 -7
  56. package/dist/external-directory-project-deny.e2e.test.js +5 -3
  57. package/dist/external-opencode-sync.js +94 -17
  58. package/dist/external-opencode-sync.test.js +206 -0
  59. package/dist/extract-sdk-error-message.test.js +13 -0
  60. package/dist/file-edit-log.js +197 -0
  61. package/dist/file-edit-log.test.js +258 -0
  62. package/dist/format-tables.js +52 -0
  63. package/dist/format-tables.test.js +93 -0
  64. package/dist/gateway-proxy-reconnect.e2e.test.js +28 -8
  65. package/dist/gateway-proxy.e2e.test.js +18 -18
  66. package/dist/git-worktree-core.js +182 -17
  67. package/dist/interaction-handler.js +416 -398
  68. package/dist/ipc-tools-plugin.js +68 -3
  69. package/dist/kimaki-opencode-plugin-loading.e2e.test.js +122 -58
  70. package/dist/kimaki-opencode-plugin.js +9 -3
  71. package/dist/kimaki-workspace-adaptor.js +33 -10
  72. package/dist/logger.js +11 -1
  73. package/dist/markdown.js +3 -1
  74. package/dist/markdown.test.js +11 -3
  75. package/dist/message-formatting.js +181 -51
  76. package/dist/message-formatting.test.js +529 -22
  77. package/dist/message-preprocessing.js +105 -1
  78. package/dist/oauth-rotation-shared.js +10 -0
  79. package/dist/openai-auth-plugin.js +1 -0
  80. package/dist/openai-auth-state.js +1 -0
  81. package/dist/opencode-command.js +28 -0
  82. package/dist/opencode-command.test.js +109 -1
  83. package/dist/opencode-interrupt-plugin.js +4 -0
  84. package/dist/opencode-interrupt-plugin.test.js +24 -0
  85. package/dist/opencode.js +82 -15
  86. package/dist/project-add.test.js +62 -0
  87. package/dist/question-abort-clears-dropdown.e2e.test.js +89 -0
  88. package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +150 -0
  89. package/dist/question-text-before-dropdown.e2e.test.js +52 -0
  90. package/dist/queue-advanced-abort.e2e.test.js +8 -10
  91. package/dist/queue-advanced-action-buttons.e2e.test.js +8 -8
  92. package/dist/queue-advanced-e2e-setup.js +78 -5
  93. package/dist/queue-advanced-footer.e2e.test.js +60 -269
  94. package/dist/queue-advanced-model-switch.e2e.test.js +9 -11
  95. package/dist/queue-advanced-permissions-typing.e2e.test.js +15 -15
  96. package/dist/queue-advanced-question.e2e.test.js +32 -27
  97. package/dist/queue-advanced-typing-interrupt.e2e.test.js +10 -12
  98. package/dist/queue-advanced-typing.e2e.test.js +7 -9
  99. package/dist/queue-delete-message.e2e.test.js +3 -3
  100. package/dist/queue-drain-after-interactive-ui.e2e.test.js +5 -5
  101. package/dist/queue-interrupt-drain.e2e.test.js +122 -12
  102. package/dist/queue-question-select-drain.e2e.test.js +34 -132
  103. package/dist/runtime-lifecycle.e2e.test.js +21 -51
  104. package/dist/schema.js +35 -1
  105. package/dist/session-handler/event-stream-state.js +391 -50
  106. package/dist/session-handler/event-stream-state.test.js +736 -8
  107. package/dist/session-handler/model-utils.js +172 -5
  108. package/dist/session-handler/model-utils.test.js +445 -0
  109. package/dist/session-handler/opencode-session-event-log.js +30 -1
  110. package/dist/session-handler/thread-ingress.test.js +41 -0
  111. package/dist/session-handler/thread-runtime-state.js +2 -0
  112. package/dist/session-handler/thread-session-runtime-summary.test.js +121 -0
  113. package/dist/session-handler/thread-session-runtime.js +574 -299
  114. package/dist/session-search.js +123 -4
  115. package/dist/session-search.test.js +248 -1
  116. package/dist/session-sleep.test.js +224 -0
  117. package/dist/session-title-rename.test.js +37 -39
  118. package/dist/sleep.e2e.test.js +494 -0
  119. package/dist/startup-time.e2e.test.js +2 -1
  120. package/dist/store.js +3 -0
  121. package/dist/system-message.js +102 -16
  122. package/dist/system-message.test.js +192 -15
  123. package/dist/task-runner.js +96 -5
  124. package/dist/task-schedule.js +87 -1
  125. package/dist/task-schedule.test.js +149 -1
  126. package/dist/test-utils.js +17 -9
  127. package/dist/thinking-utils.js +10 -0
  128. package/dist/thinking-utils.test.js +35 -0
  129. package/dist/thread-message-queue.e2e.test.js +65 -213
  130. package/dist/undo-redo.e2e.test.js +17 -9
  131. package/dist/voice-attachment.js +23 -1
  132. package/dist/voice-handler.js +21 -4
  133. package/dist/voice-message.e2e.test.js +144 -30
  134. package/dist/voice-routing-input.e2e.test.js +195 -0
  135. package/dist/voice-routing-question.e2e.test.js +125 -0
  136. package/dist/voice.js +28 -8
  137. package/dist/voice.test.js +96 -30
  138. package/dist/worktree-clone-isolation.e2e.test.js +188 -0
  139. package/dist/worktree-lifecycle.e2e.test.js +55 -36
  140. package/dist/worktrees.js +226 -443
  141. package/dist/worktrees.test.js +435 -58
  142. package/dist/xai-auth-plugin.js +1 -0
  143. package/dist/xai-auth-state.js +1 -0
  144. package/package.json +8 -6
  145. package/skills/bump-holocron-consumers/SKILL.md +137 -0
  146. package/skills/critique/SKILL.md +4 -4
  147. package/skills/errore/SKILL.md +60 -218
  148. package/skills/goke/SKILL.md +150 -10
  149. package/skills/holocron/SKILL.md +154 -17
  150. package/skills/npm-package/SKILL.md +55 -35
  151. package/skills/profano/SKILL.md +17 -2
  152. package/skills/spiceflow/SKILL.md +39 -3
  153. package/skills/strada/SKILL.md +39 -0
  154. package/skills/zele/SKILL.md +19 -0
  155. package/src/agent-model.e2e.test.ts +287 -186
  156. package/src/anthropic-account-identity.ts +20 -1
  157. package/src/anthropic-auth-plugin.test.ts +19 -0
  158. package/src/anthropic-auth-plugin.ts +12 -16
  159. package/src/anthropic-auth-state.ts +1 -0
  160. package/src/bash-tool-schema-plugin.test.ts +55 -0
  161. package/src/bash-tool-schema-plugin.ts +46 -0
  162. package/src/bin.ts +78 -26
  163. package/src/cli-commands/maintenance.ts +59 -13
  164. package/src/cli-commands/project.ts +14 -35
  165. package/src/cli-commands/send.ts +31 -1
  166. package/src/cli-commands/session.ts +374 -114
  167. package/src/cli-commands/task.ts +34 -15
  168. package/src/cli-parsing.test.ts +103 -4
  169. package/src/cli-runner.test.ts +49 -0
  170. package/src/cli-runner.ts +44 -7
  171. package/src/cli-send-thread.e2e.test.ts +9 -11
  172. package/src/cli.ts +53 -0
  173. package/src/commands/abort.ts +6 -1
  174. package/src/commands/action-buttons.ts +2 -1
  175. package/src/commands/add-project.ts +4 -8
  176. package/src/commands/agent.ts +205 -29
  177. package/src/commands/ask-question.test.ts +48 -1
  178. package/src/commands/ask-question.ts +23 -33
  179. package/src/commands/btw.ts +40 -5
  180. package/src/commands/file-upload.ts +2 -1
  181. package/src/commands/fork-subagent.ts +2 -3
  182. package/src/commands/fork.ts +4 -3
  183. package/src/commands/last-sessions.ts +10 -4
  184. package/src/commands/login.ts +6 -0
  185. package/src/commands/merge-worktree.ts +54 -16
  186. package/src/commands/model-variant.ts +9 -1
  187. package/src/commands/model.ts +20 -9
  188. package/src/commands/multioauth.ts +1 -0
  189. package/src/commands/new-worktree.ts +138 -55
  190. package/src/commands/queue.ts +3 -2
  191. package/src/commands/resume.ts +4 -3
  192. package/src/commands/session.ts +94 -70
  193. package/src/commands/user-command.ts +6 -6
  194. package/src/commands/worktrees.ts +5 -12
  195. package/src/config.ts +18 -1
  196. package/src/cpu-profiler.test.ts +183 -0
  197. package/src/cpu-profiler.ts +293 -0
  198. package/src/database.ts +235 -3
  199. package/src/db.test.ts +293 -0
  200. package/src/db.ts +38 -0
  201. package/src/default-channel-provisioning.test.ts +155 -0
  202. package/src/discord-bot.ts +78 -36
  203. package/src/discord-command-registration.test.ts +20 -0
  204. package/src/discord-command-registration.ts +42 -12
  205. package/src/discord-utils.test.ts +176 -0
  206. package/src/discord-utils.ts +152 -7
  207. package/src/errors.ts +12 -0
  208. package/src/exec-async.ts +20 -11
  209. package/src/external-directory-permissions.e2e.test.ts +11 -7
  210. package/src/external-directory-project-deny.e2e.test.ts +5 -3
  211. package/src/external-opencode-sync.test.ts +248 -0
  212. package/src/external-opencode-sync.ts +122 -19
  213. package/src/extract-sdk-error-message.test.ts +18 -0
  214. package/src/file-edit-log.test.ts +287 -0
  215. package/src/file-edit-log.ts +283 -0
  216. package/src/format-tables.test.ts +101 -0
  217. package/src/format-tables.ts +75 -0
  218. package/src/gateway-proxy-reconnect.e2e.test.ts +38 -8
  219. package/src/gateway-proxy.e2e.test.ts +18 -17
  220. package/src/git-worktree-core.ts +247 -20
  221. package/src/interaction-handler.ts +36 -13
  222. package/src/ipc-tools-plugin.ts +82 -5
  223. package/src/kimaki-opencode-plugin-loading.e2e.test.ts +188 -97
  224. package/src/kimaki-opencode-plugin.ts +9 -3
  225. package/src/kimaki-workspace-adaptor.ts +40 -11
  226. package/src/logger.ts +14 -4
  227. package/src/markdown.test.ts +13 -3
  228. package/src/markdown.ts +4 -1
  229. package/src/message-formatting.test.ts +672 -25
  230. package/src/message-formatting.ts +242 -53
  231. package/src/message-preprocessing.ts +116 -1
  232. package/src/oauth-rotation-shared.ts +10 -0
  233. package/src/openai-auth-plugin.ts +1 -0
  234. package/src/openai-auth-state.ts +1 -0
  235. package/src/opencode-command.test.ts +140 -0
  236. package/src/opencode-command.ts +38 -0
  237. package/src/opencode-interrupt-plugin.test.ts +32 -0
  238. package/src/opencode-interrupt-plugin.ts +3 -0
  239. package/src/opencode.ts +115 -15
  240. package/src/project-add.test.ts +80 -0
  241. package/src/question-abort-clears-dropdown.e2e.test.ts +109 -0
  242. package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +172 -0
  243. package/src/question-text-before-dropdown.e2e.test.ts +67 -0
  244. package/src/queue-advanced-abort.e2e.test.ts +8 -9
  245. package/src/queue-advanced-action-buttons.e2e.test.ts +8 -8
  246. package/src/queue-advanced-e2e-setup.ts +83 -4
  247. package/src/queue-advanced-footer.e2e.test.ts +67 -315
  248. package/src/queue-advanced-model-switch.e2e.test.ts +9 -10
  249. package/src/queue-advanced-permissions-typing.e2e.test.ts +15 -15
  250. package/src/queue-advanced-question.e2e.test.ts +36 -30
  251. package/src/queue-advanced-typing-interrupt.e2e.test.ts +11 -11
  252. package/src/queue-advanced-typing.e2e.test.ts +8 -8
  253. package/src/queue-delete-message.e2e.test.ts +3 -3
  254. package/src/queue-drain-after-interactive-ui.e2e.test.ts +5 -5
  255. package/src/queue-interrupt-drain.e2e.test.ts +133 -12
  256. package/src/queue-question-select-drain.e2e.test.ts +36 -161
  257. package/src/runtime-lifecycle.e2e.test.ts +21 -54
  258. package/src/schema.sql +15 -2
  259. package/src/schema.ts +37 -1
  260. package/src/session-handler/event-stream-state.test.ts +790 -7
  261. package/src/session-handler/event-stream-state.ts +568 -53
  262. package/src/session-handler/model-utils.test.ts +537 -0
  263. package/src/session-handler/model-utils.ts +281 -5
  264. package/src/session-handler/opencode-session-event-log.ts +31 -1
  265. package/src/session-handler/thread-ingress.test.ts +54 -0
  266. package/src/session-handler/thread-runtime-state.ts +3 -0
  267. package/src/session-handler/thread-session-runtime-summary.test.ts +169 -0
  268. package/src/session-handler/thread-session-runtime.ts +712 -358
  269. package/src/session-search.test.ts +281 -0
  270. package/src/session-search.ts +241 -7
  271. package/src/session-sleep.test.ts +294 -0
  272. package/src/session-title-rename.test.ts +54 -48
  273. package/src/sleep.e2e.test.ts +565 -0
  274. package/src/startup-time.e2e.test.ts +5 -1
  275. package/src/store.ts +22 -0
  276. package/src/system-message.test.ts +226 -15
  277. package/src/system-message.ts +130 -16
  278. package/src/task-runner.ts +138 -3
  279. package/src/task-schedule.test.ts +170 -0
  280. package/src/task-schedule.ts +141 -1
  281. package/src/test-utils.ts +21 -10
  282. package/src/thinking-utils.test.ts +46 -0
  283. package/src/thinking-utils.ts +21 -0
  284. package/src/thread-message-queue.e2e.test.ts +65 -245
  285. package/src/undo-redo.e2e.test.ts +17 -9
  286. package/src/voice-attachment.ts +29 -1
  287. package/src/voice-handler.ts +23 -4
  288. package/src/voice-message.e2e.test.ts +149 -29
  289. package/src/voice-routing-input.e2e.test.ts +203 -0
  290. package/src/voice-routing-question.e2e.test.ts +131 -0
  291. package/src/voice.test.ts +107 -29
  292. package/src/voice.ts +34 -6
  293. package/src/worktree-clone-isolation.e2e.test.ts +213 -0
  294. package/src/worktree-lifecycle.e2e.test.ts +55 -35
  295. package/src/worktrees.test.ts +505 -64
  296. package/src/worktrees.ts +257 -603
  297. package/src/xai-auth-plugin.ts +1 -0
  298. package/src/xai-auth-state.ts +1 -0
  299. package/dist/message-finish-field.e2e.test.js +0 -169
  300. package/dist/subagent-rate-limit-plugin.js +0 -184
  301. package/src/message-finish-field.e2e.test.ts +0 -196
  302. package/src/subagent-rate-limit-plugin.ts +0 -228
@@ -0,0 +1,137 @@
1
+ ---
2
+ name: bump-holocron-consumers
3
+ repo: remorses/holocron
4
+ description: >
5
+ Bump @holocron.so/vite and @holocron.so/cli in Tommy's other GitHub repos.
6
+ Use when asked to update holocron versions across remorses consumer sites,
7
+ find repos that depend on holocron.so/vite or holocron.so/cli, or sync
8
+ docs-site packages after a holocron release.
9
+ ---
10
+
11
+ # Bump Holocron in consumer repos
12
+
13
+ After a holocron npm release, bump **@holocron.so/vite** and **@holocron.so/cli** in Tommy's other repos. Do this on **local clones**. Do not clone missing repos unless asked. Do not commit unless asked.
14
+
15
+ Load the **pnpm** skill before running any `pnpm update`.
16
+
17
+ ## 1. Read the latest versions
18
+
19
+ ```bash
20
+ npm view @holocron.so/vite version
21
+ npm view @holocron.so/cli version
22
+ ```
23
+
24
+ Confirm they match `vite/package.json` and `cli/package.json` in this repo. Never guess.
25
+
26
+ ## 2. Search GitHub
27
+
28
+ ```bash
29
+ gh search code '@holocron.so/vite' --owner remorses --filename package.json --limit 100 --json repository,path,textMatches
30
+
31
+ gh search code '@holocron.so/cli' --owner remorses --filename package.json --limit 50 --json repository,path,textMatches
32
+ ```
33
+
34
+ Read the **full** JSON. Never pipe to `head` or `tail`.
35
+
36
+ Treat a hit as a real dependency only when it is in `dependencies` or `devDependencies`. Skip `packageExtensions`, the holocron package's own `"name"` field, and `workspace:^`.
37
+
38
+ ## 3. Skip these
39
+
40
+ - **remorses/holocron** itself (`workspace:^`)
41
+ - Specifiers already set to **`latest`** (template, throwaway test sites)
42
+ - Empty directories and non-git folders
43
+ - Repos not on this machine, unless the user asks to clone them
44
+
45
+ Typical `latest` skips: `holocron-template`, `context-dev-docs`, `palmier-pro-docs`, `example-holocron-again`, `holocron-testing-again`, `holocron-example-againagain`, `holocron-test-antoher`, `holocron-docs-new-again`, `holocron-test-2`.
46
+
47
+ ## 4. Map GitHub repo to a local folder
48
+
49
+ Search these roots, in order:
50
+
51
+ 1. `/Users/morse/Documents/GitHub/<name>`
52
+ 2. `/Users/morse/Documents/<name>`
53
+ 3. The alias table below
54
+ 4. `git remote -v` on nearby folders when the folder name differs from the GitHub name
55
+
56
+ ```bash
57
+ for d in /Users/morse/Documents/GitHub/*/ /Users/morse/Documents/*/; do
58
+ url=$(git -C "$d" remote get-url origin 2>/dev/null) || continue
59
+ case "$url" in
60
+ *remorses/kimaki*) echo "kimaki -> $d" ;;
61
+ *remorses/gpuix*) echo "gpuix -> $d" ;;
62
+ *remorses/opensession*) echo "opensession -> $d" ;;
63
+ *remorses/traforo*) echo "traforo -> $d" ;;
64
+ *remorses/spiceflow*) echo "spiceflow -> $d" ;;
65
+ esac
66
+ done
67
+ ```
68
+
69
+ **Known aliases** (verify `origin` if a path is missing):
70
+
71
+ | GitHub | Local path | Notes |
72
+ |---|---|---|
73
+ | remorses/kimaki | `/Users/morse/Documents/GitHub/kimakivoice` | `/GitHub/kimaki` is an empty dir. Dep lives in `website/` |
74
+ | remorses/gpuix | `/Users/morse/Documents/GitHub/gpuixlocal` | `website/` is **not** in bun workspaces. Update from that folder |
75
+ | remorses/opensession | `/Users/morse/Documents/GitHub/opensessions` | vite + cli |
76
+ | remorses/traforo | `/Users/morse/Documents/GitHub/kimakivoice/traforo` | git submodule. kimaki workspace also includes `./traforo/website` |
77
+ | remorses/spiceflow | `/Users/morse/Documents/spiceflow` | Not under GitHub/. Local HEAD can lag origin and omit `website/` |
78
+ | remorses/playwriter | `/Users/morse/Documents/GitHub/playwriter` | `website/` |
79
+ | remorses/termcast | `/Users/morse/Documents/GitHub/termcast` | `website/` |
80
+ | remorses/egaki | `/Users/morse/Documents/GitHub/egaki` | `website/` |
81
+ | remorses/sigillo | `/Users/morse/Documents/GitHub/sigillo` | Dep is in **`app/`**, not website/ |
82
+ | remorses/subrouter | `/Users/morse/Documents/GitHub/subrouter` | Prefer this clone over `kimakivoice/subrouter` |
83
+ | remorses/strada | `/Users/morse/Documents/GitHub/strada` | `website/` |
84
+ | remorses/notaku | `/Users/morse/Documents/GitHub/notaku` | `website/` |
85
+
86
+ Repos often **not** on disk: `akarso-private`, `dmcafreemusic`. Skip them. Report them.
87
+
88
+ If `website/` is missing locally, do not `git pull` to create it unless asked. Report that the clone is behind.
89
+
90
+ ## 5. Update
91
+
92
+ Pick the package manager from the lockfile at the **repo root** (`pnpm-lock.yaml` vs `bun.lock`).
93
+
94
+ **pnpm workspace** (run at repo root):
95
+
96
+ ```bash
97
+ pnpm update -r --latest @holocron.so/vite
98
+ pnpm update -r --latest @holocron.so/cli
99
+ ```
100
+
101
+ **bun**, from the folder whose `package.json` lists the dep (often `website/`):
102
+
103
+ ```bash
104
+ bun update --latest @holocron.so/vite
105
+ bun update --latest @holocron.so/cli
106
+ ```
107
+
108
+ Never hand-edit `package.json` to bump a version. Never use `pnpm add` to update. Never add `pnpm.overrides`.
109
+
110
+ If the dep folder is **not** a workspace member (gpuix `website/`), run the update **inside that folder**, not at the repo root.
111
+
112
+ Keep the existing specifier style. Exact `0.33.0` stays exact. Caret `^0.30.0` stays caret.
113
+
114
+ ## 6. Same-day publish / minimum release age
115
+
116
+ Global pnpm `minimumReleaseAge` is **1440** minutes. `@holocron.so/*` is already on the exclude list. If install still fails with:
117
+
118
+ ```text
119
+ ERR_PNPM_NO_MATCHING_VERSION No matching version found for @holocron.so/cli@^X.Y.Z
120
+ The latest release of @holocron.so/cli is "X.Y.Z".
121
+ ```
122
+
123
+ the project is pinned to an older pnpm via `packageManager` (notaku uses `pnpm@10.16.1`) and the exclude glob is ignored. Retry with:
124
+
125
+ ```bash
126
+ npm_config_minimum_release_age=0 pnpm --filter website update @holocron.so/vite@<latest>
127
+ ```
128
+
129
+ Do not write `minimum-release-age=0` into the consumer's `.npmrc`.
130
+
131
+ ## 7. Verify, then stop
132
+
133
+ Read each touched `package.json` and confirm the specifier is the latest. Then stop. Do not commit. Do not push. Do not deploy.
134
+
135
+ If the user asks to commit, commit **only** the holocron package.json + lockfile hunks in each consumer repo. Use that repo's own git. Never commit unrelated dirty files.
136
+
137
+ When a new GitHub-to-folder alias is discovered, add it to the table in this skill.
@@ -74,17 +74,17 @@ critique hunks list
74
74
  critique hunks list --filter "src/**/*.ts"
75
75
 
76
76
  # Stage specific hunks by ID
77
- critique hunks add 'src/main.ts:@-10,6+10,7'
78
- critique hunks add 'src/main.ts:@-10,6+10,7' 'src/utils.ts:@-5,3+5,4'
77
+ critique hunks add 'src/main.ts:@a1b2c3d4e5f6'
78
+ critique hunks add 'src/main.ts:@a1b2c3d4e5f6' 'src/utils.ts:@f6e5d4c3b2a1'
79
79
  ```
80
80
 
81
- Hunk ID format: `file:@-oldStart,oldLines+newStart,newLines` — derived from the `@@` diff header, stable across runs.
81
+ Hunk ID format: `file:@<hash>` or `file:@<hash>.1`. The hash is from added and removed lines, so unrelated edits above or below the hunk do not change the ID. Duplicate payloads in the same file get `.1`, `.2`. Old `file:@-oldStart,...` IDs are no longer accepted. `hunks list` still prints the `@@` header under each ID.
82
82
 
83
83
  **Typical workflow:**
84
84
 
85
85
  ```bash
86
86
  critique hunks list # see all unstaged hunks
87
- critique hunks add 'file:@-10,6+10,7' # stage only your hunks
87
+ critique hunks add 'file:@a1b2c3d4e5f6' # stage only your hunks
88
88
  git commit -m "your changes" # commit separately
89
89
  ```
90
90
 
@@ -1,17 +1,25 @@
1
1
  ---
2
2
  name: errore
3
- description: >
4
- errore is Go-style error handling for TypeScript: return errors instead of throwing them.
5
- Instead of Go's two-value tuple (val, err), functions return a single Error | T union.
6
- Instead of checking err != nil, you check instanceof Error. TypeScript narrows the type
7
- automatically — forget to check and your code won't compile. No wrapper types, no Result
8
- monads, just unions and instanceof. The errore npm package provides helper utilities
9
- (createTaggedError, tryAsync, matchError, findCause, partition) but the core pattern is
10
- zero-dependency. Benefits: every error is visible in the return type, callers can't forget
11
- to handle errors, flat control flow with early returns instead of nested try-catch, and
12
- errors carry typed properties with cause chains for debugging. ALWAYS read this skill when
13
- a repo uses the errore "errors as values" convention (errore.org). ALWAYS use errore for
14
- new TypeScript projects.
3
+ description: |
4
+ Go-style error handling for TypeScript: return errors as a plain `Error | T` union
5
+ instead of throwing. `instanceof Error` narrows the type — no Result wrapper. The gist:
6
+
7
+ ```ts
8
+ class NotFoundError extends errore.createTaggedError({
9
+ name: 'NotFoundError',
10
+ message: 'User $id not found',
11
+ }) {}
12
+
13
+ async function getUser(id: string) {
14
+ const user = await db.findUser(id).catch((e) => new DbError({ cause: e }))
15
+ if (user instanceof Error) return user // early return, like Go's err != nil
16
+ if (!user) return new NotFoundError({ id })
17
+ return user // caller narrows the same way
18
+ }
19
+ ```
20
+
21
+ ALWAYS read this skill when a repo uses the errore convention (errore.org).
22
+ ALWAYS use errore for new TypeScript projects.
15
23
  version: 0.11.0
16
24
  ---
17
25
 
@@ -27,72 +35,42 @@ console.log(user.name) // TypeScript knows: User
27
35
 
28
36
  ## Rules
29
37
 
38
+ Each rule is one line; the details live once in the pattern sections below.
39
+
30
40
  1. Always `import * as errore from 'errore'` — namespace import, never destructure
31
41
  2. Never throw for expected failures — return errors as values
32
42
  3. Never return `unknown | Error` — the union collapses to `unknown`, breaks narrowing. Common trap: `res.json()` returns `unknown`, so `return await res.json()` makes the return type `MyError | unknown` → `unknown`. Fix: cast with `as` → `return (await res.json()) as User`
33
- 4. Avoid `try-catch` for control flow — use `.catch()` for async boundaries, `errore.try` for sync boundaries
34
- 5. Use `createTaggedError` for domain errors — gives you `_tag`, typed properties, `$variable` interpolation, `cause`, `findCause`, `toJSON`, and fingerprinting
35
- 6. Let TypeScript infer return types — only add explicit annotations when they improve readability (complex unions, public APIs) or when inference produces a wider type than intended
36
- 7. Use `cause` to wrap errors — `new MyError({ ..., cause: originalError })`
37
- 8. Use `| null` for optional values, not `| undefined` — three-way narrowing: `instanceof Error`, `=== null`, then value
38
- 9. Use `const` + expressions, never `let` + try-catch — ternaries, IIFEs, `instanceof Error`
39
- 10. Always handle errors inside `if` branches with early exits, keep the happy path at root — like Go's `if err != nil { return err }`, check the error, exit (return/continue/break), and continue the success path at the top indentation level. This makes the happy path readable top-to-bottom with minimal nesting
40
- 11. Always include `Error` handler in `matchError` — required fallback for plain Error instances
41
- 12. Use `.catch()` for async boundaries, `errore.try` for sync boundaries — only at the lowest call stack level where you interact with uncontrolled dependencies (third-party libs, `JSON.parse`, `fetch`, file I/O). Your own code should return errors as values, not throw.
42
- 13. Always wrap `.catch()` in a tagged domain error — `.catch((e) => new MyError({ cause: e }))`. The `.catch()` callback receives `any`, but wrapping in a typed error gives the union a concrete type. Never use `.catch((e) => e as Error)` — always wrap.
43
- 14. Always pass `cause` in `.catch()` callbacks — `.catch((e) => new MyError({ cause: e }))`, never `.catch(() => new MyError())`. Without `cause`, the original error is lost and `isAbortError` can't walk the chain to detect aborts. The `cause` preserves the full error chain for debugging and abort detection.
44
- 15. Always prefer `errore.try` over `errore.tryFn` — they are the same function, but `errore.try` is the canonical name
45
- 16. Use `errore.isAbortError` to detect abort errors — never check `error.name === 'AbortError'` manually, because tagged abort errors have their tag as `.name`
46
- 17. Custom abort errors MUST extend `errore.AbortError` — so `isAbortError` detects them in the cause chain even when wrapped by `.catch()`
47
- 18. Keep abort checks flat — check `isAbortError(result)` first as its own early return, then `result instanceof Error` as a separate early return. Never nest `isAbortError` inside `instanceof Error`:
43
+ 4. Convert exceptions to values only at boundaries — `.catch()` for async, `errore.try` for sync (see [Boundaries](#boundaries-catch-for-async-erroretry-for-sync)). Never `try-catch` for control flow
44
+ 5. Always wrap boundary catches in a tagged domain error with `cause` — `.catch((e) => new MyError({ cause: e }))`. Never `.catch((e) => e as Error)`, never omit `cause` (it preserves the chain for debugging and abort detection)
45
+ 6. Use `createTaggedError` for domain errors (see [Defining Errors](#defining-errors)); use `cause` to wrap errors
46
+ 7. Use `| null` for optional values, not `| undefined` — three-way narrowing: `instanceof Error`, `=== null`, then value
47
+ 8. Use `const` + expressions, never `let` + try-catch (see [Expressions over Statements](#expressions-over-statements))
48
+ 9. Handle errors inside `if` branches with early exits, keep the happy path at root indentation (see [Flat Control Flow](#flat-control-flow))
49
+ 10. Always include `Error` handler in `matchError` — required fallback for plain Error instances
50
+ 11. Always prefer `errore.try` over `errore.tryFn` — same function, `errore.try` is the canonical name
51
+ 12. Abort: custom abort errors MUST extend `errore.AbortError`, detect with `errore.isAbortError` (never `error.name === 'AbortError'`), and check `isAbortError(result)` as its own early return before `instanceof Error` (see [Abort & Cancellation](#abort--cancellation))
52
+ 13. Don't reassign after error early returns — TypeScript narrows the original variable; a `const narrowed = result` alias is redundant
53
+ 14. Write `instanceof Error` early returns on one line — `if (result instanceof Error) return result`. Use a `{` block only when the branch has more than one statement
54
+ 15. Always log errors that are not propagated — when an error branch doesn't `return` or `throw` (loops with `continue`, fallback branches, fire-and-forget calls), add a `console.warn`/`console.error` so the failure leaves a trace. Propagated errors don't need logging — the caller handles them:
48
55
 
49
56
  ```ts
50
- const result = await fetchData({ signal }).catch(
51
- (e) => new FetchError({ cause: e }),
57
+ const emailResult = await sendEmail(user.email).catch(
58
+ (e) => new EmailError({ email: user.email, cause: e }),
52
59
  )
53
- if (errore.isAbortError(result)) return 'Request timed out'
54
- if (result instanceof Error) return `Failed: ${result.message}`
55
- ```
56
-
57
- 19. Don't reassign after error early returns — TypeScript narrows the original variable automatically after `instanceof Error` checks return. A `const narrowed = result` alias is redundant:
58
-
59
- ```ts
60
- const result = await fetch(url).catch((e) => new FetchError({ cause: e }))
61
- if (result instanceof Error) return `Failed: ${result.message}`
62
- await result.json() // TS knows result is Response here
63
- ```
64
-
65
- 20. Always write `instanceof Error` early returns on one line — no `{` block, no extra lines. `if (x instanceof Error) return x` keeps the happy path readable and reduces visual noise. Only use a block when the branch has more than one statement:
66
-
67
- ```ts
68
- // good — one line, no block
69
- if (result instanceof Error) return result
70
- if (user instanceof Error) return user
71
-
72
- // bad — block for a single return adds noise
73
- if (result instanceof Error) {
74
- return result
60
+ if (emailResult instanceof Error) {
61
+ console.warn('Failed to send email:', emailResult.message)
75
62
  }
76
63
  ```
77
64
 
78
- 21. Always log errors that are not propagated — when an error branch doesn't `return` or `throw` the error (i.e. the error is intentionally swallowed), add a `console.warn` or `console.error` so failures are visible during debugging. Silent error swallowing makes bugs invisible:
65
+ 16. Always hint a **solution** in user-facing error messages — include a login URL, CLI command, or missing flag so agents and users can self-correct instead of guessing. Applies to HTTP responses and CLI output; internal errors (DB failures, parse errors) can stay technical:
79
66
 
80
67
  ```ts
81
- // BAD: error silently ignored — if sync fails you'll never know
82
- const result = await syncToCloud(data)
83
- if (result instanceof Error) {
84
- // nothing here — silent failure
85
- }
86
-
87
- // GOOD: log before continuing — error is visible in logs
88
- const result = await syncToCloud(data)
89
- if (result instanceof Error) {
90
- console.warn('Cloud sync failed:', result.message)
91
- }
68
+ class UnauthorizedError extends errore.createTaggedError({
69
+ name: 'UnauthorizedError',
70
+ message: 'Not logged in. Sign in at $loginUrl or run: $loginCommand',
71
+ }) {}
92
72
  ```
93
73
 
94
- > Propagated errors (`return error`) don't need logging — the caller handles them. But errors you choose to ignore must leave a trace. This applies to loops with `continue`, fallback branches, and any path where the error is intentionally dropped.
95
-
96
74
  ## TypeScript Rules
97
75
 
98
76
  - **Object args over positional** — `({id, retries})` not `(id, retries)` for functions with 2+ params
@@ -103,24 +81,7 @@ console.log(user.name) // TypeScript knows: User
103
81
  - **No uninitialized `let`** — use IIFE with returns instead of `let x; if (...) { x = ... }`
104
82
  - **Type empty arrays** — `const items: string[] = []` not `const items = []`
105
83
  - **Module imports for node builtins** — `import fs from 'node:fs'` then `fs.readFileSync(...)`, not named imports
106
- - **Let TypeScript infer return types** — don't annotate return types by default. TypeScript infers them from the code and the inferred type is always correct. Only add an explicit return type when it genuinely improves readability (complex unions, public API boundaries) or when inference produces a wider type than intended:
107
-
108
- ```ts
109
- // let inference do its job
110
- function getUser(id: string) {
111
- const user = await db.find(id)
112
- if (!user) return new NotFoundError({ id })
113
- return user
114
- }
115
-
116
- // explicit annotation when it adds clarity on a complex public API
117
- function processRequest(
118
- req: Request,
119
- ): Promise<ValidationError | AuthError | DbError | null | Response> {
120
- // ...
121
- }
122
- ```
123
-
84
+ - **Let TypeScript infer return types** — inferred types are always correct. Annotate only when it genuinely improves readability (complex unions, public API boundaries) or when inference produces a wider type than intended
124
85
  - **`.filter(isTruthy)` not `.filter(Boolean)`** — `Boolean` doesn't narrow types, so `(T | null)[]` stays `(T | null)[]` after filtering. Use a type guard:
125
86
 
126
87
  ```ts
@@ -130,53 +91,11 @@ console.log(user.name) // TypeScript knows: User
130
91
  const items = results.filter(isTruthy)
131
92
  ```
132
93
 
133
- - **`controller.abort()` must use typed errors** — `abort(reason)` throws `reason` as-is. MUST pass a tagged error extending `errore.AbortError`, NEVER `new Error()` or a string — otherwise `isAbortError` can't detect it in the cause chain:
134
-
135
- ```ts
136
- class TimeoutError extends errore.createTaggedError({
137
- name: 'TimeoutError',
138
- message: 'Request timed out for $operation',
139
- extends: errore.AbortError,
140
- }) {}
141
- controller.abort(new TimeoutError({ operation: 'fetch' }))
142
- ```
143
-
144
- - **Never silently suppress errors** — empty `catch {}` and unlogged error branches hide failures. With errore you rarely need catch at all, but at any boundary where an error is not propagated, always log it (see rule 20):
145
-
146
- ```ts
147
- const emailResult = await sendEmail(user.email).catch(
148
- (e) => new EmailError({ email: user.email, cause: e }),
149
- )
150
- if (emailResult instanceof Error) {
151
- console.warn('Failed to send email:', emailResult.message)
152
- }
153
- ```
154
-
155
94
  ## Flat Control Flow
156
95
 
157
96
  Keep block nesting minimal. Every level of indentation is cognitive load. The ideal function reads top to bottom at root level — checks and early returns, no `else`, no nested `if`, no `try-catch`.
158
97
 
159
- **Core pattern** — call → check error → exit if error → continue at root. This is the single most important structural rule.
160
-
161
- **Go:**
162
-
163
- ```go
164
- user, err := getUser(id)
165
- if err != nil {
166
- return fmt.Errorf("get user: %w", err)
167
- }
168
- // user is valid here, at root level
169
-
170
- posts, err := getPosts(user.ID)
171
- if err != nil {
172
- return fmt.Errorf("get posts: %w", err)
173
- }
174
- // posts is valid here, at root level
175
-
176
- return render(user, posts)
177
- ```
178
-
179
- **errore (identical structure):**
98
+ **Core pattern** — call → check error → exit if error → continue at root. This is the single most important structural rule. Identical structure to Go's `if err != nil { return err }` blocks — speed bumps the reader skips over while scanning the left edge:
180
99
 
181
100
  ```ts
182
101
  const user = await getUser(id)
@@ -188,8 +107,6 @@ if (posts instanceof Error) return posts
188
107
  return render(user, posts)
189
108
  ```
190
109
 
191
- The reader scans the left edge of the function to follow the happy path — just like reading a Go function where `if err != nil` blocks are speed bumps you skip over.
192
-
193
110
  **No `else`** — early return eliminates it: `if (x) return 'A'; return 'B'`
194
111
 
195
112
  **No `else if` chains** — sequence of early-return `if` blocks:
@@ -203,7 +120,7 @@ function getStatus(code: number): string {
203
120
  }
204
121
  ```
205
122
 
206
- **Flatten nested `if`** — invert conditions and return early. `if (A) { if (B) { ... } }` becomes `if (!A) return; if (!B) return; ...`. The transformation rule: take the outermost `if` condition, negate it, return the failure case, then continue at root level. Repeat for each nested `if`. The happy path falls through to the end.
123
+ **Flatten nested `if`** — invert conditions and return early. `if (A) { if (B) { ... } }` becomes `if (!A) return; if (!B) return; ...`. Take the outermost `if` condition, negate it, return the failure case, then continue at root level. Repeat for each nested `if`. The happy path falls through to the end.
207
124
 
208
125
  **Avoid `try-catch` for control flow** — `try-catch` is the worst offender for nesting. It forces a two-branch structure (`try` + `catch`) and hides which line threw. Convert exceptions to values at boundaries:
209
126
 
@@ -228,26 +145,6 @@ async function loadConfig(): Promise<Config> {
228
145
 
229
146
  **Errors in branches, happy path at root** — always handle errors inside `if` blocks, never success logic. Error handling goes in branches with early exits. Putting success logic inside `if` blocks inverts the flow and buries the happy path. **If you see `!(x instanceof Error)` in a condition, you've inverted the pattern — flip it.**
230
147
 
231
- **Keep the happy path at minimum indentation** — the reader scans down the left edge to follow the main logic:
232
-
233
- ```ts
234
- async function handleRequest(req: Request): Promise<AppError | Response> {
235
- const body = await parseBody(req)
236
- if (body instanceof Error) return body
237
-
238
- const user = await authenticate(req.headers)
239
- if (user instanceof Error) return user
240
-
241
- const permission = checkPermission(user, body.resource)
242
- if (permission instanceof Error) return permission
243
-
244
- const result = await execute(body.action, body.resource)
245
- if (result instanceof Error) return result
246
-
247
- return new Response(JSON.stringify(result), { status: 200 })
248
- }
249
- ```
250
-
251
148
  Same in loops — error in `if` + `continue`, happy path flat:
252
149
 
253
150
  ```ts
@@ -330,47 +227,11 @@ async function getUser(id: string) {
330
227
 
331
228
  > Return the error, don't throw it. The return type tells callers exactly what can go wrong.
332
229
 
333
- ### Handling Errors (Early Return)
334
-
335
- ```ts
336
- const user = await getUser(id)
337
- if (user instanceof Error) return user
338
-
339
- const posts = await getPosts(user.id)
340
- if (posts instanceof Error) return posts
341
-
342
- return posts
343
- ```
230
+ ### Boundaries (.catch for async, errore.try for sync)
344
231
 
345
- > Each error is checked at the point it occurs. TypeScript narrows the type after each check.
232
+ `.catch()` and `errore.try` should only appear at the **lowest level** of your call stack — right at the boundary with code you don't control (third-party libraries, `JSON.parse`, `fetch`, file I/O, etc.). Your own functions return errors as values, so they never need `.catch()` or `try`.
346
233
 
347
- ### Wrapping External Libraries
348
-
349
- ```ts
350
- async function fetchJson<T>(url: string): Promise<NetworkError | T> {
351
- const response = await fetch(url).catch(
352
- (e) => new NetworkError({ url, reason: 'Fetch failed', cause: e }),
353
- )
354
- if (response instanceof Error) return response
355
-
356
- if (!response.ok) {
357
- return new NetworkError({ url, reason: `HTTP ${response.status}` })
358
- }
359
-
360
- const data = await (response.json() as Promise<T>).catch(
361
- (e) => new NetworkError({ url, reason: 'Invalid JSON', cause: e }),
362
- )
363
- return data
364
- }
365
- ```
366
-
367
- > `.catch()` on a promise converts rejections to typed errors. TypeScript infers the union (`Response | NetworkError`) automatically. Use `errore.try` for sync boundaries (`JSON.parse`, etc.).
368
-
369
- ### Boundary Rule (.catch for async, errore.try for sync)
370
-
371
- `.catch()` and `errore.try` should only appear at the **lowest level** of your call stack — right at the boundary with code you don't control (third-party libraries, `JSON.parse`, `fetch`, file I/O, etc.). Your own functions should never throw, so they never need `.catch()` or `try`.
372
-
373
- For **async** boundaries: use `.catch((e) => new MyError({ cause: e }))` directly on the promise. TypeScript infers the union automatically. For **sync** boundaries: use `errore.try(() => ..., (e) => ...)`. The `.catch()` callback receives `any` (Promise rejections are untyped), but wrapping in a typed error gives the union a concrete type — no `as` assertions needed.
234
+ For **async** boundaries: `.catch((e) => new MyError({ cause: e }))` directly on the promise. For **sync** boundaries: `errore.try(() => ..., (e) => ...)`. The `.catch()` callback receives `any` (Promise rejections are untyped), but wrapping in a typed error gives the union a concrete type — no `as` assertions needed. TypeScript infers the union automatically:
374
235
 
375
236
  ```ts
376
237
  async function getUser(id: string) {
@@ -379,6 +240,8 @@ async function getUser(id: string) {
379
240
  )
380
241
  if (res instanceof Error) return res
381
242
 
243
+ if (!res.ok) return new NetworkError({ url: `/users/${id}`, reason: `HTTP ${res.status}` })
244
+
382
245
  const data = await (res.json() as Promise<UserPayload>).catch(
383
246
  (e) => new NetworkError({ url: `/users/${id}`, cause: e }),
384
247
  )
@@ -389,7 +252,7 @@ async function getUser(id: string) {
389
252
  }
390
253
  ```
391
254
 
392
- > Think of `.catch()` and `errore.try` as the **adapter** between the throwing world (external code) and the errore world (errors as values). Once you've converted exceptions to values at the boundary, everything above is plain `instanceof` checks. Your own functions return errors as values — they never need `.catch()` or `try`.
255
+ > Think of `.catch()` and `errore.try` as the **adapter** between the throwing world (external code) and the errore world (errors as values). Once you've converted exceptions to values at the boundary, everything above is plain `instanceof` checks.
393
256
 
394
257
  ### Optional Values (| null)
395
258
 
@@ -447,7 +310,7 @@ return res.status(response.status).json(response.body)
447
310
 
448
311
  ### Resource Cleanup (defer) — Replacing try/finally with `using`
449
312
 
450
- `try/finally` has a structural problem: **every resource adds a nesting level**. Two resources = two levels of indentation. The business logic gets buried deeper with each resource, and cleanup is split across `finally` blocks far from where the resource was acquired. `await using` + `DisposableStack` keeps the function flat — one `cleanup.defer()` per resource, same indentation whether you have one resource or ten. Cleanup runs automatically in reverse order on every exit path.
313
+ `try/finally` has a structural problem: **every resource adds a nesting level**. Two resources = two levels of indentation, and cleanup is split across `finally` blocks far from where the resource was acquired. `await using` + `DisposableStack` keeps the function flat — one `cleanup.defer()` per resource, same indentation whether you have one resource or ten. Cleanup runs automatically in reverse (LIFO) order on every exit path — normal return, early error return, or exception.
451
314
 
452
315
  **tsconfig requirement:** add `"ESNext.Disposable"` to `lib`:
453
316
 
@@ -459,29 +322,6 @@ return res.status(response.status).json(response.body)
459
322
  }
460
323
  ```
461
324
 
462
- **Before — nested try/finally:**
463
-
464
- ```ts
465
- async function importData(url: string, dbUrl: string) {
466
- const db = await connectDb(dbUrl)
467
- try {
468
- const tmpFile = await createTempFile()
469
- try {
470
- const data = await (await fetch(url)).text()
471
- await tmpFile.write(data)
472
- await db.import(tmpFile.path)
473
- return { rows: await db.count() }
474
- } finally {
475
- await tmpFile.delete()
476
- }
477
- } finally {
478
- await db.close()
479
- }
480
- }
481
- ```
482
-
483
- **After — flat with `await using`:**
484
-
485
325
  ```ts
486
326
  async function importData(url: string, dbUrl: string): Promise<ImportError | { rows: number }> {
487
327
  await using cleanup = new errore.AsyncDisposableStack()
@@ -503,7 +343,7 @@ async function importData(url: string, dbUrl: string): Promise<ImportError | { r
503
343
  }
504
344
  ```
505
345
 
506
- > `await using` guarantees cleanup on every exit path — normal return, early error return, or exception. Resources release in LIFO order. Adding a resource is one line (`cleanup.defer()`), not another nesting level. The errore polyfill handles the runtime; the tsconfig `lib` entry handles the types.
346
+ > Adding a resource is one line (`cleanup.defer()`), not another nesting level. The errore polyfill handles the runtime; the tsconfig `lib` entry handles the types.
507
347
 
508
348
  ### Fallback Values
509
349
 
@@ -570,7 +410,7 @@ async function legacyHandler(id: string) {
570
410
 
571
411
  ### Converting `{ data, error }` Returns
572
412
 
573
- Some SDKs (Supabase, Stripe, etc.) return `{ data, error }` instead of throwing. Destructure inline, check `error` first (truthy, not `instanceof` — most SDKs return plain objects), wrap in a tagged error, then continue with `data`:
413
+ Some SDKs (Supabase, Stripe, etc.) return `{ data, error }` instead of throwing. Destructure inline, check `error` first with a truthy check (not `instanceof` — most SDKs return plain objects), wrap in a tagged error, then continue with `data`:
574
414
 
575
415
  ```ts
576
416
  const { data, error } = await supabase.from('users').select('*').eq('id', id)
@@ -579,7 +419,7 @@ if (data === null) return new NotFoundError({ id })
579
419
  // data is narrowed here
580
420
  ```
581
421
 
582
- > If the SDK's `error` is already an `Error` instance you can return it directly, but wrapping in a domain error is better — gives you `_tag`, typed properties, and `cause` chain. Check `error` with truthy check, not `instanceof Error`, since most SDK error objects are plain objects.
422
+ > Wrapping in a domain error is better than returning the SDK's error directly — gives you `_tag`, typed properties, and the `cause` chain.
583
423
 
584
424
  ### Partition: Splitting Successes and Failures
585
425
 
@@ -595,9 +435,11 @@ errors.forEach((e) => console.warn('Failed:', e.message))
595
435
 
596
436
  ### Abort & Cancellation
597
437
 
598
- `controller.abort(reason)` throws `reason` as-is — whatever you pass is what `.catch()` receives. This means you MUST pass a typed error extending `errore.AbortError`, never a plain `Error` or string.
438
+ `controller.abort(reason)` throws `reason` as-is — whatever you pass is what `.catch()` receives. This means you MUST pass a typed error extending `errore.AbortError`, never a plain `Error` or string — otherwise `isAbortError` can't detect it.
439
+
440
+ Always use `errore.isAbortError(error)` to detect abort errors — never check `error.name === 'AbortError'` manually, because tagged abort errors have their tag as `.name`. `isAbortError` walks the entire `.cause` chain, so it works even when the abort error is wrapped by `.catch()`.
599
441
 
600
- Always use `errore.isAbortError(error)` to detect abort errors. It walks the entire `.cause` chain, so it works even when the abort error is wrapped by `.catch()`.
442
+ Keep abort checks flat: `isAbortError(result)` first as its own early return, then `result instanceof Error` as a separate early return. Never nest one inside the other.
601
443
 
602
444
  ```ts
603
445
  import * as errore from 'errore'