@otto-assistant/bridge 0.4.92

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 (483) hide show
  1. package/bin.js +2 -0
  2. package/dist/agent-model.e2e.test.js +755 -0
  3. package/dist/ai-tool-to-genai.js +233 -0
  4. package/dist/ai-tool-to-genai.test.js +267 -0
  5. package/dist/ai-tool.js +6 -0
  6. package/dist/anthropic-auth-plugin.js +728 -0
  7. package/dist/anthropic-auth-plugin.test.js +125 -0
  8. package/dist/anthropic-auth-state.js +231 -0
  9. package/dist/bin.js +90 -0
  10. package/dist/channel-management.js +227 -0
  11. package/dist/cli-parsing.test.js +137 -0
  12. package/dist/cli-send-thread.e2e.test.js +356 -0
  13. package/dist/cli.js +3276 -0
  14. package/dist/commands/abort.js +65 -0
  15. package/dist/commands/action-buttons.js +245 -0
  16. package/dist/commands/add-project.js +113 -0
  17. package/dist/commands/agent.js +335 -0
  18. package/dist/commands/ask-question.js +274 -0
  19. package/dist/commands/btw.js +116 -0
  20. package/dist/commands/compact.js +120 -0
  21. package/dist/commands/context-usage.js +140 -0
  22. package/dist/commands/create-new-project.js +130 -0
  23. package/dist/commands/diff.js +63 -0
  24. package/dist/commands/file-upload.js +275 -0
  25. package/dist/commands/fork.js +220 -0
  26. package/dist/commands/gemini-apikey.js +70 -0
  27. package/dist/commands/login.js +885 -0
  28. package/dist/commands/mcp.js +239 -0
  29. package/dist/commands/memory-snapshot.js +24 -0
  30. package/dist/commands/mention-mode.js +44 -0
  31. package/dist/commands/merge-worktree.js +159 -0
  32. package/dist/commands/model-variant.js +364 -0
  33. package/dist/commands/model.js +776 -0
  34. package/dist/commands/new-worktree.js +366 -0
  35. package/dist/commands/paginated-select.js +57 -0
  36. package/dist/commands/permissions.js +274 -0
  37. package/dist/commands/queue.js +206 -0
  38. package/dist/commands/remove-project.js +115 -0
  39. package/dist/commands/restart-opencode-server.js +127 -0
  40. package/dist/commands/resume.js +149 -0
  41. package/dist/commands/run-command.js +79 -0
  42. package/dist/commands/screenshare.js +303 -0
  43. package/dist/commands/screenshare.test.js +20 -0
  44. package/dist/commands/session-id.js +78 -0
  45. package/dist/commands/session.js +176 -0
  46. package/dist/commands/share.js +80 -0
  47. package/dist/commands/tasks.js +205 -0
  48. package/dist/commands/types.js +2 -0
  49. package/dist/commands/undo-redo.js +305 -0
  50. package/dist/commands/unset-model.js +138 -0
  51. package/dist/commands/upgrade.js +42 -0
  52. package/dist/commands/user-command.js +155 -0
  53. package/dist/commands/verbosity.js +125 -0
  54. package/dist/commands/worktree-settings.js +43 -0
  55. package/dist/commands/worktrees.js +410 -0
  56. package/dist/condense-memory.js +33 -0
  57. package/dist/config.js +94 -0
  58. package/dist/context-awareness-plugin.js +363 -0
  59. package/dist/context-awareness-plugin.test.js +124 -0
  60. package/dist/critique-utils.js +95 -0
  61. package/dist/database.js +1310 -0
  62. package/dist/db.js +251 -0
  63. package/dist/db.test.js +138 -0
  64. package/dist/debounce-timeout.js +28 -0
  65. package/dist/debounced-process-flush.js +77 -0
  66. package/dist/discord-bot.js +1008 -0
  67. package/dist/discord-command-registration.js +524 -0
  68. package/dist/discord-urls.js +81 -0
  69. package/dist/discord-utils.js +591 -0
  70. package/dist/discord-utils.test.js +134 -0
  71. package/dist/errors.js +157 -0
  72. package/dist/escape-backticks.test.js +429 -0
  73. package/dist/event-stream-real-capture.e2e.test.js +533 -0
  74. package/dist/eventsource-parser.test.js +327 -0
  75. package/dist/exec-async.js +26 -0
  76. package/dist/external-opencode-sync.js +480 -0
  77. package/dist/format-tables.js +302 -0
  78. package/dist/format-tables.test.js +308 -0
  79. package/dist/forum-sync/config.js +79 -0
  80. package/dist/forum-sync/discord-operations.js +154 -0
  81. package/dist/forum-sync/index.js +5 -0
  82. package/dist/forum-sync/markdown.js +113 -0
  83. package/dist/forum-sync/sync-to-discord.js +417 -0
  84. package/dist/forum-sync/sync-to-files.js +190 -0
  85. package/dist/forum-sync/types.js +53 -0
  86. package/dist/forum-sync/watchers.js +307 -0
  87. package/dist/gateway-proxy-reconnect.e2e.test.js +394 -0
  88. package/dist/gateway-proxy.e2e.test.js +483 -0
  89. package/dist/genai-worker-wrapper.js +111 -0
  90. package/dist/genai-worker.js +311 -0
  91. package/dist/genai.js +232 -0
  92. package/dist/generated/browser.js +17 -0
  93. package/dist/generated/client.js +37 -0
  94. package/dist/generated/commonInputTypes.js +10 -0
  95. package/dist/generated/enums.js +52 -0
  96. package/dist/generated/internal/class.js +49 -0
  97. package/dist/generated/internal/prismaNamespace.js +253 -0
  98. package/dist/generated/internal/prismaNamespaceBrowser.js +223 -0
  99. package/dist/generated/models/bot_api_keys.js +1 -0
  100. package/dist/generated/models/bot_tokens.js +1 -0
  101. package/dist/generated/models/channel_agents.js +1 -0
  102. package/dist/generated/models/channel_directories.js +1 -0
  103. package/dist/generated/models/channel_mention_mode.js +1 -0
  104. package/dist/generated/models/channel_models.js +1 -0
  105. package/dist/generated/models/channel_verbosity.js +1 -0
  106. package/dist/generated/models/channel_worktrees.js +1 -0
  107. package/dist/generated/models/forum_sync_configs.js +1 -0
  108. package/dist/generated/models/global_models.js +1 -0
  109. package/dist/generated/models/ipc_requests.js +1 -0
  110. package/dist/generated/models/part_messages.js +1 -0
  111. package/dist/generated/models/scheduled_tasks.js +1 -0
  112. package/dist/generated/models/session_agents.js +1 -0
  113. package/dist/generated/models/session_events.js +1 -0
  114. package/dist/generated/models/session_models.js +1 -0
  115. package/dist/generated/models/session_start_sources.js +1 -0
  116. package/dist/generated/models/thread_sessions.js +1 -0
  117. package/dist/generated/models/thread_worktrees.js +1 -0
  118. package/dist/generated/models.js +1 -0
  119. package/dist/heap-monitor.js +122 -0
  120. package/dist/hrana-server.js +263 -0
  121. package/dist/hrana-server.test.js +370 -0
  122. package/dist/html-actions.js +123 -0
  123. package/dist/html-actions.test.js +70 -0
  124. package/dist/html-components.js +117 -0
  125. package/dist/html-components.test.js +34 -0
  126. package/dist/image-optimizer-plugin.js +153 -0
  127. package/dist/image-utils.js +112 -0
  128. package/dist/interaction-handler.js +397 -0
  129. package/dist/ipc-polling.js +252 -0
  130. package/dist/ipc-tools-plugin.js +193 -0
  131. package/dist/kimaki-digital-twin.e2e.test.js +161 -0
  132. package/dist/kimaki-opencode-plugin-loading.e2e.test.js +87 -0
  133. package/dist/kimaki-opencode-plugin.js +17 -0
  134. package/dist/kimaki-opencode-plugin.test.js +98 -0
  135. package/dist/limit-heading-depth.js +25 -0
  136. package/dist/limit-heading-depth.test.js +105 -0
  137. package/dist/logger.js +165 -0
  138. package/dist/markdown.js +342 -0
  139. package/dist/markdown.test.js +257 -0
  140. package/dist/message-finish-field.e2e.test.js +165 -0
  141. package/dist/message-formatting.js +413 -0
  142. package/dist/message-formatting.test.js +73 -0
  143. package/dist/message-preprocessing.js +330 -0
  144. package/dist/onboarding-tutorial.js +172 -0
  145. package/dist/onboarding-welcome.js +37 -0
  146. package/dist/openai-realtime.js +224 -0
  147. package/dist/opencode-command-detection.js +65 -0
  148. package/dist/opencode-command-detection.test.js +240 -0
  149. package/dist/opencode-command.js +129 -0
  150. package/dist/opencode-command.test.js +48 -0
  151. package/dist/opencode-interrupt-plugin.js +361 -0
  152. package/dist/opencode-interrupt-plugin.test.js +458 -0
  153. package/dist/opencode.js +861 -0
  154. package/dist/otto/branding.js +22 -0
  155. package/dist/otto/index.js +21 -0
  156. package/dist/parse-permission-rules.test.js +117 -0
  157. package/dist/patch-text-parser.js +97 -0
  158. package/dist/plugin-logger.js +59 -0
  159. package/dist/privacy-sanitizer.js +105 -0
  160. package/dist/queue-advanced-abort.e2e.test.js +293 -0
  161. package/dist/queue-advanced-action-buttons.e2e.test.js +206 -0
  162. package/dist/queue-advanced-e2e-setup.js +786 -0
  163. package/dist/queue-advanced-footer.e2e.test.js +472 -0
  164. package/dist/queue-advanced-model-switch.e2e.test.js +299 -0
  165. package/dist/queue-advanced-permissions-typing.e2e.test.js +180 -0
  166. package/dist/queue-advanced-question.e2e.test.js +261 -0
  167. package/dist/queue-advanced-typing-interrupt.e2e.test.js +114 -0
  168. package/dist/queue-advanced-typing.e2e.test.js +153 -0
  169. package/dist/queue-drain-after-interactive-ui.e2e.test.js +119 -0
  170. package/dist/queue-interrupt-drain.e2e.test.js +135 -0
  171. package/dist/queue-question-select-drain.e2e.test.js +120 -0
  172. package/dist/runtime-idle-sweeper.js +52 -0
  173. package/dist/runtime-lifecycle.e2e.test.js +508 -0
  174. package/dist/sentry.js +23 -0
  175. package/dist/session-handler/agent-utils.js +67 -0
  176. package/dist/session-handler/event-stream-state.js +420 -0
  177. package/dist/session-handler/event-stream-state.test.js +563 -0
  178. package/dist/session-handler/model-utils.js +124 -0
  179. package/dist/session-handler/opencode-session-event-log.js +94 -0
  180. package/dist/session-handler/thread-runtime-state.js +104 -0
  181. package/dist/session-handler/thread-session-runtime.js +3258 -0
  182. package/dist/session-handler.js +9 -0
  183. package/dist/session-search.js +100 -0
  184. package/dist/session-search.test.js +40 -0
  185. package/dist/session-title-rename.test.js +80 -0
  186. package/dist/startup-service.js +153 -0
  187. package/dist/startup-time.e2e.test.js +296 -0
  188. package/dist/store.js +17 -0
  189. package/dist/system-message.js +613 -0
  190. package/dist/system-message.test.js +602 -0
  191. package/dist/task-runner.js +295 -0
  192. package/dist/task-schedule.js +209 -0
  193. package/dist/task-schedule.test.js +71 -0
  194. package/dist/test-utils.js +299 -0
  195. package/dist/thinking-utils.js +35 -0
  196. package/dist/thread-message-queue.e2e.test.js +999 -0
  197. package/dist/tools.js +357 -0
  198. package/dist/undo-redo.e2e.test.js +161 -0
  199. package/dist/unnest-code-blocks.js +146 -0
  200. package/dist/unnest-code-blocks.test.js +673 -0
  201. package/dist/upgrade.js +114 -0
  202. package/dist/utils.js +144 -0
  203. package/dist/voice-attachment.js +34 -0
  204. package/dist/voice-handler.js +646 -0
  205. package/dist/voice-message.e2e.test.js +1021 -0
  206. package/dist/voice.js +447 -0
  207. package/dist/voice.test.js +235 -0
  208. package/dist/wait-session.js +94 -0
  209. package/dist/websockify.js +69 -0
  210. package/dist/worker-types.js +4 -0
  211. package/dist/worktree-lifecycle.e2e.test.js +308 -0
  212. package/dist/worktree-utils.js +3 -0
  213. package/dist/worktrees.js +929 -0
  214. package/dist/worktrees.test.js +189 -0
  215. package/dist/xml.js +92 -0
  216. package/dist/xml.test.js +32 -0
  217. package/package.json +98 -0
  218. package/schema.prisma +295 -0
  219. package/skills/batch/SKILL.md +87 -0
  220. package/skills/critique/SKILL.md +112 -0
  221. package/skills/egaki/SKILL.md +100 -0
  222. package/skills/errore/SKILL.md +647 -0
  223. package/skills/event-sourcing-state/SKILL.md +252 -0
  224. package/skills/gitchamber/SKILL.md +93 -0
  225. package/skills/goke/SKILL.md +644 -0
  226. package/skills/jitter/EDITOR.md +219 -0
  227. package/skills/jitter/EXPORT-INTERNALS.md +309 -0
  228. package/skills/jitter/SKILL.md +158 -0
  229. package/skills/jitter/jitter-clipboard.json +1042 -0
  230. package/skills/jitter/package.json +14 -0
  231. package/skills/jitter/tsconfig.json +15 -0
  232. package/skills/jitter/utils/actions.ts +212 -0
  233. package/skills/jitter/utils/export.ts +114 -0
  234. package/skills/jitter/utils/index.ts +141 -0
  235. package/skills/jitter/utils/snapshot.ts +154 -0
  236. package/skills/jitter/utils/traverse.ts +246 -0
  237. package/skills/jitter/utils/types.ts +279 -0
  238. package/skills/jitter/utils/wait.ts +133 -0
  239. package/skills/lintcn/SKILL.md +873 -0
  240. package/skills/new-skill/SKILL.md +211 -0
  241. package/skills/npm-package/SKILL.md +239 -0
  242. package/skills/playwriter/SKILL.md +35 -0
  243. package/skills/proxyman/SKILL.md +215 -0
  244. package/skills/security-review/SKILL.md +208 -0
  245. package/skills/simplify/SKILL.md +58 -0
  246. package/skills/spiceflow/SKILL.md +14 -0
  247. package/skills/termcast/SKILL.md +945 -0
  248. package/skills/tuistory/SKILL.md +250 -0
  249. package/skills/usecomputer/SKILL.md +264 -0
  250. package/skills/x-articles/SKILL.md +554 -0
  251. package/skills/zele/SKILL.md +112 -0
  252. package/skills/zustand-centralized-state/SKILL.md +1004 -0
  253. package/src/agent-model.e2e.test.ts +976 -0
  254. package/src/ai-tool-to-genai.test.ts +296 -0
  255. package/src/ai-tool-to-genai.ts +283 -0
  256. package/src/ai-tool.ts +39 -0
  257. package/src/anthropic-auth-plugin.test.ts +159 -0
  258. package/src/anthropic-auth-plugin.ts +861 -0
  259. package/src/anthropic-auth-state.ts +282 -0
  260. package/src/bin.ts +111 -0
  261. package/src/channel-management.ts +334 -0
  262. package/src/cli-parsing.test.ts +195 -0
  263. package/src/cli-send-thread.e2e.test.ts +464 -0
  264. package/src/cli.ts +4581 -0
  265. package/src/commands/abort.ts +89 -0
  266. package/src/commands/action-buttons.ts +364 -0
  267. package/src/commands/add-project.ts +149 -0
  268. package/src/commands/agent.ts +473 -0
  269. package/src/commands/ask-question.ts +390 -0
  270. package/src/commands/btw.ts +164 -0
  271. package/src/commands/compact.ts +157 -0
  272. package/src/commands/context-usage.ts +199 -0
  273. package/src/commands/create-new-project.ts +190 -0
  274. package/src/commands/diff.ts +91 -0
  275. package/src/commands/file-upload.ts +389 -0
  276. package/src/commands/fork.ts +321 -0
  277. package/src/commands/gemini-apikey.ts +104 -0
  278. package/src/commands/login.ts +1173 -0
  279. package/src/commands/mcp.ts +307 -0
  280. package/src/commands/memory-snapshot.ts +30 -0
  281. package/src/commands/mention-mode.ts +68 -0
  282. package/src/commands/merge-worktree.ts +223 -0
  283. package/src/commands/model-variant.ts +483 -0
  284. package/src/commands/model.ts +1053 -0
  285. package/src/commands/new-worktree.ts +510 -0
  286. package/src/commands/paginated-select.ts +81 -0
  287. package/src/commands/permissions.ts +397 -0
  288. package/src/commands/queue.ts +271 -0
  289. package/src/commands/remove-project.ts +155 -0
  290. package/src/commands/restart-opencode-server.ts +162 -0
  291. package/src/commands/resume.ts +230 -0
  292. package/src/commands/run-command.ts +123 -0
  293. package/src/commands/screenshare.test.ts +30 -0
  294. package/src/commands/screenshare.ts +366 -0
  295. package/src/commands/session-id.ts +109 -0
  296. package/src/commands/session.ts +227 -0
  297. package/src/commands/share.ts +106 -0
  298. package/src/commands/tasks.ts +293 -0
  299. package/src/commands/types.ts +25 -0
  300. package/src/commands/undo-redo.ts +386 -0
  301. package/src/commands/unset-model.ts +173 -0
  302. package/src/commands/upgrade.ts +52 -0
  303. package/src/commands/user-command.ts +198 -0
  304. package/src/commands/verbosity.ts +173 -0
  305. package/src/commands/worktree-settings.ts +70 -0
  306. package/src/commands/worktrees.ts +552 -0
  307. package/src/condense-memory.ts +36 -0
  308. package/src/config.ts +111 -0
  309. package/src/context-awareness-plugin.test.ts +142 -0
  310. package/src/context-awareness-plugin.ts +510 -0
  311. package/src/critique-utils.ts +139 -0
  312. package/src/database.ts +1876 -0
  313. package/src/db.test.ts +162 -0
  314. package/src/db.ts +286 -0
  315. package/src/debounce-timeout.ts +43 -0
  316. package/src/debounced-process-flush.ts +104 -0
  317. package/src/discord-bot.ts +1330 -0
  318. package/src/discord-command-registration.ts +693 -0
  319. package/src/discord-urls.ts +88 -0
  320. package/src/discord-utils.test.ts +153 -0
  321. package/src/discord-utils.ts +800 -0
  322. package/src/errors.ts +201 -0
  323. package/src/escape-backticks.test.ts +469 -0
  324. package/src/event-stream-real-capture.e2e.test.ts +692 -0
  325. package/src/eventsource-parser.test.ts +351 -0
  326. package/src/exec-async.ts +35 -0
  327. package/src/external-opencode-sync.ts +685 -0
  328. package/src/format-tables.test.ts +335 -0
  329. package/src/format-tables.ts +445 -0
  330. package/src/forum-sync/config.ts +92 -0
  331. package/src/forum-sync/discord-operations.ts +241 -0
  332. package/src/forum-sync/index.ts +9 -0
  333. package/src/forum-sync/markdown.ts +172 -0
  334. package/src/forum-sync/sync-to-discord.ts +595 -0
  335. package/src/forum-sync/sync-to-files.ts +294 -0
  336. package/src/forum-sync/types.ts +175 -0
  337. package/src/forum-sync/watchers.ts +454 -0
  338. package/src/gateway-proxy-reconnect.e2e.test.ts +523 -0
  339. package/src/gateway-proxy.e2e.test.ts +640 -0
  340. package/src/genai-worker-wrapper.ts +164 -0
  341. package/src/genai-worker.ts +386 -0
  342. package/src/genai.ts +321 -0
  343. package/src/generated/browser.ts +114 -0
  344. package/src/generated/client.ts +138 -0
  345. package/src/generated/commonInputTypes.ts +736 -0
  346. package/src/generated/enums.ts +88 -0
  347. package/src/generated/internal/class.ts +384 -0
  348. package/src/generated/internal/prismaNamespace.ts +2386 -0
  349. package/src/generated/internal/prismaNamespaceBrowser.ts +326 -0
  350. package/src/generated/models/bot_api_keys.ts +1288 -0
  351. package/src/generated/models/bot_tokens.ts +1656 -0
  352. package/src/generated/models/channel_agents.ts +1256 -0
  353. package/src/generated/models/channel_directories.ts +1859 -0
  354. package/src/generated/models/channel_mention_mode.ts +1300 -0
  355. package/src/generated/models/channel_models.ts +1288 -0
  356. package/src/generated/models/channel_verbosity.ts +1228 -0
  357. package/src/generated/models/channel_worktrees.ts +1300 -0
  358. package/src/generated/models/forum_sync_configs.ts +1452 -0
  359. package/src/generated/models/global_models.ts +1288 -0
  360. package/src/generated/models/ipc_requests.ts +1485 -0
  361. package/src/generated/models/part_messages.ts +1302 -0
  362. package/src/generated/models/scheduled_tasks.ts +2320 -0
  363. package/src/generated/models/session_agents.ts +1086 -0
  364. package/src/generated/models/session_events.ts +1439 -0
  365. package/src/generated/models/session_models.ts +1114 -0
  366. package/src/generated/models/session_start_sources.ts +1408 -0
  367. package/src/generated/models/thread_sessions.ts +1781 -0
  368. package/src/generated/models/thread_worktrees.ts +1356 -0
  369. package/src/generated/models.ts +30 -0
  370. package/src/heap-monitor.ts +152 -0
  371. package/src/hrana-server.test.ts +434 -0
  372. package/src/hrana-server.ts +314 -0
  373. package/src/html-actions.test.ts +87 -0
  374. package/src/html-actions.ts +174 -0
  375. package/src/html-components.test.ts +38 -0
  376. package/src/html-components.ts +181 -0
  377. package/src/image-optimizer-plugin.ts +194 -0
  378. package/src/image-utils.ts +149 -0
  379. package/src/interaction-handler.ts +576 -0
  380. package/src/ipc-polling.ts +326 -0
  381. package/src/ipc-tools-plugin.ts +236 -0
  382. package/src/kimaki-digital-twin.e2e.test.ts +199 -0
  383. package/src/kimaki-opencode-plugin-loading.e2e.test.ts +109 -0
  384. package/src/kimaki-opencode-plugin.test.ts +108 -0
  385. package/src/kimaki-opencode-plugin.ts +18 -0
  386. package/src/limit-heading-depth.test.ts +116 -0
  387. package/src/limit-heading-depth.ts +26 -0
  388. package/src/logger.ts +208 -0
  389. package/src/markdown.test.ts +308 -0
  390. package/src/markdown.ts +410 -0
  391. package/src/message-finish-field.e2e.test.ts +192 -0
  392. package/src/message-formatting.test.ts +81 -0
  393. package/src/message-formatting.ts +533 -0
  394. package/src/message-preprocessing.ts +455 -0
  395. package/src/onboarding-tutorial.ts +176 -0
  396. package/src/onboarding-welcome.ts +49 -0
  397. package/src/openai-realtime.ts +358 -0
  398. package/src/opencode-command-detection.test.ts +307 -0
  399. package/src/opencode-command-detection.ts +76 -0
  400. package/src/opencode-command.test.ts +70 -0
  401. package/src/opencode-command.ts +188 -0
  402. package/src/opencode-interrupt-plugin.test.ts +677 -0
  403. package/src/opencode-interrupt-plugin.ts +477 -0
  404. package/src/opencode.ts +1110 -0
  405. package/src/otto/branding.ts +23 -0
  406. package/src/otto/index.ts +22 -0
  407. package/src/parse-permission-rules.test.ts +127 -0
  408. package/src/patch-text-parser.ts +107 -0
  409. package/src/plugin-logger.ts +68 -0
  410. package/src/privacy-sanitizer.ts +142 -0
  411. package/src/queue-advanced-abort.e2e.test.ts +382 -0
  412. package/src/queue-advanced-action-buttons.e2e.test.ts +268 -0
  413. package/src/queue-advanced-e2e-setup.ts +873 -0
  414. package/src/queue-advanced-footer.e2e.test.ts +576 -0
  415. package/src/queue-advanced-model-switch.e2e.test.ts +383 -0
  416. package/src/queue-advanced-permissions-typing.e2e.test.ts +245 -0
  417. package/src/queue-advanced-question.e2e.test.ts +316 -0
  418. package/src/queue-advanced-typing-interrupt.e2e.test.ts +146 -0
  419. package/src/queue-advanced-typing.e2e.test.ts +199 -0
  420. package/src/queue-drain-after-interactive-ui.e2e.test.ts +151 -0
  421. package/src/queue-interrupt-drain.e2e.test.ts +166 -0
  422. package/src/queue-question-select-drain.e2e.test.ts +152 -0
  423. package/src/runtime-idle-sweeper.ts +76 -0
  424. package/src/runtime-lifecycle.e2e.test.ts +641 -0
  425. package/src/schema.sql +173 -0
  426. package/src/sentry.ts +26 -0
  427. package/src/session-handler/agent-utils.ts +97 -0
  428. package/src/session-handler/event-stream-fixtures/real-session-action-buttons.jsonl +45 -0
  429. package/src/session-handler/event-stream-fixtures/real-session-footer-suppressed-on-pre-idle-interrupt.jsonl +40 -0
  430. package/src/session-handler/event-stream-fixtures/real-session-permission-external-file.jsonl +23 -0
  431. package/src/session-handler/event-stream-fixtures/real-session-task-normal.jsonl +22 -0
  432. package/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl +277 -0
  433. package/src/session-handler/event-stream-fixtures/real-session-task-user-interruption.jsonl +46 -0
  434. package/src/session-handler/event-stream-fixtures/session-abort-after-idle-race.jsonl +21 -0
  435. package/src/session-handler/event-stream-fixtures/session-concurrent-messages-serialized.jsonl +56 -0
  436. package/src/session-handler/event-stream-fixtures/session-explicit-abort.jsonl +44 -0
  437. package/src/session-handler/event-stream-fixtures/session-normal-completion.jsonl +29 -0
  438. package/src/session-handler/event-stream-fixtures/session-tool-call-noisy-stream.jsonl +29 -0
  439. package/src/session-handler/event-stream-fixtures/session-two-completions-same-session.jsonl +50 -0
  440. package/src/session-handler/event-stream-fixtures/session-user-interruption.jsonl +59 -0
  441. package/src/session-handler/event-stream-fixtures/session-voice-queued-followup.jsonl +52 -0
  442. package/src/session-handler/event-stream-state.test.ts +645 -0
  443. package/src/session-handler/event-stream-state.ts +608 -0
  444. package/src/session-handler/model-utils.ts +183 -0
  445. package/src/session-handler/opencode-session-event-log.ts +130 -0
  446. package/src/session-handler/thread-runtime-state.ts +212 -0
  447. package/src/session-handler/thread-session-runtime.ts +4281 -0
  448. package/src/session-handler.ts +15 -0
  449. package/src/session-search.test.ts +50 -0
  450. package/src/session-search.ts +148 -0
  451. package/src/session-title-rename.test.ts +112 -0
  452. package/src/startup-service.ts +200 -0
  453. package/src/startup-time.e2e.test.ts +373 -0
  454. package/src/store.ts +122 -0
  455. package/src/system-message.test.ts +612 -0
  456. package/src/system-message.ts +723 -0
  457. package/src/task-runner.ts +421 -0
  458. package/src/task-schedule.test.ts +84 -0
  459. package/src/task-schedule.ts +311 -0
  460. package/src/test-utils.ts +435 -0
  461. package/src/thinking-utils.ts +61 -0
  462. package/src/thread-message-queue.e2e.test.ts +1219 -0
  463. package/src/tools.ts +430 -0
  464. package/src/undici.d.ts +12 -0
  465. package/src/undo-redo.e2e.test.ts +209 -0
  466. package/src/unnest-code-blocks.test.ts +713 -0
  467. package/src/unnest-code-blocks.ts +185 -0
  468. package/src/upgrade.ts +127 -0
  469. package/src/utils.ts +212 -0
  470. package/src/voice-attachment.ts +51 -0
  471. package/src/voice-handler.ts +908 -0
  472. package/src/voice-message.e2e.test.ts +1255 -0
  473. package/src/voice.test.ts +281 -0
  474. package/src/voice.ts +627 -0
  475. package/src/wait-session.ts +147 -0
  476. package/src/websockify.ts +101 -0
  477. package/src/worker-types.ts +64 -0
  478. package/src/worktree-lifecycle.e2e.test.ts +391 -0
  479. package/src/worktree-utils.ts +4 -0
  480. package/src/worktrees.test.ts +223 -0
  481. package/src/worktrees.ts +1294 -0
  482. package/src/xml.test.ts +38 -0
  483. package/src/xml.ts +121 -0
@@ -0,0 +1,873 @@
1
+ ---
2
+ name: lintcn
3
+ description: |
4
+ Type-aware TypeScript lint rules in .lintcn/ Go files. Only load this skill when creating, editing, or debugging rule files.
5
+
6
+ To just run the linter: `npx lintcn lint` (or `--fix`, `--tsconfig <path>`). Finds .lintcn/ by walking up from cwd. First build ~30s, cached ~1s. In monorepos, run from each package folder, not the root.
7
+
8
+ Warnings don't fail CI and only show for git-changed files by default. Use `--all-warnings` to see them across the entire codebase.
9
+ ---
10
+
11
+ # lintcn — Writing Custom tsgolint Lint Rules
12
+
13
+ tsgolint rules are Go functions that listen for TypeScript AST nodes and use the
14
+ TypeScript type checker for type-aware analysis. Each rule lives in its own
15
+ subfolder under `.lintcn/` and is compiled into a custom tsgolint binary.
16
+
17
+ **Every rule MUST be in a subfolder** — flat `.go` files in `.lintcn/` root are
18
+ not supported. The subfolder name = Go package name = rule identity.
19
+
20
+ Always run `go build ./...` inside `.lintcn/` to validate rules compile.
21
+ Always run `go test -v ./...` inside `.lintcn/` to run tests.
22
+
23
+ ## Directory Layout
24
+
25
+ Each rule is a subfolder. The Go package name must match the folder name:
26
+
27
+ ```
28
+ .lintcn/
29
+ no_floating_promises/
30
+ no_floating_promises.go ← rule source (committed)
31
+ no_floating_promises_test.go ← tests (committed)
32
+ options.go ← rule options struct
33
+ await_thenable/
34
+ await_thenable.go
35
+ await_thenable_test.go
36
+ my_custom_rule/
37
+ my_custom_rule.go
38
+ .gitignore ← ignores generated Go files
39
+ go.mod ← generated
40
+ go.work ← generated
41
+ .tsgolint/ ← symlink to cached source (gitignored)
42
+ ```
43
+
44
+ ## Adding Rules
45
+
46
+ ```bash
47
+ # Add a rule folder from tsgolint
48
+ npx lintcn add https://github.com/oxc-project/tsgolint/tree/main/internal/rules/no_floating_promises
49
+
50
+ # Add by file URL (auto-fetches the whole folder)
51
+ npx lintcn add https://github.com/oxc-project/tsgolint/blob/main/internal/rules/await_thenable/await_thenable.go
52
+
53
+ # List installed rules
54
+ npx lintcn list
55
+
56
+ # Remove a rule (deletes the whole subfolder)
57
+ npx lintcn remove no-floating-promises
58
+
59
+ # Lint your project
60
+ npx lintcn lint
61
+ ```
62
+
63
+ ## Rule Anatomy
64
+
65
+ Every rule is a `rule.Rule` struct with a `Name` and a `Run` function.
66
+ `Run` receives a `RuleContext` and returns a `RuleListeners` map — a map from
67
+ `ast.Kind` to callback functions. The linter walks the AST and calls your
68
+ callback when it encounters a node of that kind.
69
+
70
+ ```go
71
+ // .lintcn/my_rule/my_rule.go
72
+ package my_rule
73
+
74
+ import (
75
+ "github.com/microsoft/typescript-go/shim/ast"
76
+ "github.com/typescript-eslint/tsgolint/internal/rule"
77
+ )
78
+
79
+ var MyRule = rule.Rule{
80
+ Name: "my-rule",
81
+ Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
82
+ return rule.RuleListeners{
83
+ ast.KindCallExpression: func(node *ast.Node) {
84
+ call := node.AsCallExpression()
85
+ // analyze the call...
86
+ ctx.ReportNode(node, rule.RuleMessage{
87
+ Id: "myError",
88
+ Description: "Something is wrong here.",
89
+ })
90
+ },
91
+ }
92
+ },
93
+ }
94
+ ```
95
+
96
+ ### Metadata Comments
97
+
98
+ Add `// lintcn:` comments at the top for CLI metadata:
99
+
100
+ ```go
101
+ // lintcn:name my-rule
102
+ // lintcn:severity warn
103
+ // lintcn:description Disallow doing X without checking Y
104
+ ```
105
+
106
+ Available directives:
107
+
108
+ | Directive | Values | Default | Description |
109
+ | -------------------- | --------------- | ----------- | -------------------- |
110
+ | `lintcn:name` | kebab-case | folder name | Rule display name |
111
+ | `lintcn:severity` | `error`, `warn` | `error` | Severity level |
112
+ | `lintcn:description` | text | empty | One-line description |
113
+ | `lintcn:source` | URL | empty | Original source URL |
114
+
115
+ ### Warning Severity
116
+
117
+ Rules with `// lintcn:severity warn`:
118
+
119
+ - Don't fail CI (exit code 0)
120
+ - Only show for git-changed/untracked files — unchanged files are skipped
121
+ - Use `--all-warnings` to see warnings across the whole codebase
122
+
123
+ Warnings are for rules that guide agents writing new code without flooding
124
+ the output with violations from the rest of the codebase. Examples:
125
+
126
+ - "Remove `as any`, the actual type is `string`"
127
+ - "This `||` fallback is unreachable, the left side is never nullish"
128
+ - "Unhandled Error return value, assign to a variable and check it"
129
+
130
+ ### Package Name
131
+
132
+ Each rule subfolder has its own Go package. The package name must match the
133
+ folder name (e.g. `package no_floating_promises` in folder `no_floating_promises/`).
134
+ The exported variable name must match the pattern `var XxxRule = rule.Rule{...}`.
135
+
136
+ ## RuleContext
137
+
138
+ `ctx rule.RuleContext` provides:
139
+
140
+ | Field | Type | Description |
141
+ | --------------------------- | -------------------------- | -------------------------- |
142
+ | `SourceFile` | `*ast.SourceFile` | Current file being linted |
143
+ | `Program` | `*compiler.Program` | Full TypeScript program |
144
+ | `TypeChecker` | `*checker.Checker` | TypeScript type checker |
145
+ | `ReportNode` | `func(node, msg)` | Report error on a node |
146
+ | `ReportNodeWithFixes` | `func(node, msg, fixesFn)` | Report with auto-fixes |
147
+ | `ReportNodeWithSuggestions` | `func(node, msg, suggFn)` | Report with suggestions |
148
+ | `ReportRange` | `func(range, msg)` | Report on a text range |
149
+ | `ReportDiagnostic` | `func(diagnostic)` | Report with labeled ranges |
150
+
151
+ ## AST Node Listeners
152
+
153
+ ### Most Useful ast.Kind Values
154
+
155
+ ```go
156
+ // Statements
157
+ ast.KindExpressionStatement // bare expression: `foo();`
158
+ ast.KindReturnStatement // `return x`
159
+ ast.KindThrowStatement // `throw x`
160
+ ast.KindIfStatement // `if (x) { ... }`
161
+ ast.KindVariableDeclaration // `const x = ...`
162
+ ast.KindForInStatement // `for (x in y)`
163
+
164
+ // Expressions
165
+ ast.KindCallExpression // `foo()` — most commonly listened
166
+ ast.KindNewExpression // `new Foo()`
167
+ ast.KindBinaryExpression // `a + b`, `a === b`, `a = b`
168
+ ast.KindPropertyAccessExpression // `obj.prop`
169
+ ast.KindElementAccessExpression // `obj[key]`
170
+ ast.KindAwaitExpression // `await x`
171
+ ast.KindConditionalExpression // `a ? b : c`
172
+ ast.KindPrefixUnaryExpression // `!x`, `-x`, `typeof x`
173
+ ast.KindTemplateExpression // `hello ${name}`
174
+ ast.KindDeleteExpression // `delete obj.x`
175
+ ast.KindVoidExpression // `void x`
176
+
177
+ // Declarations
178
+ ast.KindFunctionDeclaration
179
+ ast.KindArrowFunction
180
+ ast.KindMethodDeclaration
181
+ ast.KindClassDeclaration
182
+ ast.KindEnumDeclaration
183
+
184
+ // Types
185
+ ast.KindUnionType // `A | B`
186
+ ast.KindIntersectionType // `A & B`
187
+ ast.KindAsExpression // `x as T`
188
+ ```
189
+
190
+ ### Enter and Exit Listeners
191
+
192
+ By default, listeners fire when the AST walker **enters** a node.
193
+ Use `rule.ListenerOnExit(kind)` to fire when the walker **exits** — useful
194
+ for scope tracking:
195
+
196
+ ```go
197
+ return rule.RuleListeners{
198
+ // enter function — push scope
199
+ ast.KindFunctionDeclaration: func(node *ast.Node) {
200
+ currentScope = &scopeInfo{upper: currentScope}
201
+ },
202
+ // exit function — pop scope and check
203
+ rule.ListenerOnExit(ast.KindFunctionDeclaration): func(node *ast.Node) {
204
+ if !currentScope.hasAwait {
205
+ ctx.ReportNode(node, msg)
206
+ }
207
+ currentScope = currentScope.upper
208
+ },
209
+ }
210
+ ```
211
+
212
+ Used by require_await, return_await, consistent_return, prefer_readonly for
213
+ tracking state across function bodies with a scope stack.
214
+
215
+ ### Allow/NotAllow Pattern Listeners
216
+
217
+ For destructuring and assignment contexts:
218
+
219
+ ```go
220
+ rule.ListenerOnAllowPattern(ast.KindObjectLiteralExpression) // inside destructuring
221
+ rule.ListenerOnNotAllowPattern(ast.KindArrayLiteralExpression) // outside destructuring
222
+ ```
223
+
224
+ Used by no_unsafe_assignment and unbound_method.
225
+
226
+ ## Type Checker APIs
227
+
228
+ ### Getting Types
229
+
230
+ ```go
231
+ // Get the type of any AST node
232
+ t := ctx.TypeChecker.GetTypeAtLocation(node)
233
+
234
+ // Get type with constraint resolution (unwraps type params)
235
+ t := utils.GetConstrainedTypeAtLocation(ctx.TypeChecker, node)
236
+
237
+ // Get the contextual type (what TypeScript expects at this position)
238
+ t := checker.Checker_getContextualType(ctx.TypeChecker, node, checker.ContextFlagsNone)
239
+
240
+ // Get the apparent type (resolves mapped types, intersections)
241
+ t := checker.Checker_getApparentType(ctx.TypeChecker, t)
242
+
243
+ // Get awaited type (unwraps Promise)
244
+ t := checker.Checker_getAwaitedType(ctx.TypeChecker, t)
245
+
246
+ // Get type from a type annotation node
247
+ t := checker.Checker_getTypeFromTypeNode(ctx.TypeChecker, typeNode)
248
+ ```
249
+
250
+ ### Type Flag Checks
251
+
252
+ TypeFlags are bitmasks — check with `utils.IsTypeFlagSet`:
253
+
254
+ ```go
255
+ // Check specific flags
256
+ if utils.IsTypeFlagSet(t, checker.TypeFlagsVoid) { return }
257
+ if utils.IsTypeFlagSet(t, checker.TypeFlagsUndefined) { return }
258
+ if utils.IsTypeFlagSet(t, checker.TypeFlagsNever) { return }
259
+ if utils.IsTypeFlagSet(t, checker.TypeFlagsAny) { return }
260
+
261
+ // Combine flags with |
262
+ if utils.IsTypeFlagSet(t, checker.TypeFlagsVoid|checker.TypeFlagsUndefined|checker.TypeFlagsNever) {
263
+ return // skip void, undefined, and never
264
+ }
265
+
266
+ // Convenience helpers
267
+ utils.IsTypeAnyType(t)
268
+ utils.IsTypeUnknownType(t)
269
+ utils.IsObjectType(t)
270
+ utils.IsTypeParameter(t)
271
+ ```
272
+
273
+ ### Union and Intersection Types
274
+
275
+ **Decomposing unions is the most common pattern** — 58 uses across all rules:
276
+
277
+ ```go
278
+ // Iterate over union parts: `Error | string` → [Error, string]
279
+ for _, part := range utils.UnionTypeParts(t) {
280
+ if utils.IsErrorLike(ctx.Program, ctx.TypeChecker, part) {
281
+ hasError = true
282
+ break
283
+ }
284
+ }
285
+
286
+ // Check if it's a union type
287
+ if utils.IsUnionType(t) { ... }
288
+ if utils.IsIntersectionType(t) { ... }
289
+
290
+ // Iterate intersection parts
291
+ for _, part := range utils.IntersectionTypeParts(t) { ... }
292
+
293
+ // Recursive predicate check across union/intersection
294
+ result := utils.TypeRecurser(t, func(t *checker.Type) bool {
295
+ return utils.IsTypeAnyType(t)
296
+ })
297
+ ```
298
+
299
+ ### Built-in Type Checks
300
+
301
+ ```go
302
+ // Error types
303
+ utils.IsErrorLike(ctx.Program, ctx.TypeChecker, t)
304
+ utils.IsReadonlyErrorLike(ctx.Program, ctx.TypeChecker, t)
305
+
306
+ // Promise types
307
+ utils.IsPromiseLike(ctx.Program, ctx.TypeChecker, t)
308
+ utils.IsThenableType(ctx.TypeChecker, node, t)
309
+
310
+ // Array types
311
+ checker.Checker_isArrayType(ctx.TypeChecker, t)
312
+ checker.IsTupleType(t)
313
+ checker.Checker_isArrayOrTupleType(ctx.TypeChecker, t)
314
+
315
+ // Generic built-in matching
316
+ utils.IsBuiltinSymbolLike(ctx.Program, ctx.TypeChecker, t, "Function")
317
+ utils.IsBuiltinSymbolLike(ctx.Program, ctx.TypeChecker, t, "RegExp")
318
+ utils.IsBuiltinSymbolLike(ctx.Program, ctx.TypeChecker, t, "ReadonlyArray")
319
+ ```
320
+
321
+ ### Type Properties and Signatures
322
+
323
+ ```go
324
+ // Get a named property from a type
325
+ prop := checker.Checker_getPropertyOfType(ctx.TypeChecker, t, "then")
326
+ if prop != nil {
327
+ propType := ctx.TypeChecker.GetTypeOfSymbolAtLocation(prop, node)
328
+ }
329
+
330
+ // Get all properties
331
+ props := checker.Checker_getPropertiesOfType(ctx.TypeChecker, t)
332
+
333
+ // Get call signatures (for callable types)
334
+ sigs := utils.GetCallSignatures(ctx.TypeChecker, t)
335
+ // or
336
+ sigs := ctx.TypeChecker.GetCallSignatures(t)
337
+
338
+ // Get signature parameters
339
+ params := checker.Signature_parameters(sig)
340
+
341
+ // Get return type of a signature
342
+ returnType := checker.Checker_getReturnTypeOfSignature(ctx.TypeChecker, sig)
343
+
344
+ // Get type arguments (for generics, arrays, tuples)
345
+ typeArgs := checker.Checker_getTypeArguments(ctx.TypeChecker, t)
346
+
347
+ // Get resolved call signature at a call site
348
+ sig := checker.Checker_getResolvedSignature(ctx.TypeChecker, callNode)
349
+ ```
350
+
351
+ ### Type Assignability
352
+
353
+ ```go
354
+ // Check if source is assignable to target
355
+ if checker.Checker_isTypeAssignableTo(ctx.TypeChecker, sourceType, targetType) {
356
+ // source extends target
357
+ }
358
+
359
+ // Get base constraint of a type parameter
360
+ constraint := checker.Checker_getBaseConstraintOfType(ctx.TypeChecker, t)
361
+ ```
362
+
363
+ ### Symbols
364
+
365
+ ```go
366
+ // Get symbol at a location
367
+ symbol := ctx.TypeChecker.GetSymbolAtLocation(node)
368
+
369
+ // Get declaration for a symbol
370
+ decl := utils.GetDeclaration(ctx.TypeChecker, node)
371
+
372
+ // Get type from symbol
373
+ t := checker.Checker_getTypeOfSymbol(ctx.TypeChecker, symbol)
374
+ t := checker.Checker_getDeclaredTypeOfSymbol(ctx.TypeChecker, symbol)
375
+
376
+ // Check if symbol comes from default library
377
+ utils.IsSymbolFromDefaultLibrary(ctx.Program, symbol)
378
+
379
+ // Get the accessed property name (works with computed properties too)
380
+ name, ok := checker.Checker_getAccessedPropertyName(ctx.TypeChecker, node)
381
+ ```
382
+
383
+ ### Formatting Types for Error Messages
384
+
385
+ ```go
386
+ typeName := ctx.TypeChecker.TypeToString(t)
387
+ // → "string", "Error | User", "Promise<number>", etc.
388
+
389
+ // Shorter type name helper
390
+ name := utils.GetTypeName(ctx.TypeChecker, t)
391
+ ```
392
+
393
+ ## AST Navigation
394
+
395
+ ### Node Casting
396
+
397
+ Every AST node is `*ast.Node`. Use `.AsXxx()` to access specific fields:
398
+
399
+ ```go
400
+ call := node.AsCallExpression()
401
+ call.Expression // the callee
402
+ call.Arguments // argument list
403
+
404
+ binary := node.AsBinaryExpression()
405
+ binary.Left
406
+ binary.Right
407
+ binary.OperatorToken.Kind // ast.KindEqualsToken, ast.KindPlusToken, etc.
408
+
409
+ prop := node.AsPropertyAccessExpression()
410
+ prop.Expression // object
411
+ prop.Name() // property name node
412
+ ```
413
+
414
+ ### Type Predicates
415
+
416
+ ```go
417
+ ast.IsCallExpression(node)
418
+ ast.IsPropertyAccessExpression(node)
419
+ ast.IsIdentifier(node)
420
+ ast.IsAccessExpression(node) // property OR element access
421
+ ast.IsBinaryExpression(node)
422
+ ast.IsAssignmentExpression(node, includeCompound) // a = b, a += b
423
+ ast.IsVoidExpression(node)
424
+ ast.IsAwaitExpression(node)
425
+ ast.IsFunctionLike(node)
426
+ ast.IsArrowFunction(node)
427
+ ast.IsStringLiteral(node)
428
+ ```
429
+
430
+ ### Skipping Parentheses
431
+
432
+ Always skip parentheses when analyzing expression content:
433
+
434
+ ```go
435
+ expression := ast.SkipParentheses(node.AsExpressionStatement().Expression)
436
+ ```
437
+
438
+ ### Walking Parents
439
+
440
+ ```go
441
+ parent := node.Parent
442
+ for parent != nil {
443
+ if ast.IsCallExpression(parent) {
444
+ // node is inside a call expression
445
+ break
446
+ }
447
+ parent = parent.Parent
448
+ }
449
+ ```
450
+
451
+ ## Reporting Errors
452
+
453
+ ### Simple Error
454
+
455
+ ```go
456
+ ctx.ReportNode(node, rule.RuleMessage{
457
+ Id: "myErrorId", // unique ID for the error
458
+ Description: "Something is wrong.",
459
+ Help: "Optional longer explanation.", // shown as help text
460
+ })
461
+ ```
462
+
463
+ ### Error with Auto-Fix
464
+
465
+ Fixes are applied automatically by the linter:
466
+
467
+ ```go
468
+ ctx.ReportNodeWithFixes(node, msg, func() []rule.RuleFix {
469
+ return []rule.RuleFix{
470
+ rule.RuleFixInsertBefore(ctx.SourceFile, node, "await "),
471
+ }
472
+ })
473
+ ```
474
+
475
+ ### Error with Suggestions
476
+
477
+ Suggestions require user confirmation:
478
+
479
+ ```go
480
+ ctx.ReportNodeWithSuggestions(node, msg, func() []rule.RuleSuggestion {
481
+ return []rule.RuleSuggestion{{
482
+ Message: rule.RuleMessage{Id: "addAwait", Description: "Add await"},
483
+ FixesArr: []rule.RuleFix{
484
+ rule.RuleFixInsertBefore(ctx.SourceFile, node, "await "),
485
+ },
486
+ }}
487
+ })
488
+ ```
489
+
490
+ ### Error with Multiple Labeled Ranges
491
+
492
+ Highlight multiple code locations:
493
+
494
+ ```go
495
+ ctx.ReportDiagnostic(rule.RuleDiagnostic{
496
+ Range: exprRange,
497
+ Message: rule.RuleMessage{Id: "typeMismatch", Description: "Types are incompatible"},
498
+ LabeledRanges: []rule.RuleLabeledRange{
499
+ {Label: fmt.Sprintf("Type: %v", leftType), Range: leftRange},
500
+ {Label: fmt.Sprintf("Type: %v", rightType), Range: rightRange},
501
+ },
502
+ })
503
+ ```
504
+
505
+ ### Fix Helpers
506
+
507
+ ```go
508
+ // Insert text before a node
509
+ rule.RuleFixInsertBefore(ctx.SourceFile, node, "await ")
510
+
511
+ // Insert text after a node
512
+ rule.RuleFixInsertAfter(node, ")")
513
+
514
+ // Replace a node with text
515
+ rule.RuleFixReplace(ctx.SourceFile, node, "newCode")
516
+
517
+ // Remove a node
518
+ rule.RuleFixRemove(ctx.SourceFile, node)
519
+
520
+ // Replace a specific text range
521
+ rule.RuleFixReplaceRange(textRange, "replacement")
522
+
523
+ // Remove a specific text range
524
+ rule.RuleFixRemoveRange(textRange)
525
+ ```
526
+
527
+ ### Getting Token Ranges for Fixes
528
+
529
+ When you need the exact range of a keyword token (like `void`, `as`, `await`):
530
+
531
+ ```go
532
+ import "github.com/microsoft/typescript-go/shim/scanner"
533
+
534
+ // Get range of token at a position
535
+ voidTokenRange := scanner.GetRangeOfTokenAtPosition(ctx.SourceFile, node.Pos())
536
+
537
+ // Get a scanner to scan forward
538
+ s := scanner.GetScannerForSourceFile(ctx.SourceFile, startPos)
539
+ tokenRange := s.TokenRange()
540
+ ```
541
+
542
+ ## Rule Options
543
+
544
+ Rules can accept configuration via JSON:
545
+
546
+ ```go
547
+ var MyRule = rule.Rule{
548
+ Name: "my-rule",
549
+ Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
550
+ opts := utils.UnmarshalOptions[MyRuleOptions](options, "my-rule")
551
+ // opts is now typed
552
+ },
553
+ }
554
+
555
+ type MyRuleOptions struct {
556
+ IgnoreVoid bool `json:"ignoreVoid"`
557
+ AllowedTypes []string `json:"allowedTypes"`
558
+ }
559
+ ```
560
+
561
+ For lintcn rules, define the options struct directly in your rule file or
562
+ in a separate `options.go` file in the same subfolder.
563
+
564
+ ## State Tracking (Scope Stacks)
565
+
566
+ When you need to track state across function boundaries (like "does this
567
+ function contain an await?"), use enter/exit listener pairs with a linked
568
+ list as a stack:
569
+
570
+ ```go
571
+ type scopeInfo struct {
572
+ hasAwait bool
573
+ upper *scopeInfo
574
+ }
575
+ var currentScope *scopeInfo
576
+
577
+ enterFunc := func(node *ast.Node) {
578
+ currentScope = &scopeInfo{upper: currentScope}
579
+ }
580
+
581
+ exitFunc := func(node *ast.Node) {
582
+ if !currentScope.hasAwait {
583
+ ctx.ReportNode(node, msg)
584
+ }
585
+ currentScope = currentScope.upper
586
+ }
587
+
588
+ return rule.RuleListeners{
589
+ ast.KindFunctionDeclaration: enterFunc,
590
+ rule.ListenerOnExit(ast.KindFunctionDeclaration): exitFunc,
591
+ ast.KindArrowFunction: enterFunc,
592
+ rule.ListenerOnExit(ast.KindArrowFunction): exitFunc,
593
+ ast.KindAwaitExpression: func(node *ast.Node) {
594
+ currentScope.hasAwait = true
595
+ },
596
+ }
597
+ ```
598
+
599
+ ## Testing
600
+
601
+ Tests use `rule_tester.RunRuleTester` which creates a TypeScript program from
602
+ inline code and runs the rule against it. The test file must use the same
603
+ package name as the rule:
604
+
605
+ ```go
606
+ // .lintcn/my_rule/my_rule_test.go
607
+ package my_rule
608
+
609
+ import (
610
+ "testing"
611
+ "github.com/typescript-eslint/tsgolint/internal/rule_tester"
612
+ "github.com/typescript-eslint/tsgolint/internal/rules/fixtures"
613
+ )
614
+
615
+ func TestMyRule(t *testing.T) {
616
+ t.Parallel()
617
+ rule_tester.RunRuleTester(
618
+ fixtures.GetRootDir(),
619
+ "tsconfig.minimal.json",
620
+ t,
621
+ &MyRule,
622
+ validCases,
623
+ invalidCases,
624
+ )
625
+ }
626
+ ```
627
+
628
+ ### Valid Test Cases (should NOT trigger)
629
+
630
+ ```go
631
+ var validCases = []rule_tester.ValidTestCase{
632
+ {Code: `const x = getUser("id");`},
633
+ {Code: `void dangerousCall();`},
634
+ // tsx support
635
+ {Code: `<div onClick={() => {}} />`, Tsx: true},
636
+ // custom filename
637
+ {Code: `import x from './foo'`, FileName: "index.ts"},
638
+ // with rule options
639
+ {Code: `getUser("id");`, Options: MyRuleOptions{IgnoreVoid: true}},
640
+ // with extra files for multi-file tests
641
+ {
642
+ Code: `import { x } from './helper';`,
643
+ Files: map[string]string{
644
+ "helper.ts": `export const x = 1;`,
645
+ },
646
+ },
647
+ }
648
+ ```
649
+
650
+ ### Invalid Test Cases (SHOULD trigger)
651
+
652
+ ```go
653
+ var invalidCases = []rule_tester.InvalidTestCase{
654
+ // Basic — just check the error fires
655
+ {
656
+ Code: `
657
+ declare function getUser(id: string): Error | { name: string };
658
+ getUser("id");
659
+ `,
660
+ Errors: []rule_tester.InvalidTestCaseError{
661
+ {MessageId: "noUnhandledError"},
662
+ },
663
+ },
664
+ // With exact position
665
+ {
666
+ Code: `getUser("id");`,
667
+ Errors: []rule_tester.InvalidTestCaseError{
668
+ {MessageId: "noUnhandledError", Line: 1, Column: 1, EndColumn: 15},
669
+ },
670
+ },
671
+ // With suggestions
672
+ {
673
+ Code: `
674
+ declare const arr: number[];
675
+ delete arr[0];
676
+ `,
677
+ Errors: []rule_tester.InvalidTestCaseError{
678
+ {
679
+ MessageId: "noArrayDelete",
680
+ Suggestions: []rule_tester.InvalidTestCaseSuggestion{
681
+ {
682
+ MessageId: "useSplice",
683
+ Output: `
684
+ declare const arr: number[];
685
+ arr.splice(0, 1);
686
+ `,
687
+ },
688
+ },
689
+ },
690
+ },
691
+ },
692
+ // With auto-fix output (code after fix applied)
693
+ {
694
+ Code: `const x = foo as any;`,
695
+ Output: []string{`const x = foo;`},
696
+ Errors: []rule_tester.InvalidTestCaseError{
697
+ {MessageId: "unsafeAssertion"},
698
+ },
699
+ },
700
+ }
701
+ ```
702
+
703
+ ### Important Test Details
704
+
705
+ - **MessageId** must match the `Id` field in your `rule.RuleMessage`
706
+ - **Line/Column** are 1-indexed, optional (omit for flexibility)
707
+ - **Output** is the code after ALL auto-fixes are applied (iterates up to 10 times)
708
+ - **Suggestions** check the output of each individual suggestion fix
709
+ - Tests run in parallel by default (`t.Parallel()`)
710
+ - Use `Only: true` on a test case to run only that test (like `.only` in vitest)
711
+ - Use `Skip: true` to skip a test case
712
+
713
+ ### Running Tests
714
+
715
+ ```bash
716
+ cd .lintcn
717
+ go test -v ./... # all tests
718
+ go test -v -run TestMyRule # specific test
719
+ go test -count=1 ./... # bypass test cache
720
+ ```
721
+
722
+ ### Snapshots
723
+
724
+ Tests generate snapshot files with the full diagnostic output — message text,
725
+ annotated source code, and underlined ranges. Run with `UPDATE_SNAPS=true` to
726
+ create or update them:
727
+
728
+ ```bash
729
+ # From the build workspace (found via `lintcn build` output path)
730
+ UPDATE_SNAPS=true go test -run TestMyRule -count=1 ./rules/my_rule/
731
+ ```
732
+
733
+ Snapshots are written to `internal/rule_tester/__snapshots__/{rule-name}.snap`
734
+ inside the cached tsgolint source. Copy them into your rule folder for reference:
735
+
736
+ ```
737
+ .lintcn/my_rule/__snapshots__/my-rule.snap
738
+ ```
739
+
740
+ **Always read the snapshot after writing tests** — it shows the exact messages
741
+ your rule produces, which is how you verify the output makes sense. Example
742
+ snapshot from `no-type-assertion`:
743
+
744
+ ```
745
+ [TestNoTypeAssertion/invalid-7 - 1]
746
+ Diagnostic 1: typeAssertion (4:14 - 4:22)
747
+ Message: Type assertion `as User ({ name: string; age: number })`.
748
+ The expression type is `Error | User`. Try removing the assertion
749
+ or narrowing the type instead.
750
+ 3 | declare const x: User | Error;
751
+ 4 | const y = x as User;
752
+ | ~~~~~~~~~
753
+ 5 |
754
+ ---
755
+
756
+ [TestNoTypeAssertion/invalid-8 - 1]
757
+ Diagnostic 1: typeAssertion (4:14 - 4:24)
758
+ Message: Type assertion `as Config ({ host: string; port: number })`.
759
+ The expression type is `Config | null`. Try removing the assertion
760
+ or narrowing the type instead.
761
+ 3 | declare const x: Config | null;
762
+ 4 | const y = x as Config;
763
+ | ~~~~~~~~~~~
764
+ 5 |
765
+ ---
766
+ ```
767
+
768
+ This shows: the message ID, position, full description text, and the source
769
+ code with the flagged range underlined. Use this to verify your error messages
770
+ are helpful and include enough type information for agents to act on.
771
+
772
+ ## Complete Rule Example: no-unhandled-error
773
+
774
+ A real rule that enforces the errore pattern — errors when a call expression
775
+ returns a type containing `Error` and the result is discarded:
776
+
777
+ ```go
778
+ // .lintcn/no_unhandled_error/no_unhandled_error.go
779
+
780
+ // lintcn:name no-unhandled-error
781
+ // lintcn:description Disallow discarding expressions that are subtypes of Error
782
+
783
+ package no_unhandled_error
784
+
785
+ import (
786
+ "github.com/microsoft/typescript-go/shim/ast"
787
+ "github.com/microsoft/typescript-go/shim/checker"
788
+ "github.com/typescript-eslint/tsgolint/internal/rule"
789
+ "github.com/typescript-eslint/tsgolint/internal/utils"
790
+ )
791
+
792
+ var NoUnhandledErrorRule = rule.Rule{
793
+ Name: "no-unhandled-error",
794
+ Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
795
+ return rule.RuleListeners{
796
+ ast.KindExpressionStatement: func(node *ast.Node) {
797
+ exprStatement := node.AsExpressionStatement()
798
+ expression := ast.SkipParentheses(exprStatement.Expression)
799
+
800
+ // void expressions are intentional discards
801
+ if ast.IsVoidExpression(expression) {
802
+ return
803
+ }
804
+
805
+ // only check call expressions and await expressions wrapping calls
806
+ innerExpr := expression
807
+ if ast.IsAwaitExpression(innerExpr) {
808
+ innerExpr = ast.SkipParentheses(innerExpr.Expression())
809
+ }
810
+ if !ast.IsCallExpression(innerExpr) {
811
+ return
812
+ }
813
+
814
+ t := ctx.TypeChecker.GetTypeAtLocation(expression)
815
+
816
+ // skip void, undefined, never
817
+ if utils.IsTypeFlagSet(t,
818
+ checker.TypeFlagsVoid|checker.TypeFlagsVoidLike|
819
+ checker.TypeFlagsUndefined|checker.TypeFlagsNever) {
820
+ return
821
+ }
822
+
823
+ // check if any union part is Error-like
824
+ for _, part := range utils.UnionTypeParts(t) {
825
+ if utils.IsErrorLike(ctx.Program, ctx.TypeChecker, part) {
826
+ ctx.ReportNode(node, rule.RuleMessage{
827
+ Id: "noUnhandledError",
828
+ Description: "Error-typed return value is not handled.",
829
+ })
830
+ return
831
+ }
832
+ }
833
+ },
834
+ }
835
+ },
836
+ }
837
+ ```
838
+
839
+ ## Go Workspace Setup
840
+
841
+ `.lintcn/` needs these generated files (created by `lintcn add` automatically):
842
+
843
+ **go.mod** — module name MUST be a child path of tsgolint for `internal/`
844
+ package access:
845
+
846
+ ```
847
+ module github.com/typescript-eslint/tsgolint/lintcn-rules
848
+
849
+ go 1.26
850
+ ```
851
+
852
+ **go.work** — workspace linking to cached tsgolint source:
853
+
854
+ ```
855
+ go 1.26
856
+
857
+ use (
858
+ .
859
+ ./.tsgolint
860
+ ./.tsgolint/typescript-go
861
+ )
862
+
863
+ replace (
864
+ github.com/microsoft/typescript-go/shim/ast => ./.tsgolint/shim/ast
865
+ github.com/microsoft/typescript-go/shim/checker => ./.tsgolint/shim/checker
866
+ // ... all 14 shim modules
867
+ )
868
+ ```
869
+
870
+ **.tsgolint/** — symlink to cached tsgolint clone (gitignored).
871
+
872
+ With this setup, gopls provides full autocomplete and go-to-definition on all
873
+ tsgolint and typescript-go APIs.