monday-cli 0.2.0 → 0.4.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.
- package/CHANGELOG.md +675 -2
- package/README.md +223 -31
- package/dist/api/assets.d.ts +326 -0
- package/dist/api/assets.d.ts.map +1 -0
- package/dist/api/assets.js +519 -0
- package/dist/api/assets.js.map +1 -0
- package/dist/api/board-favorites.d.ts +329 -0
- package/dist/api/board-favorites.d.ts.map +1 -0
- package/dist/api/board-favorites.js +353 -0
- package/dist/api/board-favorites.js.map +1 -0
- package/dist/api/board-mutation-result.d.ts +9 -5
- package/dist/api/board-mutation-result.d.ts.map +1 -1
- package/dist/api/board-mutation-result.js +9 -5
- package/dist/api/board-mutation-result.js.map +1 -1
- package/dist/api/board-relation-validation.d.ts +161 -0
- package/dist/api/board-relation-validation.d.ts.map +1 -0
- package/dist/api/board-relation-validation.js +317 -0
- package/dist/api/board-relation-validation.js.map +1 -0
- package/dist/api/cache.d.ts +14 -5
- package/dist/api/cache.d.ts.map +1 -1
- package/dist/api/cache.js +8 -10
- package/dist/api/cache.js.map +1 -1
- package/dist/api/column-mapping.js +2 -2
- package/dist/api/column-mapping.js.map +1 -1
- package/dist/api/column-mutation-result.d.ts +9 -5
- package/dist/api/column-mutation-result.d.ts.map +1 -1
- package/dist/api/column-mutation-result.js +9 -5
- package/dist/api/column-mutation-result.js.map +1 -1
- package/dist/api/column-types.d.ts +37 -14
- package/dist/api/column-types.d.ts.map +1 -1
- package/dist/api/column-types.js +47 -6
- package/dist/api/column-types.js.map +1 -1
- package/dist/api/column-values.d.ts +234 -31
- package/dist/api/column-values.d.ts.map +1 -1
- package/dist/api/column-values.js +560 -124
- package/dist/api/column-values.js.map +1 -1
- package/dist/api/cross-board-search.d.ts +501 -0
- package/dist/api/cross-board-search.d.ts.map +1 -0
- package/dist/api/cross-board-search.js +547 -0
- package/dist/api/cross-board-search.js.map +1 -0
- package/dist/api/dev-conventions.d.ts +1038 -0
- package/dist/api/dev-conventions.d.ts.map +1 -0
- package/dist/api/dev-conventions.js +1556 -0
- package/dist/api/dev-conventions.js.map +1 -0
- package/dist/api/documents.d.ts +519 -0
- package/dist/api/documents.d.ts.map +1 -0
- package/dist/api/documents.js +586 -0
- package/dist/api/documents.js.map +1 -0
- package/dist/api/dry-run.d.ts +32 -5
- package/dist/api/dry-run.d.ts.map +1 -1
- package/dist/api/dry-run.js +149 -32
- package/dist/api/dry-run.js.map +1 -1
- package/dist/api/errors.d.ts.map +1 -1
- package/dist/api/errors.js +28 -7
- package/dist/api/errors.js.map +1 -1
- package/dist/api/group-mutation-result.d.ts +9 -5
- package/dist/api/group-mutation-result.d.ts.map +1 -1
- package/dist/api/group-mutation-result.js +9 -5
- package/dist/api/group-mutation-result.js.map +1 -1
- package/dist/api/item-history-projection.d.ts +919 -0
- package/dist/api/item-history-projection.d.ts.map +1 -0
- package/dist/api/item-history-projection.js +1104 -0
- package/dist/api/item-history-projection.js.map +1 -0
- package/dist/api/item-mutation-execute.d.ts +82 -0
- package/dist/api/item-mutation-execute.d.ts.map +1 -0
- package/dist/api/item-mutation-execute.js +199 -0
- package/dist/api/item-mutation-execute.js.map +1 -0
- package/dist/api/item-watch.d.ts +263 -0
- package/dist/api/item-watch.d.ts.map +1 -0
- package/dist/api/item-watch.js +709 -0
- package/dist/api/item-watch.js.map +1 -0
- package/dist/api/multipart-transport.d.ts +223 -0
- package/dist/api/multipart-transport.d.ts.map +1 -0
- package/dist/api/multipart-transport.js +274 -0
- package/dist/api/multipart-transport.js.map +1 -0
- package/dist/api/notifications.d.ts +156 -0
- package/dist/api/notifications.d.ts.map +1 -0
- package/dist/api/notifications.js +215 -0
- package/dist/api/notifications.js.map +1 -0
- package/dist/api/oauth-test-helper.d.ts +64 -0
- package/dist/api/oauth-test-helper.d.ts.map +1 -0
- package/dist/api/oauth-test-helper.js +179 -0
- package/dist/api/oauth-test-helper.js.map +1 -0
- package/dist/api/oauth.d.ts +198 -0
- package/dist/api/oauth.d.ts.map +1 -0
- package/dist/api/oauth.js +471 -0
- package/dist/api/oauth.js.map +1 -0
- package/dist/api/parallel-dispatch.d.ts +155 -0
- package/dist/api/parallel-dispatch.d.ts.map +1 -0
- package/dist/api/parallel-dispatch.js +243 -0
- package/dist/api/parallel-dispatch.js.map +1 -0
- package/dist/api/partial-success-bulk.d.ts +480 -0
- package/dist/api/partial-success-bulk.d.ts.map +1 -0
- package/dist/api/partial-success-bulk.js +436 -0
- package/dist/api/partial-success-bulk.js.map +1 -0
- package/dist/api/partial-success-mutation.d.ts +13 -1
- package/dist/api/partial-success-mutation.d.ts.map +1 -1
- package/dist/api/partial-success-mutation.js +5 -1
- package/dist/api/partial-success-mutation.js.map +1 -1
- package/dist/api/people.d.ts +54 -1
- package/dist/api/people.d.ts.map +1 -1
- package/dist/api/people.js +27 -3
- package/dist/api/people.js.map +1 -1
- package/dist/api/probes.d.ts +487 -0
- package/dist/api/probes.d.ts.map +1 -0
- package/dist/api/probes.js +881 -0
- package/dist/api/probes.js.map +1 -0
- package/dist/api/raw-document.d.ts.map +1 -1
- package/dist/api/raw-document.js +2 -2
- package/dist/api/raw-document.js.map +1 -1
- package/dist/api/raw-write.d.ts +12 -4
- package/dist/api/raw-write.d.ts.map +1 -1
- package/dist/api/raw-write.js +32 -14
- package/dist/api/raw-write.js.map +1 -1
- package/dist/api/resolution-context.d.ts +23 -11
- package/dist/api/resolution-context.d.ts.map +1 -1
- package/dist/api/resolution-context.js +53 -12
- package/dist/api/resolution-context.js.map +1 -1
- package/dist/api/resolution-pass.d.ts +30 -1
- package/dist/api/resolution-pass.d.ts.map +1 -1
- package/dist/api/resolution-pass.js +36 -1
- package/dist/api/resolution-pass.js.map +1 -1
- package/dist/api/resolve-client.d.ts +22 -0
- package/dist/api/resolve-client.d.ts.map +1 -1
- package/dist/api/resolve-client.js +9 -1
- package/dist/api/resolve-client.js.map +1 -1
- package/dist/api/response-root.d.ts +92 -46
- package/dist/api/response-root.d.ts.map +1 -1
- package/dist/api/response-root.js +93 -41
- package/dist/api/response-root.js.map +1 -1
- package/dist/api/tag-directory.d.ts +154 -0
- package/dist/api/tag-directory.d.ts.map +1 -0
- package/dist/api/tag-directory.js +325 -0
- package/dist/api/tag-directory.js.map +1 -0
- package/dist/api/time-tracking.d.ts +165 -0
- package/dist/api/time-tracking.d.ts.map +1 -0
- package/dist/api/time-tracking.js +135 -0
- package/dist/api/time-tracking.js.map +1 -0
- package/dist/api/transport.js +3 -3
- package/dist/api/transport.js.map +1 -1
- package/dist/api/usage.d.ts +190 -0
- package/dist/api/usage.d.ts.map +1 -0
- package/dist/api/usage.js +194 -0
- package/dist/api/usage.js.map +1 -0
- package/dist/api/users-fan-out-mutation.d.ts.map +1 -1
- package/dist/api/users-fan-out-mutation.js +10 -5
- package/dist/api/users-fan-out-mutation.js.map +1 -1
- package/dist/api/webhooks.d.ts +357 -0
- package/dist/api/webhooks.d.ts.map +1 -0
- package/dist/api/webhooks.js +333 -0
- package/dist/api/webhooks.js.map +1 -0
- package/dist/cli/envelope-out.d.ts +18 -1
- package/dist/cli/envelope-out.d.ts.map +1 -1
- package/dist/cli/envelope-out.js +16 -2
- package/dist/cli/envelope-out.js.map +1 -1
- package/dist/cli/program.d.ts.map +1 -1
- package/dist/cli/program.js +120 -1
- package/dist/cli/program.js.map +1 -1
- package/dist/cli/run.d.ts +32 -0
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +3 -0
- package/dist/cli/run.js.map +1 -1
- package/dist/commands/account/tags.d.ts +37 -0
- package/dist/commands/account/tags.d.ts.map +1 -0
- package/dist/commands/account/tags.js +84 -0
- package/dist/commands/account/tags.js.map +1 -0
- package/dist/commands/auth/login.d.ts +14 -0
- package/dist/commands/auth/login.d.ts.map +1 -0
- package/dist/commands/auth/login.js +314 -0
- package/dist/commands/auth/login.js.map +1 -0
- package/dist/commands/auth/logout.d.ts +28 -0
- package/dist/commands/auth/logout.d.ts.map +1 -0
- package/dist/commands/auth/logout.js +94 -0
- package/dist/commands/auth/logout.js.map +1 -0
- package/dist/commands/board/archive.d.ts.map +1 -1
- package/dist/commands/board/archive.js +14 -14
- package/dist/commands/board/archive.js.map +1 -1
- package/dist/commands/board/column-create.d.ts +9 -8
- package/dist/commands/board/column-create.d.ts.map +1 -1
- package/dist/commands/board/column-create.js +61 -51
- package/dist/commands/board/column-create.js.map +1 -1
- package/dist/commands/board/column-delete.d.ts.map +1 -1
- package/dist/commands/board/column-delete.js +15 -16
- package/dist/commands/board/column-delete.js.map +1 -1
- package/dist/commands/board/column-update.d.ts.map +1 -1
- package/dist/commands/board/column-update.js +23 -22
- package/dist/commands/board/column-update.js.map +1 -1
- package/dist/commands/board/create.d.ts.map +1 -1
- package/dist/commands/board/create.js +14 -17
- package/dist/commands/board/create.js.map +1 -1
- package/dist/commands/board/delete.d.ts.map +1 -1
- package/dist/commands/board/delete.js +12 -15
- package/dist/commands/board/delete.js.map +1 -1
- package/dist/commands/board/describe.d.ts.map +1 -1
- package/dist/commands/board/describe.js +30 -0
- package/dist/commands/board/describe.js.map +1 -1
- package/dist/commands/board/duplicate.d.ts.map +1 -1
- package/dist/commands/board/duplicate.js +12 -13
- package/dist/commands/board/duplicate.js.map +1 -1
- package/dist/commands/board/favorites.d.ts +33 -0
- package/dist/commands/board/favorites.d.ts.map +1 -0
- package/dist/commands/board/favorites.js +74 -0
- package/dist/commands/board/favorites.js.map +1 -0
- package/dist/commands/board/find.d.ts +1 -1
- package/dist/commands/board/group-archive.d.ts.map +1 -1
- package/dist/commands/board/group-archive.js +12 -16
- package/dist/commands/board/group-archive.js.map +1 -1
- package/dist/commands/board/group-create.d.ts.map +1 -1
- package/dist/commands/board/group-create.js +9 -19
- package/dist/commands/board/group-create.js.map +1 -1
- package/dist/commands/board/group-delete.d.ts.map +1 -1
- package/dist/commands/board/group-delete.js +12 -16
- package/dist/commands/board/group-delete.js.map +1 -1
- package/dist/commands/board/group-duplicate.d.ts.map +1 -1
- package/dist/commands/board/group-duplicate.js +12 -16
- package/dist/commands/board/group-duplicate.js.map +1 -1
- package/dist/commands/board/group-update.d.ts.map +1 -1
- package/dist/commands/board/group-update.js +12 -11
- package/dist/commands/board/group-update.js.map +1 -1
- package/dist/commands/board/list.d.ts +1 -1
- package/dist/commands/board/update.d.ts.map +1 -1
- package/dist/commands/board/update.js +16 -11
- package/dist/commands/board/update.js.map +1 -1
- package/dist/commands/cache/list.d.ts +2 -0
- package/dist/commands/cache/list.d.ts.map +1 -1
- package/dist/commands/cache/list.js +2 -2
- package/dist/commands/cache/list.js.map +1 -1
- package/dist/commands/completion.d.ts +188 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +418 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/dev/_shared.d.ts +40 -0
- package/dist/commands/dev/_shared.d.ts.map +1 -0
- package/dist/commands/dev/_shared.js +104 -0
- package/dist/commands/dev/_shared.js.map +1 -0
- package/dist/commands/dev/configure.d.ts +36 -0
- package/dist/commands/dev/configure.d.ts.map +1 -0
- package/dist/commands/dev/configure.js +145 -0
- package/dist/commands/dev/configure.js.map +1 -0
- package/dist/commands/dev/discover.d.ts +34 -0
- package/dist/commands/dev/discover.d.ts.map +1 -0
- package/dist/commands/dev/discover.js +117 -0
- package/dist/commands/dev/discover.js.map +1 -0
- package/dist/commands/dev/doctor.d.ts +39 -0
- package/dist/commands/dev/doctor.d.ts.map +1 -0
- package/dist/commands/dev/doctor.js +91 -0
- package/dist/commands/dev/doctor.js.map +1 -0
- package/dist/commands/dev/epic/items.d.ts +24 -0
- package/dist/commands/dev/epic/items.d.ts.map +1 -0
- package/dist/commands/dev/epic/items.js +103 -0
- package/dist/commands/dev/epic/items.js.map +1 -0
- package/dist/commands/dev/epic/list.d.ts +36 -0
- package/dist/commands/dev/epic/list.d.ts.map +1 -0
- package/dist/commands/dev/epic/list.js +120 -0
- package/dist/commands/dev/epic/list.js.map +1 -0
- package/dist/commands/dev/release/list.d.ts +21 -0
- package/dist/commands/dev/release/list.d.ts.map +1 -0
- package/dist/commands/dev/release/list.js +73 -0
- package/dist/commands/dev/release/list.js.map +1 -0
- package/dist/commands/dev/sprint/current.d.ts +24 -0
- package/dist/commands/dev/sprint/current.d.ts.map +1 -0
- package/dist/commands/dev/sprint/current.js +90 -0
- package/dist/commands/dev/sprint/current.js.map +1 -0
- package/dist/commands/dev/sprint/items.d.ts +34 -0
- package/dist/commands/dev/sprint/items.d.ts.map +1 -0
- package/dist/commands/dev/sprint/items.js +118 -0
- package/dist/commands/dev/sprint/items.js.map +1 -0
- package/dist/commands/dev/sprint/list.d.ts +41 -0
- package/dist/commands/dev/sprint/list.d.ts.map +1 -0
- package/dist/commands/dev/sprint/list.js +104 -0
- package/dist/commands/dev/sprint/list.js.map +1 -0
- package/dist/commands/dev/task/block.d.ts +29 -0
- package/dist/commands/dev/task/block.d.ts.map +1 -0
- package/dist/commands/dev/task/block.js +106 -0
- package/dist/commands/dev/task/block.js.map +1 -0
- package/dist/commands/dev/task/done.d.ts +30 -0
- package/dist/commands/dev/task/done.d.ts.map +1 -0
- package/dist/commands/dev/task/done.js +113 -0
- package/dist/commands/dev/task/done.js.map +1 -0
- package/dist/commands/dev/task/list.d.ts +42 -0
- package/dist/commands/dev/task/list.d.ts.map +1 -0
- package/dist/commands/dev/task/list.js +227 -0
- package/dist/commands/dev/task/list.js.map +1 -0
- package/dist/commands/dev/task/start.d.ts +29 -0
- package/dist/commands/dev/task/start.d.ts.map +1 -0
- package/dist/commands/dev/task/start.js +90 -0
- package/dist/commands/dev/task/start.js.map +1 -0
- package/dist/commands/doc/get.d.ts +46 -0
- package/dist/commands/doc/get.d.ts.map +1 -0
- package/dist/commands/doc/get.js +95 -0
- package/dist/commands/doc/get.js.map +1 -0
- package/dist/commands/doc/list.d.ts +83 -0
- package/dist/commands/doc/list.d.ts.map +1 -0
- package/dist/commands/doc/list.js +248 -0
- package/dist/commands/doc/list.js.map +1 -0
- package/dist/commands/emit.d.ts.map +1 -1
- package/dist/commands/emit.js +5 -3
- package/dist/commands/emit.js.map +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +141 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/item/archive.d.ts.map +1 -1
- package/dist/commands/item/archive.js +11 -0
- package/dist/commands/item/archive.js.map +1 -1
- package/dist/commands/item/clear.d.ts.map +1 -1
- package/dist/commands/item/clear.js +15 -0
- package/dist/commands/item/clear.js.map +1 -1
- package/dist/commands/item/create.d.ts.map +1 -1
- package/dist/commands/item/create.js +41 -8
- package/dist/commands/item/create.js.map +1 -1
- package/dist/commands/item/delete.d.ts.map +1 -1
- package/dist/commands/item/delete.js +11 -0
- package/dist/commands/item/delete.js.map +1 -1
- package/dist/commands/item/duplicate.d.ts.map +1 -1
- package/dist/commands/item/duplicate.js +12 -0
- package/dist/commands/item/duplicate.js.map +1 -1
- package/dist/commands/item/history.d.ts +60 -0
- package/dist/commands/item/history.d.ts.map +1 -0
- package/dist/commands/item/history.js +309 -0
- package/dist/commands/item/history.js.map +1 -0
- package/dist/commands/item/list.d.ts.map +1 -1
- package/dist/commands/item/list.js +16 -13
- package/dist/commands/item/list.js.map +1 -1
- package/dist/commands/item/move.d.ts.map +1 -1
- package/dist/commands/item/move.js +41 -7
- package/dist/commands/item/move.js.map +1 -1
- package/dist/commands/item/search.d.ts +99 -15
- package/dist/commands/item/search.d.ts.map +1 -1
- package/dist/commands/item/search.js +480 -36
- package/dist/commands/item/search.js.map +1 -1
- package/dist/commands/item/set.d.ts.map +1 -1
- package/dist/commands/item/set.js +52 -8
- package/dist/commands/item/set.js.map +1 -1
- package/dist/commands/item/time-track/start.d.ts +61 -0
- package/dist/commands/item/time-track/start.d.ts.map +1 -0
- package/dist/commands/item/time-track/start.js +138 -0
- package/dist/commands/item/time-track/start.js.map +1 -0
- package/dist/commands/item/time-track/stop.d.ts +32 -0
- package/dist/commands/item/time-track/stop.d.ts.map +1 -0
- package/dist/commands/item/time-track/stop.js +97 -0
- package/dist/commands/item/time-track/stop.js.map +1 -0
- package/dist/commands/item/update.d.ts +2 -0
- package/dist/commands/item/update.d.ts.map +1 -1
- package/dist/commands/item/update.js +164 -113
- package/dist/commands/item/update.js.map +1 -1
- package/dist/commands/item/upload.d.ts +108 -0
- package/dist/commands/item/upload.d.ts.map +1 -0
- package/dist/commands/item/upload.js +370 -0
- package/dist/commands/item/upload.js.map +1 -0
- package/dist/commands/item/upsert.d.ts.map +1 -1
- package/dist/commands/item/upsert.js +48 -1
- package/dist/commands/item/upsert.js.map +1 -1
- package/dist/commands/item/watch.d.ts +90 -0
- package/dist/commands/item/watch.d.ts.map +1 -0
- package/dist/commands/item/watch.js +342 -0
- package/dist/commands/item/watch.js.map +1 -0
- package/dist/commands/notification/send.d.ts +60 -0
- package/dist/commands/notification/send.d.ts.map +1 -0
- package/dist/commands/notification/send.js +147 -0
- package/dist/commands/notification/send.js.map +1 -0
- package/dist/commands/parse-argv.d.ts.map +1 -1
- package/dist/commands/parse-argv.js +14 -4
- package/dist/commands/parse-argv.js.map +1 -1
- package/dist/commands/raw/index.d.ts.map +1 -1
- package/dist/commands/raw/index.js +13 -15
- package/dist/commands/raw/index.js.map +1 -1
- package/dist/commands/run-by-id-lookup.d.ts.map +1 -1
- package/dist/commands/run-by-id-lookup.js +2 -2
- package/dist/commands/run-by-id-lookup.js.map +1 -1
- package/dist/commands/schema/index.d.ts +2 -0
- package/dist/commands/schema/index.d.ts.map +1 -1
- package/dist/commands/status.d.ts +120 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +365 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/update/body-source.d.ts.map +1 -1
- package/dist/commands/update/body-source.js +2 -2
- package/dist/commands/update/body-source.js.map +1 -1
- package/dist/commands/update/create.d.ts +2 -3
- package/dist/commands/update/create.d.ts.map +1 -1
- package/dist/commands/update/create.js +15 -3
- package/dist/commands/update/create.js.map +1 -1
- package/dist/commands/update/delete.d.ts.map +1 -1
- package/dist/commands/update/delete.js +11 -0
- package/dist/commands/update/delete.js.map +1 -1
- package/dist/commands/update/edit.d.ts.map +1 -1
- package/dist/commands/update/edit.js +11 -0
- package/dist/commands/update/edit.js.map +1 -1
- package/dist/commands/update/list.d.ts.map +1 -1
- package/dist/commands/update/list.js +15 -12
- package/dist/commands/update/list.js.map +1 -1
- package/dist/commands/update/reply.d.ts.map +1 -1
- package/dist/commands/update/reply.js +11 -0
- package/dist/commands/update/reply.js.map +1 -1
- package/dist/commands/update/toggle.d.ts.map +1 -1
- package/dist/commands/update/toggle.js +13 -0
- package/dist/commands/update/toggle.js.map +1 -1
- package/dist/commands/update/upload.d.ts +69 -0
- package/dist/commands/update/upload.d.ts.map +1 -0
- package/dist/commands/update/upload.js +235 -0
- package/dist/commands/update/upload.js.map +1 -0
- package/dist/commands/usage.d.ts +58 -0
- package/dist/commands/usage.d.ts.map +1 -0
- package/dist/commands/usage.js +94 -0
- package/dist/commands/usage.js.map +1 -0
- package/dist/commands/webhook/create.d.ts +74 -0
- package/dist/commands/webhook/create.d.ts.map +1 -0
- package/dist/commands/webhook/create.js +150 -0
- package/dist/commands/webhook/create.js.map +1 -0
- package/dist/commands/webhook/delete.d.ts +46 -0
- package/dist/commands/webhook/delete.d.ts.map +1 -0
- package/dist/commands/webhook/delete.js +141 -0
- package/dist/commands/webhook/delete.js.map +1 -0
- package/dist/commands/webhook/list.d.ts +23 -0
- package/dist/commands/webhook/list.d.ts.map +1 -0
- package/dist/commands/webhook/list.js +68 -0
- package/dist/commands/webhook/list.js.map +1 -0
- package/dist/commands/workspace/create.d.ts.map +1 -1
- package/dist/commands/workspace/create.js +16 -0
- package/dist/commands/workspace/create.js.map +1 -1
- package/dist/commands/workspace/delete.d.ts.map +1 -1
- package/dist/commands/workspace/delete.js +13 -13
- package/dist/commands/workspace/delete.js.map +1 -1
- package/dist/commands/workspace/list.d.ts +1 -1
- package/dist/commands/workspace/update.d.ts.map +1 -1
- package/dist/commands/workspace/update.js +15 -15
- package/dist/commands/workspace/update.js.map +1 -1
- package/dist/config/credentials.d.ts +189 -0
- package/dist/config/credentials.d.ts.map +1 -0
- package/dist/config/credentials.js +300 -0
- package/dist/config/credentials.js.map +1 -0
- package/dist/config/profiles.d.ts +125 -0
- package/dist/config/profiles.d.ts.map +1 -0
- package/dist/config/profiles.js +227 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/types/global-flags.d.ts +1 -1
- package/dist/types/global-flags.d.ts.map +1 -1
- package/dist/types/global-flags.js +28 -16
- package/dist/types/global-flags.js.map +1 -1
- package/dist/types/ids.d.ts +4 -0
- package/dist/types/ids.d.ts.map +1 -1
- package/dist/types/ids.js +12 -3
- package/dist/types/ids.js.map +1 -1
- package/dist/utils/errors.d.ts +57 -3
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +69 -2
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/fs.d.ts +35 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +36 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/json.d.ts +60 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +86 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/utils/mime.d.ts +24 -0
- package/dist/utils/mime.d.ts.map +1 -0
- package/dist/utils/mime.js +64 -0
- package/dist/utils/mime.js.map +1 -0
- package/dist/utils/output/envelope.d.ts +30 -0
- package/dist/utils/output/envelope.d.ts.map +1 -1
- package/dist/utils/output/envelope.js +26 -0
- package/dist/utils/output/envelope.js.map +1 -1
- package/dist/utils/output/ndjson.d.ts +90 -3
- package/dist/utils/output/ndjson.d.ts.map +1 -1
- package/dist/utils/output/ndjson.js +33 -0
- package/dist/utils/output/ndjson.js.map +1 -1
- package/dist/utils/redact.d.ts.map +1 -1
- package/dist/utils/redact.js +31 -0
- package/dist/utils/redact.js.map +1 -1
- package/dist/utils/signal.d.ts +42 -0
- package/dist/utils/signal.d.ts.map +1 -0
- package/dist/utils/signal.js +45 -0
- package/dist/utils/signal.js.map +1 -0
- package/package.json +2 -1
- package/dist/commands/account/client-helper.d.ts +0 -37
- package/dist/commands/account/client-helper.d.ts.map +0 -1
- package/dist/commands/account/client-helper.js +0 -55
- 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
|