oh-my-claude-sisyphus 3.6.0 → 3.6.1

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 (314) hide show
  1. package/commands/omc-setup.md +3 -3
  2. package/commands/release.md +1 -1
  3. package/dist/__tests__/analytics/transcript-scanner.test.js +69 -27
  4. package/dist/__tests__/analytics/transcript-scanner.test.js.map +1 -1
  5. package/dist/__tests__/installer.test.js +2 -1
  6. package/dist/__tests__/installer.test.js.map +1 -1
  7. package/dist/analytics/session-manager.d.ts +24 -0
  8. package/dist/analytics/session-manager.d.ts.map +1 -1
  9. package/dist/analytics/session-manager.js +98 -9
  10. package/dist/analytics/session-manager.js.map +1 -1
  11. package/dist/analytics/transcript-scanner.d.ts +9 -8
  12. package/dist/analytics/transcript-scanner.d.ts.map +1 -1
  13. package/dist/analytics/transcript-scanner.js +146 -16
  14. package/dist/analytics/transcript-scanner.js.map +1 -1
  15. package/dist/cli/analytics.js +0 -0
  16. package/dist/cli/index.js +0 -0
  17. package/dist/hooks/empty-message-sanitizer/__tests__/index.test.d.ts +2 -0
  18. package/dist/hooks/empty-message-sanitizer/__tests__/index.test.d.ts.map +1 -0
  19. package/dist/hooks/empty-message-sanitizer/__tests__/index.test.js +416 -0
  20. package/dist/hooks/empty-message-sanitizer/__tests__/index.test.js.map +1 -0
  21. package/dist/hooks/keyword-detector/__tests__/index.test.d.ts +2 -0
  22. package/dist/hooks/keyword-detector/__tests__/index.test.d.ts.map +1 -0
  23. package/dist/hooks/keyword-detector/__tests__/index.test.js +427 -0
  24. package/dist/hooks/keyword-detector/__tests__/index.test.js.map +1 -0
  25. package/dist/hooks/think-mode/__tests__/index.test.d.ts +2 -0
  26. package/dist/hooks/think-mode/__tests__/index.test.d.ts.map +1 -0
  27. package/dist/hooks/think-mode/__tests__/index.test.js +556 -0
  28. package/dist/hooks/think-mode/__tests__/index.test.js.map +1 -0
  29. package/dist/installer/index.d.ts.map +1 -1
  30. package/dist/installer/index.js +8 -0
  31. package/dist/installer/index.js.map +1 -1
  32. package/package.json +1 -1
  33. package/skills/omc-setup/SKILL.md +27 -3
  34. package/skills/release/SKILL.md +1 -1
  35. package/dist/__tests__/analytics/analytics-summary.test.d.ts +0 -2
  36. package/dist/__tests__/analytics/analytics-summary.test.d.ts.map +0 -1
  37. package/dist/__tests__/analytics/analytics-summary.test.js +0 -267
  38. package/dist/__tests__/analytics/analytics-summary.test.js.map +0 -1
  39. package/dist/__tests__/analytics/cost-estimator.test.d.ts +0 -2
  40. package/dist/__tests__/analytics/cost-estimator.test.d.ts.map +0 -1
  41. package/dist/__tests__/analytics/cost-estimator.test.js +0 -212
  42. package/dist/__tests__/analytics/cost-estimator.test.js.map +0 -1
  43. package/dist/__tests__/hooks/auto-slash-command/executor.test.d.ts +0 -7
  44. package/dist/__tests__/hooks/auto-slash-command/executor.test.d.ts.map +0 -1
  45. package/dist/__tests__/hooks/auto-slash-command/executor.test.js +0 -374
  46. package/dist/__tests__/hooks/auto-slash-command/executor.test.js.map +0 -1
  47. package/dist/__tests__/hud/auto-tracking.integration.test.d.ts +0 -2
  48. package/dist/__tests__/hud/auto-tracking.integration.test.d.ts.map +0 -1
  49. package/dist/__tests__/hud/auto-tracking.integration.test.js +0 -12
  50. package/dist/__tests__/hud/auto-tracking.integration.test.js.map +0 -1
  51. package/dist/__tests__/learned-skills/config.test.d.ts +0 -2
  52. package/dist/__tests__/learned-skills/config.test.d.ts.map +0 -1
  53. package/dist/__tests__/learned-skills/config.test.js +0 -37
  54. package/dist/__tests__/learned-skills/config.test.js.map +0 -1
  55. package/dist/__tests__/learned-skills/detector.test.d.ts +0 -2
  56. package/dist/__tests__/learned-skills/detector.test.d.ts.map +0 -1
  57. package/dist/__tests__/learned-skills/detector.test.js +0 -99
  58. package/dist/__tests__/learned-skills/detector.test.js.map +0 -1
  59. package/dist/__tests__/learned-skills/finder.test.d.ts +0 -2
  60. package/dist/__tests__/learned-skills/finder.test.d.ts.map +0 -1
  61. package/dist/__tests__/learned-skills/finder.test.js +0 -59
  62. package/dist/__tests__/learned-skills/finder.test.js.map +0 -1
  63. package/dist/__tests__/learned-skills/loader.test.d.ts +0 -2
  64. package/dist/__tests__/learned-skills/loader.test.d.ts.map +0 -1
  65. package/dist/__tests__/learned-skills/loader.test.js +0 -69
  66. package/dist/__tests__/learned-skills/loader.test.js.map +0 -1
  67. package/dist/__tests__/learned-skills/parser.test.d.ts +0 -2
  68. package/dist/__tests__/learned-skills/parser.test.d.ts.map +0 -1
  69. package/dist/__tests__/learned-skills/parser.test.js +0 -81
  70. package/dist/__tests__/learned-skills/parser.test.js.map +0 -1
  71. package/dist/__tests__/learned-skills/validator.test.d.ts +0 -2
  72. package/dist/__tests__/learned-skills/validator.test.d.ts.map +0 -1
  73. package/dist/__tests__/learned-skills/validator.test.js +0 -85
  74. package/dist/__tests__/learned-skills/validator.test.js.map +0 -1
  75. package/dist/agents/codex-agents.d.ts +0 -20
  76. package/dist/agents/codex-agents.d.ts.map +0 -1
  77. package/dist/agents/codex-agents.js +0 -36
  78. package/dist/agents/codex-agents.js.map +0 -1
  79. package/dist/agents/document-writer.d.ts +0 -11
  80. package/dist/agents/document-writer.d.ts.map +0 -1
  81. package/dist/agents/document-writer.js +0 -209
  82. package/dist/agents/document-writer.js.map +0 -1
  83. package/dist/agents/frontend-engineer.d.ts +0 -11
  84. package/dist/agents/frontend-engineer.d.ts.map +0 -1
  85. package/dist/agents/frontend-engineer.js +0 -115
  86. package/dist/agents/frontend-engineer.js.map +0 -1
  87. package/dist/agents/librarian.d.ts +0 -12
  88. package/dist/agents/librarian.d.ts.map +0 -1
  89. package/dist/agents/librarian.js +0 -103
  90. package/dist/agents/librarian.js.map +0 -1
  91. package/dist/agents/metis.d.ts +0 -12
  92. package/dist/agents/metis.d.ts.map +0 -1
  93. package/dist/agents/metis.js +0 -117
  94. package/dist/agents/metis.js.map +0 -1
  95. package/dist/agents/momus.d.ts +0 -12
  96. package/dist/agents/momus.d.ts.map +0 -1
  97. package/dist/agents/momus.js +0 -128
  98. package/dist/agents/momus.js.map +0 -1
  99. package/dist/agents/multimodal-looker.d.ts +0 -11
  100. package/dist/agents/multimodal-looker.d.ts.map +0 -1
  101. package/dist/agents/multimodal-looker.js +0 -70
  102. package/dist/agents/multimodal-looker.js.map +0 -1
  103. package/dist/agents/oracle.d.ts +0 -13
  104. package/dist/agents/oracle.d.ts.map +0 -1
  105. package/dist/agents/oracle.js +0 -191
  106. package/dist/agents/oracle.js.map +0 -1
  107. package/dist/agents/orchestrator-sisyphus.d.ts +0 -11
  108. package/dist/agents/orchestrator-sisyphus.d.ts.map +0 -1
  109. package/dist/agents/orchestrator-sisyphus.js +0 -115
  110. package/dist/agents/orchestrator-sisyphus.js.map +0 -1
  111. package/dist/agents/prometheus.d.ts +0 -12
  112. package/dist/agents/prometheus.d.ts.map +0 -1
  113. package/dist/agents/prometheus.js +0 -195
  114. package/dist/agents/prometheus.js.map +0 -1
  115. package/dist/agents/sisyphus-junior.d.ts +0 -12
  116. package/dist/agents/sisyphus-junior.d.ts.map +0 -1
  117. package/dist/agents/sisyphus-junior.js +0 -93
  118. package/dist/agents/sisyphus-junior.js.map +0 -1
  119. package/dist/cli/components/CostDashboard.d.ts +0 -15
  120. package/dist/cli/components/CostDashboard.d.ts.map +0 -1
  121. package/dist/cli/components/CostDashboard.js +0 -15
  122. package/dist/cli/components/CostDashboard.js.map +0 -1
  123. package/dist/cli/components/LiveStats.d.ts +0 -16
  124. package/dist/cli/components/LiveStats.d.ts.map +0 -1
  125. package/dist/cli/components/LiveStats.js +0 -16
  126. package/dist/cli/components/LiveStats.js.map +0 -1
  127. package/dist/cli/components/SessionBrowser.d.ts +0 -14
  128. package/dist/cli/components/SessionBrowser.d.ts.map +0 -1
  129. package/dist/cli/components/SessionBrowser.js +0 -14
  130. package/dist/cli/components/SessionBrowser.js.map +0 -1
  131. package/dist/cli/tui.d.ts +0 -21
  132. package/dist/cli/tui.d.ts.map +0 -1
  133. package/dist/cli/tui.js +0 -21
  134. package/dist/cli/tui.js.map +0 -1
  135. package/dist/hooks/autopilot/signals.d.ts +0 -20
  136. package/dist/hooks/autopilot/signals.d.ts.map +0 -1
  137. package/dist/hooks/autopilot/signals.js +0 -75
  138. package/dist/hooks/autopilot/signals.js.map +0 -1
  139. package/dist/hooks/autopilot/summary.d.ts +0 -27
  140. package/dist/hooks/autopilot/summary.d.ts.map +0 -1
  141. package/dist/hooks/autopilot/summary.js +0 -160
  142. package/dist/hooks/autopilot/summary.js.map +0 -1
  143. package/dist/hooks/autopilot/transition.d.ts +0 -39
  144. package/dist/hooks/autopilot/transition.d.ts.map +0 -1
  145. package/dist/hooks/autopilot/transition.js +0 -216
  146. package/dist/hooks/autopilot/transition.js.map +0 -1
  147. package/dist/hooks/context-window-limit-recovery/constants.d.ts +0 -28
  148. package/dist/hooks/context-window-limit-recovery/constants.d.ts.map +0 -1
  149. package/dist/hooks/context-window-limit-recovery/constants.js +0 -85
  150. package/dist/hooks/context-window-limit-recovery/constants.js.map +0 -1
  151. package/dist/hooks/context-window-limit-recovery/index.d.ts +0 -62
  152. package/dist/hooks/context-window-limit-recovery/index.d.ts.map +0 -1
  153. package/dist/hooks/context-window-limit-recovery/index.js +0 -201
  154. package/dist/hooks/context-window-limit-recovery/index.js.map +0 -1
  155. package/dist/hooks/context-window-limit-recovery/parser.d.ts +0 -31
  156. package/dist/hooks/context-window-limit-recovery/parser.d.ts.map +0 -1
  157. package/dist/hooks/context-window-limit-recovery/parser.js +0 -241
  158. package/dist/hooks/context-window-limit-recovery/parser.js.map +0 -1
  159. package/dist/hooks/context-window-limit-recovery/types.d.ts +0 -84
  160. package/dist/hooks/context-window-limit-recovery/types.d.ts.map +0 -1
  161. package/dist/hooks/context-window-limit-recovery/types.js +0 -34
  162. package/dist/hooks/context-window-limit-recovery/types.js.map +0 -1
  163. package/dist/hooks/edit-error-recovery/index.d.ts +0 -62
  164. package/dist/hooks/edit-error-recovery/index.d.ts.map +0 -1
  165. package/dist/hooks/edit-error-recovery/index.js +0 -89
  166. package/dist/hooks/edit-error-recovery/index.js.map +0 -1
  167. package/dist/hooks/learned-skills/config.d.ts +0 -53
  168. package/dist/hooks/learned-skills/config.d.ts.map +0 -1
  169. package/dist/hooks/learned-skills/config.js +0 -103
  170. package/dist/hooks/learned-skills/config.js.map +0 -1
  171. package/dist/hooks/learned-skills/constants.d.ts +0 -24
  172. package/dist/hooks/learned-skills/constants.d.ts.map +0 -1
  173. package/dist/hooks/learned-skills/constants.js +0 -26
  174. package/dist/hooks/learned-skills/constants.js.map +0 -1
  175. package/dist/hooks/learned-skills/detection-hook.d.ts +0 -39
  176. package/dist/hooks/learned-skills/detection-hook.d.ts.map +0 -1
  177. package/dist/hooks/learned-skills/detection-hook.js +0 -83
  178. package/dist/hooks/learned-skills/detection-hook.js.map +0 -1
  179. package/dist/hooks/learned-skills/detector.d.ts +0 -30
  180. package/dist/hooks/learned-skills/detector.d.ts.map +0 -1
  181. package/dist/hooks/learned-skills/detector.js +0 -150
  182. package/dist/hooks/learned-skills/detector.js.map +0 -1
  183. package/dist/hooks/learned-skills/finder.d.ts +0 -21
  184. package/dist/hooks/learned-skills/finder.d.ts.map +0 -1
  185. package/dist/hooks/learned-skills/finder.js +0 -117
  186. package/dist/hooks/learned-skills/finder.js.map +0 -1
  187. package/dist/hooks/learned-skills/index.d.ts +0 -62
  188. package/dist/hooks/learned-skills/index.d.ts.map +0 -1
  189. package/dist/hooks/learned-skills/index.js +0 -137
  190. package/dist/hooks/learned-skills/index.js.map +0 -1
  191. package/dist/hooks/learned-skills/loader.d.ts +0 -20
  192. package/dist/hooks/learned-skills/loader.d.ts.map +0 -1
  193. package/dist/hooks/learned-skills/loader.js +0 -107
  194. package/dist/hooks/learned-skills/loader.js.map +0 -1
  195. package/dist/hooks/learned-skills/parser.d.ts +0 -21
  196. package/dist/hooks/learned-skills/parser.d.ts.map +0 -1
  197. package/dist/hooks/learned-skills/parser.js +0 -190
  198. package/dist/hooks/learned-skills/parser.js.map +0 -1
  199. package/dist/hooks/learned-skills/promotion.d.ts +0 -29
  200. package/dist/hooks/learned-skills/promotion.d.ts.map +0 -1
  201. package/dist/hooks/learned-skills/promotion.js +0 -87
  202. package/dist/hooks/learned-skills/promotion.js.map +0 -1
  203. package/dist/hooks/learned-skills/types.d.ts +0 -109
  204. package/dist/hooks/learned-skills/types.d.ts.map +0 -1
  205. package/dist/hooks/learned-skills/types.js +0 -8
  206. package/dist/hooks/learned-skills/types.js.map +0 -1
  207. package/dist/hooks/learned-skills/validator.d.ts +0 -15
  208. package/dist/hooks/learned-skills/validator.d.ts.map +0 -1
  209. package/dist/hooks/learned-skills/validator.js +0 -87
  210. package/dist/hooks/learned-skills/validator.js.map +0 -1
  211. package/dist/hooks/learned-skills/writer.d.ts +0 -27
  212. package/dist/hooks/learned-skills/writer.d.ts.map +0 -1
  213. package/dist/hooks/learned-skills/writer.js +0 -126
  214. package/dist/hooks/learned-skills/writer.js.map +0 -1
  215. package/dist/hooks/mnemosyne/config.d.ts +0 -53
  216. package/dist/hooks/mnemosyne/config.d.ts.map +0 -1
  217. package/dist/hooks/mnemosyne/config.js +0 -103
  218. package/dist/hooks/mnemosyne/config.js.map +0 -1
  219. package/dist/hooks/mnemosyne/constants.d.ts +0 -24
  220. package/dist/hooks/mnemosyne/constants.d.ts.map +0 -1
  221. package/dist/hooks/mnemosyne/constants.js +0 -26
  222. package/dist/hooks/mnemosyne/constants.js.map +0 -1
  223. package/dist/hooks/mnemosyne/detection-hook.d.ts +0 -39
  224. package/dist/hooks/mnemosyne/detection-hook.d.ts.map +0 -1
  225. package/dist/hooks/mnemosyne/detection-hook.js +0 -83
  226. package/dist/hooks/mnemosyne/detection-hook.js.map +0 -1
  227. package/dist/hooks/mnemosyne/detector.d.ts +0 -30
  228. package/dist/hooks/mnemosyne/detector.d.ts.map +0 -1
  229. package/dist/hooks/mnemosyne/detector.js +0 -150
  230. package/dist/hooks/mnemosyne/detector.js.map +0 -1
  231. package/dist/hooks/mnemosyne/finder.d.ts +0 -21
  232. package/dist/hooks/mnemosyne/finder.d.ts.map +0 -1
  233. package/dist/hooks/mnemosyne/finder.js +0 -117
  234. package/dist/hooks/mnemosyne/finder.js.map +0 -1
  235. package/dist/hooks/mnemosyne/index.d.ts +0 -62
  236. package/dist/hooks/mnemosyne/index.d.ts.map +0 -1
  237. package/dist/hooks/mnemosyne/index.js +0 -137
  238. package/dist/hooks/mnemosyne/index.js.map +0 -1
  239. package/dist/hooks/mnemosyne/loader.d.ts +0 -20
  240. package/dist/hooks/mnemosyne/loader.d.ts.map +0 -1
  241. package/dist/hooks/mnemosyne/loader.js +0 -113
  242. package/dist/hooks/mnemosyne/loader.js.map +0 -1
  243. package/dist/hooks/mnemosyne/parser.d.ts +0 -21
  244. package/dist/hooks/mnemosyne/parser.d.ts.map +0 -1
  245. package/dist/hooks/mnemosyne/parser.js +0 -190
  246. package/dist/hooks/mnemosyne/parser.js.map +0 -1
  247. package/dist/hooks/mnemosyne/promotion.d.ts +0 -29
  248. package/dist/hooks/mnemosyne/promotion.d.ts.map +0 -1
  249. package/dist/hooks/mnemosyne/promotion.js +0 -87
  250. package/dist/hooks/mnemosyne/promotion.js.map +0 -1
  251. package/dist/hooks/mnemosyne/types.d.ts +0 -109
  252. package/dist/hooks/mnemosyne/types.d.ts.map +0 -1
  253. package/dist/hooks/mnemosyne/types.js +0 -8
  254. package/dist/hooks/mnemosyne/types.js.map +0 -1
  255. package/dist/hooks/mnemosyne/validator.d.ts +0 -15
  256. package/dist/hooks/mnemosyne/validator.d.ts.map +0 -1
  257. package/dist/hooks/mnemosyne/validator.js +0 -87
  258. package/dist/hooks/mnemosyne/validator.js.map +0 -1
  259. package/dist/hooks/mnemosyne/writer.d.ts +0 -27
  260. package/dist/hooks/mnemosyne/writer.d.ts.map +0 -1
  261. package/dist/hooks/mnemosyne/writer.js +0 -126
  262. package/dist/hooks/mnemosyne/writer.js.map +0 -1
  263. package/dist/hooks/ralph-loop/index.d.ts +0 -116
  264. package/dist/hooks/ralph-loop/index.d.ts.map +0 -1
  265. package/dist/hooks/ralph-loop/index.js +0 -322
  266. package/dist/hooks/ralph-loop/index.js.map +0 -1
  267. package/dist/hooks/ralph-prd/index.d.ts +0 -130
  268. package/dist/hooks/ralph-prd/index.d.ts.map +0 -1
  269. package/dist/hooks/ralph-prd/index.js +0 -310
  270. package/dist/hooks/ralph-prd/index.js.map +0 -1
  271. package/dist/hooks/ralph-progress/index.d.ts +0 -102
  272. package/dist/hooks/ralph-progress/index.d.ts.map +0 -1
  273. package/dist/hooks/ralph-progress/index.js +0 -408
  274. package/dist/hooks/ralph-progress/index.js.map +0 -1
  275. package/dist/hooks/ralph-verifier/index.d.ts +0 -72
  276. package/dist/hooks/ralph-verifier/index.d.ts.map +0 -1
  277. package/dist/hooks/ralph-verifier/index.js +0 -223
  278. package/dist/hooks/ralph-verifier/index.js.map +0 -1
  279. package/dist/hooks/session-recovery/constants.d.ts +0 -56
  280. package/dist/hooks/session-recovery/constants.d.ts.map +0 -1
  281. package/dist/hooks/session-recovery/constants.js +0 -78
  282. package/dist/hooks/session-recovery/constants.js.map +0 -1
  283. package/dist/hooks/session-recovery/index.d.ts +0 -53
  284. package/dist/hooks/session-recovery/index.d.ts.map +0 -1
  285. package/dist/hooks/session-recovery/index.js +0 -321
  286. package/dist/hooks/session-recovery/index.js.map +0 -1
  287. package/dist/hooks/session-recovery/storage.d.ts +0 -76
  288. package/dist/hooks/session-recovery/storage.d.ts.map +0 -1
  289. package/dist/hooks/session-recovery/storage.js +0 -383
  290. package/dist/hooks/session-recovery/storage.js.map +0 -1
  291. package/dist/hooks/session-recovery/types.d.ts +0 -145
  292. package/dist/hooks/session-recovery/types.d.ts.map +0 -1
  293. package/dist/hooks/session-recovery/types.js +0 -8
  294. package/dist/hooks/session-recovery/types.js.map +0 -1
  295. package/dist/hooks/sisyphus-orchestrator/constants.d.ts +0 -23
  296. package/dist/hooks/sisyphus-orchestrator/constants.d.ts.map +0 -1
  297. package/dist/hooks/sisyphus-orchestrator/constants.js +0 -142
  298. package/dist/hooks/sisyphus-orchestrator/constants.js.map +0 -1
  299. package/dist/hooks/sisyphus-orchestrator/index.d.ts +0 -113
  300. package/dist/hooks/sisyphus-orchestrator/index.d.ts.map +0 -1
  301. package/dist/hooks/sisyphus-orchestrator/index.js +0 -309
  302. package/dist/hooks/sisyphus-orchestrator/index.js.map +0 -1
  303. package/dist/hooks/ultraqa-loop/index.d.ts +0 -94
  304. package/dist/hooks/ultraqa-loop/index.d.ts.map +0 -1
  305. package/dist/hooks/ultraqa-loop/index.js +0 -216
  306. package/dist/hooks/ultraqa-loop/index.js.map +0 -1
  307. package/dist/hooks/ultrawork-state/index.d.ts +0 -62
  308. package/dist/hooks/ultrawork-state/index.d.ts.map +0 -1
  309. package/dist/hooks/ultrawork-state/index.js +0 -208
  310. package/dist/hooks/ultrawork-state/index.js.map +0 -1
  311. package/dist/hud/sisyphus-state.d.ts +0 -31
  312. package/dist/hud/sisyphus-state.d.ts.map +0 -1
  313. package/dist/hud/sisyphus-state.js +0 -163
  314. package/dist/hud/sisyphus-state.js.map +0 -1
@@ -2,6 +2,21 @@ import { readdir, stat } from 'fs/promises';
2
2
  import { existsSync } from 'fs';
3
3
  import { join } from 'path';
4
4
  import { homedir } from 'os';
5
+ /**
6
+ * Check if the encoded path looks like a Windows path (starts with drive letter)
7
+ * Examples: "C--Users-user-project" or "D--work-project"
8
+ */
9
+ function isWindowsEncodedPath(dirName) {
10
+ return /^[A-Za-z]-/.test(dirName);
11
+ }
12
+ /**
13
+ * Normalize decoded path to use OS-native separators consistently
14
+ */
15
+ function normalizePathForOS(decodedPath) {
16
+ // On Windows, convert forward slashes to backslashes for consistency
17
+ // But existsSync works with both, so we normalize to forward slashes for cross-platform
18
+ return decodedPath.replace(/\\/g, '/');
19
+ }
5
20
  /**
6
21
  * UUID regex pattern for session IDs
7
22
  */
@@ -10,32 +25,138 @@ const UUID_REGEX = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12
10
25
  * Decode project directory name back to original path.
11
26
  *
12
27
  * The encoding scheme used by Claude Code is lossy - it converts all path
13
- * separators (/) to dashes (-), but legitimate dashes in directory names
28
+ * separators (/ or \) to dashes (-), but legitimate dashes in directory names
14
29
  * also become dashes, making them indistinguishable.
15
30
  *
16
- * Strategy:
17
- * 1. Try simple decode (all dashes -> slashes) and check if path exists
18
- * 2. If not, try to reconstruct by checking filesystem for partial matches
19
- * 3. Fall back to simple decode if nothing else works
31
+ * Encoding patterns:
32
+ * - Unix: "/home/user/project" "-home-user-project"
33
+ * - Windows: "C:\Users\user\project" "C--Users-user-project"
20
34
  *
21
- * Example: "-home-bellman-my-project"
22
- * - Simple decode: "/home/bellman/my/project" (WRONG if "my-project" is one dir)
23
- * - Smart decode: "/home/bellman/my-project" (checks filesystem)
35
+ * Strategy:
36
+ * 1. Detect if it's a Windows or Unix encoded path
37
+ * 2. Try simple decode (all dashes -> slashes) and check if path exists
38
+ * 3. If not, try to reconstruct by checking filesystem for partial matches
39
+ * 4. Fall back to simple decode if nothing else works
24
40
  *
25
41
  * @internal Exported for testing
26
42
  */
27
43
  export function decodeProjectPath(dirName) {
28
- if (!dirName.startsWith('-')) {
29
- return dirName;
44
+ // Handle Windows encoded paths (e.g., "C--Users-user-project")
45
+ if (isWindowsEncodedPath(dirName)) {
46
+ return decodeWindowsPath(dirName);
47
+ }
48
+ // Handle Unix encoded paths (e.g., "-home-user-project")
49
+ if (dirName.startsWith('-')) {
50
+ return decodeUnixPath(dirName);
51
+ }
52
+ // Not an encoded path, return as-is
53
+ return dirName;
54
+ }
55
+ /**
56
+ * Split path string preserving consecutive hyphens as single segments.
57
+ * e.g., "a--b-c" → ["a", "-", "b", "c"] (the "-" represents a hyphen in original name)
58
+ */
59
+ function splitPreservingConsecutiveHyphens(str) {
60
+ const result = [];
61
+ let current = '';
62
+ let i = 0;
63
+ while (i < str.length) {
64
+ if (str[i] === '-') {
65
+ if (current) {
66
+ result.push(current);
67
+ current = '';
68
+ }
69
+ // Check for consecutive hyphens
70
+ if (i + 1 < str.length && str[i + 1] === '-') {
71
+ // Consecutive hyphens - this means the original had a hyphen
72
+ // Push empty string as marker, will be joined with hyphen later
73
+ result.push('');
74
+ i++; // Skip the second hyphen
75
+ }
76
+ i++;
77
+ }
78
+ else {
79
+ current += str[i];
80
+ i++;
81
+ }
82
+ }
83
+ if (current) {
84
+ result.push(current);
85
+ }
86
+ return result;
87
+ }
88
+ /**
89
+ * Decode Windows encoded path (e.g., "C--Users-user-project" → "C:/Users/user/project")
90
+ */
91
+ function decodeWindowsPath(dirName) {
92
+ const driveLetter = dirName[0];
93
+ const rest = dirName.slice(2); // Skip "X-"
94
+ // Simple decode: drive letter + colon + rest with dashes as slashes
95
+ const simplePath = `${driveLetter}:/${rest.replace(/-/g, '/')}`;
96
+ // Normalize double slashes that might occur from empty segments
97
+ const normalizedSimple = simplePath.replace(/\/+/g, '/');
98
+ // If simple decode exists, we're done
99
+ if (existsSync(normalizedSimple)) {
100
+ return normalizedSimple;
101
+ }
102
+ // Try to reconstruct by checking filesystem for partial matches
103
+ // Use special splitting to handle consecutive hyphens
104
+ const segments = splitPreservingConsecutiveHyphens(rest);
105
+ if (segments.length === 0) {
106
+ return `${driveLetter}:/`;
107
+ }
108
+ const possiblePaths = [];
109
+ // Generate all possible interpretations by trying different hyphen positions
110
+ function generatePaths(parts, index, currentPath) {
111
+ if (index >= parts.length) {
112
+ possiblePaths.push(currentPath);
113
+ return;
114
+ }
115
+ const part = parts[index];
116
+ // Empty string means this was a consecutive hyphen - must join with previous
117
+ if (part === '' && currentPath) {
118
+ const pathParts = currentPath.split('/');
119
+ const lastPart = pathParts.pop() || '';
120
+ const newPath = pathParts.join('/') + '/' + lastPart + '-';
121
+ generatePaths(parts, index + 1, newPath);
122
+ return;
123
+ }
124
+ // Try adding next segment as a new directory
125
+ const newDir = currentPath + '/' + part;
126
+ generatePaths(parts, index + 1, newDir);
127
+ // Try combining with previous segment using hyphen (if not first segment)
128
+ if (index > 0 && currentPath) {
129
+ const pathParts = currentPath.split('/');
130
+ const lastPart = pathParts.pop() || '';
131
+ const newPath = pathParts.join('/') + '/' + lastPart + '-' + part;
132
+ generatePaths(parts, index + 1, newPath);
133
+ }
30
134
  }
135
+ generatePaths(segments, 0, `${driveLetter}:`);
136
+ // Find the first path that exists on filesystem
137
+ for (const path of possiblePaths) {
138
+ if (existsSync(path)) {
139
+ return path;
140
+ }
141
+ }
142
+ // Fall back to simple decode
143
+ return normalizedSimple;
144
+ }
145
+ /**
146
+ * Decode Unix encoded path (e.g., "-home-user-project" → "/home/user/project")
147
+ */
148
+ function decodeUnixPath(dirName) {
31
149
  // Simple decode: replace all dashes with slashes
32
150
  const simplePath = '/' + dirName.slice(1).replace(/-/g, '/');
151
+ // Normalize double slashes
152
+ const normalizedSimple = simplePath.replace(/\/+/g, '/');
33
153
  // If simple decode exists, we're done
34
- if (existsSync(simplePath)) {
35
- return simplePath;
154
+ if (existsSync(normalizedSimple)) {
155
+ return normalizedSimple;
36
156
  }
37
157
  // Try to reconstruct by checking filesystem for partial matches
38
- const segments = dirName.slice(1).split('-');
158
+ // Use special splitting to handle consecutive hyphens
159
+ const segments = splitPreservingConsecutiveHyphens(dirName.slice(1));
39
160
  const possiblePaths = [];
40
161
  // Generate all possible interpretations by trying different hyphen positions
41
162
  function generatePaths(parts, index, currentPath) {
@@ -43,13 +164,22 @@ export function decodeProjectPath(dirName) {
43
164
  possiblePaths.push(currentPath);
44
165
  return;
45
166
  }
167
+ const part = parts[index];
168
+ // Empty string means this was a consecutive hyphen - must join with previous
169
+ if (part === '' && currentPath) {
170
+ const pathParts = currentPath.split('/');
171
+ const lastPart = pathParts.pop() || '';
172
+ const newPath = pathParts.join('/') + '/' + lastPart + '-';
173
+ generatePaths(parts, index + 1, newPath);
174
+ return;
175
+ }
46
176
  // Try adding next segment as a new directory
47
- generatePaths(parts, index + 1, currentPath + '/' + parts[index]);
177
+ generatePaths(parts, index + 1, currentPath + '/' + part);
48
178
  // Try combining with previous segment using hyphen (if not first segment)
49
179
  if (index > 0 && currentPath) {
50
180
  const pathParts = currentPath.split('/');
51
181
  const lastPart = pathParts.pop() || '';
52
- const newPath = pathParts.join('/') + '/' + lastPart + '-' + parts[index];
182
+ const newPath = pathParts.join('/') + '/' + lastPart + '-' + part;
53
183
  generatePaths(parts, index + 1, newPath);
54
184
  }
55
185
  }
@@ -61,7 +191,7 @@ export function decodeProjectPath(dirName) {
61
191
  }
62
192
  }
63
193
  // Fall back to simple decode
64
- return simplePath;
194
+ return normalizedSimple;
65
195
  }
66
196
  /**
67
197
  * Check if a path matches a glob pattern (simple implementation)
@@ -1 +1 @@
1
- {"version":3,"file":"transcript-scanner.js","sourceRoot":"","sources":["../../src/analytics/transcript-scanner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transcript-scanner.js","sourceRoot":"","sources":["../../src/analytics/transcript-scanner.ts"],"names":[],"mappings":"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"}
File without changes
package/dist/cli/index.js CHANGED
File without changes
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../../src/hooks/empty-message-sanitizer/__tests__/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,416 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { hasTextContent, isToolPart, hasValidContent, sanitizeMessage, sanitizeMessages, createEmptyMessageSanitizerHook, PLACEHOLDER_TEXT, TOOL_PART_TYPES, HOOK_NAME, } from '../index.js';
3
+ // Helper to create message parts
4
+ function createTextPart(text, id) {
5
+ return {
6
+ id: id || `part-${Date.now()}`,
7
+ type: 'text',
8
+ text,
9
+ };
10
+ }
11
+ function createToolPart(type, id) {
12
+ return {
13
+ id: id || `part-${Date.now()}`,
14
+ type,
15
+ };
16
+ }
17
+ // Helper to create messages
18
+ function createMessage(role, parts, id) {
19
+ return {
20
+ info: {
21
+ id: id || `msg-${Date.now()}`,
22
+ role,
23
+ sessionID: 'test-session',
24
+ },
25
+ parts,
26
+ };
27
+ }
28
+ describe('empty-message-sanitizer', () => {
29
+ describe('hasTextContent', () => {
30
+ it('should return true for part with non-empty text', () => {
31
+ const part = createTextPart('Hello');
32
+ expect(hasTextContent(part)).toBe(true);
33
+ });
34
+ it('should return false for part with empty text', () => {
35
+ const part = createTextPart('');
36
+ expect(hasTextContent(part)).toBe(false);
37
+ });
38
+ it('should return false for part with whitespace only', () => {
39
+ const part = createTextPart(' \n\t ');
40
+ expect(hasTextContent(part)).toBe(false);
41
+ });
42
+ it('should return false for part with undefined text', () => {
43
+ const part = createTextPart(undefined);
44
+ expect(hasTextContent(part)).toBe(false);
45
+ });
46
+ it('should return false for non-text part types', () => {
47
+ const part = createToolPart('tool_use');
48
+ expect(hasTextContent(part)).toBe(false);
49
+ });
50
+ it('should return true for text with only newlines but also content', () => {
51
+ const part = createTextPart('\nHello\n');
52
+ expect(hasTextContent(part)).toBe(true);
53
+ });
54
+ it('should return false for null-like text value', () => {
55
+ const part = { type: 'text', text: null };
56
+ expect(hasTextContent(part)).toBe(false);
57
+ });
58
+ });
59
+ describe('isToolPart', () => {
60
+ it('should return true for tool part type', () => {
61
+ const part = createToolPart('tool');
62
+ expect(isToolPart(part)).toBe(true);
63
+ });
64
+ it('should return true for tool_use part type', () => {
65
+ const part = createToolPart('tool_use');
66
+ expect(isToolPart(part)).toBe(true);
67
+ });
68
+ it('should return true for tool_result part type', () => {
69
+ const part = createToolPart('tool_result');
70
+ expect(isToolPart(part)).toBe(true);
71
+ });
72
+ it('should return false for text part type', () => {
73
+ const part = createTextPart('text content');
74
+ expect(isToolPart(part)).toBe(false);
75
+ });
76
+ it('should return false for image part type', () => {
77
+ const part = { type: 'image' };
78
+ expect(isToolPart(part)).toBe(false);
79
+ });
80
+ it('should return false for unknown part type', () => {
81
+ const part = { type: 'unknown_type' };
82
+ expect(isToolPart(part)).toBe(false);
83
+ });
84
+ it('should use TOOL_PART_TYPES constant', () => {
85
+ expect(TOOL_PART_TYPES.has('tool')).toBe(true);
86
+ expect(TOOL_PART_TYPES.has('tool_use')).toBe(true);
87
+ expect(TOOL_PART_TYPES.has('tool_result')).toBe(true);
88
+ });
89
+ });
90
+ describe('hasValidContent', () => {
91
+ it('should return true for parts with non-empty text', () => {
92
+ const parts = [createTextPart('Hello')];
93
+ expect(hasValidContent(parts)).toBe(true);
94
+ });
95
+ it('should return true for parts with tool part', () => {
96
+ const parts = [createToolPart('tool_use')];
97
+ expect(hasValidContent(parts)).toBe(true);
98
+ });
99
+ it('should return true for parts with both text and tool', () => {
100
+ const parts = [
101
+ createTextPart('Hello'),
102
+ createToolPart('tool_use'),
103
+ ];
104
+ expect(hasValidContent(parts)).toBe(true);
105
+ });
106
+ it('should return false for empty parts array', () => {
107
+ expect(hasValidContent([])).toBe(false);
108
+ });
109
+ it('should return false for parts with only empty text', () => {
110
+ const parts = [createTextPart(''), createTextPart(' ')];
111
+ expect(hasValidContent(parts)).toBe(false);
112
+ });
113
+ it('should return false for parts with undefined text', () => {
114
+ const parts = [createTextPart(undefined)];
115
+ expect(hasValidContent(parts)).toBe(false);
116
+ });
117
+ it('should return true when one part has valid text among empties', () => {
118
+ const parts = [
119
+ createTextPart(''),
120
+ createTextPart('Valid'),
121
+ createTextPart(' '),
122
+ ];
123
+ expect(hasValidContent(parts)).toBe(true);
124
+ });
125
+ it('should return true when tool part exists among empty text parts', () => {
126
+ const parts = [
127
+ createTextPart(''),
128
+ createToolPart('tool_result'),
129
+ ];
130
+ expect(hasValidContent(parts)).toBe(true);
131
+ });
132
+ });
133
+ describe('sanitizeMessage', () => {
134
+ it('should not modify message with valid text content', () => {
135
+ const message = createMessage('user', [createTextPart('Hello')]);
136
+ const result = sanitizeMessage(message, false);
137
+ expect(result).toBe(false);
138
+ expect(message.parts[0].text).toBe('Hello');
139
+ });
140
+ it('should not modify message with tool part', () => {
141
+ const message = createMessage('assistant', [createToolPart('tool_use')]);
142
+ const result = sanitizeMessage(message, false);
143
+ expect(result).toBe(false);
144
+ });
145
+ it('should skip final assistant message', () => {
146
+ const message = createMessage('assistant', []);
147
+ const result = sanitizeMessage(message, true);
148
+ expect(result).toBe(false);
149
+ expect(message.parts.length).toBe(0);
150
+ });
151
+ it('should sanitize non-final assistant message with empty content', () => {
152
+ const message = createMessage('assistant', []);
153
+ const result = sanitizeMessage(message, false);
154
+ expect(result).toBe(true);
155
+ expect(message.parts.length).toBe(1);
156
+ expect(message.parts[0].text).toBe(PLACEHOLDER_TEXT);
157
+ expect(message.parts[0].synthetic).toBe(true);
158
+ });
159
+ it('should sanitize user message with empty parts array', () => {
160
+ const message = createMessage('user', []);
161
+ const result = sanitizeMessage(message, false);
162
+ expect(result).toBe(true);
163
+ expect(message.parts.length).toBe(1);
164
+ expect(message.parts[0].text).toBe(PLACEHOLDER_TEXT);
165
+ });
166
+ it('should replace existing empty text part', () => {
167
+ const message = createMessage('user', [createTextPart('')]);
168
+ const result = sanitizeMessage(message, false);
169
+ expect(result).toBe(true);
170
+ expect(message.parts.length).toBe(1);
171
+ expect(message.parts[0].text).toBe(PLACEHOLDER_TEXT);
172
+ expect(message.parts[0].synthetic).toBe(true);
173
+ });
174
+ it('should replace whitespace-only text part', () => {
175
+ const message = createMessage('user', [createTextPart(' \n ')]);
176
+ const result = sanitizeMessage(message, false);
177
+ expect(result).toBe(true);
178
+ expect(message.parts[0].text).toBe(PLACEHOLDER_TEXT);
179
+ });
180
+ it('should insert text part before tool part when no text exists', () => {
181
+ const message = createMessage('user', [createToolPart('tool_use')]);
182
+ const originalLength = message.parts.length;
183
+ const result = sanitizeMessage(message, false);
184
+ expect(result).toBe(false); // Tool part counts as valid content
185
+ });
186
+ it('should append text part when no tool parts exist', () => {
187
+ const message = createMessage('user', []);
188
+ sanitizeMessage(message, false);
189
+ expect(message.parts.length).toBe(1);
190
+ expect(message.parts[0].type).toBe('text');
191
+ });
192
+ it('should use custom placeholder text', () => {
193
+ const message = createMessage('user', []);
194
+ const customPlaceholder = '[custom placeholder]';
195
+ sanitizeMessage(message, false, customPlaceholder);
196
+ expect(message.parts[0].text).toBe(customPlaceholder);
197
+ });
198
+ it('should set synthetic flag on injected parts', () => {
199
+ const message = createMessage('user', []);
200
+ sanitizeMessage(message, false);
201
+ expect(message.parts[0].synthetic).toBe(true);
202
+ });
203
+ it('should sanitize empty text parts alongside valid content', () => {
204
+ const message = createMessage('user', [
205
+ createTextPart('Valid'),
206
+ createTextPart(''),
207
+ ]);
208
+ const result = sanitizeMessage(message, false);
209
+ expect(result).toBe(true);
210
+ expect(message.parts[1].text).toBe(PLACEHOLDER_TEXT);
211
+ expect(message.parts[1].synthetic).toBe(true);
212
+ });
213
+ it('should not modify non-empty text alongside empty text', () => {
214
+ const message = createMessage('user', [
215
+ createTextPart('Valid'),
216
+ createTextPart(''),
217
+ ]);
218
+ sanitizeMessage(message, false);
219
+ expect(message.parts[0].text).toBe('Valid');
220
+ expect(message.parts[0].synthetic).toBeUndefined();
221
+ });
222
+ it('should handle message with multiple empty text parts', () => {
223
+ const message = createMessage('user', [
224
+ createTextPart(''),
225
+ createTextPart(' '),
226
+ ]);
227
+ sanitizeMessage(message, false);
228
+ // First empty text part should be replaced
229
+ expect(message.parts[0].text).toBe(PLACEHOLDER_TEXT);
230
+ });
231
+ });
232
+ describe('sanitizeMessages', () => {
233
+ it('should sanitize all messages in input', () => {
234
+ const input = {
235
+ messages: [
236
+ createMessage('user', []),
237
+ createMessage('assistant', [createTextPart('')]),
238
+ createMessage('user', [createTextPart('Valid')]),
239
+ ],
240
+ };
241
+ const result = sanitizeMessages(input);
242
+ expect(result.sanitizedCount).toBe(2);
243
+ expect(result.modified).toBe(true);
244
+ });
245
+ it('should return modified false when no sanitization needed', () => {
246
+ const input = {
247
+ messages: [
248
+ createMessage('user', [createTextPart('Hello')]),
249
+ createMessage('assistant', [createTextPart('World')]),
250
+ ],
251
+ };
252
+ const result = sanitizeMessages(input);
253
+ expect(result.sanitizedCount).toBe(0);
254
+ expect(result.modified).toBe(false);
255
+ });
256
+ it('should skip final assistant message', () => {
257
+ const input = {
258
+ messages: [
259
+ createMessage('user', [createTextPart('Hello')]),
260
+ createMessage('assistant', []), // Last message, assistant with empty content
261
+ ],
262
+ };
263
+ const result = sanitizeMessages(input);
264
+ expect(result.sanitizedCount).toBe(0);
265
+ expect(input.messages[1].parts.length).toBe(0);
266
+ });
267
+ it('should use custom placeholder text from config', () => {
268
+ const input = {
269
+ messages: [createMessage('user', [])],
270
+ };
271
+ const result = sanitizeMessages(input, { placeholderText: '[custom]' });
272
+ expect(input.messages[0].parts[0].text).toBe('[custom]');
273
+ });
274
+ it('should return messages array in output', () => {
275
+ const input = {
276
+ messages: [createMessage('user', [createTextPart('Test')])],
277
+ };
278
+ const result = sanitizeMessages(input);
279
+ expect(result.messages).toBe(input.messages);
280
+ });
281
+ it('should handle empty messages array', () => {
282
+ const input = {
283
+ messages: [],
284
+ };
285
+ const result = sanitizeMessages(input);
286
+ expect(result.sanitizedCount).toBe(0);
287
+ expect(result.modified).toBe(false);
288
+ });
289
+ it('should sanitize non-final assistant message in the middle', () => {
290
+ const input = {
291
+ messages: [
292
+ createMessage('user', [createTextPart('Hello')]),
293
+ createMessage('assistant', []), // Not last, should be sanitized
294
+ createMessage('user', [createTextPart('Follow up')]),
295
+ ],
296
+ };
297
+ const result = sanitizeMessages(input);
298
+ expect(result.sanitizedCount).toBe(1);
299
+ expect(input.messages[1].parts[0].text).toBe(PLACEHOLDER_TEXT);
300
+ });
301
+ it('should handle single message array', () => {
302
+ const input = {
303
+ messages: [createMessage('user', [])],
304
+ };
305
+ const result = sanitizeMessages(input);
306
+ // Single user message is not the "last assistant", so should be sanitized
307
+ expect(result.sanitizedCount).toBe(1);
308
+ });
309
+ it('should preserve sessionId in input', () => {
310
+ const input = {
311
+ messages: [createMessage('user', [createTextPart('Test')])],
312
+ sessionId: 'test-session-123',
313
+ };
314
+ const result = sanitizeMessages(input);
315
+ expect(result.messages).toBe(input.messages);
316
+ });
317
+ });
318
+ describe('createEmptyMessageSanitizerHook', () => {
319
+ it('should create hook with sanitize method', () => {
320
+ const hook = createEmptyMessageSanitizerHook();
321
+ expect(typeof hook.sanitize).toBe('function');
322
+ });
323
+ it('should create hook with getName method', () => {
324
+ const hook = createEmptyMessageSanitizerHook();
325
+ expect(typeof hook.getName).toBe('function');
326
+ expect(hook.getName()).toBe(HOOK_NAME);
327
+ });
328
+ it('should sanitize messages via hook sanitize method', () => {
329
+ const hook = createEmptyMessageSanitizerHook();
330
+ const input = {
331
+ messages: [createMessage('user', [])],
332
+ };
333
+ const result = hook.sanitize(input);
334
+ expect(result.sanitizedCount).toBe(1);
335
+ expect(result.modified).toBe(true);
336
+ });
337
+ it('should use custom placeholder from config', () => {
338
+ const hook = createEmptyMessageSanitizerHook({ placeholderText: '[hook custom]' });
339
+ const input = {
340
+ messages: [createMessage('user', [])],
341
+ };
342
+ hook.sanitize(input);
343
+ expect(input.messages[0].parts[0].text).toBe('[hook custom]');
344
+ });
345
+ it('should use default placeholder when no config', () => {
346
+ const hook = createEmptyMessageSanitizerHook();
347
+ const input = {
348
+ messages: [createMessage('user', [])],
349
+ };
350
+ hook.sanitize(input);
351
+ expect(input.messages[0].parts[0].text).toBe(PLACEHOLDER_TEXT);
352
+ });
353
+ });
354
+ describe('constants', () => {
355
+ it('should export PLACEHOLDER_TEXT', () => {
356
+ expect(PLACEHOLDER_TEXT).toBe('[user interrupted]');
357
+ });
358
+ it('should export HOOK_NAME', () => {
359
+ expect(HOOK_NAME).toBe('empty-message-sanitizer');
360
+ });
361
+ it('should export TOOL_PART_TYPES with correct values', () => {
362
+ expect(TOOL_PART_TYPES.size).toBe(3);
363
+ expect(TOOL_PART_TYPES.has('tool')).toBe(true);
364
+ expect(TOOL_PART_TYPES.has('tool_use')).toBe(true);
365
+ expect(TOOL_PART_TYPES.has('tool_result')).toBe(true);
366
+ });
367
+ });
368
+ describe('edge cases', () => {
369
+ it('should handle message with mixed valid and invalid parts', () => {
370
+ const message = createMessage('user', [
371
+ createTextPart(''),
372
+ createToolPart('tool_use'),
373
+ createTextPart(' '),
374
+ createTextPart('Valid'),
375
+ ]);
376
+ const result = sanitizeMessage(message, false);
377
+ // Empty text parts should be sanitized
378
+ expect(result).toBe(true);
379
+ });
380
+ it('should handle very long placeholder text', () => {
381
+ const longPlaceholder = 'x'.repeat(1000);
382
+ const message = createMessage('user', []);
383
+ sanitizeMessage(message, false, longPlaceholder);
384
+ expect(message.parts[0].text).toBe(longPlaceholder);
385
+ });
386
+ it('should handle special characters in text', () => {
387
+ const message = createMessage('user', [createTextPart('!@#$%^&*()')]);
388
+ const result = sanitizeMessage(message, false);
389
+ expect(result).toBe(false);
390
+ expect(message.parts[0].text).toBe('!@#$%^&*()');
391
+ });
392
+ it('should handle unicode text', () => {
393
+ const message = createMessage('user', [createTextPart('한글 テスト 中文')]);
394
+ const result = sanitizeMessage(message, false);
395
+ expect(result).toBe(false);
396
+ expect(message.parts[0].text).toBe('한글 テスト 中文');
397
+ });
398
+ it('should handle emoji text', () => {
399
+ const message = createMessage('user', [createTextPart('Hello 👋 World 🌍')]);
400
+ const result = sanitizeMessage(message, false);
401
+ expect(result).toBe(false);
402
+ });
403
+ it('should preserve message info when sanitizing', () => {
404
+ const message = createMessage('user', [], 'my-custom-id');
405
+ sanitizeMessage(message, false);
406
+ expect(message.info.id).toBe('my-custom-id');
407
+ expect(message.info.role).toBe('user');
408
+ });
409
+ it('should set correct messageID on synthetic part', () => {
410
+ const message = createMessage('user', [], 'test-msg-id');
411
+ sanitizeMessage(message, false);
412
+ expect(message.parts[0].messageID).toBe('test-msg-id');
413
+ });
414
+ });
415
+ });
416
+ //# sourceMappingURL=index.test.js.map