@stanco323/oh-my-claude-code 4.10.3 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (387) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +65 -18
  4. package/bridge/cli.cjs +3030 -1784
  5. package/bridge/mcp-server.cjs +128 -98
  6. package/bridge/runtime-cli.cjs +174 -136
  7. package/bridge/team-bridge.cjs +102 -96
  8. package/bridge/team-mcp.cjs +40 -29
  9. package/bridge/team.js +102 -88
  10. package/dist/__tests__/auto-update.test.js +8 -9
  11. package/dist/__tests__/auto-update.test.js.map +1 -1
  12. package/dist/__tests__/cli-config-stop-callback.test.js +26 -0
  13. package/dist/__tests__/cli-config-stop-callback.test.js.map +1 -1
  14. package/dist/__tests__/config-dir.test.d.ts +2 -0
  15. package/dist/__tests__/config-dir.test.d.ts.map +1 -0
  16. package/dist/__tests__/config-dir.test.js +184 -0
  17. package/dist/__tests__/config-dir.test.js.map +1 -0
  18. package/dist/__tests__/delegation-enforcement-levels.test.js +45 -1
  19. package/dist/__tests__/delegation-enforcement-levels.test.js.map +1 -1
  20. package/dist/__tests__/doctor-conflicts.test.js +9 -7
  21. package/dist/__tests__/doctor-conflicts.test.js.map +1 -1
  22. package/dist/__tests__/hooks-command-escaping.test.js +17 -10
  23. package/dist/__tests__/hooks-command-escaping.test.js.map +1 -1
  24. package/dist/__tests__/hud/cli-diagnostic.test.js +1 -1
  25. package/dist/__tests__/hud/cli-diagnostic.test.js.map +1 -1
  26. package/dist/__tests__/hud/usage-api-lock.test.js +5 -5
  27. package/dist/__tests__/hud/usage-api-lock.test.js.map +1 -1
  28. package/dist/__tests__/hud/usage-api-stale.test.js +2 -2
  29. package/dist/__tests__/hud/usage-api-stale.test.js.map +1 -1
  30. package/dist/__tests__/hud/usage-api.test.js +98 -0
  31. package/dist/__tests__/hud/usage-api.test.js.map +1 -1
  32. package/dist/__tests__/hud-api-key-source.test.js +2 -2
  33. package/dist/__tests__/hud-api-key-source.test.js.map +1 -1
  34. package/dist/__tests__/hud-marketplace-resolution.test.js +3 -1
  35. package/dist/__tests__/hud-marketplace-resolution.test.js.map +1 -1
  36. package/dist/__tests__/hud-windows.test.js +7 -6
  37. package/dist/__tests__/hud-windows.test.js.map +1 -1
  38. package/dist/__tests__/installer-omc-reference.test.js +122 -6
  39. package/dist/__tests__/installer-omc-reference.test.js.map +1 -1
  40. package/dist/__tests__/installer.test.js +33 -9
  41. package/dist/__tests__/installer.test.js.map +1 -1
  42. package/dist/__tests__/omc-tools-server.test.js +5 -5
  43. package/dist/__tests__/pre-tool-enforcer.test.js +34 -14
  44. package/dist/__tests__/pre-tool-enforcer.test.js.map +1 -1
  45. package/dist/__tests__/preemptive-compaction-hook.test.d.ts +2 -0
  46. package/dist/__tests__/preemptive-compaction-hook.test.d.ts.map +1 -0
  47. package/dist/__tests__/preemptive-compaction-hook.test.js +163 -0
  48. package/dist/__tests__/preemptive-compaction-hook.test.js.map +1 -0
  49. package/dist/__tests__/purge-stale-cache.test.js +1 -1
  50. package/dist/__tests__/purge-stale-cache.test.js.map +1 -1
  51. package/dist/__tests__/release-generation.test.d.ts +2 -0
  52. package/dist/__tests__/release-generation.test.d.ts.map +1 -0
  53. package/dist/__tests__/release-generation.test.js +79 -0
  54. package/dist/__tests__/release-generation.test.js.map +1 -0
  55. package/dist/__tests__/runtime-guidance-plan-ralph.test.d.ts +2 -0
  56. package/dist/__tests__/runtime-guidance-plan-ralph.test.d.ts.map +1 -0
  57. package/dist/__tests__/runtime-guidance-plan-ralph.test.js +87 -0
  58. package/dist/__tests__/runtime-guidance-plan-ralph.test.js.map +1 -0
  59. package/dist/__tests__/session-history-search.test.js +31 -3
  60. package/dist/__tests__/session-history-search.test.js.map +1 -1
  61. package/dist/__tests__/setup-claude-md-script.test.js +9 -1
  62. package/dist/__tests__/setup-claude-md-script.test.js.map +1 -1
  63. package/dist/__tests__/setup-no-plugin-flag.test.d.ts +2 -0
  64. package/dist/__tests__/setup-no-plugin-flag.test.d.ts.map +1 -0
  65. package/dist/__tests__/setup-no-plugin-flag.test.js +15 -0
  66. package/dist/__tests__/setup-no-plugin-flag.test.js.map +1 -0
  67. package/dist/__tests__/shared-memory.test.js +40 -2
  68. package/dist/__tests__/shared-memory.test.js.map +1 -1
  69. package/dist/__tests__/skills.test.js +45 -1
  70. package/dist/__tests__/skills.test.js.map +1 -1
  71. package/dist/cli/__tests__/launch.test.js +48 -14
  72. package/dist/cli/__tests__/launch.test.js.map +1 -1
  73. package/dist/cli/commands/__tests__/team-role-shorthand.test.d.ts +2 -0
  74. package/dist/cli/commands/__tests__/team-role-shorthand.test.d.ts.map +1 -0
  75. package/dist/cli/commands/__tests__/team-role-shorthand.test.js +63 -0
  76. package/dist/cli/commands/__tests__/team-role-shorthand.test.js.map +1 -0
  77. package/dist/cli/commands/__tests__/team.test.js +21 -0
  78. package/dist/cli/commands/__tests__/team.test.js.map +1 -1
  79. package/dist/cli/commands/adapt.d.ts.map +1 -1
  80. package/dist/cli/commands/adapt.js.map +1 -1
  81. package/dist/cli/commands/doctor-conflicts.js +1 -1
  82. package/dist/cli/commands/doctor-conflicts.js.map +1 -1
  83. package/dist/cli/commands/team.d.ts.map +1 -1
  84. package/dist/cli/commands/team.js +30 -17
  85. package/dist/cli/commands/team.js.map +1 -1
  86. package/dist/cli/index.js +16 -7
  87. package/dist/cli/index.js.map +1 -1
  88. package/dist/cli/launch.d.ts.map +1 -1
  89. package/dist/cli/launch.js +22 -14
  90. package/dist/cli/launch.js.map +1 -1
  91. package/dist/commands/index.js +1 -1
  92. package/dist/commands/index.js.map +1 -1
  93. package/dist/constants/names.d.ts +1 -0
  94. package/dist/constants/names.d.ts.map +1 -1
  95. package/dist/constants/names.js +1 -0
  96. package/dist/constants/names.js.map +1 -1
  97. package/dist/features/auto-update.d.ts.map +1 -1
  98. package/dist/features/auto-update.js +4 -4
  99. package/dist/features/auto-update.js.map +1 -1
  100. package/dist/features/background-agent/manager.js +1 -1
  101. package/dist/features/background-agent/manager.js.map +1 -1
  102. package/dist/features/builtin-skills/runtime-guidance.d.ts.map +1 -1
  103. package/dist/features/builtin-skills/runtime-guidance.js +35 -3
  104. package/dist/features/builtin-skills/runtime-guidance.js.map +1 -1
  105. package/dist/features/builtin-skills/skills.d.ts.map +1 -1
  106. package/dist/features/builtin-skills/skills.js +53 -1
  107. package/dist/features/builtin-skills/skills.js.map +1 -1
  108. package/dist/features/session-history-search/index.d.ts.map +1 -1
  109. package/dist/features/session-history-search/index.js +1 -4
  110. package/dist/features/session-history-search/index.js.map +1 -1
  111. package/dist/hooks/__tests__/bridge-routing.test.js +4 -0
  112. package/dist/hooks/__tests__/bridge-routing.test.js.map +1 -1
  113. package/dist/hooks/__tests__/bridge.test.js +42 -1
  114. package/dist/hooks/__tests__/bridge.test.js.map +1 -1
  115. package/dist/hooks/auto-slash-command/executor.js +2 -2
  116. package/dist/hooks/auto-slash-command/executor.js.map +1 -1
  117. package/dist/hooks/autopilot/enforcement.d.ts.map +1 -1
  118. package/dist/hooks/autopilot/enforcement.js +20 -4
  119. package/dist/hooks/autopilot/enforcement.js.map +1 -1
  120. package/dist/hooks/bridge.d.ts +15 -0
  121. package/dist/hooks/bridge.d.ts.map +1 -1
  122. package/dist/hooks/bridge.js +38 -1
  123. package/dist/hooks/bridge.js.map +1 -1
  124. package/dist/hooks/factcheck/__tests__/factcheck.test.js +6 -5
  125. package/dist/hooks/factcheck/__tests__/factcheck.test.js.map +1 -1
  126. package/dist/hooks/factcheck/config.d.ts +2 -2
  127. package/dist/hooks/factcheck/config.d.ts.map +1 -1
  128. package/dist/hooks/factcheck/config.js +6 -4
  129. package/dist/hooks/factcheck/config.js.map +1 -1
  130. package/dist/hooks/index.d.ts +1 -1
  131. package/dist/hooks/index.d.ts.map +1 -1
  132. package/dist/hooks/index.js +1 -1
  133. package/dist/hooks/index.js.map +1 -1
  134. package/dist/hooks/learner/auto-invoke.js +1 -1
  135. package/dist/hooks/learner/auto-invoke.js.map +1 -1
  136. package/dist/hooks/learner/config.js +1 -1
  137. package/dist/hooks/learner/config.js.map +1 -1
  138. package/dist/hooks/learner/constants.js +1 -1
  139. package/dist/hooks/learner/constants.js.map +1 -1
  140. package/dist/hooks/omc-orchestrator/constants.d.ts +1 -1
  141. package/dist/hooks/omc-orchestrator/constants.d.ts.map +1 -1
  142. package/dist/hooks/omc-orchestrator/constants.js +2 -2
  143. package/dist/hooks/omc-orchestrator/constants.js.map +1 -1
  144. package/dist/hooks/omc-orchestrator/index.d.ts.map +1 -1
  145. package/dist/hooks/omc-orchestrator/index.js +10 -7
  146. package/dist/hooks/omc-orchestrator/index.js.map +1 -1
  147. package/dist/hooks/permission-handler/index.js +1 -1
  148. package/dist/hooks/permission-handler/index.js.map +1 -1
  149. package/dist/hooks/persistent-mode/index.d.ts.map +1 -1
  150. package/dist/hooks/persistent-mode/index.js +21 -4
  151. package/dist/hooks/persistent-mode/index.js.map +1 -1
  152. package/dist/hooks/persistent-mode/stop-hook-blocking.test.js +91 -0
  153. package/dist/hooks/persistent-mode/stop-hook-blocking.test.js.map +1 -1
  154. package/dist/hooks/rules-injector/constants.d.ts +0 -2
  155. package/dist/hooks/rules-injector/constants.d.ts.map +1 -1
  156. package/dist/hooks/rules-injector/constants.js +0 -2
  157. package/dist/hooks/rules-injector/constants.js.map +1 -1
  158. package/dist/hooks/rules-injector/finder.d.ts +3 -3
  159. package/dist/hooks/rules-injector/finder.d.ts.map +1 -1
  160. package/dist/hooks/rules-injector/finder.js +7 -6
  161. package/dist/hooks/rules-injector/finder.js.map +1 -1
  162. package/dist/hooks/rules-injector/index.d.ts +1 -1
  163. package/dist/hooks/rules-injector/index.d.ts.map +1 -1
  164. package/dist/hooks/rules-injector/index.js +3 -6
  165. package/dist/hooks/rules-injector/index.js.map +1 -1
  166. package/dist/hooks/setup/__tests__/stdin-symlink.test.d.ts +2 -0
  167. package/dist/hooks/setup/__tests__/stdin-symlink.test.d.ts.map +1 -0
  168. package/dist/hooks/setup/__tests__/stdin-symlink.test.js +184 -0
  169. package/dist/hooks/setup/__tests__/stdin-symlink.test.js.map +1 -0
  170. package/dist/hooks/setup/index.d.ts +13 -0
  171. package/dist/hooks/setup/index.d.ts.map +1 -1
  172. package/dist/hooks/setup/index.js +100 -2
  173. package/dist/hooks/setup/index.js.map +1 -1
  174. package/dist/hooks/skill-bridge.cjs +19 -13
  175. package/dist/hooks/subagent-tracker/index.d.ts.map +1 -1
  176. package/dist/hooks/subagent-tracker/index.js +7 -3
  177. package/dist/hooks/subagent-tracker/index.js.map +1 -1
  178. package/dist/hooks/todo-continuation/index.js +1 -1
  179. package/dist/hooks/todo-continuation/index.js.map +1 -1
  180. package/dist/hooks/wiki/__tests__/ingest.test.d.ts +5 -0
  181. package/dist/hooks/wiki/__tests__/ingest.test.d.ts.map +1 -0
  182. package/dist/hooks/wiki/__tests__/ingest.test.js +180 -0
  183. package/dist/hooks/wiki/__tests__/ingest.test.js.map +1 -0
  184. package/dist/hooks/wiki/__tests__/lint.test.d.ts +5 -0
  185. package/dist/hooks/wiki/__tests__/lint.test.d.ts.map +1 -0
  186. package/dist/hooks/wiki/__tests__/lint.test.js +162 -0
  187. package/dist/hooks/wiki/__tests__/lint.test.js.map +1 -0
  188. package/dist/hooks/wiki/__tests__/query.test.d.ts +5 -0
  189. package/dist/hooks/wiki/__tests__/query.test.d.ts.map +1 -0
  190. package/dist/hooks/wiki/__tests__/query.test.js +119 -0
  191. package/dist/hooks/wiki/__tests__/query.test.js.map +1 -0
  192. package/dist/hooks/wiki/__tests__/session-hooks.test.d.ts +5 -0
  193. package/dist/hooks/wiki/__tests__/session-hooks.test.d.ts.map +1 -0
  194. package/dist/hooks/wiki/__tests__/session-hooks.test.js +40 -0
  195. package/dist/hooks/wiki/__tests__/session-hooks.test.js.map +1 -0
  196. package/dist/hooks/wiki/__tests__/storage.test.d.ts +5 -0
  197. package/dist/hooks/wiki/__tests__/storage.test.d.ts.map +1 -0
  198. package/dist/hooks/wiki/__tests__/storage.test.js +277 -0
  199. package/dist/hooks/wiki/__tests__/storage.test.js.map +1 -0
  200. package/dist/hooks/wiki/index.d.ts +13 -0
  201. package/dist/hooks/wiki/index.d.ts.map +1 -0
  202. package/dist/hooks/wiki/index.js +16 -0
  203. package/dist/hooks/wiki/index.js.map +1 -0
  204. package/dist/hooks/wiki/ingest.d.ts +20 -0
  205. package/dist/hooks/wiki/ingest.d.ts.map +1 -0
  206. package/dist/hooks/wiki/ingest.js +115 -0
  207. package/dist/hooks/wiki/ingest.js.map +1 -0
  208. package/dist/hooks/wiki/lint.d.ts +25 -0
  209. package/dist/hooks/wiki/lint.d.ts.map +1 -0
  210. package/dist/hooks/wiki/lint.js +166 -0
  211. package/dist/hooks/wiki/lint.js.map +1 -0
  212. package/dist/hooks/wiki/query.d.ts +27 -0
  213. package/dist/hooks/wiki/query.d.ts.map +1 -0
  214. package/dist/hooks/wiki/query.js +97 -0
  215. package/dist/hooks/wiki/query.js.map +1 -0
  216. package/dist/hooks/wiki/session-hooks.d.ts +42 -0
  217. package/dist/hooks/wiki/session-hooks.d.ts.map +1 -0
  218. package/dist/hooks/wiki/session-hooks.js +228 -0
  219. package/dist/hooks/wiki/session-hooks.js.map +1 -0
  220. package/dist/hooks/wiki/storage.d.ts +73 -0
  221. package/dist/hooks/wiki/storage.d.ts.map +1 -0
  222. package/dist/hooks/wiki/storage.js +343 -0
  223. package/dist/hooks/wiki/storage.js.map +1 -0
  224. package/dist/hooks/wiki/types.d.ts +136 -0
  225. package/dist/hooks/wiki/types.d.ts.map +1 -0
  226. package/dist/hooks/wiki/types.js +19 -0
  227. package/dist/hooks/wiki/types.js.map +1 -0
  228. package/dist/hud/custom-rate-provider.js +1 -1
  229. package/dist/hud/custom-rate-provider.js.map +1 -1
  230. package/dist/hud/elements/api-key-source.js +1 -1
  231. package/dist/hud/elements/api-key-source.js.map +1 -1
  232. package/dist/hud/index.js +1 -1
  233. package/dist/hud/index.js.map +1 -1
  234. package/dist/hud/state.js +1 -1
  235. package/dist/hud/state.js.map +1 -1
  236. package/dist/hud/usage-api.d.ts.map +1 -1
  237. package/dist/hud/usage-api.js +6 -2
  238. package/dist/hud/usage-api.js.map +1 -1
  239. package/dist/installer/__tests__/standalone-hook-reconcile.test.js +3 -0
  240. package/dist/installer/__tests__/standalone-hook-reconcile.test.js.map +1 -1
  241. package/dist/installer/hooks.d.ts +0 -2
  242. package/dist/installer/hooks.d.ts.map +1 -1
  243. package/dist/installer/hooks.js +1 -5
  244. package/dist/installer/hooks.js.map +1 -1
  245. package/dist/installer/index.d.ts +3 -0
  246. package/dist/installer/index.d.ts.map +1 -1
  247. package/dist/installer/index.js +176 -32
  248. package/dist/installer/index.js.map +1 -1
  249. package/dist/installer/mcp-registry.js +2 -2
  250. package/dist/installer/mcp-registry.js.map +1 -1
  251. package/dist/lib/release-generation.d.ts +20 -0
  252. package/dist/lib/release-generation.d.ts.map +1 -0
  253. package/dist/lib/release-generation.js +198 -0
  254. package/dist/lib/release-generation.js.map +1 -0
  255. package/dist/lib/shared-memory.d.ts +2 -1
  256. package/dist/lib/shared-memory.d.ts.map +1 -1
  257. package/dist/lib/shared-memory.js +4 -2
  258. package/dist/lib/shared-memory.js.map +1 -1
  259. package/dist/lib/worktree-paths.d.ts.map +1 -1
  260. package/dist/lib/worktree-paths.js +36 -10
  261. package/dist/lib/worktree-paths.js.map +1 -1
  262. package/dist/mcp/omc-tools-server.d.ts +2 -0
  263. package/dist/mcp/omc-tools-server.d.ts.map +1 -1
  264. package/dist/mcp/omc-tools-server.js +9 -2
  265. package/dist/mcp/omc-tools-server.js.map +1 -1
  266. package/dist/notifications/__tests__/config-merge.test.js +1 -1
  267. package/dist/notifications/__tests__/config-merge.test.js.map +1 -1
  268. package/dist/notifications/__tests__/profiles.test.js +1 -1
  269. package/dist/notifications/__tests__/profiles.test.js.map +1 -1
  270. package/dist/notifications/config.js +1 -1
  271. package/dist/notifications/config.js.map +1 -1
  272. package/dist/notifications/hook-config.js +1 -1
  273. package/dist/notifications/hook-config.js.map +1 -1
  274. package/dist/openclaw/__tests__/config.test.js +1 -1
  275. package/dist/openclaw/__tests__/config.test.js.map +1 -1
  276. package/dist/openclaw/config.js +1 -1
  277. package/dist/openclaw/config.js.map +1 -1
  278. package/dist/skills/__tests__/mingw-escape.test.js +1 -1
  279. package/dist/skills/__tests__/mingw-escape.test.js.map +1 -1
  280. package/dist/team/__tests__/api-interop.dispatch.test.js +2 -1
  281. package/dist/team/__tests__/api-interop.dispatch.test.js.map +1 -1
  282. package/dist/team/__tests__/bridge-integration.test.js +5 -3
  283. package/dist/team/__tests__/bridge-integration.test.js.map +1 -1
  284. package/dist/team/__tests__/edge-cases.test.js +6 -7
  285. package/dist/team/__tests__/edge-cases.test.js.map +1 -1
  286. package/dist/team/__tests__/inbox-outbox.test.js +2 -2
  287. package/dist/team/__tests__/inbox-outbox.test.js.map +1 -1
  288. package/dist/team/__tests__/message-router.test.js +6 -5
  289. package/dist/team/__tests__/message-router.test.js.map +1 -1
  290. package/dist/team/__tests__/outbox-reader.test.js +2 -2
  291. package/dist/team/__tests__/outbox-reader.test.js.map +1 -1
  292. package/dist/team/__tests__/runtime-v2.dispatch.test.js +4 -4
  293. package/dist/team/__tests__/runtime-v2.dispatch.test.js.map +1 -1
  294. package/dist/team/__tests__/shell-affinity.test.js +16 -0
  295. package/dist/team/__tests__/shell-affinity.test.js.map +1 -1
  296. package/dist/team/__tests__/team-registration.test.js +3 -2
  297. package/dist/team/__tests__/team-registration.test.js.map +1 -1
  298. package/dist/team/__tests__/team-status.test.js +3 -3
  299. package/dist/team/__tests__/team-status.test.js.map +1 -1
  300. package/dist/team/__tests__/tmux-session.test.js +6 -1
  301. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  302. package/dist/team/bridge-entry.js +1 -1
  303. package/dist/team/bridge-entry.js.map +1 -1
  304. package/dist/team/inbox-outbox.js +1 -1
  305. package/dist/team/inbox-outbox.js.map +1 -1
  306. package/dist/team/message-router.js +1 -1
  307. package/dist/team/message-router.js.map +1 -1
  308. package/dist/team/outbox-reader.js +1 -1
  309. package/dist/team/outbox-reader.js.map +1 -1
  310. package/dist/team/runtime-v2.d.ts.map +1 -1
  311. package/dist/team/runtime-v2.js +6 -17
  312. package/dist/team/runtime-v2.js.map +1 -1
  313. package/dist/team/task-file-ops.js +1 -1
  314. package/dist/team/task-file-ops.js.map +1 -1
  315. package/dist/team/team-registration.js +1 -1
  316. package/dist/team/team-registration.js.map +1 -1
  317. package/dist/team/team-status.js +1 -1
  318. package/dist/team/team-status.js.map +1 -1
  319. package/dist/team/tmux-session.d.ts +1 -1
  320. package/dist/team/tmux-session.d.ts.map +1 -1
  321. package/dist/team/tmux-session.js +39 -4
  322. package/dist/team/tmux-session.js.map +1 -1
  323. package/dist/team/unified-team.js +1 -1
  324. package/dist/team/unified-team.js.map +1 -1
  325. package/dist/tools/shared-memory-tools.d.ts.map +1 -1
  326. package/dist/tools/shared-memory-tools.js +2 -1
  327. package/dist/tools/shared-memory-tools.js.map +1 -1
  328. package/dist/tools/skills-tools.d.ts.map +1 -1
  329. package/dist/tools/skills-tools.js +3 -2
  330. package/dist/tools/skills-tools.js.map +1 -1
  331. package/dist/tools/wiki-tools.d.ts +74 -0
  332. package/dist/tools/wiki-tools.d.ts.map +1 -0
  333. package/dist/tools/wiki-tools.js +370 -0
  334. package/dist/tools/wiki-tools.js.map +1 -0
  335. package/dist/utils/config-dir.d.ts +21 -1
  336. package/dist/utils/config-dir.d.ts.map +1 -1
  337. package/dist/utils/config-dir.js +45 -4
  338. package/dist/utils/config-dir.js.map +1 -1
  339. package/dist/utils/paths.d.ts +0 -5
  340. package/dist/utils/paths.d.ts.map +1 -1
  341. package/dist/utils/paths.js +1 -8
  342. package/dist/utils/paths.js.map +1 -1
  343. package/docs/CLAUDE.md +1 -1
  344. package/hooks/hooks.json +15 -0
  345. package/package.json +1 -1
  346. package/scripts/cleanup-orphans.mjs +2 -2
  347. package/scripts/context-guard-stop.mjs +10 -4
  348. package/scripts/find-node.sh +13 -1
  349. package/scripts/keyword-detector.mjs +9 -2
  350. package/scripts/lib/config-dir.cjs +31 -0
  351. package/scripts/lib/config-dir.mjs +29 -0
  352. package/scripts/lib/config-dir.sh +18 -0
  353. package/scripts/lib/pre-tool-enforcer-preflight.mjs +63 -0
  354. package/scripts/persistent-mode.cjs +29 -6
  355. package/scripts/persistent-mode.mjs +24 -4
  356. package/scripts/plugin-setup.mjs +18 -7
  357. package/scripts/post-tool-verifier.mjs +200 -7
  358. package/scripts/pre-tool-enforcer.mjs +15 -56
  359. package/scripts/release.ts +158 -224
  360. package/scripts/session-start.mjs +6 -4
  361. package/scripts/session-summary.mjs +6 -1
  362. package/scripts/setup-claude-md.sh +4 -2
  363. package/scripts/setup-progress.sh +4 -1
  364. package/scripts/skill-injector.mjs +2 -1
  365. package/scripts/sync-version.sh +3 -3
  366. package/scripts/test-pr25.sh +9 -5
  367. package/scripts/uninstall.sh +5 -2
  368. package/scripts/wiki-pre-compact.mjs +17 -0
  369. package/scripts/wiki-session-end.mjs +17 -0
  370. package/scripts/wiki-session-start.mjs +17 -0
  371. package/skills/cancel/SKILL.md +4 -4
  372. package/skills/configure-notifications/SKILL.md +12 -12
  373. package/skills/hud/SKILL.md +4 -4
  374. package/skills/learner/SKILL.md +1 -1
  375. package/skills/omc-doctor/SKILL.md +25 -25
  376. package/skills/omc-setup/SKILL.md +1 -1
  377. package/skills/omc-setup/phases/02-configure.md +2 -2
  378. package/skills/omc-setup/phases/03-integrations.md +7 -7
  379. package/skills/omc-setup/phases/04-welcome.md +3 -3
  380. package/skills/ralph/SKILL.md +6 -2
  381. package/skills/skill/SKILL.md +9 -9
  382. package/skills/team/SKILL.md +1 -1
  383. package/skills/wiki/SKILL.md +67 -0
  384. package/templates/hooks/keyword-detector.mjs +4 -2
  385. package/templates/hooks/lib/config-dir.mjs +29 -0
  386. package/templates/hooks/persistent-mode.mjs +24 -4
  387. package/templates/hooks/session-start.mjs +7 -4
@@ -3,7 +3,8 @@
3
3
  * Release Automation Script
4
4
  *
5
5
  * Automates version bumping, changelog generation, and release notes creation.
6
- * Uses conventional commits to categorize changes automatically.
6
+ * Uses merged PR metadata when available so changelog content, PR counts,
7
+ * and contributors all reflect the same release dataset.
7
8
  *
8
9
  * Usage:
9
10
  * npm run release -- patch # Bump patch version
@@ -18,10 +19,25 @@ import { join, resolve } from 'path';
18
19
  import { execSync } from 'child_process';
19
20
  import { fileURLToPath } from 'url';
20
21
  import { dirname } from 'path';
22
+ import {
23
+ type ReleasePullRequest,
24
+ type ReleaseNoteEntry,
25
+ extractPullRequestNumbers,
26
+ isReleasePullRequest,
27
+ deriveContributorLogins,
28
+ buildReleaseNoteEntriesFromPullRequests,
29
+ categorizeReleaseNoteEntries,
30
+ generateChangelog,
31
+ generateReleaseBody,
32
+ } from '../src/lib/release-generation.ts';
21
33
 
22
34
  const __filename = fileURLToPath(import.meta.url);
23
35
  const __dirname = dirname(__filename);
24
36
  const ROOT = resolve(__dirname, '..');
37
+ const DEFAULT_REPO_SLUG = 'Yeachan-Heo/oh-my-claudecode';
38
+ const REPO_SLUG = process.env.GITHUB_REPOSITORY || DEFAULT_REPO_SLUG;
39
+ const REPO_URL = `https://github.com/${REPO_SLUG}`;
40
+ const GITHUB_API_URL = process.env.GITHUB_API_URL || 'https://api.github.com';
25
41
 
26
42
  // ── Colors ──────────────────────────────────────────────────────────────────
27
43
 
@@ -51,9 +67,16 @@ interface ParsedCommit {
51
67
  raw: string;
52
68
  }
53
69
 
54
- interface ChangelogSection {
70
+ interface GitHubPullRequestResponse {
55
71
  title: string;
56
- entries: string[];
72
+ user?: { login?: string | null } | null;
73
+ head?: { ref?: string | null } | null;
74
+ }
75
+
76
+ interface GitHubCompareResponse {
77
+ commits?: Array<{
78
+ author?: { login?: string | null } | null;
79
+ }>;
57
80
  }
58
81
 
59
82
  // ── Version helpers ─────────────────────────────────────────────────────────
@@ -85,243 +108,123 @@ function bumpVersion(current: string, bump: string): string {
85
108
 
86
109
  // ── Git helpers ─────────────────────────────────────────────────────────────
87
110
 
88
- function getCommitsSinceTag(tag: string): string[] {
111
+ function getGitLog(tag: string, format: string, flags: string[] = []): string[] {
89
112
  const range = tag ? `${tag}..HEAD` : 'HEAD';
90
- const raw = execSync(
91
- `git log ${range} --format="%H|%s" --no-merges`,
92
- { cwd: ROOT, encoding: 'utf-8' }
93
- ).trim();
113
+ const cmd = ['git', 'log', range, `--format=${JSON.stringify(format)}`, ...flags].join(' ');
114
+ const raw = execSync(cmd, { cwd: ROOT, encoding: 'utf-8' }).trim();
94
115
  return raw ? raw.split('\n') : [];
95
116
  }
96
117
 
97
- function getMergeCommitsSinceTag(tag: string): string[] {
98
- const range = tag ? `${tag}..HEAD` : 'HEAD';
99
- const raw = execSync(
100
- `git log ${range} --format="%s" --merges`,
101
- { cwd: ROOT, encoding: 'utf-8' }
102
- ).trim();
103
- return raw ? raw.split('\n') : [];
118
+ function getCommitLinesSinceTag(tag: string): string[] {
119
+ return getGitLog(tag, '%H|%s');
104
120
  }
105
121
 
106
- function getContributors(tag: string): string[] {
107
- const merges = getMergeCommitsSinceTag(tag);
108
- const contributors = new Set<string>();
109
-
110
- for (const msg of merges) {
111
- const match = msg.match(/from\s+([^/]+)\//);
112
- if (match && match[1]) {
113
- const user = match[1].trim();
114
- if (user && !user.startsWith('#')) {
115
- contributors.add(user);
116
- }
117
- }
118
- }
119
-
120
- return [...contributors].sort();
122
+ function getNonMergeCommitLinesSinceTag(tag: string): string[] {
123
+ return getGitLog(tag, '%H|%s', ['--no-merges']);
121
124
  }
122
125
 
123
- function getPRCount(tag: string): number {
124
- const merges = getMergeCommitsSinceTag(tag);
125
- return merges.filter(m => m.startsWith('Merge pull request')).length;
126
+ function getHeadSha(): string {
127
+ return execSync('git rev-parse HEAD', { cwd: ROOT, encoding: 'utf-8' }).trim();
126
128
  }
127
129
 
128
- // ── Commit parsing ──────────────────────────────────────────────────────────
129
-
130
- const CONVENTIONAL_RE = /^(?<type>[a-z]+)(?:\((?<scope>[^)]*)\))?:\s*(?<desc>.+)$/;
130
+ // ── Commit / PR parsing ─────────────────────────────────────────────────────
131
131
 
132
132
  function parseCommit(line: string): ParsedCommit | null {
133
133
  const [hash, ...rest] = line.split('|');
134
134
  const raw = rest.join('|');
135
135
 
136
136
  if (!raw) return null;
137
-
138
- // Skip merge commits, chore(release) version bumps
139
137
  if (raw.startsWith('Merge ')) return null;
140
138
  if (raw.match(/^chore\(release\)/i)) return null;
141
139
 
142
- const match = raw.match(CONVENTIONAL_RE);
143
- if (!match?.groups) return null;
140
+ const conventionalMatch = raw.match(/^(?<type>[a-z]+)(?:\((?<scope>[^)]*)\))?:\s*(?<desc>.+)$/);
141
+ if (!conventionalMatch?.groups) return null;
144
142
 
145
143
  const prMatch = raw.match(/\(#(\d+)\)/);
146
144
 
147
145
  return {
148
146
  hash: hash.trim(),
149
- type: match.groups.type,
150
- scope: match.groups.scope || '',
151
- description: match.groups.desc.replace(/\s*\(#\d+\)$/, '').trim(),
147
+ type: conventionalMatch.groups.type,
148
+ scope: conventionalMatch.groups.scope || '',
149
+ description: conventionalMatch.groups.desc.replace(/\s*\(#\d+\)$/, '').trim(),
152
150
  prNumber: prMatch ? prMatch[1] : null,
153
151
  raw,
154
152
  };
155
153
  }
156
154
 
157
- // ── Categorization ──────────────────────────────────────────────────────────
158
-
159
- function categorize(commits: ParsedCommit[]): Map<string, ParsedCommit[]> {
160
- const categories = new Map<string, ParsedCommit[]>();
161
-
162
- for (const commit of commits) {
163
- let category: string;
164
-
165
- if (commit.type === 'feat') {
166
- category = 'features';
167
- } else if (commit.type === 'fix' && /^(security|deps)$/.test(commit.scope)) {
168
- category = 'security';
169
- } else if (commit.type === 'fix') {
170
- category = 'fixes';
171
- } else if (commit.type === 'refactor') {
172
- category = 'refactoring';
173
- } else if (commit.type === 'docs') {
174
- category = 'docs';
175
- } else if (commit.type === 'chore' && commit.scope === 'deps') {
176
- category = 'security';
177
- } else if (commit.type === 'perf') {
178
- category = 'features';
179
- } else {
180
- // skip test, chore, ci, build, style
181
- continue;
182
- }
183
-
184
- if (!categories.has(category)) categories.set(category, []);
185
- categories.get(category)!.push(commit);
186
- }
187
-
188
- return categories;
155
+ function toReleaseNoteEntryFromCommit(commit: ParsedCommit): ReleaseNoteEntry {
156
+ return {
157
+ type: commit.type,
158
+ scope: commit.scope,
159
+ description: commit.description,
160
+ prNumber: commit.prNumber,
161
+ };
189
162
  }
190
163
 
191
- // ── Changelog generation ────────────────────────────────────────────────────
192
164
 
193
- function formatEntry(commit: ParsedCommit): string {
194
- const scope = commit.scope ? `(${commit.scope})` : '';
195
- const pr = commit.prNumber ? ` (#${commit.prNumber})` : '';
196
- return `- **${commit.type}${scope}: ${commit.description}**${pr}`;
197
- }
198
-
199
- function generateTitle(categories: Map<string, ParsedCommit[]>): string {
200
- const parts: string[] = [];
201
-
202
- if (categories.has('features')) {
203
- // Pick the most notable feature keywords
204
- const feats = categories.get('features')!;
205
- const keywords = feats
206
- .slice(0, 3)
207
- .map(f => {
208
- // Extract key noun from description
209
- const words = f.description.split(/\s+/);
210
- return words.slice(0, 3).join(' ');
211
- });
212
- parts.push(...keywords);
213
- }
214
- if (categories.has('security')) parts.push('Security Hardening');
215
- if (categories.has('fixes') && !parts.length) parts.push('Bug Fixes');
165
+ // ── GitHub metadata helpers ─────────────────────────────────────────────────
216
166
 
217
- if (parts.length === 0) return 'Maintenance Release';
218
- if (parts.length <= 3) return parts.join(', ');
219
- return parts.slice(0, 3).join(', ');
220
- }
167
+ function getGitHubApiHeaders(): Record<string, string> {
168
+ const headers: Record<string, string> = {
169
+ Accept: 'application/vnd.github+json',
170
+ 'User-Agent': 'oh-my-claudecode-release-script',
171
+ };
221
172
 
222
- function generateSummary(categories: Map<string, ParsedCommit[]>, prCount: number): string {
223
- const parts: string[] = [];
224
- if (categories.has('features')) parts.push(`**${categories.get('features')!.length} new features**`);
225
- if (categories.has('security')) parts.push(`**security hardening**`);
226
- if (categories.has('fixes')) parts.push(`**${categories.get('fixes')!.length} bug fixes**`);
173
+ const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
174
+ if (token) headers.Authorization = `Bearer ${token}`;
227
175
 
228
- if (parts.length === 0) return 'Maintenance release with internal improvements.';
229
- return `Release with ${parts.join(', ')} across ${prCount}+ merged PRs.`;
176
+ return headers;
230
177
  }
231
178
 
232
- function generateChangelog(
233
- version: string,
234
- categories: Map<string, ParsedCommit[]>,
235
- prCount: number,
236
- ): string {
237
- const title = generateTitle(categories);
238
- const summary = generateSummary(categories, prCount);
239
-
240
- const sections: ChangelogSection[] = [];
241
-
242
- // Highlights: top features + security
243
- const highlights: string[] = [];
244
- if (categories.has('features')) {
245
- for (const f of categories.get('features')!.slice(0, 5)) {
246
- highlights.push(formatEntry(f));
247
- }
248
- }
249
- if (categories.has('security')) {
250
- for (const s of categories.get('security')!.slice(0, 3)) {
251
- highlights.push(formatEntry(s));
252
- }
253
- }
254
- if (highlights.length) sections.push({ title: 'Highlights', entries: highlights });
255
-
256
- // New Features
257
- if (categories.has('features')) {
258
- sections.push({ title: 'New Features', entries: categories.get('features')!.map(formatEntry) });
259
- }
260
-
261
- // Security & Hardening
262
- if (categories.has('security')) {
263
- sections.push({ title: 'Security & Hardening', entries: categories.get('security')!.map(formatEntry) });
264
- }
265
-
266
- // Bug Fixes
267
- if (categories.has('fixes')) {
268
- sections.push({ title: 'Bug Fixes', entries: categories.get('fixes')!.map(formatEntry) });
269
- }
270
-
271
- // Refactoring
272
- if (categories.has('refactoring')) {
273
- sections.push({ title: 'Refactoring', entries: categories.get('refactoring')!.map(formatEntry) });
274
- }
275
-
276
- // Documentation
277
- if (categories.has('docs')) {
278
- sections.push({ title: 'Documentation', entries: categories.get('docs')!.map(formatEntry) });
179
+ async function fetchJson<T>(url: string): Promise<T | null> {
180
+ try {
181
+ const response = await fetch(url, { headers: getGitHubApiHeaders() });
182
+ if (!response.ok) return null;
183
+ return await response.json() as T;
184
+ } catch {
185
+ return null;
279
186
  }
187
+ }
280
188
 
281
- // Stats
282
- const featCount = categories.get('features')?.length ?? 0;
283
- const fixCount = categories.get('fixes')?.length ?? 0;
284
- const secCount = categories.get('security')?.length ?? 0;
285
- const statsLine = `- **${prCount}+ PRs merged** | **${featCount} new features** | **${fixCount} bug fixes** | **${secCount} security/hardening improvements**`;
189
+ function getRepoApiPath(): string {
190
+ return REPO_SLUG
191
+ .split('/')
192
+ .map(part => encodeURIComponent(part))
193
+ .join('/');
194
+ }
286
195
 
287
- // Assemble
288
- let md = `# oh-my-claudecode v${version}: ${title}\n\n`;
289
- md += `## Release Notes\n\n${summary}\n`;
196
+ async function fetchPullRequestMetadata(prNumbers: string[]): Promise<ReleasePullRequest[]> {
197
+ const repoPath = getRepoApiPath();
198
+ const records = await Promise.all(prNumbers.map(async number => {
199
+ const data = await fetchJson<GitHubPullRequestResponse>(
200
+ `${GITHUB_API_URL}/repos/${repoPath}/pulls/${encodeURIComponent(number)}`
201
+ );
290
202
 
291
- for (const section of sections) {
292
- md += `\n### ${section.title}\n\n`;
293
- md += section.entries.join('\n') + '\n';
294
- }
203
+ if (!data) return null;
295
204
 
296
- md += `\n### Stats\n\n${statsLine}\n`;
205
+ return {
206
+ number,
207
+ title: data.title,
208
+ author: data.user?.login ?? null,
209
+ headRefName: data.head?.ref ?? null,
210
+ } satisfies ReleasePullRequest;
211
+ }));
297
212
 
298
- return md;
213
+ return records.filter((record): record is ReleasePullRequest => record !== null);
299
214
  }
300
215
 
301
- function generateReleaseBody(
302
- version: string,
303
- changelog: string,
304
- contributors: string[],
305
- prevTag: string,
306
- ): string {
307
- let body = changelog;
308
-
309
- body += `\n### Install / Update\n\n`;
310
- body += '```bash\n';
311
- body += `npm install -g oh-my-claude-sisyphus@${version}\n`;
312
- body += '```\n\n';
313
- body += 'Or reinstall the plugin:\n```bash\nclaude /install-plugin oh-my-claudecode\n```\n';
314
-
315
- if (prevTag) {
316
- body += `\n**Full Changelog**: https://github.com/Yeachan-Heo/oh-my-claudecode/compare/${prevTag}...v${version}\n`;
317
- }
216
+ async function fetchCompareCommitAuthors(prevTag: string): Promise<string[]> {
217
+ if (!prevTag) return [];
318
218
 
319
- if (contributors.length > 0) {
320
- body += `\n## Contributors\n\nThank you to all contributors who made this release possible!\n\n`;
321
- body += contributors.map(u => `@${u}`).join(' ') + '\n';
322
- }
219
+ const repoPath = getRepoApiPath();
220
+ const headRef = process.env.GITHUB_SHA || getHeadSha();
221
+ const data = await fetchJson<GitHubCompareResponse>(
222
+ `${GITHUB_API_URL}/repos/${repoPath}/compare/${encodeURIComponent(prevTag)}...${encodeURIComponent(headRef)}`
223
+ );
323
224
 
324
- return body;
225
+ return (data?.commits ?? [])
226
+ .map(commit => commit.author?.login ?? null)
227
+ .filter((author): author is string => Boolean(author));
325
228
  }
326
229
 
327
230
  // ── Version file bumping ────────────────────────────────────────────────────
@@ -329,7 +232,6 @@ function generateReleaseBody(
329
232
  function bumpVersionFiles(newVersion: string, dryRun: boolean): string[] {
330
233
  const changes: string[] = [];
331
234
 
332
- // 1. package.json
333
235
  const pkgPath = join(ROOT, 'package.json');
334
236
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
335
237
  if (pkg.version !== newVersion) {
@@ -338,7 +240,6 @@ function bumpVersionFiles(newVersion: string, dryRun: boolean): string[] {
338
240
  changes.push(`package.json: ${pkg.version} → ${newVersion}`);
339
241
  }
340
242
 
341
- // 2. .claude-plugin/plugin.json
342
243
  const pluginPath = join(ROOT, '.claude-plugin/plugin.json');
343
244
  if (existsSync(pluginPath)) {
344
245
  const content = readFileSync(pluginPath, 'utf-8');
@@ -349,7 +250,6 @@ function bumpVersionFiles(newVersion: string, dryRun: boolean): string[] {
349
250
  }
350
251
  }
351
252
 
352
- // 3. .claude-plugin/marketplace.json (has 2 version fields)
353
253
  const marketPath = join(ROOT, '.claude-plugin/marketplace.json');
354
254
  if (existsSync(marketPath)) {
355
255
  const content = readFileSync(marketPath, 'utf-8');
@@ -360,7 +260,6 @@ function bumpVersionFiles(newVersion: string, dryRun: boolean): string[] {
360
260
  }
361
261
  }
362
262
 
363
- // 4. docs/CLAUDE.md version marker
364
263
  const claudeMdPath = join(ROOT, 'docs/CLAUDE.md');
365
264
  if (existsSync(claudeMdPath)) {
366
265
  const content = readFileSync(claudeMdPath, 'utf-8');
@@ -371,7 +270,6 @@ function bumpVersionFiles(newVersion: string, dryRun: boolean): string[] {
371
270
  }
372
271
  }
373
272
 
374
- // 5. package-lock.json (via npm)
375
273
  if (!dryRun) {
376
274
  try {
377
275
  execSync('npm install --package-lock-only --ignore-scripts 2>/dev/null', { cwd: ROOT });
@@ -386,9 +284,28 @@ function bumpVersionFiles(newVersion: string, dryRun: boolean): string[] {
386
284
  return changes;
387
285
  }
388
286
 
287
+ function buildFallbackPullRequests(prNumbers: string[], subjects: string[]): ReleasePullRequest[] {
288
+ return prNumbers.map(number => {
289
+ const subject = subjects.find(entry => entry.includes(`(#${number})`));
290
+ const mergeSubject = subjects.find(entry => entry.startsWith(`Merge pull request #${number} `));
291
+ const headRefMatch = mergeSubject?.match(/from\s+[^/]+\/(.+)$/);
292
+
293
+ return {
294
+ number,
295
+ title: subject ? subject.replace(/\s*\(#\d+\)$/, '').trim() : `PR #${number}`,
296
+ author: null,
297
+ headRefName: headRefMatch?.[1] ?? null,
298
+ } satisfies ReleasePullRequest;
299
+ });
300
+ }
301
+
302
+ function isMainModule(): boolean {
303
+ return process.argv[1] ? resolve(process.argv[1]) === __filename : false;
304
+ }
305
+
389
306
  // ── Main ────────────────────────────────────────────────────────────────────
390
307
 
391
- function main(): void {
308
+ async function main(): Promise<void> {
392
309
  const args = process.argv.slice(2);
393
310
  const dryRun = args.includes('--dry-run');
394
311
  const help = args.includes('--help') || args.includes('-h');
@@ -409,7 +326,7 @@ ${clr('Examples:', c.cyan)}
409
326
 
410
327
  ${clr('What it does:', c.cyan)}
411
328
  1. Bumps version in all 5 files (package.json, plugin.json, marketplace.json, docs/CLAUDE.md, lockfile)
412
- 2. Generates CHANGELOG.md from conventional commits
329
+ 2. Generates CHANGELOG.md from the merged PR set when metadata is available
413
330
  3. Generates .github/release-body.md with contributor @mentions
414
331
  4. Runs sync-metadata to update doc badges
415
332
 
@@ -435,31 +352,45 @@ ${clr('After running:', c.cyan)}
435
352
  console.log(` Previous tag: ${clr(prevTag || '(none)', c.dim)}`);
436
353
  if (dryRun) console.log(clr('\n DRY RUN — no files will be modified\n', c.yellow));
437
354
 
438
- // 1. Parse commits
439
- const rawCommits = getCommitsSinceTag(prevTag);
440
- const parsed = rawCommits.map(parseCommit).filter((c): c is ParsedCommit => c !== null);
441
- const categories = categorize(parsed);
442
- const prCount = getPRCount(prevTag);
443
- const contributors = getContributors(prevTag);
444
-
445
- console.log(clr('\nšŸ“Š Commit Analysis', c.cyan));
446
- console.log(` Total commits: ${rawCommits.length}`);
447
- console.log(` Parsed conventional: ${parsed.length}`);
448
- console.log(` PRs merged: ${prCount}`);
355
+ const allCommitLines = getCommitLinesSinceTag(prevTag);
356
+ const allSubjects = allCommitLines.map(line => line.split('|').slice(1).join('|'));
357
+ const fallbackCommits = getNonMergeCommitLinesSinceTag(prevTag)
358
+ .map(parseCommit)
359
+ .filter((commit): commit is ParsedCommit => commit !== null);
360
+
361
+ const extractedPrNumbers = extractPullRequestNumbers(allSubjects);
362
+ const fetchedPullRequests = await fetchPullRequestMetadata(extractedPrNumbers);
363
+ const pullRequests = fetchedPullRequests.length > 0
364
+ ? fetchedPullRequests
365
+ : buildFallbackPullRequests(extractedPrNumbers, allSubjects);
366
+ const userFacingPullRequests = pullRequests.filter(pr => !isReleasePullRequest(pr));
367
+ const compareCommitAuthors = fetchedPullRequests.length > 0 ? await fetchCompareCommitAuthors(prevTag) : [];
368
+ const contributors = deriveContributorLogins(userFacingPullRequests, compareCommitAuthors);
369
+
370
+ const usingPullRequests = fetchedPullRequests.length > 0;
371
+ const releaseEntries = usingPullRequests
372
+ ? buildReleaseNoteEntriesFromPullRequests(userFacingPullRequests)
373
+ : fallbackCommits.map(toReleaseNoteEntryFromCommit);
374
+ const categories = categorizeReleaseNoteEntries(releaseEntries);
375
+ const prCount = userFacingPullRequests.length > 0 ? userFacingPullRequests.length : extractedPrNumbers.length;
376
+
377
+ console.log(clr('\nšŸ“Š Release Analysis', c.cyan));
378
+ console.log(` Total commits: ${allCommitLines.length}`);
379
+ console.log(` Extracted PRs: ${extractedPrNumbers.length}`);
380
+ console.log(` User-facing PRs: ${prCount}`);
381
+ console.log(` Metadata source: ${usingPullRequests ? 'GitHub PR metadata' : 'git fallback'}`);
449
382
  console.log(` Contributors: ${contributors.join(', ') || '(none)'}`);
450
383
 
451
- for (const [cat, commits] of categories) {
452
- console.log(` ${cat}: ${commits.length}`);
384
+ for (const [cat, entries] of categories) {
385
+ console.log(` ${cat}: ${entries.length}`);
453
386
  }
454
387
 
455
- // 2. Bump version files
456
388
  console.log(clr('\nšŸ“¦ Version Bump', c.cyan));
457
389
  const versionChanges = bumpVersionFiles(newVersion, dryRun);
458
390
  for (const change of versionChanges) {
459
391
  console.log(` ${clr('āœ“', c.green)} ${change}`);
460
392
  }
461
393
 
462
- // 3. Generate CHANGELOG
463
394
  console.log(clr('\nšŸ“ Changelog', c.cyan));
464
395
  const changelog = generateChangelog(newVersion, categories, prCount);
465
396
  if (!dryRun) {
@@ -472,9 +403,8 @@ ${clr('After running:', c.cyan)}
472
403
  console.log(clr('--- End Preview ---\n', c.dim));
473
404
  }
474
405
 
475
- // 4. Generate release body
476
406
  console.log(clr('\nšŸ“‹ Release Body', c.cyan));
477
- const releaseBody = generateReleaseBody(newVersion, changelog, contributors, prevTag);
407
+ const releaseBody = generateReleaseBody(newVersion, changelog, contributors, prevTag, REPO_URL);
478
408
  const releaseBodyPath = join(ROOT, '.github/release-body.md');
479
409
  if (!dryRun) {
480
410
  writeFileSync(releaseBodyPath, releaseBody, 'utf-8');
@@ -483,7 +413,6 @@ ${clr('After running:', c.cyan)}
483
413
  console.log(` ${clr('→', c.yellow)} Would write .github/release-body.md`);
484
414
  }
485
415
 
486
- // 5. Run sync-metadata
487
416
  console.log(clr('\nšŸ”„ Sync Metadata', c.cyan));
488
417
  if (!dryRun) {
489
418
  try {
@@ -495,17 +424,22 @@ ${clr('After running:', c.cyan)}
495
424
  console.log(` ${clr('→', c.yellow)} Would run sync-metadata`);
496
425
  }
497
426
 
498
- // 6. Next steps
499
427
  console.log(clr('\nāœ… Done!', c.green));
500
428
  if (!dryRun) {
501
429
  console.log(clr('\nNext steps:', c.bold));
502
430
  console.log(` 1. ${clr(`git add -A && git commit -m "chore(release): bump version to v${newVersion}"`, c.cyan)}`);
503
- console.log(` 2. ${clr(`git push origin dev`, c.cyan)}`);
504
- console.log(` 3. Wait for CI green`);
505
- console.log(` 4. ${clr(`git checkout main && git merge dev && git push origin main`, c.cyan)}`);
431
+ console.log(` 2. ${clr('git push origin dev', c.cyan)}`);
432
+ console.log(' 3. Wait for CI green');
433
+ console.log(` 4. ${clr('git checkout main && git merge dev && git push origin main', c.cyan)}`);
506
434
  console.log(` 5. ${clr(`git tag -a v${newVersion} -m "v${newVersion}" && git push origin v${newVersion}`, c.cyan)}`);
507
- console.log(` 6. release.yml handles npm publish + GitHub release automatically`);
435
+ console.log(' 6. release.yml handles npm publish + GitHub release automatically');
508
436
  }
509
437
  }
510
438
 
511
- main();
439
+ if (isMainModule()) {
440
+ void main().catch((error: unknown) => {
441
+ const message = error instanceof Error ? error.message : String(error);
442
+ console.error(clr(`\nāœ– ${message}`, c.red));
443
+ process.exit(1);
444
+ });
445
+ }
@@ -8,14 +8,14 @@
8
8
 
9
9
  import { existsSync, readFileSync, readdirSync, rmSync, mkdirSync, writeFileSync, symlinkSync, lstatSync, readlinkSync, unlinkSync, renameSync } from 'fs';
10
10
  import { join, dirname } from 'path';
11
- import { homedir } from 'os';
12
11
  import { fileURLToPath, pathToFileURL } from 'url';
12
+ import { getClaudeConfigDir } from './lib/config-dir.mjs';
13
13
 
14
14
  const __filename = fileURLToPath(import.meta.url);
15
15
  const __dirname = dirname(__filename);
16
16
 
17
17
  /** Claude config directory (respects CLAUDE_CONFIG_DIR env var) */
18
- const configDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
18
+ const configDir = getClaudeConfigDir();
19
19
 
20
20
  // Import timeout-protected stdin reader (prevents hangs on Linux/Windows, see issue #240, #524)
21
21
  let readStdin;
@@ -482,8 +482,10 @@ Treat this as prior-session context only. Prioritize the user's newest request,
482
482
  `);
483
483
  }
484
484
 
485
- // Check for incomplete todos (project-local only, not global ~/.claude/todos/)
486
- // NOTE: We intentionally do NOT scan the global ~/.claude/todos/ directory.
485
+ // Check for incomplete todos (project-local only, not global
486
+ // [$CLAUDE_CONFIG_DIR|~/.claude]/todos/)
487
+ // NOTE: We intentionally do NOT scan the global
488
+ // [$CLAUDE_CONFIG_DIR|~/.claude]/todos/ directory.
487
489
  // That directory accumulates todo files from ALL past sessions across all
488
490
  // projects, causing phantom task counts in fresh sessions (see issue #354).
489
491
  const localTodoPaths = [
@@ -126,7 +126,12 @@ function readSummaryState(stateDir, sessionId) {
126
126
  * Write summary state to disk (scoped by sessionId).
127
127
  */
128
128
  function writeSummaryState(stateDir, sessionId, state) {
129
- mkdirSync(stateDir, { recursive: true });
129
+ try {
130
+ mkdirSync(stateDir, { recursive: true });
131
+ } catch (err) {
132
+ // On Windows, concurrent hooks can throw EEXIST even with recursive:true
133
+ if (err?.code !== 'EEXIST') throw err;
134
+ }
130
135
  const statePath = join(stateDir, `session-summary-${sessionId}.json`);
131
136
  writeFileSync(statePath, JSON.stringify(state, null, 2));
132
137
  }
@@ -13,13 +13,15 @@ INSTALL_STYLE="${2:-overwrite}"
13
13
  DOWNLOAD_URL="https://raw.githubusercontent.com/Yeachan-Heo/oh-my-claudecode/main/docs/CLAUDE.md"
14
14
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
15
  SCRIPT_PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
16
+ . "$SCRIPT_DIR/lib/config-dir.sh"
16
17
 
17
18
  # Resolve active plugin root from installed_plugins.json.
18
19
  # Handles stale CLAUDE_PLUGIN_ROOT when a session was started before a plugin
19
20
  # update (e.g. 4.8.2 session invoking setup after updating to 4.9.0).
20
21
  # Same pattern as run.cjs resolveTarget() fallback.
21
22
  resolve_active_plugin_root() {
22
- local config_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
23
+ local config_dir
24
+ config_dir="$(resolve_claude_config_dir)"
23
25
  local installed_plugins="${config_dir}/plugins/installed_plugins.json"
24
26
 
25
27
  if [ -f "$installed_plugins" ] && command -v jq >/dev/null 2>&1; then
@@ -89,7 +91,7 @@ EOF
89
91
  }
90
92
 
91
93
  # Determine target path
92
- CONFIG_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
94
+ CONFIG_DIR="$(resolve_claude_config_dir)"
93
95
  if [ "$MODE" = "local" ]; then
94
96
  mkdir -p .claude/skills/omc-reference
95
97
  TARGET_PATH=".claude/CLAUDE.md"
@@ -8,8 +8,11 @@
8
8
 
9
9
  set -euo pipefail
10
10
 
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ . "$SCRIPT_DIR/lib/config-dir.sh"
13
+
11
14
  STATE_FILE=".omc/state/setup-state.json"
12
- CONFIG_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
15
+ CONFIG_DIR="$(resolve_claude_config_dir)"
13
16
  CONFIG_FILE="$CONFIG_DIR/.omc-config.json"
14
17
 
15
18
  # Cross-platform ISO date to epoch conversion
@@ -13,6 +13,7 @@
13
13
  import { existsSync, readdirSync, readFileSync, realpathSync } from 'fs';
14
14
  import { join, basename } from 'path';
15
15
  import { homedir } from 'os';
16
+ import { getClaudeConfigDir } from './lib/config-dir.mjs';
16
17
  import { readStdin } from './lib/stdin.mjs';
17
18
  import { createRequire } from 'module';
18
19
 
@@ -26,7 +27,7 @@ try {
26
27
  }
27
28
 
28
29
  // Constants (used by fallback)
29
- const cfgDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
30
+ const cfgDir = getClaudeConfigDir();
30
31
  const USER_SKILLS_DIR = join(cfgDir, 'skills', 'omc-learned');
31
32
  const GLOBAL_SKILLS_DIR = join(homedir(), '.omc', 'skills');
32
33
  const PROJECT_SKILLS_SUBDIR = join('.omc', 'skills');