monday-cli 0.2.0 → 0.3.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 (418) hide show
  1. package/CHANGELOG.md +324 -2
  2. package/README.md +128 -30
  3. package/dist/api/board-favorites.d.ts +329 -0
  4. package/dist/api/board-favorites.d.ts.map +1 -0
  5. package/dist/api/board-favorites.js +353 -0
  6. package/dist/api/board-favorites.js.map +1 -0
  7. package/dist/api/board-mutation-result.d.ts +9 -5
  8. package/dist/api/board-mutation-result.d.ts.map +1 -1
  9. package/dist/api/board-mutation-result.js +9 -5
  10. package/dist/api/board-mutation-result.js.map +1 -1
  11. package/dist/api/board-relation-validation.d.ts +161 -0
  12. package/dist/api/board-relation-validation.d.ts.map +1 -0
  13. package/dist/api/board-relation-validation.js +317 -0
  14. package/dist/api/board-relation-validation.js.map +1 -0
  15. package/dist/api/cache.d.ts +14 -5
  16. package/dist/api/cache.d.ts.map +1 -1
  17. package/dist/api/cache.js +8 -10
  18. package/dist/api/cache.js.map +1 -1
  19. package/dist/api/column-mapping.js +2 -2
  20. package/dist/api/column-mapping.js.map +1 -1
  21. package/dist/api/column-mutation-result.d.ts +9 -5
  22. package/dist/api/column-mutation-result.d.ts.map +1 -1
  23. package/dist/api/column-mutation-result.js +9 -5
  24. package/dist/api/column-mutation-result.js.map +1 -1
  25. package/dist/api/column-types.d.ts +26 -7
  26. package/dist/api/column-types.d.ts.map +1 -1
  27. package/dist/api/column-types.js +42 -3
  28. package/dist/api/column-types.js.map +1 -1
  29. package/dist/api/column-values.d.ts +228 -31
  30. package/dist/api/column-values.d.ts.map +1 -1
  31. package/dist/api/column-values.js +551 -124
  32. package/dist/api/column-values.js.map +1 -1
  33. package/dist/api/cross-board-search.d.ts +501 -0
  34. package/dist/api/cross-board-search.d.ts.map +1 -0
  35. package/dist/api/cross-board-search.js +547 -0
  36. package/dist/api/cross-board-search.js.map +1 -0
  37. package/dist/api/dev-conventions.d.ts +1038 -0
  38. package/dist/api/dev-conventions.d.ts.map +1 -0
  39. package/dist/api/dev-conventions.js +1556 -0
  40. package/dist/api/dev-conventions.js.map +1 -0
  41. package/dist/api/dry-run.d.ts +32 -5
  42. package/dist/api/dry-run.d.ts.map +1 -1
  43. package/dist/api/dry-run.js +149 -32
  44. package/dist/api/dry-run.js.map +1 -1
  45. package/dist/api/errors.d.ts.map +1 -1
  46. package/dist/api/errors.js +28 -7
  47. package/dist/api/errors.js.map +1 -1
  48. package/dist/api/group-mutation-result.d.ts +9 -5
  49. package/dist/api/group-mutation-result.d.ts.map +1 -1
  50. package/dist/api/group-mutation-result.js +9 -5
  51. package/dist/api/group-mutation-result.js.map +1 -1
  52. package/dist/api/item-history-projection.d.ts +919 -0
  53. package/dist/api/item-history-projection.d.ts.map +1 -0
  54. package/dist/api/item-history-projection.js +1104 -0
  55. package/dist/api/item-history-projection.js.map +1 -0
  56. package/dist/api/item-mutation-execute.d.ts +82 -0
  57. package/dist/api/item-mutation-execute.d.ts.map +1 -0
  58. package/dist/api/item-mutation-execute.js +199 -0
  59. package/dist/api/item-mutation-execute.js.map +1 -0
  60. package/dist/api/notifications.d.ts +156 -0
  61. package/dist/api/notifications.d.ts.map +1 -0
  62. package/dist/api/notifications.js +215 -0
  63. package/dist/api/notifications.js.map +1 -0
  64. package/dist/api/oauth-test-helper.d.ts +64 -0
  65. package/dist/api/oauth-test-helper.d.ts.map +1 -0
  66. package/dist/api/oauth-test-helper.js +179 -0
  67. package/dist/api/oauth-test-helper.js.map +1 -0
  68. package/dist/api/oauth.d.ts +198 -0
  69. package/dist/api/oauth.d.ts.map +1 -0
  70. package/dist/api/oauth.js +471 -0
  71. package/dist/api/oauth.js.map +1 -0
  72. package/dist/api/partial-success-bulk.d.ts +422 -0
  73. package/dist/api/partial-success-bulk.d.ts.map +1 -0
  74. package/dist/api/partial-success-bulk.js +378 -0
  75. package/dist/api/partial-success-bulk.js.map +1 -0
  76. package/dist/api/people.d.ts +54 -1
  77. package/dist/api/people.d.ts.map +1 -1
  78. package/dist/api/people.js +27 -3
  79. package/dist/api/people.js.map +1 -1
  80. package/dist/api/probes.d.ts +487 -0
  81. package/dist/api/probes.d.ts.map +1 -0
  82. package/dist/api/probes.js +881 -0
  83. package/dist/api/probes.js.map +1 -0
  84. package/dist/api/raw-document.d.ts.map +1 -1
  85. package/dist/api/raw-document.js +2 -2
  86. package/dist/api/raw-document.js.map +1 -1
  87. package/dist/api/raw-write.d.ts.map +1 -1
  88. package/dist/api/raw-write.js +11 -3
  89. package/dist/api/raw-write.js.map +1 -1
  90. package/dist/api/resolution-context.d.ts +23 -11
  91. package/dist/api/resolution-context.d.ts.map +1 -1
  92. package/dist/api/resolution-context.js +53 -12
  93. package/dist/api/resolution-context.js.map +1 -1
  94. package/dist/api/resolution-pass.d.ts +30 -1
  95. package/dist/api/resolution-pass.d.ts.map +1 -1
  96. package/dist/api/resolution-pass.js +36 -1
  97. package/dist/api/resolution-pass.js.map +1 -1
  98. package/dist/api/resolve-client.d.ts +11 -0
  99. package/dist/api/resolve-client.d.ts.map +1 -1
  100. package/dist/api/resolve-client.js +1 -1
  101. package/dist/api/resolve-client.js.map +1 -1
  102. package/dist/api/response-root.d.ts +92 -46
  103. package/dist/api/response-root.d.ts.map +1 -1
  104. package/dist/api/response-root.js +93 -41
  105. package/dist/api/response-root.js.map +1 -1
  106. package/dist/api/tag-directory.d.ts +154 -0
  107. package/dist/api/tag-directory.d.ts.map +1 -0
  108. package/dist/api/tag-directory.js +325 -0
  109. package/dist/api/tag-directory.js.map +1 -0
  110. package/dist/api/time-tracking.d.ts +165 -0
  111. package/dist/api/time-tracking.d.ts.map +1 -0
  112. package/dist/api/time-tracking.js +135 -0
  113. package/dist/api/time-tracking.js.map +1 -0
  114. package/dist/api/transport.js +3 -3
  115. package/dist/api/transport.js.map +1 -1
  116. package/dist/api/usage.d.ts +190 -0
  117. package/dist/api/usage.d.ts.map +1 -0
  118. package/dist/api/usage.js +194 -0
  119. package/dist/api/usage.js.map +1 -0
  120. package/dist/api/users-fan-out-mutation.d.ts.map +1 -1
  121. package/dist/api/users-fan-out-mutation.js +10 -5
  122. package/dist/api/users-fan-out-mutation.js.map +1 -1
  123. package/dist/api/webhooks.d.ts +357 -0
  124. package/dist/api/webhooks.d.ts.map +1 -0
  125. package/dist/api/webhooks.js +333 -0
  126. package/dist/api/webhooks.js.map +1 -0
  127. package/dist/cli/envelope-out.d.ts +18 -1
  128. package/dist/cli/envelope-out.d.ts.map +1 -1
  129. package/dist/cli/envelope-out.js +16 -2
  130. package/dist/cli/envelope-out.js.map +1 -1
  131. package/dist/cli/program.d.ts.map +1 -1
  132. package/dist/cli/program.js +120 -1
  133. package/dist/cli/program.js.map +1 -1
  134. package/dist/cli/run.d.ts +12 -0
  135. package/dist/cli/run.d.ts.map +1 -1
  136. package/dist/cli/run.js +2 -0
  137. package/dist/cli/run.js.map +1 -1
  138. package/dist/commands/account/tags.d.ts +37 -0
  139. package/dist/commands/account/tags.d.ts.map +1 -0
  140. package/dist/commands/account/tags.js +84 -0
  141. package/dist/commands/account/tags.js.map +1 -0
  142. package/dist/commands/auth/login.d.ts +14 -0
  143. package/dist/commands/auth/login.d.ts.map +1 -0
  144. package/dist/commands/auth/login.js +314 -0
  145. package/dist/commands/auth/login.js.map +1 -0
  146. package/dist/commands/auth/logout.d.ts +28 -0
  147. package/dist/commands/auth/logout.d.ts.map +1 -0
  148. package/dist/commands/auth/logout.js +94 -0
  149. package/dist/commands/auth/logout.js.map +1 -0
  150. package/dist/commands/board/archive.d.ts.map +1 -1
  151. package/dist/commands/board/archive.js +14 -14
  152. package/dist/commands/board/archive.js.map +1 -1
  153. package/dist/commands/board/column-create.d.ts +3 -3
  154. package/dist/commands/board/column-create.d.ts.map +1 -1
  155. package/dist/commands/board/column-create.js +52 -45
  156. package/dist/commands/board/column-create.js.map +1 -1
  157. package/dist/commands/board/column-delete.d.ts.map +1 -1
  158. package/dist/commands/board/column-delete.js +15 -16
  159. package/dist/commands/board/column-delete.js.map +1 -1
  160. package/dist/commands/board/column-update.d.ts.map +1 -1
  161. package/dist/commands/board/column-update.js +23 -22
  162. package/dist/commands/board/column-update.js.map +1 -1
  163. package/dist/commands/board/create.d.ts.map +1 -1
  164. package/dist/commands/board/create.js +14 -17
  165. package/dist/commands/board/create.js.map +1 -1
  166. package/dist/commands/board/delete.d.ts.map +1 -1
  167. package/dist/commands/board/delete.js +12 -15
  168. package/dist/commands/board/delete.js.map +1 -1
  169. package/dist/commands/board/describe.d.ts.map +1 -1
  170. package/dist/commands/board/describe.js +30 -0
  171. package/dist/commands/board/describe.js.map +1 -1
  172. package/dist/commands/board/duplicate.d.ts.map +1 -1
  173. package/dist/commands/board/duplicate.js +12 -13
  174. package/dist/commands/board/duplicate.js.map +1 -1
  175. package/dist/commands/board/favorites.d.ts +33 -0
  176. package/dist/commands/board/favorites.d.ts.map +1 -0
  177. package/dist/commands/board/favorites.js +74 -0
  178. package/dist/commands/board/favorites.js.map +1 -0
  179. package/dist/commands/board/find.d.ts +1 -1
  180. package/dist/commands/board/group-archive.d.ts.map +1 -1
  181. package/dist/commands/board/group-archive.js +12 -16
  182. package/dist/commands/board/group-archive.js.map +1 -1
  183. package/dist/commands/board/group-create.d.ts.map +1 -1
  184. package/dist/commands/board/group-create.js +9 -19
  185. package/dist/commands/board/group-create.js.map +1 -1
  186. package/dist/commands/board/group-delete.d.ts.map +1 -1
  187. package/dist/commands/board/group-delete.js +12 -16
  188. package/dist/commands/board/group-delete.js.map +1 -1
  189. package/dist/commands/board/group-duplicate.d.ts.map +1 -1
  190. package/dist/commands/board/group-duplicate.js +12 -16
  191. package/dist/commands/board/group-duplicate.js.map +1 -1
  192. package/dist/commands/board/group-update.d.ts.map +1 -1
  193. package/dist/commands/board/group-update.js +12 -11
  194. package/dist/commands/board/group-update.js.map +1 -1
  195. package/dist/commands/board/list.d.ts +1 -1
  196. package/dist/commands/board/update.d.ts.map +1 -1
  197. package/dist/commands/board/update.js +16 -11
  198. package/dist/commands/board/update.js.map +1 -1
  199. package/dist/commands/cache/list.d.ts +2 -0
  200. package/dist/commands/cache/list.d.ts.map +1 -1
  201. package/dist/commands/cache/list.js +2 -2
  202. package/dist/commands/cache/list.js.map +1 -1
  203. package/dist/commands/dev/_shared.d.ts +40 -0
  204. package/dist/commands/dev/_shared.d.ts.map +1 -0
  205. package/dist/commands/dev/_shared.js +104 -0
  206. package/dist/commands/dev/_shared.js.map +1 -0
  207. package/dist/commands/dev/configure.d.ts +36 -0
  208. package/dist/commands/dev/configure.d.ts.map +1 -0
  209. package/dist/commands/dev/configure.js +145 -0
  210. package/dist/commands/dev/configure.js.map +1 -0
  211. package/dist/commands/dev/discover.d.ts +34 -0
  212. package/dist/commands/dev/discover.d.ts.map +1 -0
  213. package/dist/commands/dev/discover.js +117 -0
  214. package/dist/commands/dev/discover.js.map +1 -0
  215. package/dist/commands/dev/doctor.d.ts +39 -0
  216. package/dist/commands/dev/doctor.d.ts.map +1 -0
  217. package/dist/commands/dev/doctor.js +91 -0
  218. package/dist/commands/dev/doctor.js.map +1 -0
  219. package/dist/commands/dev/epic/items.d.ts +24 -0
  220. package/dist/commands/dev/epic/items.d.ts.map +1 -0
  221. package/dist/commands/dev/epic/items.js +103 -0
  222. package/dist/commands/dev/epic/items.js.map +1 -0
  223. package/dist/commands/dev/epic/list.d.ts +36 -0
  224. package/dist/commands/dev/epic/list.d.ts.map +1 -0
  225. package/dist/commands/dev/epic/list.js +120 -0
  226. package/dist/commands/dev/epic/list.js.map +1 -0
  227. package/dist/commands/dev/release/list.d.ts +21 -0
  228. package/dist/commands/dev/release/list.d.ts.map +1 -0
  229. package/dist/commands/dev/release/list.js +73 -0
  230. package/dist/commands/dev/release/list.js.map +1 -0
  231. package/dist/commands/dev/sprint/current.d.ts +24 -0
  232. package/dist/commands/dev/sprint/current.d.ts.map +1 -0
  233. package/dist/commands/dev/sprint/current.js +90 -0
  234. package/dist/commands/dev/sprint/current.js.map +1 -0
  235. package/dist/commands/dev/sprint/items.d.ts +34 -0
  236. package/dist/commands/dev/sprint/items.d.ts.map +1 -0
  237. package/dist/commands/dev/sprint/items.js +118 -0
  238. package/dist/commands/dev/sprint/items.js.map +1 -0
  239. package/dist/commands/dev/sprint/list.d.ts +41 -0
  240. package/dist/commands/dev/sprint/list.d.ts.map +1 -0
  241. package/dist/commands/dev/sprint/list.js +104 -0
  242. package/dist/commands/dev/sprint/list.js.map +1 -0
  243. package/dist/commands/dev/task/block.d.ts +29 -0
  244. package/dist/commands/dev/task/block.d.ts.map +1 -0
  245. package/dist/commands/dev/task/block.js +106 -0
  246. package/dist/commands/dev/task/block.js.map +1 -0
  247. package/dist/commands/dev/task/done.d.ts +30 -0
  248. package/dist/commands/dev/task/done.d.ts.map +1 -0
  249. package/dist/commands/dev/task/done.js +113 -0
  250. package/dist/commands/dev/task/done.js.map +1 -0
  251. package/dist/commands/dev/task/list.d.ts +42 -0
  252. package/dist/commands/dev/task/list.d.ts.map +1 -0
  253. package/dist/commands/dev/task/list.js +227 -0
  254. package/dist/commands/dev/task/list.js.map +1 -0
  255. package/dist/commands/dev/task/start.d.ts +29 -0
  256. package/dist/commands/dev/task/start.d.ts.map +1 -0
  257. package/dist/commands/dev/task/start.js +90 -0
  258. package/dist/commands/dev/task/start.js.map +1 -0
  259. package/dist/commands/emit.d.ts.map +1 -1
  260. package/dist/commands/emit.js +5 -3
  261. package/dist/commands/emit.js.map +1 -1
  262. package/dist/commands/index.d.ts.map +1 -1
  263. package/dist/commands/index.js +95 -0
  264. package/dist/commands/index.js.map +1 -1
  265. package/dist/commands/item/archive.d.ts.map +1 -1
  266. package/dist/commands/item/archive.js +11 -0
  267. package/dist/commands/item/archive.js.map +1 -1
  268. package/dist/commands/item/clear.d.ts.map +1 -1
  269. package/dist/commands/item/clear.js +15 -0
  270. package/dist/commands/item/clear.js.map +1 -1
  271. package/dist/commands/item/create.d.ts.map +1 -1
  272. package/dist/commands/item/create.js +41 -8
  273. package/dist/commands/item/create.js.map +1 -1
  274. package/dist/commands/item/delete.d.ts.map +1 -1
  275. package/dist/commands/item/delete.js +11 -0
  276. package/dist/commands/item/delete.js.map +1 -1
  277. package/dist/commands/item/duplicate.d.ts.map +1 -1
  278. package/dist/commands/item/duplicate.js +12 -0
  279. package/dist/commands/item/duplicate.js.map +1 -1
  280. package/dist/commands/item/history.d.ts +60 -0
  281. package/dist/commands/item/history.d.ts.map +1 -0
  282. package/dist/commands/item/history.js +309 -0
  283. package/dist/commands/item/history.js.map +1 -0
  284. package/dist/commands/item/list.d.ts.map +1 -1
  285. package/dist/commands/item/list.js +16 -13
  286. package/dist/commands/item/list.js.map +1 -1
  287. package/dist/commands/item/move.d.ts.map +1 -1
  288. package/dist/commands/item/move.js +41 -7
  289. package/dist/commands/item/move.js.map +1 -1
  290. package/dist/commands/item/search.d.ts +99 -15
  291. package/dist/commands/item/search.d.ts.map +1 -1
  292. package/dist/commands/item/search.js +480 -36
  293. package/dist/commands/item/search.js.map +1 -1
  294. package/dist/commands/item/set.d.ts.map +1 -1
  295. package/dist/commands/item/set.js +52 -8
  296. package/dist/commands/item/set.js.map +1 -1
  297. package/dist/commands/item/time-track/start.d.ts +61 -0
  298. package/dist/commands/item/time-track/start.d.ts.map +1 -0
  299. package/dist/commands/item/time-track/start.js +138 -0
  300. package/dist/commands/item/time-track/start.js.map +1 -0
  301. package/dist/commands/item/time-track/stop.d.ts +32 -0
  302. package/dist/commands/item/time-track/stop.d.ts.map +1 -0
  303. package/dist/commands/item/time-track/stop.js +97 -0
  304. package/dist/commands/item/time-track/stop.js.map +1 -0
  305. package/dist/commands/item/update.d.ts +1 -0
  306. package/dist/commands/item/update.d.ts.map +1 -1
  307. package/dist/commands/item/update.js +103 -113
  308. package/dist/commands/item/update.js.map +1 -1
  309. package/dist/commands/item/upsert.d.ts.map +1 -1
  310. package/dist/commands/item/upsert.js +48 -1
  311. package/dist/commands/item/upsert.js.map +1 -1
  312. package/dist/commands/notification/send.d.ts +60 -0
  313. package/dist/commands/notification/send.d.ts.map +1 -0
  314. package/dist/commands/notification/send.js +147 -0
  315. package/dist/commands/notification/send.js.map +1 -0
  316. package/dist/commands/parse-argv.d.ts.map +1 -1
  317. package/dist/commands/parse-argv.js +14 -4
  318. package/dist/commands/parse-argv.js.map +1 -1
  319. package/dist/commands/raw/index.d.ts.map +1 -1
  320. package/dist/commands/raw/index.js +13 -15
  321. package/dist/commands/raw/index.js.map +1 -1
  322. package/dist/commands/run-by-id-lookup.d.ts.map +1 -1
  323. package/dist/commands/run-by-id-lookup.js +2 -2
  324. package/dist/commands/run-by-id-lookup.js.map +1 -1
  325. package/dist/commands/schema/index.d.ts +2 -0
  326. package/dist/commands/schema/index.d.ts.map +1 -1
  327. package/dist/commands/status.d.ts +120 -0
  328. package/dist/commands/status.d.ts.map +1 -0
  329. package/dist/commands/status.js +365 -0
  330. package/dist/commands/status.js.map +1 -0
  331. package/dist/commands/update/body-source.d.ts.map +1 -1
  332. package/dist/commands/update/body-source.js +2 -2
  333. package/dist/commands/update/body-source.js.map +1 -1
  334. package/dist/commands/update/create.d.ts +2 -3
  335. package/dist/commands/update/create.d.ts.map +1 -1
  336. package/dist/commands/update/create.js +15 -3
  337. package/dist/commands/update/create.js.map +1 -1
  338. package/dist/commands/update/delete.d.ts.map +1 -1
  339. package/dist/commands/update/delete.js +11 -0
  340. package/dist/commands/update/delete.js.map +1 -1
  341. package/dist/commands/update/edit.d.ts.map +1 -1
  342. package/dist/commands/update/edit.js +11 -0
  343. package/dist/commands/update/edit.js.map +1 -1
  344. package/dist/commands/update/list.d.ts.map +1 -1
  345. package/dist/commands/update/list.js +15 -12
  346. package/dist/commands/update/list.js.map +1 -1
  347. package/dist/commands/update/reply.d.ts.map +1 -1
  348. package/dist/commands/update/reply.js +11 -0
  349. package/dist/commands/update/reply.js.map +1 -1
  350. package/dist/commands/update/toggle.d.ts.map +1 -1
  351. package/dist/commands/update/toggle.js +13 -0
  352. package/dist/commands/update/toggle.js.map +1 -1
  353. package/dist/commands/usage.d.ts +58 -0
  354. package/dist/commands/usage.d.ts.map +1 -0
  355. package/dist/commands/usage.js +94 -0
  356. package/dist/commands/usage.js.map +1 -0
  357. package/dist/commands/webhook/create.d.ts +74 -0
  358. package/dist/commands/webhook/create.d.ts.map +1 -0
  359. package/dist/commands/webhook/create.js +150 -0
  360. package/dist/commands/webhook/create.js.map +1 -0
  361. package/dist/commands/webhook/delete.d.ts +46 -0
  362. package/dist/commands/webhook/delete.d.ts.map +1 -0
  363. package/dist/commands/webhook/delete.js +141 -0
  364. package/dist/commands/webhook/delete.js.map +1 -0
  365. package/dist/commands/webhook/list.d.ts +23 -0
  366. package/dist/commands/webhook/list.d.ts.map +1 -0
  367. package/dist/commands/webhook/list.js +68 -0
  368. package/dist/commands/webhook/list.js.map +1 -0
  369. package/dist/commands/workspace/create.d.ts.map +1 -1
  370. package/dist/commands/workspace/create.js +16 -0
  371. package/dist/commands/workspace/create.js.map +1 -1
  372. package/dist/commands/workspace/delete.d.ts.map +1 -1
  373. package/dist/commands/workspace/delete.js +13 -13
  374. package/dist/commands/workspace/delete.js.map +1 -1
  375. package/dist/commands/workspace/list.d.ts +1 -1
  376. package/dist/commands/workspace/update.d.ts.map +1 -1
  377. package/dist/commands/workspace/update.js +15 -15
  378. package/dist/commands/workspace/update.js.map +1 -1
  379. package/dist/config/credentials.d.ts +189 -0
  380. package/dist/config/credentials.d.ts.map +1 -0
  381. package/dist/config/credentials.js +300 -0
  382. package/dist/config/credentials.js.map +1 -0
  383. package/dist/config/profiles.d.ts +125 -0
  384. package/dist/config/profiles.d.ts.map +1 -0
  385. package/dist/config/profiles.js +227 -0
  386. package/dist/config/profiles.js.map +1 -0
  387. package/dist/types/global-flags.d.ts +1 -1
  388. package/dist/types/global-flags.d.ts.map +1 -1
  389. package/dist/types/global-flags.js +28 -16
  390. package/dist/types/global-flags.js.map +1 -1
  391. package/dist/types/ids.d.ts +2 -0
  392. package/dist/types/ids.d.ts.map +1 -1
  393. package/dist/types/ids.js +5 -3
  394. package/dist/types/ids.js.map +1 -1
  395. package/dist/utils/errors.d.ts +57 -3
  396. package/dist/utils/errors.d.ts.map +1 -1
  397. package/dist/utils/errors.js +69 -2
  398. package/dist/utils/errors.js.map +1 -1
  399. package/dist/utils/fs.d.ts +35 -0
  400. package/dist/utils/fs.d.ts.map +1 -0
  401. package/dist/utils/fs.js +36 -0
  402. package/dist/utils/fs.js.map +1 -0
  403. package/dist/utils/json.d.ts +60 -0
  404. package/dist/utils/json.d.ts.map +1 -0
  405. package/dist/utils/json.js +86 -0
  406. package/dist/utils/json.js.map +1 -0
  407. package/dist/utils/output/ndjson.d.ts +65 -3
  408. package/dist/utils/output/ndjson.d.ts.map +1 -1
  409. package/dist/utils/output/ndjson.js +21 -0
  410. package/dist/utils/output/ndjson.js.map +1 -1
  411. package/dist/utils/redact.d.ts.map +1 -1
  412. package/dist/utils/redact.js +31 -0
  413. package/dist/utils/redact.js.map +1 -1
  414. package/package.json +2 -1
  415. package/dist/commands/account/client-helper.d.ts +0 -37
  416. package/dist/commands/account/client-helper.d.ts.map +0 -1
  417. package/dist/commands/account/client-helper.js +0 -55
  418. package/dist/commands/account/client-helper.js.map +0 -1
@@ -0,0 +1,1556 @@
1
+ /**
2
+ * Monday Dev convention helpers for the v0.3-M26 `monday dev …`
3
+ * namespace (`cli-design.md` §2.7 + §5.9 + §11.3 + §13 v0.3 entry;
4
+ * `v0.3-plan.md` §3 M26).
5
+ *
6
+ * **Monday Dev is convention, not API (cli-design §2.7).** No
7
+ * dedicated GraphQL surface — `dev sprint current`, `dev task done`,
8
+ * etc. are *workflow shortcuts* that resolve to standard board /
9
+ * item CRUD operations against per-profile-configured board IDs.
10
+ * This module owns the convention-to-board-ID resolution, the
11
+ * per-profile `[profiles.<name>.dev]` reader / writer, and the
12
+ * board-name-based discovery heuristic that seeds the config from
13
+ * a Monday Dev template-shaped workspace.
14
+ *
15
+ * **What this module owns.**
16
+ *
17
+ * 1. The {@link DevMapping} alias over `profiles.ts:profileDevBlockSchema`
18
+ * — single source of truth for the per-profile board mapping
19
+ * shape. Re-exported here for namespace-clarity at the
20
+ * `dev`-command call sites; the schema itself lives next to
21
+ * the profile-file reader so v0.3-M21's multi-profile config
22
+ * shape stays single-source-of-truth.
23
+ * 2. The {@link DevDiscoverOutput} + {@link DevConfigureOutput} +
24
+ * {@link DevDoctorOutput} output projections — pinned at this
25
+ * pre-flight so `monday schema` reads consistent shapes across
26
+ * the three setup verbs.
27
+ * 3. Pure helpers: {@link matchBoardByConvention} (name-based
28
+ * heuristic — exact-match-then-substring against the
29
+ * Monday Dev template's stock board names) +
30
+ * {@link buildDiscoverMappingFromMatches} (collapses the
31
+ * heuristic's per-noun matches into a {@link DevMapping}).
32
+ * Real implementations — the heuristic is content-addressed
33
+ * against board names so it shipped testable at pre-flight
34
+ * alongside the runtime fetchers (M26a IMPL `19755e3` landed
35
+ * the `boards(state: all, workspace_ids:)` walker with the
36
+ * `Board.type === 'board'` filter + per-board metadata
37
+ * hydration).
38
+ * 4. Runtime fetchers: {@link discoverDevBoards} (walks accessible
39
+ * boards + applies the heuristic) + {@link runDevDoctor}
40
+ * (validates the active profile's mapping against current board
41
+ * shape) + {@link loadDevMapping} (reads
42
+ * `[profiles.<name>.dev]`) + {@link saveDevMapping} (writes
43
+ * `[profiles.<name>.dev]` via atomic TOML round-trip). The
44
+ * pre-flight `c8 ignore start/stop` wraps dropped at M26a IMPL
45
+ * (`19755e3`) alongside the per-fetcher wire bodies.
46
+ *
47
+ * **Empirical-probe findings pinned at M26a IMPL (2026-05-11, against
48
+ * `api.monday.com`, API version `2026-01`) — `scripts/probe/m26-
49
+ * dev-discover.ts` + `scripts/probe/m26-board-kind.ts` +
50
+ * `scripts/probe/m26-board-type.ts`:**
51
+ *
52
+ * - **Stock template names unchanged.** Live Monday Dev workspace
53
+ * surfaces `Tasks` / `Epics` / `Bugs Queue` matching the pinned
54
+ * {@link DEV_NOUN_PATTERNS} (substring tolerance handles the
55
+ * `Queue` suffix on bugs). Decision 1 closure stands; no
56
+ * `DEV_NOUN_PATTERNS` amendment needed.
57
+ * - **`Board.type === 'board'` filter required.** Monday's
58
+ * `boards()` walker returns `sub_items_board` virtual entries
59
+ * (auto-generated `Subitems of <BoardName>` boards) that pollute
60
+ * the substring heuristic — `Subitems of Tasks` matches the
61
+ * `tasks` pattern, creating an ambiguous match that prevents
62
+ * auto-mapping. The walker filters to `type === 'board'`,
63
+ * silently dropping `sub_items_board` / `custom_object` /
64
+ * `document` entries. Behavior-equivalent refinement, NOT a
65
+ * contract amendment — {@link DiscoverBoardCandidate} schema
66
+ * unchanged.
67
+ * - **`state: all` walker filter.** Per the M26 IMPL handoff
68
+ * guidance (don't filter by board state at the walker), the
69
+ * walker passes `state: all` so the heuristic sees archived /
70
+ * deleted boards too; the action body surfaces them on
71
+ * {@link DevDiscoverOutput.matches} for agent-side review.
72
+ * - **`board_kind`-`public`/`private`/`share` only.** Does NOT
73
+ * discriminate subitem boards — `Subitems of Tasks` reports
74
+ * `board_kind: 'public'`, identical to the parent `Tasks`
75
+ * board. Hence the `Board.type` filter rather than a
76
+ * `board_kind` filter.
77
+ *
78
+ * **What this module does NOT own.**
79
+ *
80
+ * - The base profile-config schema + read path. Lives in
81
+ * `src/config/profiles.ts` per v0.3-M21 (`loadProfilesConfig`,
82
+ * `profilesConfigSchema`, `profileDevBlockSchema`). This module
83
+ * re-exports the dev-block schema as `devMappingSchema` for
84
+ * namespace clarity + owns the dev-block WRITE-back path
85
+ * (`saveDevMapping`); the parent profile shape stays under
86
+ * `src/config/profiles.ts`.
87
+ * - Per-workflow-verb wire calls (sprint/epic/release/task
88
+ * reads + writes). Those route through existing api/* modules
89
+ * (`items-page-walker`, `item-mutation-execute`, etc.) — the
90
+ * dev namespace contributes the *resolver* that maps `current
91
+ * sprint` to the right item-search input, not new wire
92
+ * primitives.
93
+ * - Convention-name overrides for non-template workspaces. The
94
+ * heuristic looks for Monday Dev template's stock English
95
+ * names; localised workspaces (Spanish "Tareas" etc.) fall
96
+ * through to `dev configure` + an explicit per-board override.
97
+ * A v0.4+ extension may add `--name-aliases` to discover.
98
+ *
99
+ * **Failure-mode routing (no new ERROR_CODES; 29 stays).** Per
100
+ * cli-design §5.9, the two `dev_*` codes pre-registered in
101
+ * `src/utils/errors.ts:ERROR_CODES` cover the namespace's failure
102
+ * modes:
103
+ *
104
+ * - `dev_not_configured` — active profile has no `[profiles.
105
+ * <name>.dev]` block AND no overrides via env vars / flags.
106
+ * Surface points at `monday dev configure` + `monday dev
107
+ * discover`.
108
+ * - `dev_board_misconfigured` — mapped board exists but doesn't
109
+ * expose the expected column (no status column on the tasks
110
+ * board, etc.). Surface points at `monday dev doctor` for
111
+ * diagnostics + `monday board describe <bid>` to inspect.
112
+ *
113
+ * Other failures (board deleted / access revoked / column-token
114
+ * collision / etc.) route through the existing 29-code registry
115
+ * (`not_found`, `column_not_found`, `unauthorized`, etc.) without
116
+ * widening.
117
+ */
118
+ import { chmod, mkdir, rename, unlink, writeFile, } from 'node:fs/promises';
119
+ import { homedir } from 'node:os';
120
+ import { join } from 'node:path';
121
+ import { randomUUID } from 'node:crypto';
122
+ import { stringify as stringifyToml } from 'smol-toml';
123
+ import { z } from 'zod';
124
+ import { ApiError, ConfigError, asError } from '../utils/errors.js';
125
+ import { unwrapOrThrow } from '../utils/parse-boundary.js';
126
+ import { PROFILES_DIR_NAME, loadProfilesConfig, profilesConfigSchema, resolveProfilesConfigPath, profileDevBlockSchema, } from '../config/profiles.js';
127
+ import { fetchItemsPage, fetchNextItemsPage, } from './items-page-walker.js';
128
+ import { paginate } from './pagination.js';
129
+ import { idFromRawItem, projectItem, } from './item-projection.js';
130
+ import { ITEM_FIELDS_FRAGMENT, parseRawItem } from './item-helpers.js';
131
+ import { executeItemMutation } from './item-mutation-execute.js';
132
+ /**
133
+ * The per-profile Monday Dev board mapping. Alias over the
134
+ * `profileDevBlockSchema` defined in `src/config/profiles.ts:49-57`
135
+ * so the dev-namespace verbs read the same shape v0.3-M21 pinned
136
+ * for the multi-profile TOML config.
137
+ *
138
+ * Field names use snake_case to match the TOML config; the CLI
139
+ * flag layer (`monday dev configure --tasks-board <bid>`) maps
140
+ * camelCase argv to snake_case at the parse boundary.
141
+ */
142
+ export const devMappingSchema = profileDevBlockSchema;
143
+ /**
144
+ * The Monday Dev template's stock noun → board-name patterns.
145
+ * The discovery heuristic uses these as the matcher seed; a
146
+ * workspace whose board names match one of these patterns (case-
147
+ * insensitive, Unicode NFC) gets auto-mapped to the corresponding
148
+ * dev-noun slot.
149
+ *
150
+ * Pinned in the order the heuristic considers them. Order matters
151
+ * only for tie-breaking when a single board matches multiple
152
+ * patterns (rare; e.g. a board named "Tasks & Bugs" matches both
153
+ * `tasks` and `bugs` — the heuristic surfaces such ambiguity on
154
+ * the success envelope's `matches[]` array via
155
+ * `matched.length > 1` rather than auto-mapping).
156
+ *
157
+ * Localised workspaces (Spanish "Tareas", French "Sprints" — same
158
+ * spelling but different language) round-trip the English form
159
+ * since the heuristic is forward-compat-extensible at v0.4 via a
160
+ * `--name-aliases` flag on `dev discover`. Today's heuristic ships
161
+ * English-only by design — Monday Dev templates default to English
162
+ * board names on new workspaces regardless of UI locale.
163
+ */
164
+ export const DEV_NOUN_PATTERNS = [
165
+ { noun: 'tasks_board', patterns: ['tasks', 'task'] },
166
+ { noun: 'sprints_board', patterns: ['sprints', 'sprint'] },
167
+ { noun: 'epics_board', patterns: ['epics', 'epic'] },
168
+ { noun: 'releases_board', patterns: ['releases', 'release'] },
169
+ { noun: 'bugs_board', patterns: ['bugs', 'bug'] },
170
+ ];
171
+ export const discoverBoardCandidateSchema = z
172
+ .object({
173
+ id: z.string().min(1),
174
+ name: z.string().min(1),
175
+ workspace_id: z.string().nullable(),
176
+ })
177
+ .strict();
178
+ export const devNounMatchResultSchema = z
179
+ .object({
180
+ noun: z.enum([
181
+ 'tasks_board',
182
+ 'sprints_board',
183
+ 'epics_board',
184
+ 'releases_board',
185
+ 'bugs_board',
186
+ ]),
187
+ matched: z.array(discoverBoardCandidateSchema),
188
+ })
189
+ .strict();
190
+ /**
191
+ * Normalises a board name for the heuristic's case-insensitive
192
+ * substring match. Unicode NFC + lowercase + collapse internal
193
+ * whitespace + trim — same shape as the column-token resolver
194
+ * (cli-design §5.3 step 2.b).
195
+ */
196
+ const normaliseBoardName = (name) => name
197
+ .normalize('NFC')
198
+ .toLocaleLowerCase('und')
199
+ .replace(/\s+/gu, ' ')
200
+ .trim();
201
+ /**
202
+ * Returns true when `candidate.name` matches any of `patterns`.
203
+ * Exact-match wins over substring-match; the matcher returns true
204
+ * on either form so the caller can rank candidates afterwards if
205
+ * needed. Pure helper — real implementation at pre-flight so the
206
+ * Codex review can verify the heuristic shape inline.
207
+ */
208
+ export const matchBoardByConvention = (candidate, patterns) => {
209
+ const normalised = normaliseBoardName(candidate.name);
210
+ for (const pattern of patterns) {
211
+ const normalisedPattern = normaliseBoardName(pattern);
212
+ if (normalised === normalisedPattern)
213
+ return true;
214
+ if (normalised.includes(normalisedPattern))
215
+ return true;
216
+ }
217
+ return false;
218
+ };
219
+ /**
220
+ * Runs the heuristic across `candidates` and groups by dev-noun.
221
+ * Returns one {@link DevNounMatchResult} per noun in
222
+ * {@link DEV_NOUN_PATTERNS} order; nouns with zero matches surface
223
+ * with empty `matched` arrays (the caller surfaces these on the
224
+ * `dev discover` success envelope's `matches[]` array via
225
+ * `matched.length === 0` — no warning code registered at M26
226
+ * pre-flight). Pure helper — real implementation at pre-flight.
227
+ */
228
+ export const groupCandidatesByDevNoun = (candidates) => DEV_NOUN_PATTERNS.map(({ noun, patterns }) => ({
229
+ noun,
230
+ matched: candidates.filter((c) => matchBoardByConvention(c, patterns)),
231
+ }));
232
+ /**
233
+ * Collapses the per-noun match results into a {@link DevMapping}.
234
+ * Nouns with exactly one match populate the mapping slot;
235
+ * zero-match or ambiguous (>1 match) nouns are omitted from the
236
+ * mapping. The caller — `dev discover` — surfaces both modes
237
+ * via the same `matches[]` array on the success envelope
238
+ * (`matched.length === 0` = unmapped; `matched.length > 1` =
239
+ * ambiguous); no separate warning code is registered at M26
240
+ * pre-flight (round-1 Codex P2-3 clarification — warning-code
241
+ * registration is per cli-design §6.1 + the `dev discover`
242
+ * surface intentionally uses data-shape rather than warnings
243
+ * for the heuristic's per-noun outcomes). Pure helper — real
244
+ * implementation at pre-flight.
245
+ */
246
+ export const buildDiscoverMappingFromMatches = (matches) => {
247
+ const mapping = {};
248
+ for (const { noun, matched } of matches) {
249
+ if (matched.length === 1) {
250
+ // Type-safe assignment via the keyof DevMapping pin on `noun`.
251
+ const slot = noun;
252
+ const value = matched[0]?.id;
253
+ if (value !== undefined) {
254
+ mapping[slot] = value;
255
+ }
256
+ }
257
+ }
258
+ return mapping;
259
+ };
260
+ export const devDiscoverOutputSchema = z
261
+ .object({
262
+ profile: z.string().min(1),
263
+ mapping: devMappingSchema,
264
+ matches: z.array(devNounMatchResultSchema),
265
+ applied: z.boolean(),
266
+ })
267
+ .strict();
268
+ export const devConfigureOutputSchema = z
269
+ .object({
270
+ profile: z.string().min(1),
271
+ mapping: devMappingSchema,
272
+ })
273
+ .strict();
274
+ /**
275
+ * Pinned check-name vocabulary for `dev doctor` (Decision 2
276
+ * closure at M26 pre-flight). The runtime body iterates this list
277
+ * in order and emits one {@link DevDoctorCheckResult} per name.
278
+ * Names are stable contract surface — agents key off them (an
279
+ * agent self-correcting after a `dev task` failure can grep the
280
+ * doctor output for `tasks_status_column_present.status === 'fail'`).
281
+ *
282
+ * Adding a check is non-breaking (additive); removing or renaming
283
+ * is a major bump.
284
+ *
285
+ * **Round-1 Codex fix (P1-1).** `sprints_state_column_present`
286
+ * was the round-0 name but every sprint verb's runtime semantics
287
+ * are date-range-derived (cli-design §5.9 + the sprint-list verb's
288
+ * `--state active|past|future` filter), not status-column-derived.
289
+ * Renamed to `sprints_date_columns_present` to match what the
290
+ * runtime body will actually inspect — start_date + end_date
291
+ * columns on the configured sprints board.
292
+ *
293
+ * **Round-1 Codex fix (P2-2).** Added `bugs_board_exists` so the
294
+ * `bugs_board` mapping slot is diagnosed by the doctor like every
295
+ * other dev-noun slot.
296
+ *
297
+ * **Round-2 Codex fix (P2-3).** Replaced `epics_to_releases_relation`
298
+ * with `tasks_to_epics_relation` — the round-1 list had a relation
299
+ * check for an epic↔release wiring that no M26 verb consumes
300
+ * (there's no `dev release items` verb at v0.3), while the
301
+ * actually-consumed epic↔task relation (`dev epic items <eid>`
302
+ * walks tasks linked to a given epic) was missing. The release-
303
+ * to-epic relation can rejoin in a v0.3.x / v0.4 follow-up when /
304
+ * if a `dev release items` verb lands. Total check count holds at
305
+ * 10.
306
+ */
307
+ export const DEV_DOCTOR_CHECK_NAMES = [
308
+ 'tasks_board_exists',
309
+ 'tasks_status_column_present',
310
+ 'tasks_status_labels_canonical',
311
+ 'sprints_board_exists',
312
+ 'sprints_date_columns_present',
313
+ 'epics_board_exists',
314
+ 'releases_board_exists',
315
+ 'bugs_board_exists',
316
+ 'tasks_to_sprints_relation',
317
+ 'tasks_to_epics_relation',
318
+ ];
319
+ /**
320
+ * One diagnostic check `dev doctor` ran against the active
321
+ * profile's mapping. `status: 'ok'` = passed; `'warn'` = the
322
+ * check raised a non-fatal concern (e.g. status column has
323
+ * non-standard labels); `'fail'` = the check found drift that
324
+ * blocks the corresponding `dev` verb (e.g. tasks board has no
325
+ * status column at all — `dev task start` would fail).
326
+ *
327
+ * Per-status `details` shape is now STRUCTURALLY pinned at M26a
328
+ * IMPL round-2 P2-1 via the {@link okCheckDetailsSchema} /
329
+ * {@link warnCheckDetailsSchema} / {@link failCheckDetailsSchema}
330
+ * discriminated union below — `monday schema dev.doctor` surfaces
331
+ * the closed {@link DEV_DOCTOR_REASONS} enum via JSON Schema
332
+ * export, and `failResult` enforces a required `reason` at
333
+ * compile time. Extra `details` keys beyond `reason` stay open
334
+ * per-status (open-ended `Record<string, unknown>` extension via
335
+ * `.loose()`) so each check emits its own context fields
336
+ * (`board_id`, `column_id`, etc.) without per-check schema
337
+ * widening.
338
+ *
339
+ * **M26 pre-flight round-1 Codex fix (P2-1).** `name` is typed
340
+ * as {@link DevDoctorCheckName} (the enum literal union) so
341
+ * `monday schema` exposes the stable vocabulary + implementation
342
+ * typos fail output-schema validation rather than silently
343
+ * passing through.
344
+ */
345
+ /**
346
+ * Pinned `details.reason` enum vocabulary surfaced by per-check
347
+ * failure paths. Closes the Decision 2 deferral (per-check
348
+ * discriminated-union pinning) at M26a IMPL — `details.reason`
349
+ * is the agent-keyable discriminator on `status: 'fail'` (and on
350
+ * `status: 'warn'` when a warn carries a structured reason, e.g.
351
+ * `settings_unparseable`).
352
+ *
353
+ * Codex M26a IMPL round-1 P2-1 fix: previously the schema accepted
354
+ * any `details: Record<string, unknown>` shape, so `monday schema`
355
+ * couldn't expose the reason vocabulary + tests couldn't catch
356
+ * typos. Pinning the enum + the "fail requires reason" refinement
357
+ * gives agents a stable branchpoint.
358
+ *
359
+ * Adding a reason is non-breaking; removing or renaming is major.
360
+ */
361
+ export const DEV_DOCTOR_REASONS = [
362
+ 'not_in_mapping',
363
+ 'not_accessible',
364
+ 'board_deleted',
365
+ 'no_tasks_board',
366
+ 'no_sprints_board',
367
+ 'no_status_column',
368
+ 'no_date_columns',
369
+ 'no_relation_column',
370
+ 'no_matching_relation',
371
+ 'no_target_board',
372
+ 'settings_unparseable',
373
+ ];
374
+ export const devDoctorReasonSchema = z.enum(DEV_DOCTOR_REASONS);
375
+ /**
376
+ * Per-status `details` shape. Codex M26a IMPL round-2 P2-1 + P2-2
377
+ * fix: the round-1 superRefine ran the `reason`-enum check
378
+ * runtime-only — `monday schema` (which calls `z.toJSONSchema(outputSchema)`)
379
+ * couldn't surface the enum vocabulary, and ok-status details were
380
+ * incidentally blocked when an ok carried any non-enum `reason`.
381
+ * Refactored into a structural discriminated union on `status`:
382
+ *
383
+ * - `ok`: open `details` object (or null) — no `reason` constraint.
384
+ * - `warn`: optional `reason: DEV_DOCTOR_REASONS` enum + open
385
+ * extras; supports both unstructured warnings (archived board)
386
+ * and structured ones (settings_unparseable).
387
+ * - `fail`: REQUIRED `reason: DEV_DOCTOR_REASONS` enum + open
388
+ * extras; `details` is non-nullable on fail (every failure
389
+ * surfaces a structured reason).
390
+ *
391
+ * Open-ended extra keys per-status mirror Monday's per-call payload
392
+ * variability — every check emits its own context fields
393
+ * (`board_id`, `column_id`, etc.) under the same status family.
394
+ */
395
+ export const okCheckDetailsSchema = z
396
+ .record(z.string(), z.unknown())
397
+ .nullable();
398
+ export const warnCheckDetailsSchema = z
399
+ .object({ reason: devDoctorReasonSchema.optional() })
400
+ .loose()
401
+ .nullable();
402
+ export const failCheckDetailsSchema = z
403
+ .object({ reason: devDoctorReasonSchema })
404
+ .loose();
405
+ export const devDoctorCheckResultOkSchema = z
406
+ .object({
407
+ name: z.enum(DEV_DOCTOR_CHECK_NAMES),
408
+ status: z.literal('ok'),
409
+ message: z.string().min(1),
410
+ details: okCheckDetailsSchema,
411
+ })
412
+ .strict();
413
+ export const devDoctorCheckResultWarnSchema = z
414
+ .object({
415
+ name: z.enum(DEV_DOCTOR_CHECK_NAMES),
416
+ status: z.literal('warn'),
417
+ message: z.string().min(1),
418
+ details: warnCheckDetailsSchema,
419
+ })
420
+ .strict();
421
+ export const devDoctorCheckResultFailSchema = z
422
+ .object({
423
+ name: z.enum(DEV_DOCTOR_CHECK_NAMES),
424
+ status: z.literal('fail'),
425
+ message: z.string().min(1),
426
+ details: failCheckDetailsSchema,
427
+ })
428
+ .strict();
429
+ export const devDoctorCheckResultSchema = z.discriminatedUnion('status', [
430
+ devDoctorCheckResultOkSchema,
431
+ devDoctorCheckResultWarnSchema,
432
+ devDoctorCheckResultFailSchema,
433
+ ]);
434
+ export const devDoctorOutputSchema = z
435
+ .object({
436
+ profile: z.string().min(1),
437
+ mapping: devMappingSchema,
438
+ checks: z.array(devDoctorCheckResultSchema),
439
+ summary: z
440
+ .object({
441
+ ok_count: z.number().int().nonnegative(),
442
+ warn_count: z.number().int().nonnegative(),
443
+ fail_count: z.number().int().nonnegative(),
444
+ })
445
+ .strict(),
446
+ })
447
+ .strict();
448
+ /**
449
+ * Internal wire-row shape for the boards walker. Carries the
450
+ * minimum fields the heuristic + the `Board.type === 'board'`
451
+ * filter need.
452
+ */
453
+ const rawDiscoverBoardRowSchema = z
454
+ .object({
455
+ id: z.string().min(1),
456
+ name: z.string(),
457
+ workspace_id: z.string().nullable(),
458
+ type: z.string().nullable(),
459
+ })
460
+ .loose();
461
+ const rawDiscoverBoardsResponseSchema = z
462
+ .object({
463
+ boards: z.array(rawDiscoverBoardRowSchema.nullable()).nullable(),
464
+ })
465
+ .loose();
466
+ /**
467
+ * Hard cap on the walker's page count. At 200 boards per page that's
468
+ * up to 10000 boards before the walker truncates — far above any
469
+ * realistic dev workspace.
470
+ */
471
+ const DISCOVER_PAGE_LIMIT = 200;
472
+ const DISCOVER_PAGE_CAP = 50;
473
+ /**
474
+ * Walks the user's accessible boards (optionally scoped to
475
+ * `workspaceId`) and groups them by dev-noun via the heuristic.
476
+ *
477
+ * **Walker contract.** Pages through `boards(limit:, page:, state:
478
+ * all[, workspace_ids:])` until a short page indicates the end of
479
+ * results OR the page cap is reached. Per the M26a IMPL handoff,
480
+ * `state: all` is passed so archived / deleted boards surface to the
481
+ * heuristic too — the action body surfaces them on
482
+ * {@link DevDiscoverOutput.matches} for agent-side review. The
483
+ * walker silently drops `Board.type !== 'board'` rows
484
+ * (`sub_items_board` virtual boards, `custom_object` entries,
485
+ * `document` entries) since those aren't valid dev-noun mapping
486
+ * targets — see the module docstring for the empirical-probe
487
+ * rationale.
488
+ */
489
+ export const discoverDevBoards = async (inputs) => {
490
+ const candidates = [];
491
+ let page = 1;
492
+ let lastComplexity;
493
+ for (;;) {
494
+ const query = inputs.workspaceId === undefined
495
+ ? `query DevDiscoverBoards($limit: Int!, $page: Int!) {
496
+ boards(limit: $limit, page: $page, state: all) {
497
+ id name workspace_id type
498
+ }
499
+ }`
500
+ : `query DevDiscoverBoardsScoped($limit: Int!, $page: Int!, $wsids: [ID!]) {
501
+ boards(limit: $limit, page: $page, state: all, workspace_ids: $wsids) {
502
+ id name workspace_id type
503
+ }
504
+ }`;
505
+ const variables = inputs.workspaceId === undefined
506
+ ? { limit: DISCOVER_PAGE_LIMIT, page }
507
+ : {
508
+ limit: DISCOVER_PAGE_LIMIT,
509
+ page,
510
+ wsids: [inputs.workspaceId],
511
+ };
512
+ const response = await inputs.client.raw(query, variables, {
513
+ operationName: inputs.workspaceId === undefined
514
+ ? 'DevDiscoverBoards'
515
+ : 'DevDiscoverBoardsScoped',
516
+ });
517
+ lastComplexity = response.complexity;
518
+ const parsed = unwrapOrThrow(rawDiscoverBoardsResponseSchema.safeParse(response.data), {
519
+ context: 'Monday `boards()` response (dev discover walker)',
520
+ hint: 'Monday may have amended the `boards()` selection set — re-probe via `scripts/probe/m26-dev-discover.ts` and amend the walker schema if so',
521
+ });
522
+ const rows = (parsed.boards ?? []).filter((b) => b !== null);
523
+ for (const row of rows) {
524
+ // Filter `type !== 'board'`: drops `sub_items_board` virtual
525
+ // entries (Monday auto-generates `Subitems of <BoardName>` for
526
+ // any board with subitems enabled), `custom_object` (Monday's
527
+ // custom-object surface), and `document` (Monday workdocs).
528
+ // Empirical-probe finding pinned at 2026-05-11; see the
529
+ // module docstring for the full rationale.
530
+ if (row.type !== 'board')
531
+ continue;
532
+ candidates.push({
533
+ id: row.id,
534
+ name: row.name,
535
+ workspace_id: row.workspace_id,
536
+ });
537
+ }
538
+ if (rows.length < DISCOVER_PAGE_LIMIT)
539
+ break;
540
+ page += 1;
541
+ if (page > DISCOVER_PAGE_CAP)
542
+ break;
543
+ }
544
+ const matches = groupCandidatesByDevNoun(candidates);
545
+ return {
546
+ candidates,
547
+ matches,
548
+ source: 'live',
549
+ cacheAgeSeconds: null,
550
+ complexity: lastComplexity,
551
+ };
552
+ };
553
+ /**
554
+ * Internal wire-row shape for the doctor's per-board hydration call.
555
+ * Carries the columns + state needed for the 10 pinned checks.
556
+ */
557
+ const rawDoctorColumnSchema = z
558
+ .object({
559
+ id: z.string(),
560
+ title: z.string(),
561
+ type: z.string(),
562
+ settings_str: z.string().nullable(),
563
+ })
564
+ .loose();
565
+ const rawDoctorBoardSchema = z
566
+ .object({
567
+ id: z.string().min(1),
568
+ name: z.string(),
569
+ state: z.string().nullable(),
570
+ columns: z.array(rawDoctorColumnSchema.nullable()).nullable(),
571
+ })
572
+ .loose();
573
+ const rawDoctorResponseSchema = z
574
+ .object({
575
+ boards: z.array(rawDoctorBoardSchema.nullable()).nullable(),
576
+ })
577
+ .loose();
578
+ /**
579
+ * Canonical status-column labels Monday's stock Tasks template
580
+ * surfaces. Used by the `tasks_status_labels_canonical` check to
581
+ * warn when the configured tasks board's status column has drifted
582
+ * from the stock label set. Case-folded match per the heuristic's
583
+ * NFC convention.
584
+ */
585
+ const CANONICAL_STATUS_LABELS = ['Done', 'Working on it', 'Stuck'];
586
+ /**
587
+ * Date-column types the `sprints_date_columns_present` check
588
+ * accepts as a valid sprint date-range column. `timeline` is a
589
+ * single-column date-range; `date` covers split start/end columns.
590
+ */
591
+ const SPRINT_DATE_COLUMN_TYPES = new Set([
592
+ 'date',
593
+ 'timeline',
594
+ ]);
595
+ const findBoardById = (boards, id) => {
596
+ if (id === undefined)
597
+ return undefined;
598
+ return boards.find((b) => b.id === id);
599
+ };
600
+ const liveColumns = (board) => (board.columns ?? []).filter((c) => c !== null);
601
+ const okResult = (name, message, details = null) => ({ name, status: 'ok', message, details });
602
+ const warnResult = (name, message, details = null) => ({ name, status: 'warn', message, details });
603
+ // Codex M26a IMPL round-2 P2-1: failResult now REQUIRES a
604
+ // `reason: DevDoctorReason` field so TypeScript enforces the
605
+ // pinned enum at every fail emit site (was previously runtime-only
606
+ // via superRefine). The check's contract is "every failure has a
607
+ // stable, agent-keyable reason".
608
+ const failResult = (name, message, details) => ({ name, status: 'fail', message, details });
609
+ /**
610
+ * `<noun>_board_exists` family. Verifies a mapping slot is set AND
611
+ * the configured board ID resolves to an accessible board via the
612
+ * `boards(ids:)` hydration. `ok` when the board exists and is
613
+ * active; `warn` when archived (still usable but flagged for
614
+ * agent review); `fail` when the slot is unset, the board ID
615
+ * returned null (deleted / inaccessible), or the board state is
616
+ * `deleted`.
617
+ */
618
+ const checkBoardExists = (name, slotName, mapping, boards) => {
619
+ const boardId = mapping[slotName];
620
+ if (boardId === undefined) {
621
+ return failResult(name, `${slotName} not configured for this profile`, {
622
+ slot: slotName,
623
+ reason: 'not_in_mapping',
624
+ hint: `set the slot via \`monday dev configure --${slotName.replace('_board', '-board')} <bid>\` or \`monday dev discover --apply\``,
625
+ });
626
+ }
627
+ const board = findBoardById(boards, boardId);
628
+ if (board === undefined) {
629
+ return failResult(name, `${slotName} (${boardId}) is not accessible — board deleted, access revoked, or board never existed`, {
630
+ slot: slotName,
631
+ board_id: boardId,
632
+ reason: 'not_accessible',
633
+ hint: 're-run `monday dev discover` to pick up the current workspace shape',
634
+ });
635
+ }
636
+ if (board.state === 'archived') {
637
+ return warnResult(name, `${slotName} (${boardId}) is archived — dev verbs will still resolve against it`, {
638
+ slot: slotName,
639
+ board_id: boardId,
640
+ board_name: board.name,
641
+ state: 'archived',
642
+ });
643
+ }
644
+ if (board.state === 'deleted') {
645
+ return failResult(name, `${slotName} (${boardId}) is in state 'deleted'`, {
646
+ slot: slotName,
647
+ board_id: boardId,
648
+ board_name: board.name,
649
+ state: 'deleted',
650
+ reason: 'board_deleted',
651
+ });
652
+ }
653
+ return okResult(name, `${slotName} (${boardId}) exists and is accessible`, {
654
+ slot: slotName,
655
+ board_id: boardId,
656
+ board_name: board.name,
657
+ state: board.state,
658
+ });
659
+ };
660
+ /**
661
+ * `tasks_status_column_present` — verifies the configured tasks
662
+ * board has a column of type `status` or `color` (Monday's two
663
+ * label-shaped column types). `fail` when no tasks board is
664
+ * configured / accessible OR the board has no status-shaped
665
+ * column.
666
+ */
667
+ const checkTasksStatusColumnPresent = (mapping, boards) => {
668
+ const name = 'tasks_status_column_present';
669
+ const tasks = findBoardById(boards, mapping.tasks_board);
670
+ if (tasks === undefined) {
671
+ return failResult(name, 'tasks board not configured or not accessible', {
672
+ reason: 'no_tasks_board',
673
+ hint: 'fix `tasks_board_exists` first (see check above)',
674
+ });
675
+ }
676
+ const statusColumn = liveColumns(tasks).find((c) => c.type === 'status' || c.type === 'color');
677
+ if (statusColumn === undefined) {
678
+ return failResult(name, `tasks board (${tasks.id}) has no status-shaped column`, {
679
+ board_id: tasks.id,
680
+ reason: 'no_status_column',
681
+ hint: 'add a Status column to the tasks board, or re-run `monday dev discover` if you intended a different board',
682
+ });
683
+ }
684
+ return okResult(name, `tasks board (${tasks.id}) has status column \`${statusColumn.id}\``, {
685
+ board_id: tasks.id,
686
+ column_id: statusColumn.id,
687
+ column_title: statusColumn.title,
688
+ column_type: statusColumn.type,
689
+ });
690
+ };
691
+ const parseStatusLabels = (settingsStr) => {
692
+ if (settingsStr === null || settingsStr.length === 0)
693
+ return null;
694
+ let parsed;
695
+ try {
696
+ parsed = JSON.parse(settingsStr);
697
+ }
698
+ catch {
699
+ return null;
700
+ }
701
+ if (parsed === null || typeof parsed !== 'object')
702
+ return null;
703
+ const labels = parsed.labels;
704
+ if (labels === null || labels === undefined || typeof labels !== 'object') {
705
+ return null;
706
+ }
707
+ return Object.values(labels)
708
+ .filter((v) => typeof v === 'string')
709
+ .map((s) => s.trim())
710
+ .filter((s) => s.length > 0);
711
+ };
712
+ /**
713
+ * `tasks_status_labels_canonical` — verifies the tasks board's
714
+ * status column carries Monday Dev's stock labels (`Done` / `Working
715
+ * on it` / `Stuck`). `warn` when one or more canonical labels are
716
+ * missing; `ok` when all three are present.
717
+ */
718
+ const checkTasksStatusLabelsCanonical = (mapping, boards) => {
719
+ const name = 'tasks_status_labels_canonical';
720
+ const tasks = findBoardById(boards, mapping.tasks_board);
721
+ if (tasks === undefined) {
722
+ return failResult(name, 'tasks board not configured or not accessible', {
723
+ reason: 'no_tasks_board',
724
+ });
725
+ }
726
+ const statusColumn = liveColumns(tasks).find((c) => c.type === 'status' || c.type === 'color');
727
+ if (statusColumn === undefined) {
728
+ return failResult(name, `tasks board (${tasks.id}) has no status-shaped column`, {
729
+ board_id: tasks.id,
730
+ reason: 'no_status_column',
731
+ });
732
+ }
733
+ const labels = parseStatusLabels(statusColumn.settings_str);
734
+ if (labels === null) {
735
+ return warnResult(name, `status column \`${statusColumn.id}\` has unparseable settings_str`, {
736
+ board_id: tasks.id,
737
+ column_id: statusColumn.id,
738
+ reason: 'settings_unparseable',
739
+ });
740
+ }
741
+ const normalised = new Set(labels.map((l) => l.toLocaleLowerCase('und')));
742
+ const missing = CANONICAL_STATUS_LABELS.filter((l) => !normalised.has(l.toLocaleLowerCase('und')));
743
+ if (missing.length > 0) {
744
+ return warnResult(name, `status column \`${statusColumn.id}\` is missing canonical labels: ${missing.join(', ')}`, {
745
+ board_id: tasks.id,
746
+ column_id: statusColumn.id,
747
+ present_labels: labels,
748
+ missing_labels: missing,
749
+ hint: 'add the missing labels via the Monday UI, or update your workflow to use the labels this column carries',
750
+ });
751
+ }
752
+ return okResult(name, `status column \`${statusColumn.id}\` has all canonical labels`, {
753
+ board_id: tasks.id,
754
+ column_id: statusColumn.id,
755
+ labels,
756
+ });
757
+ };
758
+ /**
759
+ * `sprints_date_columns_present` — verifies the sprints board has a
760
+ * date-range column (a `timeline` column or split `date` start/end
761
+ * columns) so the sprint-state filter on `dev sprint list --state`
762
+ * can derive active / past / future from the date range.
763
+ */
764
+ const checkSprintsDateColumnsPresent = (mapping, boards) => {
765
+ const name = 'sprints_date_columns_present';
766
+ const sprints = findBoardById(boards, mapping.sprints_board);
767
+ if (sprints === undefined) {
768
+ return failResult(name, 'sprints board not configured or not accessible', {
769
+ reason: 'no_sprints_board',
770
+ });
771
+ }
772
+ const dateColumns = liveColumns(sprints).filter((c) => SPRINT_DATE_COLUMN_TYPES.has(c.type));
773
+ if (dateColumns.length === 0) {
774
+ return failResult(name, `sprints board (${sprints.id}) has no date-range column (need at least one of: timeline, date)`, {
775
+ board_id: sprints.id,
776
+ reason: 'no_date_columns',
777
+ hint: 'add a Timeline column to the sprints board for date-range-derived sprint state',
778
+ });
779
+ }
780
+ const timeline = dateColumns.find((c) => c.type === 'timeline');
781
+ if (timeline !== undefined) {
782
+ return okResult(name, `sprints board (${sprints.id}) has timeline column \`${timeline.id}\``, {
783
+ board_id: sprints.id,
784
+ column_id: timeline.id,
785
+ column_type: 'timeline',
786
+ });
787
+ }
788
+ const dateCols = dateColumns.filter((c) => c.type === 'date');
789
+ if (dateCols.length < 2) {
790
+ return warnResult(name, `sprints board (${sprints.id}) has only ${String(dateCols.length)} date column(s); need either a timeline column or split start/end date columns`, {
791
+ board_id: sprints.id,
792
+ date_column_ids: dateCols.map((c) => c.id),
793
+ hint: 'add a second date column for the sprint end date, or migrate to a timeline column',
794
+ });
795
+ }
796
+ return okResult(name, `sprints board (${sprints.id}) has ${String(dateCols.length)} date columns (start/end)`, {
797
+ board_id: sprints.id,
798
+ date_column_ids: dateCols.map((c) => c.id),
799
+ });
800
+ };
801
+ const parseBoardRelationTargets = (settingsStr) => {
802
+ if (settingsStr === null || settingsStr.length === 0)
803
+ return null;
804
+ let parsed;
805
+ try {
806
+ parsed = JSON.parse(settingsStr);
807
+ }
808
+ catch {
809
+ return null;
810
+ }
811
+ if (parsed === null || typeof parsed !== 'object')
812
+ return null;
813
+ const ids = parsed.boardIds
814
+ ?? parsed.board_ids;
815
+ if (!Array.isArray(ids))
816
+ return null;
817
+ return ids
818
+ .map((v) => (typeof v === 'string' ? v : typeof v === 'number' ? String(v) : ''))
819
+ .filter((s) => s.length > 0);
820
+ };
821
+ /**
822
+ * `tasks_to_<target>_relation` family. Verifies the tasks board has
823
+ * a `board_relation` column whose `settings_str` references the
824
+ * target board's ID.
825
+ */
826
+ const checkBoardRelation = (name, targetSlot, mapping, boards) => {
827
+ const tasks = findBoardById(boards, mapping.tasks_board);
828
+ if (tasks === undefined) {
829
+ return failResult(name, 'tasks board not configured or not accessible', {
830
+ reason: 'no_tasks_board',
831
+ });
832
+ }
833
+ const targetBoardId = mapping[targetSlot];
834
+ if (targetBoardId === undefined) {
835
+ return failResult(name, `${targetSlot} not configured — cannot verify board_relation wiring`, {
836
+ target_slot: targetSlot,
837
+ reason: 'no_target_board',
838
+ });
839
+ }
840
+ const relationColumns = liveColumns(tasks).filter((c) => c.type === 'board_relation');
841
+ if (relationColumns.length === 0) {
842
+ return failResult(name, `tasks board (${tasks.id}) has no board_relation columns`, {
843
+ board_id: tasks.id,
844
+ target_slot: targetSlot,
845
+ target_board_id: targetBoardId,
846
+ reason: 'no_relation_column',
847
+ hint: 'add a Connect Boards column on the tasks board pointing to the target board',
848
+ });
849
+ }
850
+ for (const col of relationColumns) {
851
+ const targets = parseBoardRelationTargets(col.settings_str);
852
+ if (targets?.includes(targetBoardId) === true) {
853
+ return okResult(name, `tasks board (${tasks.id}) column \`${col.id}\` links to ${targetSlot} (${targetBoardId})`, {
854
+ board_id: tasks.id,
855
+ column_id: col.id,
856
+ target_slot: targetSlot,
857
+ target_board_id: targetBoardId,
858
+ });
859
+ }
860
+ }
861
+ return failResult(name, `no board_relation column on tasks board (${tasks.id}) links to ${targetSlot} (${targetBoardId})`, {
862
+ board_id: tasks.id,
863
+ target_slot: targetSlot,
864
+ target_board_id: targetBoardId,
865
+ relation_column_ids: relationColumns.map((c) => c.id),
866
+ reason: 'no_matching_relation',
867
+ hint: `update one of the relation columns to target board ${targetBoardId}, or run \`monday dev configure --${targetSlot.replace('_board', '-board')} <correct-bid>\``,
868
+ });
869
+ };
870
+ /**
871
+ * Hydrates every configured board in `mapping` via a single
872
+ * `boards(ids:)` call so the doctor checks operate over in-memory
873
+ * data without extra round-trips. Returns `complexity: null` when
874
+ * no boards are configured (no wire call made).
875
+ */
876
+ const hydrateDoctorBoards = async (client, mapping) => {
877
+ const configuredIds = Array.from(new Set(Object.values(mapping).filter((v) => typeof v === 'string' && v.length > 0)));
878
+ if (configuredIds.length === 0) {
879
+ return { boards: [], complexity: null };
880
+ }
881
+ const response = await client.raw(`query DevDoctorBoards($ids: [ID!]!) {
882
+ boards(ids: $ids, state: all) {
883
+ id name state
884
+ columns { id title type settings_str }
885
+ }
886
+ }`, { ids: configuredIds }, { operationName: 'DevDoctorBoards' });
887
+ const parsed = unwrapOrThrow(rawDoctorResponseSchema.safeParse(response.data), {
888
+ context: 'Monday `boards(ids:)` response (dev doctor)',
889
+ hint: 'Monday may have amended the `boards(ids:)` selection set — re-probe and amend the doctor schema if so',
890
+ });
891
+ const boards = (parsed.boards ?? []).filter((b) => b !== null);
892
+ return { boards, complexity: response.complexity };
893
+ };
894
+ /**
895
+ * Runs every {@link DEV_DOCTOR_CHECK_NAMES} check against the
896
+ * `inputs.mapping`. Returns a per-check result list + a summary
897
+ * count. One `boards(ids:)` call hydrates every configured board's
898
+ * metadata; the 10 checks operate over the hydrated data.
899
+ *
900
+ * The verb's exit code stays 0 regardless of per-check `fail`
901
+ * counts — `dev doctor`'s success is "diagnostics completed";
902
+ * agents inspect `data.summary.fail_count` for drift.
903
+ * `dev_board_misconfigured` is reserved for the case where the
904
+ * doctor itself can't complete (no boards hydrated at all, etc.) —
905
+ * not surfaced here at this milestone (no configured boards = empty
906
+ * mapping = every `<noun>_board_exists` check fails, which is the
907
+ * correct diagnostic signal).
908
+ */
909
+ export const runDevDoctor = async (inputs) => {
910
+ const { boards, complexity } = await hydrateDoctorBoards(inputs.client, inputs.mapping);
911
+ const checks = [];
912
+ for (const name of DEV_DOCTOR_CHECK_NAMES) {
913
+ switch (name) {
914
+ case 'tasks_board_exists':
915
+ checks.push(checkBoardExists(name, 'tasks_board', inputs.mapping, boards));
916
+ break;
917
+ case 'tasks_status_column_present':
918
+ checks.push(checkTasksStatusColumnPresent(inputs.mapping, boards));
919
+ break;
920
+ case 'tasks_status_labels_canonical':
921
+ checks.push(checkTasksStatusLabelsCanonical(inputs.mapping, boards));
922
+ break;
923
+ case 'sprints_board_exists':
924
+ checks.push(checkBoardExists(name, 'sprints_board', inputs.mapping, boards));
925
+ break;
926
+ case 'sprints_date_columns_present':
927
+ checks.push(checkSprintsDateColumnsPresent(inputs.mapping, boards));
928
+ break;
929
+ case 'epics_board_exists':
930
+ checks.push(checkBoardExists(name, 'epics_board', inputs.mapping, boards));
931
+ break;
932
+ case 'releases_board_exists':
933
+ checks.push(checkBoardExists(name, 'releases_board', inputs.mapping, boards));
934
+ break;
935
+ case 'bugs_board_exists':
936
+ checks.push(checkBoardExists(name, 'bugs_board', inputs.mapping, boards));
937
+ break;
938
+ case 'tasks_to_sprints_relation':
939
+ checks.push(checkBoardRelation(name, 'sprints_board', inputs.mapping, boards));
940
+ break;
941
+ case 'tasks_to_epics_relation':
942
+ checks.push(checkBoardRelation(name, 'epics_board', inputs.mapping, boards));
943
+ break;
944
+ }
945
+ }
946
+ const summary = {
947
+ ok_count: checks.filter((c) => c.status === 'ok').length,
948
+ warn_count: checks.filter((c) => c.status === 'warn').length,
949
+ fail_count: checks.filter((c) => c.status === 'fail').length,
950
+ };
951
+ return { checks, summary, source: 'live', cacheAgeSeconds: null, complexity };
952
+ };
953
+ /**
954
+ * Reads the active profile's `[profiles.<name>.dev]` block. Throws
955
+ * `dev_not_configured` when:
956
+ * - no `config.toml` exists at all (`details.reason:
957
+ * "no_config_file"`),
958
+ * - the named profile is absent from the config
959
+ * (`details.reason: "profile_absent"`), OR
960
+ * - the named profile exists but has no `dev` sub-block
961
+ * (`details.reason: "no_dev_block"`).
962
+ *
963
+ * Each surface points the agent at `monday dev configure` /
964
+ * `monday dev discover --apply` via `details.hint`.
965
+ */
966
+ export const loadDevMapping = async (profile, options = {}) => {
967
+ const config = await loadProfilesConfig(options);
968
+ if (config === undefined) {
969
+ throw new ApiError('dev_not_configured', `Monday Dev mapping not configured for profile \`${profile}\` — no \`~/.monday-cli/config.toml\``, {
970
+ details: {
971
+ profile,
972
+ reason: 'no_config_file',
973
+ hint: 'run `monday dev discover --apply` to auto-detect Monday Dev boards, or `monday dev configure --tasks-board <bid> ...` to set them explicitly',
974
+ },
975
+ });
976
+ }
977
+ const entry = config.profiles[profile];
978
+ if (entry === undefined) {
979
+ throw new ApiError('dev_not_configured', `Monday Dev mapping not configured for profile \`${profile}\` — profile absent from \`config.toml\``, {
980
+ details: {
981
+ profile,
982
+ reason: 'profile_absent',
983
+ available_profiles: Object.keys(config.profiles),
984
+ hint: 'create the profile via `monday auth login --profile <name>`, or run `monday dev configure --profile <name> ...`',
985
+ },
986
+ });
987
+ }
988
+ if (entry.dev === undefined) {
989
+ throw new ApiError('dev_not_configured', `Monday Dev mapping not configured for profile \`${profile}\` — no \`[profiles.${profile}.dev]\` block`, {
990
+ details: {
991
+ profile,
992
+ reason: 'no_dev_block',
993
+ hint: 'run `monday dev discover --apply` to auto-detect, or `monday dev configure --tasks-board <bid> ...` to set explicit mappings',
994
+ },
995
+ });
996
+ }
997
+ return entry.dev;
998
+ };
999
+ /** Filesystem mode constant for the config.toml file — mirrors
1000
+ * credentials.ts's discipline (`.claude/rules/security.md`): files
1001
+ * under `~/.monday-cli/` carry user-scoped data even when not
1002
+ * directly token-bearing, so 0600 is the conservative default.
1003
+ */
1004
+ const CONFIG_FILE_MODE = 0o600;
1005
+ /**
1006
+ * Atomically writes the supplied `mapping` into
1007
+ * `profiles[profile].dev` in `~/.monday-cli/config.toml`. Creates
1008
+ * the file (and the named profile entry) if absent.
1009
+ *
1010
+ * **TOML round-trip behavior.** `smol-toml`'s `stringify` produces
1011
+ * canonical TOML output — comments and bespoke formatting from the
1012
+ * original file are NOT preserved. This is a contract correction
1013
+ * vs the M26 pre-flight docstring claim (to be flagged in the
1014
+ * M26a close-docs sweep's post-mortem). Mitigation: most config.toml
1015
+ * files are CLI-managed (`monday auth login` populates the
1016
+ * credentials side; this helper populates the dev side), so the
1017
+ * comment-preservation concern is narrow. A future v0.4 string-
1018
+ * surgery write path could preserve comments outside the dev block
1019
+ * if user demand surfaces.
1020
+ *
1021
+ * **Disk discipline (mirrors `src/config/credentials.ts`):**
1022
+ * 1. `mkdir({ recursive: true, mode: 0o700 })` + explicit `chmod
1023
+ * 0o700` on the parent dir.
1024
+ * 2. `writeFile(tmpPath, payload, { mode: 0o600 })`.
1025
+ * 3. `chmod(tmpPath, 0o600)` (re-applied since `writeFile`'s
1026
+ * `mode` is advisory under umask).
1027
+ * 4. `rename(tmpPath, finalPath)` (atomic on the same filesystem).
1028
+ *
1029
+ * **Idempotent:** re-writing the same mapping produces the same
1030
+ * bytes (modulo formatting). When `mapping` carries every existing
1031
+ * slot at the same value, the write is functionally a no-op.
1032
+ */
1033
+ export const saveDevMapping = async (profile, mapping, options = {}) => {
1034
+ // Load existing config (or start fresh with empty profiles map).
1035
+ const existing = await loadProfilesConfig(options);
1036
+ const baseConfig = existing ?? { profiles: {} };
1037
+ // Merge the dev block into the named profile entry. Preserves
1038
+ // every non-dev slot on the profile (api_token_env, api_version,
1039
+ // default_workspace, timezone) and every other profile in the
1040
+ // config file.
1041
+ const existingEntry = baseConfig.profiles[profile] ?? {};
1042
+ const nextEntry = {
1043
+ ...existingEntry,
1044
+ dev: mapping,
1045
+ };
1046
+ const nextConfig = {
1047
+ ...baseConfig,
1048
+ profiles: {
1049
+ ...baseConfig.profiles,
1050
+ [profile]: nextEntry,
1051
+ },
1052
+ };
1053
+ // Re-validate the full config before write so a caller passing a
1054
+ // malformed mapping (bypassing the per-field BoardIdSchema at the
1055
+ // argv layer) can't slip a bad file onto disk.
1056
+ const validated = profilesConfigSchema.parse(nextConfig);
1057
+ const fullPath = resolveProfilesConfigPath(options);
1058
+ const dir = join(options.home ?? homedir(), PROFILES_DIR_NAME);
1059
+ // Ensure secure directory (mirrors credentials.ts).
1060
+ try {
1061
+ await mkdir(dir, { recursive: true, mode: 0o700 });
1062
+ await chmod(dir, 0o700);
1063
+ }
1064
+ catch (err) {
1065
+ // Disk-full / permissions-denied path; not reproducible from a
1066
+ // unit test against a tmp dir.
1067
+ /* c8 ignore start */
1068
+ throw new ConfigError(`cannot prepare config directory ${dir}`, {
1069
+ cause: asError(err),
1070
+ details: { path: dir },
1071
+ });
1072
+ /* c8 ignore stop */
1073
+ }
1074
+ const payload = stringifyToml(validated);
1075
+ const tmpPath = `${fullPath}.${randomUUID()}.tmp`;
1076
+ try {
1077
+ await writeFile(tmpPath, payload, { mode: CONFIG_FILE_MODE });
1078
+ await chmod(tmpPath, CONFIG_FILE_MODE);
1079
+ await rename(tmpPath, fullPath);
1080
+ }
1081
+ catch (err) {
1082
+ // Disk-full / atomic-rename failure path; not reproducible from
1083
+ // a unit test against a tmp dir.
1084
+ /* c8 ignore start */
1085
+ await unlink(tmpPath).catch(() => undefined);
1086
+ throw new ConfigError(`cannot write config file ${fullPath}`, {
1087
+ cause: asError(err),
1088
+ details: { path: fullPath },
1089
+ });
1090
+ /* c8 ignore stop */
1091
+ }
1092
+ };
1093
+ // =============================================================
1094
+ // M26b workflow-verb helpers — shared between dev sprint/epic/
1095
+ // release/task verbs (cli-design §5.9 + §11.3; v0.3-plan §3 M26).
1096
+ //
1097
+ // The M26b verbs hydrate the configured dev board(s) by ID, walk
1098
+ // items_page on them, resolve board_relation columns + canonical
1099
+ // status labels — same shape recurring across verbs. Lifted here
1100
+ // rather than the per-verb files so the wire-call surface lives
1101
+ // one module away from the action body.
1102
+ // =============================================================
1103
+ /**
1104
+ * Sprint date-range state literal — `active` (today within range),
1105
+ * `past` (range ended before today), `future` (range starts after
1106
+ * today). Surfaced as the argv shape for `dev sprint list --state`
1107
+ * + the classification output of {@link classifySprint}.
1108
+ *
1109
+ * R-NEW-38 lift (post-M26b drift sweep): hoisted from
1110
+ * `commands/dev/sprint/list.ts:_internals` after the 3-consumer
1111
+ * threshold fired across `sprint/list.ts` + `sprint/current.ts` +
1112
+ * `task/list.ts` (the verb-file-to-verb-file cross-import via
1113
+ * `_internals` was the anti-pattern that surfaced the lift).
1114
+ */
1115
+ export const SPRINT_STATE_LITERALS = ['active', 'past', 'future'];
1116
+ /**
1117
+ * Parses a YYYY-MM-DD string into an epoch-ms day boundary (UTC).
1118
+ * NaN-guards per the M24 round-2 P3-1 precedent (`4c83860`) —
1119
+ * returns `null` on an unparseable / malformed date so the caller
1120
+ * falls through to the `past` default rather than emitting NaN-
1121
+ * shaped state buckets.
1122
+ */
1123
+ export const dayEpoch = (raw) => {
1124
+ if (raw === null || raw === undefined || raw.length === 0)
1125
+ return null;
1126
+ // Truncate any time component — Monday's date columns carry just
1127
+ // YYYY-MM-DD; timeline columns carry plain YYYY-MM-DD too.
1128
+ const head = raw.slice(0, 10);
1129
+ const epoch = Date.parse(`${head}T00:00:00Z`);
1130
+ if (Number.isNaN(epoch))
1131
+ return null;
1132
+ return epoch;
1133
+ };
1134
+ const firstDate = (col) => {
1135
+ if (col === undefined)
1136
+ return null;
1137
+ return typeof col.date === 'string' ? col.date : null;
1138
+ };
1139
+ /**
1140
+ * Extracts a sprint's date range from its projected columns. Prefers
1141
+ * a `timeline` column (parses `value.from` / `value.to`); falls back
1142
+ * to the first two `date` columns sorted by id (single date column
1143
+ * = single-day range; reversed start/end auto-normalised by epoch).
1144
+ * Returns `null` when no usable date columns are present.
1145
+ */
1146
+ export const extractDateRange = (item) => {
1147
+ const cols = Object.values(item.columns);
1148
+ // Prefer the timeline column when present.
1149
+ const timeline = cols.find((c) => c.type === 'timeline');
1150
+ if (timeline !== undefined && timeline.value !== null && typeof timeline.value === 'object') {
1151
+ const v = timeline.value;
1152
+ const start = typeof v.from === 'string' ? dayEpoch(v.from) : null;
1153
+ const end = typeof v.to === 'string' ? dayEpoch(v.to) : null;
1154
+ if (start !== null && end !== null) {
1155
+ return { start, end };
1156
+ }
1157
+ }
1158
+ // Fall back to date columns (sorted by id for deterministic
1159
+ // start/end assignment).
1160
+ const dateCols = cols
1161
+ .filter((c) => c.type === 'date')
1162
+ .slice()
1163
+ .sort((a, b) => a.id.localeCompare(b.id));
1164
+ const firstEpoch = dateCols.length > 0
1165
+ ? dayEpoch(firstDate(dateCols[0]))
1166
+ : null;
1167
+ const secondEpoch = dateCols.length > 1
1168
+ ? dayEpoch(firstDate(dateCols[1]))
1169
+ : null;
1170
+ if (firstEpoch === null)
1171
+ return null;
1172
+ if (secondEpoch === null) {
1173
+ // Single date column = single-day "range".
1174
+ return { start: firstEpoch, end: firstEpoch };
1175
+ }
1176
+ // Order-normalise — lowest epoch is start regardless of column id
1177
+ // order (a user who named columns "end_date" / "start_date" would
1178
+ // otherwise get reversed ranges).
1179
+ if (secondEpoch < firstEpoch) {
1180
+ return { start: secondEpoch, end: firstEpoch };
1181
+ }
1182
+ return { start: firstEpoch, end: secondEpoch };
1183
+ };
1184
+ /**
1185
+ * Classifies a sprint's state from its date range + the current
1186
+ * day's epoch. Sprints without a resolvable date range default to
1187
+ * `past` so a `--state past` filter catches misconfigured rows
1188
+ * (the structural drift is diagnosed via `dev doctor`'s
1189
+ * `sprints_date_columns_present` check; no warning code).
1190
+ */
1191
+ export const classifySprint = (range, todayEpoch) => {
1192
+ if (range === null)
1193
+ return 'past';
1194
+ if (todayEpoch < range.start)
1195
+ return 'future';
1196
+ if (todayEpoch > range.end)
1197
+ return 'past';
1198
+ return 'active';
1199
+ };
1200
+ /**
1201
+ * True iff the `details.issues` array carries exactly the
1202
+ * `boards`/`too_small` zod issue `fetchItemsPage`'s `.min(1)` schema
1203
+ * raises on an empty `boards` response. Used by
1204
+ * {@link walkDevBoardItems} to narrow the `internal_error` →
1205
+ * `dev_board_misconfigured` rewrap to the specific runtime mapping
1206
+ * drift surface; other schema-drift parse failures (e.g. Monday
1207
+ * adding a required field we haven't modeled) keep their original
1208
+ * `internal_error` + `details.issues` shape (Codex round-2 P2-1 fix).
1209
+ */
1210
+ const isEmptyBoardsArrayIssue = (details) => {
1211
+ const issues = details?.issues;
1212
+ if (!Array.isArray(issues))
1213
+ return false;
1214
+ return issues.some((issue) => {
1215
+ if (typeof issue !== 'object' || issue === null)
1216
+ return false;
1217
+ const i = issue;
1218
+ return i.path === 'boards' && i.code === 'too_small';
1219
+ });
1220
+ };
1221
+ /**
1222
+ * Walks every page of `items_page` on the supplied board and projects
1223
+ * the rows through the M4 {@link projectItem} contract. Used by the
1224
+ * read-side dev workflow verbs (`dev sprint list/items/current`,
1225
+ * `dev epic list/items`, `dev release list`, `dev task list`).
1226
+ *
1227
+ * Skips board-metadata cache loading — dev verbs don't expose
1228
+ * `--columns` selection, and the items_page rows include
1229
+ * `column { title }` per the {@link ITEM_FIELDS_FRAGMENT}, so the
1230
+ * fallback title path on {@link projectItem} is sufficient. Returns
1231
+ * the `complexity` from the *last* response so the verb's success
1232
+ * envelope reflects the freshest budget snapshot per `cli-design.md`
1233
+ * §6.1 — mirrors the {@link paginate} walker's idiom.
1234
+ */
1235
+ export const walkDevBoardItems = async (inputs) => {
1236
+ let result;
1237
+ try {
1238
+ result = await paginate({
1239
+ fetchInitial: (effectiveLimit) => fetchItemsPage({
1240
+ client: inputs.client,
1241
+ operationName: inputs.operationName,
1242
+ boardId: inputs.boardId,
1243
+ limit: effectiveLimit,
1244
+ queryParams: inputs.queryParams,
1245
+ itemFields: ITEM_FIELDS_FRAGMENT,
1246
+ itemSchema: walkerItemSchema,
1247
+ }),
1248
+ fetchNext: (cursor, effectiveLimit) => fetchNextItemsPage({
1249
+ client: inputs.client,
1250
+ operationName: `${inputs.operationName}Next`,
1251
+ cursor,
1252
+ limit: effectiveLimit,
1253
+ itemFields: ITEM_FIELDS_FRAGMENT,
1254
+ itemSchema: walkerItemSchema,
1255
+ }),
1256
+ extractPage: (r) => r.data,
1257
+ getId: idFromRawItem,
1258
+ all: true,
1259
+ now: inputs.now,
1260
+ });
1261
+ }
1262
+ catch (err) {
1263
+ // Codex M26b IMPL round-1 P2-2 + round-2 P2-1: an inaccessible
1264
+ // dev board (deleted / access revoked / never existed) returns
1265
+ // `{boards: []}`, which `fetchItemsPage`'s `.min(1)` schema rejects
1266
+ // as malformed → bare `internal_error`. For a dev workflow read
1267
+ // that's runtime mapping drift; rewrap to the namespace-stable
1268
+ // `dev_board_misconfigured` with `reason: 'not_accessible'`,
1269
+ // mirroring `hydrateDevBoardColumns`'s shape so the per-verb error
1270
+ // surface is consistent across read + mutation paths.
1271
+ //
1272
+ // **Narrowed to the exact `boards`/`too_small` zod issue** (round-2
1273
+ // P2-1 fix) so genuine schema drift on the items_page payload
1274
+ // (e.g. Monday adding a required field we haven't modeled) still
1275
+ // surfaces as `internal_error` with the full `details.issues`
1276
+ // array intact — without the narrowing, ANY parse failure carrying
1277
+ // this board's `board_id` would have collapsed into a misleading
1278
+ // `not_accessible` rewrap that drops the failing field path.
1279
+ if (err instanceof ApiError &&
1280
+ err.code === 'internal_error' &&
1281
+ err.details?.board_id ===
1282
+ inputs.boardId &&
1283
+ isEmptyBoardsArrayIssue(err.details)) {
1284
+ throw new ApiError('dev_board_misconfigured', `board ${inputs.boardId} is not accessible — deleted, access revoked, or never existed`, {
1285
+ cause: err,
1286
+ details: {
1287
+ board_id: inputs.boardId,
1288
+ reason: 'not_accessible',
1289
+ hint: 'run `monday dev doctor` to diagnose, then re-run `monday dev discover --apply` or `monday dev configure` to update the mapping',
1290
+ },
1291
+ });
1292
+ }
1293
+ throw err;
1294
+ }
1295
+ const items = result.items.map((raw) => projectItem({ raw: parseRawItem(raw) }));
1296
+ return { items, complexity: result.complexity };
1297
+ };
1298
+ const walkerItemSchema = z.unknown();
1299
+ /**
1300
+ * Hydrates one board's `columns { id title type settings_str }` slot
1301
+ * via a single `boards(ids:)` call. Used by mutation verbs
1302
+ * (`dev task start/done/block`) to resolve the status column ID +
1303
+ * label vocabulary, and by the relation-filter verbs
1304
+ * (`dev sprint items`, `dev epic items`) to find the board_relation
1305
+ * column linking the tasks board to a target.
1306
+ */
1307
+ export const hydrateDevBoardColumns = async (client, boardId, operationName) => {
1308
+ const response = await client.raw(`query ${operationName}($ids: [ID!]!) {
1309
+ boards(ids: $ids, state: all) {
1310
+ id
1311
+ columns { id title type settings_str }
1312
+ }
1313
+ }`, { ids: [boardId] }, { operationName });
1314
+ const parsed = unwrapOrThrow(rawDoctorResponseSchema.safeParse(response.data), {
1315
+ context: `Monday \`boards(ids:)\` response (${operationName})`,
1316
+ details: { board_id: boardId },
1317
+ });
1318
+ const boards = (parsed.boards ?? []).filter((b) => b !== null);
1319
+ if (boards.length === 0) {
1320
+ throw new ApiError('dev_board_misconfigured', `board ${boardId} is not accessible — deleted, access revoked, or never existed`, {
1321
+ details: {
1322
+ board_id: boardId,
1323
+ reason: 'not_accessible',
1324
+ hint: 'run `monday dev doctor` to diagnose, then re-run `monday dev discover --apply` or `monday dev configure` to update the mapping',
1325
+ },
1326
+ });
1327
+ }
1328
+ const board = boards[0];
1329
+ /* c8 ignore next 3 */
1330
+ if (board === undefined) {
1331
+ throw new ApiError('internal_error', `${operationName}: empty boards array`);
1332
+ }
1333
+ const columns = (board.columns ?? []).filter((c) => c !== null);
1334
+ return { columns, complexity: response.complexity };
1335
+ };
1336
+ /**
1337
+ * Walks `columns` looking for a `board_relation` column whose
1338
+ * `settings_str.boardIds` (or `board_ids`) array includes
1339
+ * `targetBoardId`. Returns the first match or `undefined`.
1340
+ *
1341
+ * Same `settings_str` parse as the doctor's
1342
+ * `checkBoardRelation` — pinned at M26a IMPL. Lifted here for
1343
+ * reuse by `dev sprint items` and `dev epic items`.
1344
+ */
1345
+ export const findRelationColumnIdToBoard = (columns, targetBoardId) => {
1346
+ for (const col of columns) {
1347
+ if (col.type !== 'board_relation')
1348
+ continue;
1349
+ const targets = parseBoardRelationTargets(col.settings_str);
1350
+ if (targets?.includes(targetBoardId) === true) {
1351
+ return col.id;
1352
+ }
1353
+ }
1354
+ return undefined;
1355
+ };
1356
+ /**
1357
+ * Extracts linked item IDs (as decimal strings) from a board_relation
1358
+ * column's parsed `value` JSON. Monday's wire shape is one of:
1359
+ * - `{linkedPulseIds: [{linkedPulseId: 123 | "123"}, ...]}`
1360
+ * - `{item_ids: [123 | "123", ...]}` (newer 2026-01 shape)
1361
+ * Returns an empty array on null / malformed / unrecognised shape.
1362
+ */
1363
+ export const extractLinkedItemIds = (value) => {
1364
+ if (value === null || typeof value !== 'object')
1365
+ return [];
1366
+ const v = value;
1367
+ const ids = [];
1368
+ const linkedPulse = v.linkedPulseIds;
1369
+ if (Array.isArray(linkedPulse)) {
1370
+ for (const entry of linkedPulse) {
1371
+ if (entry === null || typeof entry !== 'object')
1372
+ continue;
1373
+ const id = entry.linkedPulseId;
1374
+ if (typeof id === 'number')
1375
+ ids.push(String(id));
1376
+ else if (typeof id === 'string' && id.length > 0)
1377
+ ids.push(id);
1378
+ }
1379
+ }
1380
+ const itemIds = v.item_ids;
1381
+ if (Array.isArray(itemIds)) {
1382
+ for (const id of itemIds) {
1383
+ if (typeof id === 'number')
1384
+ ids.push(String(id));
1385
+ else if (typeof id === 'string' && id.length > 0)
1386
+ ids.push(id);
1387
+ }
1388
+ }
1389
+ return ids;
1390
+ };
1391
+ /**
1392
+ * Finds the status (or color) column on a board. Returns the column
1393
+ * + the parsed labels (id → label text). Throws
1394
+ * `dev_board_misconfigured` with `reason: 'no_status_column'` when
1395
+ * no status column is present (mirrors the doctor's
1396
+ * `tasks_status_column_present` fail surface; if doctor passes, this
1397
+ * lookup also passes).
1398
+ */
1399
+ export const resolveStatusColumn = (boardId, columns) => {
1400
+ const col = columns.find((c) => c.type === 'status' || c.type === 'color');
1401
+ if (col === undefined) {
1402
+ throw new ApiError('dev_board_misconfigured', `board ${boardId} has no status column`, {
1403
+ details: {
1404
+ board_id: boardId,
1405
+ reason: 'no_status_column',
1406
+ hint: 'add a Status column to the tasks board, then re-run `monday dev doctor` to verify',
1407
+ },
1408
+ });
1409
+ }
1410
+ const parsed = parseStatusLabels(col.settings_str);
1411
+ const map = new Map();
1412
+ if (parsed !== null) {
1413
+ for (const label of parsed) {
1414
+ map.set(label.toLocaleLowerCase('und'), label);
1415
+ }
1416
+ }
1417
+ return { columnId: col.id, labels: map };
1418
+ };
1419
+ /**
1420
+ * Resolves a canonical Monday Dev status label ("Working on it" /
1421
+ * "Done" / "Stuck") to the actual label text written on the
1422
+ * configured status column — case-insensitive match. Returns the
1423
+ * exact stored form so the subsequent
1424
+ * `change_simple_column_value` flips against bytes Monday accepts
1425
+ * (the wire is case-sensitive on the value).
1426
+ *
1427
+ * Throws `dev_board_misconfigured` with
1428
+ * `reason: 'no_status_column'` when the canonical label isn't
1429
+ * present on the column — points at `monday dev doctor` for
1430
+ * diagnostics. (Mirrors the doctor's
1431
+ * `tasks_status_labels_canonical` warn surface; the doctor's warn
1432
+ * doesn't block a workflow verb at the doctor layer, but the
1433
+ * workflow verb itself can't proceed without a matching label.)
1434
+ */
1435
+ export const resolveCanonicalLabel = (boardId, columnId, labels, canonical) => {
1436
+ const match = labels.get(canonical.toLocaleLowerCase('und'));
1437
+ if (match !== undefined)
1438
+ return match;
1439
+ throw new ApiError('dev_board_misconfigured', `tasks board ${boardId} status column \`${columnId}\` has no \`${canonical}\` label`, {
1440
+ details: {
1441
+ board_id: boardId,
1442
+ column_id: columnId,
1443
+ reason: 'no_status_column',
1444
+ canonical_label: canonical,
1445
+ present_labels: Array.from(labels.values()),
1446
+ hint: `add the \`${canonical}\` label to the status column, or run \`monday dev doctor\` to inspect the configured labels`,
1447
+ },
1448
+ });
1449
+ };
1450
+ /**
1451
+ * Flips a task's status column to the supplied canonical label
1452
+ * ("Working on it" / "Done" / "Stuck") on the configured tasks
1453
+ * board.
1454
+ *
1455
+ * **3-consumer helper.** `dev task start` + `dev task done` +
1456
+ * `dev task block` all share this exact shape (hydrate tasks board
1457
+ * columns → find status column → resolve canonical label → fire
1458
+ * `change_simple_column_value`). Lifted here at M26b IMPL so the
1459
+ * three verb files stay focused on their side-effects (start: none;
1460
+ * done: optional comment; block: required comment).
1461
+ *
1462
+ * Returns the post-mutation {@link ProjectedItem}, the resolved
1463
+ * status `columnId` + `label` for any caller that wants to log them,
1464
+ * and the `complexity` accumulated across the hydrate + mutation
1465
+ * calls (caller picks the freshest snapshot for envelope meta).
1466
+ */
1467
+ /**
1468
+ * Wire-shape parser for the `create_update` mutation Monday returns
1469
+ * on `dev task done --message` + `dev task block --reason` side-
1470
+ * effects. Mirrors the parse-boundary discipline `src/commands/
1471
+ * update/create.ts` uses (`assertResponseFieldPresent` + zod parse)
1472
+ * so the side-effect's `update_id` lands typed rather than via the
1473
+ * compile-time-only `client.raw<T>` generic.
1474
+ *
1475
+ * Codex M26b IMPL round-1 P2-3 fix: prior to this helper, both task
1476
+ * verbs read `response.data.create_update.id` via an unparsed
1477
+ * generic, which would have surfaced a raw TypeError on a malformed
1478
+ * response (and silently accepted a missing `id` field as
1479
+ * `undefined`).
1480
+ */
1481
+ const createUpdateResponseSchema = z
1482
+ .object({
1483
+ create_update: z
1484
+ .object({ id: z.string().min(1) })
1485
+ .strict()
1486
+ .nullable(),
1487
+ })
1488
+ .loose();
1489
+ /**
1490
+ * Builds the `create_update` mutation document with the supplied
1491
+ * operation name embedded as the GraphQL named-operation. Codex
1492
+ * round-2 P1-1 fix: prior to this builder, the doc was statically
1493
+ * named `DevCreateUpdate` while the wire `operationName` was per-
1494
+ * verb (`DevTaskDoneCreateUpdate` / `DevTaskBlockCreateUpdate`).
1495
+ * GraphQL servers may reject mismatched operationName + named-op
1496
+ * pairs ("Operation 'DevTaskDoneCreateUpdate' not found"); making
1497
+ * the two agree at every call site removes the drift class.
1498
+ * Mirrors the static pattern `executeItemMutation` uses (constants
1499
+ * where doc name + operationName always match).
1500
+ */
1501
+ const buildCreateUpdateMutation = (operationName) => `
1502
+ mutation ${operationName}($itemId: ID!, $body: String!) {
1503
+ create_update(item_id: $itemId, body: $body) {
1504
+ id
1505
+ }
1506
+ }
1507
+ `;
1508
+ /**
1509
+ * Fires the `create_update` mutation for a dev task side-effect
1510
+ * (`task done --message` / `task block --reason`) and returns the
1511
+ * created update's ID + the wire complexity. Shared by both verbs
1512
+ * (3-consumer threshold not yet reached, but the parse-boundary
1513
+ * discipline matters at every site).
1514
+ *
1515
+ * Throws `internal_error` when Monday's response carries
1516
+ * `create_update: null` (the documented null-payload escape hatch
1517
+ * for failed update creation; mirrors M5b's `internal_error` shape
1518
+ * per `item-mutation-result.ts`'s `caller_handles` semantics).
1519
+ */
1520
+ export const fireDevCreateUpdate = async (inputs) => {
1521
+ const response = await inputs.client.raw(buildCreateUpdateMutation(inputs.operationName), { itemId: inputs.itemId, body: inputs.body }, { operationName: inputs.operationName });
1522
+ const parsed = unwrapOrThrow(createUpdateResponseSchema.safeParse(response.data), {
1523
+ context: `Monday returned a malformed ${inputs.operationName} response`,
1524
+ details: { item_id: inputs.itemId },
1525
+ });
1526
+ if (parsed.create_update === null) {
1527
+ throw new ApiError('internal_error', `Monday returned no update payload from create_update for item ${inputs.itemId}`, { details: { item_id: inputs.itemId } });
1528
+ }
1529
+ return {
1530
+ updateId: parsed.create_update.id,
1531
+ complexity: response.complexity,
1532
+ };
1533
+ };
1534
+ export const flipTaskStatus = async (inputs) => {
1535
+ const { columns, complexity: hydrateComplexity } = await hydrateDevBoardColumns(inputs.client, inputs.tasksBoard, inputs.hydrateOperation);
1536
+ const { columnId, labels } = resolveStatusColumn(inputs.tasksBoard, columns);
1537
+ const label = resolveCanonicalLabel(inputs.tasksBoard, columnId, labels, inputs.canonical);
1538
+ const mutation = {
1539
+ kind: 'change_simple_column_value',
1540
+ columnId,
1541
+ value: label,
1542
+ };
1543
+ const result = await executeItemMutation(inputs.client, {
1544
+ mutation,
1545
+ itemId: inputs.itemId,
1546
+ boardId: inputs.tasksBoard,
1547
+ createLabelsIfMissing: false,
1548
+ });
1549
+ return {
1550
+ projected: result.projected,
1551
+ columnId,
1552
+ label,
1553
+ complexity: result.response.complexity ?? hydrateComplexity,
1554
+ };
1555
+ };
1556
+ //# sourceMappingURL=dev-conventions.js.map