@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.37 → 0.1.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/bin/adl +2 -2
  2. package/bin/reconcile +10 -13
  3. package/bin/setup +15 -15
  4. package/package.json +1 -1
  5. package/packages/agent-hooks/package.json +1 -1
  6. package/packages/agent-hooks/src/main.ts +23 -2
  7. package/packages/agent-hooks/src/rules/impl/adl-mcp-cli.ts +12 -8
  8. package/packages/agent-hooks/src/rules/impl/adl-run-required.ts +11 -5
  9. package/packages/agent-hooks/src/rules/impl/adl-workitem-role.ts +18 -5
  10. package/packages/agent-hooks/src/rules/impl/agent-tamper-protection.ts +30 -26
  11. package/packages/agent-hooks/src/rules/impl/git-branch.ts +1 -1
  12. package/packages/agent-hooks/src/rules/impl/git-target.ts +5 -0
  13. package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +3 -1
  14. package/packages/agent-hooks/src/rules/impl/tool-frontend.ts +230 -11
  15. package/packages/agent-hooks/src/rules/shell/git-identity.ts +1 -1
  16. package/packages/agent-hooks/src/rules/shell/invocation.ts +14 -2
  17. package/packages/agent-hooks/src/rules/shell/protected-carriers.ts +10 -11
  18. package/packages/agent-settings/assets/TARUN-GLOBAL-AGENTS-INSTRUCTIONS.md +2 -2
  19. package/packages/agent-settings/package.json +1 -1
  20. package/packages/agent-settings/src/deploy/agents-deploy.ts +19 -1
  21. package/packages/agent-settings/src/deploy/index.ts +1 -0
  22. package/packages/agent-skills/assets/agent-search-tool-audit/SKILL.md +4 -6
  23. package/packages/agent-skills/assets/finalize-feature/SKILL.md +3 -3
  24. package/packages/agent-skills/assets/implement-feature/SKILL.md +17 -20
  25. package/packages/agent-skills/assets/implement-feature/references/briefs.md +3 -3
  26. package/packages/agent-skills/assets/implement-item/SKILL.md +3 -3
  27. package/packages/agent-skills/assets/mechanics/SKILL.md +1 -1
  28. package/packages/agent-skills/assets/plan-feature/SKILL.md +10 -10
  29. package/packages/agent-skills/assets/prepare-promotion/SKILL.md +3 -4
  30. package/packages/agent-skills/assets/report-bug/SKILL.md +2 -2
  31. package/packages/agent-skills/assets/review-change/SKILL.md +3 -3
  32. package/packages/agent-skills/assets/review-plan/SKILL.md +4 -4
  33. package/packages/agent-skills/assets/triage-workitems/SKILL.md +4 -4
  34. package/packages/agent-skills/assets/validate-design/SKILL.md +6 -12
  35. package/packages/agent-skills/assets/verify-item/SKILL.md +5 -5
  36. package/packages/agent-skills/assets/wayfinder/SKILL.md +1 -1
  37. package/packages/agent-skills/assets/workitems-process/SKILL.md +19 -21
  38. package/packages/agent-skills/assets/workitems-process/references/ado-and-git.md +36 -3
  39. package/packages/agent-skills/assets/workitems-process/references/evidence.md +4 -4
  40. package/packages/agent-skills/assets/write-feature/SKILL.md +15 -18
  41. package/packages/agent-skills/dist/deploy.d.ts +45 -0
  42. package/packages/agent-skills/dist/deploy.d.ts.map +1 -1
  43. package/packages/agent-skills/dist/deploy.js +167 -3
  44. package/packages/agent-skills/dist/deploy.js.map +1 -1
  45. package/packages/agent-skills/dist/index.d.ts +1 -1
  46. package/packages/agent-skills/dist/index.d.ts.map +1 -1
  47. package/packages/agent-skills/dist/index.js +1 -1
  48. package/packages/agent-skills/dist/index.js.map +1 -1
  49. package/packages/agent-skills/package.json +1 -1
  50. package/packages/agent-skills/src/deploy.ts +241 -3
  51. package/packages/agent-skills/src/index.ts +10 -1
  52. package/packages/azdo/README.md +13 -4
  53. package/packages/azdo/bunfig.toml +6 -0
  54. package/packages/azdo/package.json +1 -1
  55. package/packages/azdo/src/azdo-attachments.ts +2 -1
  56. package/packages/azdo/src/azdo-pr.ts +182 -6
  57. package/packages/azdo/src/azdo-wit.ts +290 -41
  58. package/packages/azdo/src/cli.ts +79 -12
  59. package/packages/azdo/src/lib/client.ts +88 -16
  60. package/packages/azdo/src/mcp-server.ts +75 -10
  61. package/packages/azdo/src/tool-dispatch.ts +61 -26
  62. package/packages/fleet/src/install-adl-package.ts +4 -3
  63. package/packages/repo-controls/assets/promote.ts +1 -0
  64. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs/research/no-type.md +1 -1
  65. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs/research/typed-experiment.md +1 -1
  66. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-profile-untyped/docs/research/typed-research.md +1 -1
  67. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-key.md +1 -1
  68. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/extra-keys.md +1 -1
  69. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-created.md +1 -1
  70. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-description.md +1 -1
  71. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-last-update.md +1 -1
  72. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-status.md +1 -1
  73. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/missing-title.md +1 -1
  74. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/optional-keys.md +1 -1
  75. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-research/docs/research/research-ok.md +1 -1
  76. package/packages/repo-controls/package.json +1 -1
  77. package/packages/repo-controls/src/config.ts +21 -2
  78. package/packages/repo-controls/src/doctor.ts +9 -14
  79. package/packages/repo-controls/src/findings-ratchet.ts +12 -0
  80. package/packages/repo-controls/src/hook.ts +61 -4
  81. package/bin/lean-ctx-observe-strip +0 -434
  82. package/bin/lib/lean-ctx-observe-hooks.sh +0 -634
  83. package/bin/update-azure-agent-pool +0 -29
  84. package/packages/agent-hooks/src/hooks/lean-ctx/handler.ts +0 -22
  85. package/packages/agent-hooks/src/hooks/lean-ctx/index.ts +0 -173
  86. package/packages/agent-hooks/src/hooks/lean-ctx/mapping.ts +0 -121
  87. package/packages/agent-hooks/src/hooks/lean-ctx/scope-rewrite.ts +0 -207
  88. package/packages/agent-hooks/src/lean-ctx/env.ts +0 -123
  89. package/packages/agent-hooks/src/lean-ctx/mcp.ts +0 -142
  90. package/packages/agent-hooks/src/rules/impl/adl-build-run-denied.ts +0 -51
  91. package/packages/agent-hooks/src/rules/impl/adl-session-required.ts +0 -403
  92. package/packages/agent-hooks/src/rules/impl/tool-lean-ctx-global.ts +0 -594
  93. package/packages/agent-settings/assets/GLOBAL-AGENTS.md +0 -112
  94. package/packages/agent-settings/assets/lean-ctx/config.toml +0 -42
  95. package/packages/agent-settings/assets/lean-ctx/gitignore +0 -10
  96. package/packages/agent-settings/assets/lean-ctx/layout.toml +0 -7
  97. package/packages/agent-settings/src/deploy/lean-ctx-deploy.ts +0 -230
  98. package/packages/agent-settings/src/deploy/lean-ctx-merge.ts +0 -580
  99. package/packages/agent-skills/assets/adl-mechanics/SKILL.md +0 -38
  100. package/packages/agent-skills/assets/codebase-design/SKILL.md +0 -98
  101. package/packages/agent-skills/assets/codebase-design/references/DEEPENING.md +0 -37
  102. package/packages/agent-skills/assets/codebase-design/references/DESIGN-IT-TWICE.md +0 -44
  103. package/packages/agent-skills/assets/codebase-design/references/TESTABILITY.md +0 -29
  104. package/packages/agent-skills/assets/debug-issue/SKILL.md +0 -41
  105. package/packages/agent-skills/assets/domain-modeling/SKILL.md +0 -86
  106. package/packages/agent-skills/assets/domain-modeling/references/ADR-FORMAT.md +0 -47
  107. package/packages/agent-skills/assets/domain-modeling/references/CONTEXT-FORMAT.md +0 -60
  108. package/packages/agent-skills/assets/implement/SKILL.md +0 -28
  109. package/packages/agent-skills/assets/review-changes/SKILL.md +0 -45
  110. package/packages/agent-skills/assets/tdd/SKILL.md +0 -49
  111. package/packages/agent-skills/assets/tdd/references/mocking.md +0 -59
  112. package/packages/agent-skills/assets/tdd/references/tests.md +0 -77
  113. package/packages/agent-skills/assets/testing/SKILL.md +0 -54
  114. package/packages/agent-skills/assets/testing/references/e2e.md +0 -37
  115. package/packages/agent-skills/assets/testing/references/integration.md +0 -37
  116. package/packages/agent-skills/assets/testing/references/unit.md +0 -32
  117. package/packages/agent-skills/assets/to-feature/SKILL.md +0 -91
  118. package/packages/agent-skills/assets/to-feature/references/FEATURE-SPEC-TEMPLATE.md +0 -88
  119. package/packages/agent-skills/assets/to-workitems/SKILL.md +0 -90
  120. package/packages/agent-skills/assets/to-workitems/references/AZURE-DEVOPS-HIERARCHY.md +0 -121
  121. package/packages/agent-skills/assets/to-workitems/references/TICKET-TEMPLATES.md +0 -70
  122. package/packages/azdo/src/workflow-rules-matrix.csv +0 -85
  123. package/packages/fleet/src/launch-agents.ts +0 -649
  124. package/packages/repo-controls/assets/GLOBAL-INVARIANTS.md +0 -21
  125. package/packages/repo-controls/assets/enable-integration-auto-complete.mjs +0 -79
  126. package/packages/repo-controls/assets/scaffold/docs/notes.md +0 -20
  127. package/packages/repo-controls/assets/scaffold/plans/GAPS-AND-FINDINGS.md +0 -12
  128. package/packages/repo-controls/azure-pipelines.yml +0 -274
  129. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/notes.md +0 -1
  130. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/fixtures/excluded/frontend/mobile/src/i18n/locales/zh-CN/strings.ts +0 -1
  131. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-clean/docs/notes.md +0 -17
  132. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-no-lines/docs/notes.md +0 -13
  133. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/product/stable-delivery.md +0 -10
  134. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/product/stable-recommendation.md +0 -12
  135. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/product/target-narration.md +0 -10
  136. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/frontend/packages/desktop/src/renderer/solo/item.ts +0 -1
  137. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/frontend/packages/desktop/src/a.ts +0 -1
  138. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/frontend/packages/desktop/src/b.ts +0 -1
  139. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/fresh/fresh.ts +0 -1
  140. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub1/only.ts +0 -1
  141. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub10/only.ts +0 -1
  142. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub11/only.ts +0 -1
  143. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub2/only.ts +0 -1
  144. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub3/only.ts +0 -1
  145. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub4/only.ts +0 -1
  146. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub5/only.ts +0 -1
  147. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub6/only.ts +0 -1
  148. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub7/only.ts +0 -1
  149. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub8/only.ts +0 -1
  150. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub9/only.ts +0 -1
  151. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/services/i18n/locales/de/common.json +0 -1
  152. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/solo/item.ts +0 -1
  153. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/vendored-crate/Cargo.toml +0 -2
  154. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/frontend/packages/desktop/src/renderer/solo/item.ts +0 -1
  155. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/frontend/packages/desktop/src/a.ts +0 -1
  156. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/frontend/packages/desktop/src/b.ts +0 -1
  157. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/frontend/packages/desktop/src/c.ts +0 -1
  158. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/frontend/packages/desktop/src/renderer/solo/item.ts +0 -1
  159. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/fresh/fresh.ts +0 -1
  160. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub1/only.ts +0 -1
  161. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub10/only.ts +0 -1
  162. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub11/only.ts +0 -1
  163. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub2/only.ts +0 -1
  164. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub3/only.ts +0 -1
  165. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub4/only.ts +0 -1
  166. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub5/only.ts +0 -1
  167. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub6/only.ts +0 -1
  168. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub7/only.ts +0 -1
  169. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub8/only.ts +0 -1
  170. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub9/only.ts +0 -1
  171. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/solo/item.ts +0 -1
  172. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unusable-register/frontend/packages/desktop/src/renderer/solo/item.ts +0 -1
  173. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/fixtures/open-work-cap-clean/plans/GAPS-AND-FINDINGS.md +0 -5
  174. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/fixtures/open-work-cap-clean/plans/features/az601-clean/spec.md +0 -10
  175. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/agent/crates/enact-providers/src/snapshots/README.md +0 -1
  176. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/backend/crates/enact-app/assets/builtin-assistants/avatars/assistant.jpg +0 -1
  177. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/backend/crates/enact-assets/assets/logos/tools/coding/tool.png +0 -1
  178. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/backend/crates/enact-session/src/backend/codex_init_prompt.md +0 -1
  179. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/mobile/assets/images/icon.png +0 -1
  180. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/packages/desktop/src/renderer/assets/logos/brand/app.png +0 -1
  181. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/packages/desktop/src/renderer/assets/themes/default-theme.png +0 -1
  182. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/public/pwa/icon-192.png +0 -1
  183. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/resources/product.png +0 -1
  184. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-stray/backend/crates/enact-session/src/backend/README.md +0 -1
  185. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/spec.md +0 -12
  186. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-violation/plans/features/az347-test-coverage/spec.md +0 -12
  187. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/__init__.py +0 -11
  188. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/check.py +0 -62
  189. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/NotAMigration.sql +0 -1
  190. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/0001_long_prefix.sql +0 -1
  191. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/001_init.sql +0 -1
  192. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/002_add_index.sql +0 -1
  193. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/003-dashed-slug.sql +0 -1
  194. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/004_MixedCase.sql +0 -1
  195. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/005_trailing space.sql +0 -1
  196. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/01_short_prefix.sql +0 -1
  197. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/not-a-migration.sql +0 -1
  198. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/test_check.py +0 -116
  199. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/docs/architecture/product/links.md +0 -20
  200. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/docs/architecture/product/route-target.md +0 -8
  201. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/docs/architecture/product/links.md +0 -20
  202. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/docs/architecture/product/route-target.md +0 -8
  203. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.changeset/declared.txt +0 -1
  204. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/INDEPENDENT-VERIFICATION.md +0 -3
  205. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/attribute-without-conversion/.gitattributes +0 -1
  206. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/attribute-without-conversion/large file.bin +0 -3
  207. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/clean/.gitattributes +0 -1
  208. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/clean/large file.bin +0 -3
  209. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/clean/small.txt +0 -1
  210. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/missing-filter/large file.bin +0 -0
  211. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/unmatched-extension/.gitattributes +0 -1
  212. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/unmatched-extension/payload.dat +0 -0
  213. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/__init__.py +0 -1
  214. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/__init__.py +0 -12
  215. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/check.py +0 -129
  216. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/fixtures/disjoint/gate-registry.yaml +0 -13
  217. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/fixtures/empty/gate-registry.yaml +0 -7
  218. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/fixtures/overlapping/gate-registry.yaml +0 -13
  219. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/test_check.py +0 -109
  220. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/__init__.py +0 -1
  221. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/__init__.py +0 -11
  222. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/check.py +0 -149
  223. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/fixtures/clean/docs/readme.md +0 -4
  224. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/fixtures/relevant/VERSION +0 -1
  225. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/test_check.py +0 -151
  226. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/__init__.py +0 -11
  227. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/check.py +0 -202
  228. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/.workflows/changesets/package.json +0 -5
  229. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/.workflows/package.json +0 -5
  230. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/CHANGELOG.md +0 -5
  231. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/VERSION +0 -1
  232. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/.workflows/changesets/package.json +0 -5
  233. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/.workflows/package.json +0 -5
  234. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/CHANGELOG.md +0 -5
  235. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/VERSION +0 -1
  236. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/test_check.py +0 -162
  237. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/check.py +0 -103
  238. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/Cargo.lock +0 -7
  239. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/Cargo.toml +0 -4
  240. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/rust-toolchain.toml +0 -3
  241. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/src/main.rs +0 -1
  242. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/frontend/bun.lock +0 -1
  243. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/Cargo.lock +0 -7
  244. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/Cargo.toml +0 -4
  245. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/rust-toolchain.toml +0 -3
  246. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/src/main.rs +0 -1
  247. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/frontend/bun.lock +0 -0
  248. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/grants-registry.yaml +0 -112
  249. package/packages/repo-controls/workflows/scripts/release/verify-npm-artifact.ts +0 -119
@@ -3,7 +3,7 @@ import { dispatchTool, type ToolArguments } from './tool-dispatch.ts';
3
3
  type Option = {
4
4
  flag: string;
5
5
  key: string;
6
- type: 'string' | 'integer' | 'integer-list' | 'json-object' | 'date' | 'boolean';
6
+ type: 'string' | 'integer' | 'number' | 'integer-list' | 'json-object' | 'date' | 'boolean';
7
7
  values?: readonly string[];
8
8
  };
9
9
 
@@ -46,6 +46,8 @@ const commands: readonly Command[] = [
46
46
  { flag: '--merge-strategy', key: 'mergeStrategy', type: 'string', values: ['Squash'] },
47
47
  { flag: '--delete-source-branch', key: 'deleteSourceBranch', type: 'boolean' },
48
48
  { flag: '--attachment-content', key: 'attachmentContent', type: 'string' },
49
+ { flag: '--add-work-items', key: 'addWorkItems', type: 'integer-list' },
50
+ { flag: '--remove-work-items', key: 'removeWorkItems', type: 'integer-list' },
49
51
  { flag: '--repository', key: 'repository', type: 'string' },
50
52
  ],
51
53
  validate: requirePullRequestWrite,
@@ -86,29 +88,76 @@ const commands: readonly Command[] = [
86
88
  options: [{ flag: '--id', key: 'id', type: 'integer' }, { flag: '--ids', key: 'ids', type: 'string' }, { flag: '--type', key: 'type', type: 'string', values: WORK_ITEM_TYPES }, { flag: '--state', key: 'state', type: 'string' }, { flag: '--assigned-to', key: 'assignedTo', type: 'string' }, { flag: '--limit', key: 'limit', type: 'integer' }, { flag: '--raw', key: 'raw', type: 'boolean' }],
87
89
  },
88
90
  {
89
- name: 'workitem-create-or-update',
90
- tool: 'workitem_create_or_update',
91
+ name: 'workitem-create-or-update-feature',
92
+ tool: 'workitem_create_or_update_feature',
91
93
  options: [
92
94
  { flag: '--id', key: 'id', type: 'integer' },
93
- { flag: '--type', key: 'type', type: 'string', values: WORK_ITEM_TYPES },
94
95
  { flag: '--title', key: 'title', type: 'string' },
95
96
  { flag: '--description', key: 'description', type: 'string' },
96
97
  { flag: '--parent-id', key: 'parentId', type: 'integer' },
97
98
  { flag: '--predecessors', key: 'predecessors', type: 'integer-list' },
98
99
  { flag: '--related', key: 'related', type: 'integer-list' },
100
+ { flag: '--remove-predecessor', key: 'removePredecessor', type: 'integer' },
101
+ { flag: '--remove-related', key: 'removeRelated', type: 'integer' },
99
102
  { flag: '--fields', key: 'fields', type: 'json-object' },
100
103
  { flag: '--state', key: 'state', type: 'string' },
101
104
  { flag: '--comment', key: 'comment', type: 'string' },
102
105
  { flag: '--assigned-to', key: 'assignedTo', type: 'string' },
103
106
  { flag: '--acceptance-criteria', key: 'acceptanceCriteria', type: 'string' },
104
- { flag: '--priority', key: 'priority', type: 'integer' },
107
+ { flag: '--priority', key: 'priority', type: 'integer', values: ['1', '2', '3', '4'] },
105
108
  { flag: '--start-date', key: 'startDate', type: 'date' },
106
109
  { flag: '--target-date', key: 'targetDate', type: 'date' },
107
110
  { flag: '--plan-revision', key: 'planRevision', type: 'integer' },
108
111
  { flag: '--plan-hash', key: 'planHash', type: 'string' },
109
112
  { flag: '--baseline-sha', key: 'baselineSha', type: 'string' },
110
113
  ],
111
- validate: (args) => { if (args.id === undefined) requireKeys(args, ['type', 'title', 'parentId']); },
114
+ validate: (args) => { if (args.id === undefined) requireKeys(args, ['title', 'parentId', 'priority', 'description']); },
115
+ },
116
+ {
117
+ name: 'workitem-create-or-update-pbi',
118
+ tool: 'workitem_create_or_update_pbi',
119
+ options: [
120
+ { flag: '--id', key: 'id', type: 'integer' },
121
+ { flag: '--title', key: 'title', type: 'string' },
122
+ { flag: '--description', key: 'description', type: 'string' },
123
+ { flag: '--parent-id', key: 'parentId', type: 'integer' },
124
+ { flag: '--related', key: 'related', type: 'integer-list' },
125
+ { flag: '--remove-related', key: 'removeRelated', type: 'integer' },
126
+ { flag: '--fields', key: 'fields', type: 'json-object' },
127
+ { flag: '--state', key: 'state', type: 'string' },
128
+ { flag: '--comment', key: 'comment', type: 'string' },
129
+ { flag: '--assigned-to', key: 'assignedTo', type: 'string' },
130
+ { flag: '--acceptance-criteria', key: 'acceptanceCriteria', type: 'string' },
131
+ { flag: '--priority', key: 'priority', type: 'integer', values: ['1', '2', '3', '4'] },
132
+ { flag: '--effort', key: 'effort', type: 'number' },
133
+ { flag: '--start-date', key: 'startDate', type: 'date' },
134
+ { flag: '--target-date', key: 'targetDate', type: 'date' },
135
+ ],
136
+ validate: (args) => { if (args.id === undefined) requireKeys(args, ['title', 'parentId', 'description', 'effort']); },
137
+ },
138
+ {
139
+ name: 'workitem-create-or-update-bug',
140
+ tool: 'workitem_create_or_update_bug',
141
+ options: [
142
+ { flag: '--id', key: 'id', type: 'integer' },
143
+ { flag: '--title', key: 'title', type: 'string' },
144
+ { flag: '--description', key: 'description', type: 'string' },
145
+ { flag: '--parent-id', key: 'parentId', type: 'integer' },
146
+ { flag: '--predecessors', key: 'predecessors', type: 'integer-list' },
147
+ { flag: '--related', key: 'related', type: 'integer-list' },
148
+ { flag: '--remove-predecessor', key: 'removePredecessor', type: 'integer' },
149
+ { flag: '--remove-related', key: 'removeRelated', type: 'integer' },
150
+ { flag: '--fields', key: 'fields', type: 'json-object' },
151
+ { flag: '--state', key: 'state', type: 'string' },
152
+ { flag: '--comment', key: 'comment', type: 'string' },
153
+ { flag: '--assigned-to', key: 'assignedTo', type: 'string' },
154
+ { flag: '--acceptance-criteria', key: 'acceptanceCriteria', type: 'string' },
155
+ { flag: '--priority', key: 'priority', type: 'integer', values: ['1', '2', '3', '4'] },
156
+ { flag: '--effort', key: 'effort', type: 'number' },
157
+ { flag: '--start-date', key: 'startDate', type: 'date' },
158
+ { flag: '--target-date', key: 'targetDate', type: 'date' },
159
+ ],
160
+ validate: (args) => { if (args.id === undefined) requireKeys(args, ['title', 'parentId', 'description', 'effort']); },
112
161
  },
113
162
  {
114
163
  name: 'workitem-comments-read',
@@ -148,12 +197,14 @@ const help = `Usage: adl mcp cli <command> [options]
148
197
 
149
198
  Commands:
150
199
  pr-list-or-get [--id ID] [--status active|completed|abandoned|all] [--repository REPOSITORY]
151
- pr-create-or-update --action create|update [--pull-request-id ID] [--title TITLE] [--description DESCRIPTION] [--auto-complete|--no-auto-complete] [--merge-strategy Squash] [--delete-source-branch] [--attachment-content TEXT] [--repository REPOSITORY]
200
+ pr-create-or-update --action create|update [--pull-request-id ID] [--title TITLE] [--description DESCRIPTION] [--auto-complete|--no-auto-complete] [--merge-strategy Squash] [--delete-source-branch] [--attachment-content TEXT] [--add-work-items ID[,ID...]] [--remove-work-items ID[,ID...]] [--repository REPOSITORY]
152
201
  pr-review --pull-request-id ID [--comment TEXT] [--vote approve|approve-with-suggestions|reset|wait-for-author|reject] [--target-branch BRANCH] [--repository REPOSITORY]
153
202
  build-list-or-get [--build-id ID] [--branch BRANCH] [--top N] [--tail N]
154
203
  build-run --id ID [--branch BRANCH]
155
204
  workitem-list-or-get [--id ID] [--ids IDS] [--type TYPE] [--state STATE] [--assigned-to IDENTITY] [--limit N] [--raw]
156
- workitem-create-or-update [--id ID] [--type 'Feature'|'Product Backlog Item'|'Bug'] [--title TITLE] [--description DESCRIPTION] [--parent-id ID] [--predecessors ID[,ID...]] [--related ID[,ID...]] [--fields JSON_OBJECT] [--state STATE] [--comment TEXT] [--assigned-to IDENTITY] [--acceptance-criteria TEXT] [--priority N] [--start-date YYYY-MM-DD] [--target-date YYYY-MM-DD] [--plan-revision N] [--plan-hash SHA256] [--baseline-sha SHA]
205
+ workitem-create-or-update-feature [--id ID] --title TITLE --parent-id ID --priority 1|2|3|4 --description DESCRIPTION [--predecessors ID[,ID...]] [--related ID[,ID...]] [--remove-predecessor ID] [--remove-related ID] [--fields JSON_OBJECT] [--state STATE] [--comment TEXT] [--assigned-to IDENTITY] [--acceptance-criteria TEXT] [--start-date YYYY-MM-DD] [--target-date YYYY-MM-DD] [--plan-revision N] [--plan-hash SHA256] [--baseline-sha SHA]
206
+ workitem-create-or-update-pbi [--id ID] --title TITLE --parent-id ID --description DESCRIPTION --effort DAYS [--related ID[,ID...]] [--remove-related ID] [--fields JSON_OBJECT] [--state STATE] [--comment TEXT] [--assigned-to IDENTITY] [--acceptance-criteria TEXT] [--priority 1|2|3|4] [--start-date YYYY-MM-DD] [--target-date YYYY-MM-DD]
207
+ workitem-create-or-update-bug [--id ID] --title TITLE --parent-id ID --description DESCRIPTION --effort DAYS [--predecessors ID[,ID...]] [--related ID[,ID...]] [--remove-predecessor ID] [--remove-related ID] [--fields JSON_OBJECT] [--state STATE] [--comment TEXT] [--assigned-to IDENTITY] [--acceptance-criteria TEXT] [--priority 1|2|3|4] [--start-date YYYY-MM-DD] [--target-date YYYY-MM-DD]
157
208
  workitem-comments-read --id ID
158
209
  workitem-attachments --id ID --action read|upload|delete [--file-name NAME] [--content TEXT]
159
210
  activity-add --note TEXT
@@ -177,6 +228,12 @@ function requirePullRequestWrite(args: ToolArguments): void {
177
228
  if (args.deleteSourceBranch !== undefined) {
178
229
  throw new Error('--delete-source-branch is only valid for pr-create-or-update update');
179
230
  }
231
+ if (args.addWorkItems !== undefined) {
232
+ throw new Error('--add-work-items is only valid for pr-create-or-update update');
233
+ }
234
+ if (args.removeWorkItems !== undefined) {
235
+ throw new Error('--remove-work-items is only valid for pr-create-or-update update');
236
+ }
180
237
  requireKeys(args, ['title', 'description']);
181
238
  return;
182
239
  }
@@ -187,12 +244,16 @@ function requirePullRequestWrite(args: ToolArguments): void {
187
244
  throw new Error('--target-branch is not supported; PRs always target integration');
188
245
  }
189
246
  requireKeys(args, ['pullRequestId']);
247
+ const hasAdd = Array.isArray(args.addWorkItems) && (args.addWorkItems as unknown[]).length > 0;
248
+ const hasRemove = Array.isArray(args.removeWorkItems) && (args.removeWorkItems as unknown[]).length > 0;
190
249
  if (
191
250
  args.title === undefined &&
192
251
  args.description === undefined &&
193
252
  args.autoComplete !== true &&
194
253
  args.mergeStrategy === undefined &&
195
- args.deleteSourceBranch !== true
254
+ args.deleteSourceBranch !== true &&
255
+ !hasAdd &&
256
+ !hasRemove
196
257
  ) {
197
258
  throw new Error('pr-create-or-update update requires metadata or completion options');
198
259
  }
@@ -250,9 +311,18 @@ function parseOptions(command: Command, tokens: readonly string[]): ToolArgument
250
311
  const value = tokens[index + 1];
251
312
  if (value === undefined || value.startsWith('--')) throw new Error(`Option requires a value: ${token}`);
252
313
  index += 1;
314
+ // An allowed-values list applies to every option that takes one, not just strings — `--priority 5` is refused here rather than at the board.
315
+ if (option.values && !option.values.includes(value)) {
316
+ throw new Error(`Invalid value for ${token}: ${value}; expected ${option.values.join(', ')}`);
317
+ }
253
318
  if (option.type === 'integer') {
254
319
  if (!/^[1-9][0-9]*$/.test(value)) throw new Error(`Option requires a positive integer: ${token}`);
255
320
  args[option.key] = Number(value);
321
+ } else if (option.type === 'number') {
322
+ // Effort is days: a fraction is a normal value, a negative is not.
323
+ const days = Number(value);
324
+ if (!Number.isFinite(days) || days < 0) throw new Error(`Option requires a non-negative number of days: ${token}`);
325
+ args[option.key] = days;
256
326
  } else if (option.type === 'integer-list') {
257
327
  args[option.key] = parsePositiveIntegerList(token, value);
258
328
  } else if (option.type === 'json-object') {
@@ -261,9 +331,6 @@ function parseOptions(command: Command, tokens: readonly string[]): ToolArgument
261
331
  args[option.key] = parseDate(token, value);
262
332
  } else {
263
333
  if (value.trim() === '') throw new Error(`Option requires a non-empty value: ${token}`);
264
- if (option.values && !option.values.includes(value)) {
265
- throw new Error(`Invalid value for ${token}: ${value}; expected ${option.values.join(', ')}`);
266
- }
267
334
  if (option.key === 'ids') {
268
335
  args[option.key] = parsePositiveIntegerList('--ids', value);
269
336
  } else {
@@ -15,18 +15,29 @@ export interface AdoClientConfig {
15
15
  }
16
16
 
17
17
  /**
18
- * The Azure DevOps organization and project every tool in this package
19
- * addresses, and the ONLY place their defaults are written.
18
+ * Azure DevOps organization, project and PR target are NEVER hardcoded.
19
+ * `resolveOrg` / `resolveProject` derive org and project at call time from
20
+ * the checkout's git remote (the canonical source of truth —
21
+ * `parseAzureRemote` reads `git remote get-url origin`) and throw with a
22
+ * "what is missing" message when the remote is not Azure-shaped.
23
+ * `PR_TARGET_BRANCH` is the one exception: every PR in this fleet opens
24
+ * into `integration` and `azdo-pr.ts`'s create path refuses any caller
25
+ * override, so it lives as a constant.
20
26
  *
21
- * `enact-webview` is a REPOSITORY inside the `Enact` project, not a project —
22
- * `azdo-wit.ts` used to default the project to it, so work-item calls hit a
23
- * different URL than the PR and build calls in the same process. Anything
24
- * needing these values imports from here rather than restating them.
27
+ * `enact-webview` is a REPOSITORY inside the live project, not a project —
28
+ * an earlier version of this file defaulted the project to it, so work-item
29
+ * calls hit a different URL than the PR and build calls in the same process.
30
+ * That drift is the bug the explicit-resolver shape is meant to prevent.
31
+ *
32
+ * `resolveRepository` keeps an `AZURE_DEVOPS_REPO` env leg as a test
33
+ * affordance: many existing tests in this package set the env var to drive
34
+ * the resolver without standing up a fixture. The production contract is
35
+ * the same as the rest — the git remote is the source of truth — but the
36
+ * env leg stays because removing it would cascade into ~14 test files. The
37
+ * test preload's `setTestDefaults` is the long-term replacement; the env leg
38
+ * can be retired when those tests are migrated to fixture-driven cwd
39
+ * arguments.
25
40
  */
26
- export const DEFAULT_ORG = 'amsterdamdatalabs';
27
- export const DEFAULT_PROJECT = 'Enact';
28
- /** The one PR target this opinionated tool opens against. */
29
- export const PR_TARGET_BRANCH = 'integration';
30
41
 
31
42
  /** Current checkout branch, or undefined when git cannot determine it. */
32
43
  export function currentBranch(cwd?: string): string | undefined {
@@ -99,6 +110,18 @@ export function parseAzureRemote(url: string): AzureRemote | null {
99
110
  * every tool call.
100
111
  */
101
112
  let remoteCache: AzureRemote | null | undefined;
113
+ /** Test seam: forget the cached remote. The remote cannot change under a real run, but a test process running multiple files in the same bun test invocation sees the cached value from the first file that called `currentRemote()` without a cwd. */
114
+ export function resetRemoteCacheForTests(): void {
115
+ remoteCache = undefined;
116
+ }
117
+ /** Test seam: install defaults that `resolveOrg` / `resolveProject` / `resolveRepository` use when no argument and no git remote are available. Real runs never call this — the working directory's git remote is the canonical source of truth. Tests call it once from a `bunfig.toml` preload so every test file inherits the same values without per-file boilerplate. */
118
+ let testDefaults: AzureRemote | null = null;
119
+ export function setTestDefaults(remote: AzureRemote): void {
120
+ testDefaults = remote;
121
+ }
122
+ export function resetTestDefaults(): void {
123
+ testDefaults = null;
124
+ }
102
125
  export function currentRemote(cwd?: string): AzureRemote | null {
103
126
  if (cwd !== undefined) {
104
127
  const probe = spawnSync('git', ['remote', 'get-url', 'origin'], { cwd, encoding: 'utf8', timeout: 5000 });
@@ -110,14 +133,51 @@ export function currentRemote(cwd?: string): AzureRemote | null {
110
133
  return remoteCache;
111
134
  }
112
135
 
136
+ /**
137
+ * Resolve the Azure DevOps organization. Order: env `AZURE_DEVOPS_ORG` →
138
+ * cwd's git remote → testDefaults → throw.
139
+ *
140
+ * The env leg is a test affordance: many existing tests in this package set
141
+ * `AZURE_DEVOPS_ORG` and `AZURE_DEVOPS_PROJECT` in `beforeAll` to drive the
142
+ * resolver against fixtures whose identity names are coupled to a specific
143
+ * project string (e.g. `'Enact Build Service (amsterdamdatalabs)'`).
144
+ * Production runs always hit the cwd branch; the env leg never fires
145
+ * because real runs never set it.
146
+ */
113
147
  export function resolveOrg(cwd?: string): string {
114
- return configured(process.env.AZURE_DEVOPS_ORG) ?? currentRemote(cwd)?.org ?? DEFAULT_ORG;
148
+ const org = configured(process.env.AZURE_DEVOPS_ORG) ?? currentRemote(cwd)?.org ?? testDefaults?.org;
149
+ if (org !== undefined) return org;
150
+ throw new Error(
151
+ 'Cannot determine the Azure DevOps organization: AZURE_DEVOPS_ORG is not set, ' +
152
+ 'and `git remote get-url origin` in this working directory is not an Azure DevOps remote. ' +
153
+ 'Set AZURE_DEVOPS_ORG or run from a checkout of the target repo.',
154
+ );
115
155
  }
116
156
 
157
+ /**
158
+ * Resolve the Azure DevOps project. Order: env `AZURE_DEVOPS_PROJECT` →
159
+ * cwd's git remote → testDefaults → throw. See `resolveOrg` for why the env
160
+ * leg is preserved.
161
+ */
117
162
  export function resolveProject(cwd?: string): string {
118
- return configured(process.env.AZURE_DEVOPS_PROJECT) ?? currentRemote(cwd)?.project ?? DEFAULT_PROJECT;
163
+ const project = configured(process.env.AZURE_DEVOPS_PROJECT) ?? currentRemote(cwd)?.project ?? testDefaults?.project;
164
+ if (project !== undefined) return project;
165
+ throw new Error(
166
+ 'Cannot determine the Azure DevOps project: AZURE_DEVOPS_PROJECT is not set, ' +
167
+ 'and `git remote get-url origin` in this working directory is not an Azure DevOps remote. ' +
168
+ 'Set AZURE_DEVOPS_PROJECT or run from a checkout of the target repo.',
169
+ );
119
170
  }
120
171
 
172
+ /**
173
+ * The branch a PR is opened against. Every PR in this fleet targets
174
+ * `integration` so promotion through G2 is uniform; `azdo-pr.ts`'s create
175
+ * path enforces this by rejecting any caller-supplied `targetBranch`
176
+ * argument before the body is built. This is a design rule, not a value
177
+ * resolved at runtime, so it lives as a constant.
178
+ */
179
+ export const PR_TARGET_BRANCH = 'integration';
180
+
121
181
  /**
122
182
  * Which repository a call addresses: the explicit per-call argument first (one
123
183
  * session can span repos), then `AZURE_DEVOPS_REPO` as an override, then the
@@ -135,10 +195,18 @@ export function resolveProject(cwd?: string): string {
135
195
  * writing somewhere real.
136
196
  */
137
197
  export function resolveRepository(repository?: string, cwd?: string): string {
138
- const resolved = configured(repository) ?? configured(process.env.AZURE_DEVOPS_REPO) ?? currentRemote(cwd)?.repository;
198
+ // Order: explicit argument > AZURE_DEVOPS_REPO env > cwd's git remote > testDefaults > throw.
199
+ // The AZURE_DEVOPS_REPO env leg is a test affordance: many existing tests in
200
+ // this package set the env var to drive the resolver without standing up a
201
+ // fixture. The production contract is the same as the rest — the git remote
202
+ // is the source of truth — but the env leg stays because removing it would
203
+ // cascade into ~14 test files. The test preload's testDefaults is the
204
+ // long-term replacement; the env leg can be retired when those tests are
205
+ // migrated to fixture-driven cwd arguments.
206
+ const resolved = configured(repository) ?? configured(process.env.AZURE_DEVOPS_REPO) ?? currentRemote(cwd)?.repository ?? testDefaults?.repository;
139
207
  if (resolved === undefined) {
140
208
  throw new Error(
141
- 'Cannot determine the Azure DevOps repository: no `repository` argument, no AZURE_DEVOPS_REPO, ' +
209
+ 'Cannot determine the Azure DevOps repository: no `repository` argument, ' +
142
210
  'and `git remote get-url origin` in this working directory is not an Azure DevOps remote. ' +
143
211
  'Pass `repository` explicitly or run from a checkout of the target repo.',
144
212
  );
@@ -151,9 +219,13 @@ export function resolveRepository(repository?: string, cwd?: string): string {
151
219
  * segments are written. The REST base appends `/_apis`; human-facing links
152
220
  * append `/_git/...`. Two builders restating this shape can disagree about
153
221
  * where a project lives just as easily as two default constants could.
222
+ *
223
+ * `cwd` is the test seam: pass the path of a fixture whose `origin` is the
224
+ * URL under test. Production callers omit it and resolve against this
225
+ * process's own working directory.
154
226
  */
155
- export function adoProjectUrl(): string {
156
- return `https://dev.azure.com/${resolveOrg()}/${resolveProject()}`;
227
+ export function adoProjectUrl(cwd?: string): string {
228
+ return `https://dev.azure.com/${resolveOrg(cwd)}/${resolveProject(cwd)}`;
157
229
  }
158
230
 
159
231
  export class AdoClient {
@@ -29,7 +29,7 @@ import {
29
29
  import pkg from '../package.json' with { type: 'json' };
30
30
 
31
31
  /**
32
- * Builds a fresh, unconnected `Server` with the 12 public tools registered.
32
+ * Builds a fresh, unconnected `Server` with the 14 public tools registered.
33
33
  * Called by `runMcpServer()` below; also usable directly by tests that want
34
34
  * to drive the server without stdio (e.g. via an in-memory transport).
35
35
  */
@@ -62,7 +62,7 @@ export function createServer(): Server {
62
62
  },
63
63
  {
64
64
  name: 'pr_create_or_update',
65
- description: 'Create a pull request from the current branch into integration, or update a pull request. The create path derives the PR kind from the source branch and the board (planning / implementation / ordinary), links the wireable work items, moves them at raise, attaches the kind\'s mandatory Feature attachment (attachmentContent) and arms auto-complete with the matching transitionWorkItems. The body must contain non-empty ## Summary, ## Why, ## Changes, ## Test plan and ## Security impact considered sections (a planning PR may write "none (docs-only)" in the last two).',
65
+ description: 'Create a pull request from the current branch into integration, or update a pull request. The create path derives the PR kind from the source branch and the board (planning / implementation / ordinary), links the wireable work items, moves them at raise, attaches the kind\'s mandatory Feature attachment (attachmentContent) and arms auto-complete with the matching transitionWorkItems. The update path also accepts addWorkItems / removeWorkItems (update only, P5) — the only way to attach work items to a PR raised as kind: ordinary after the create call. The body must contain non-empty ## Summary, ## Why, ## Changes, ## Test plan and ## Security impact considered sections (a planning PR may write "none (docs-only)" in the last two).',
66
66
  inputSchema: {
67
67
  type: 'object',
68
68
  properties: {
@@ -74,6 +74,8 @@ export function createServer(): Server {
74
74
  mergeStrategy: { type: 'string', enum: ['Squash'] },
75
75
  deleteSourceBranch: { type: 'boolean' },
76
76
  attachmentContent: { type: 'string', description: 'Create only: the content of the kind\'s mandatory Feature attachment (plan-r<N>-<sha7>.md or impl-report-pr<id>.md); refused without it on planning/implementation PRs' },
77
+ addWorkItems: { type: 'array', items: { type: 'number' }, description: 'P5 update only: positive integer ids to link to the PR. For each id, JSON-PATCH `add /relations/-` against `/_apis/wit/workitems/{id}?api-version=7.1` with `application/json-patch+json` and an `ArtifactLink` of rel `Pull Request` pointing at `vstfs:///Git/PullRequestId/{projectId}%2F{repoId}%2F{prId}`. The relation is read back through `GET .../pullRequests/{prId}/workItems`. An id already linked is skipped (idempotent). An id in both addWorkItems and removeWorkItems is rejected (no PATCH issued).' },
78
+ removeWorkItems: { type: 'array', items: { type: 'number' }, description: 'P5 update only: positive integer ids to unlink from the PR. JSON-PATCH `remove /relations/{index}` against the work item, where {index} is the position of the matching `ArtifactLink` (matched by url). An id in both addWorkItems and removeWorkItems is rejected (no PATCH issued).' },
77
79
  repository: { type: 'string', description: 'Repository name; defaults to AZURE_DEVOPS_REPO' },
78
80
  },
79
81
  required: ['action'],
@@ -125,7 +127,7 @@ export function createServer(): Server {
125
127
  },
126
128
  {
127
129
  name: 'workitem_list_or_get',
128
- description: 'Get one or many tracked work items, or query the scoped backlog. Each item carries effort, failureReason and the Feature plan fields planRevision, planHash and baselineSha (all null when unset).',
130
+ description: 'Get one or many tracked work items, or query the scoped backlog. Each item carries effort, priority, acceptanceCriteria, failureReason and the Feature plan fields planRevision, planHash and baselineSha (all null when unset).',
129
131
  inputSchema: {
130
132
  type: 'object',
131
133
  properties: {
@@ -134,17 +136,80 @@ export function createServer(): Server {
134
136
  },
135
137
  },
136
138
  {
137
- name: 'workitem_create_or_update',
138
- description: 'Create a tracked work item under its required parent, or update an existing item. The plan fields (planRevision, planHash, baselineSha) are Feature-only and map to the Custom.PlanRevision / Custom.PlanHash / Custom.BaselineSha process fields.',
139
+ name: 'workitem_create_or_update_feature',
140
+ description: "Create a Feature under an Epic, or update an existing Feature. Mandatory: title, parentId (an Epic), priority (1=Very High, 2=High, 3=Medium, 4=Low) and a description carrying the intent.md sections Why / Outcome / Constraints / Non-goals — on create and while the Feature is at New; acceptanceCriteria (itemised per requirements.md) plus the three plan fields on the New → In Progress move; a comment on every update. Features are created only at New.",
139
141
  inputSchema: {
140
142
  type: 'object',
141
143
  properties: {
142
- id: { type: 'number' }, type: { type: 'string', enum: ['Feature', 'Product Backlog Item', 'Bug'] },
144
+ id: { type: 'number' },
145
+ title: { type: 'string' },
146
+ description: { type: 'string', description: 'Must carry the intent.md sections ## Why, ## Outcome, ## Constraints, ## Non-goals' },
147
+ parentId: { type: 'number', description: 'The Epic this Feature hangs under' },
148
+ predecessors: { type: 'array', items: { type: 'number' } },
149
+ related: { type: 'array', items: { type: 'number' } },
150
+ removePredecessor: { type: 'number', description: 'az698: drop a single predecessor link by id; idempotent on a missing link. Update path only.' },
151
+ removeRelated: { type: 'number', description: 'az698: drop a single related link by id; idempotent on a missing link. Update path only.' },
152
+ fields: { type: 'object' },
153
+ state: { type: 'string' },
154
+ comment: { type: 'string', description: 'Mandatory on every update' },
155
+ assignedTo: { type: 'string' },
156
+ acceptanceCriteria: { type: 'string', description: 'Required on the New → In Progress move, itemised per requirements.md' },
157
+ priority: { type: 'number', enum: [1, 2, 3, 4], description: '1=Very High, 2=High, 3=Medium, 4=Low (Microsoft.VSTS.Common.Priority)' },
158
+ startDate: { type: 'string' },
159
+ targetDate: { type: 'string' },
160
+ planRevision: { type: 'number', description: 'Required on the New → In Progress move: which plan revision G1 approves (Custom.PlanRevision)' },
161
+ planHash: { type: 'string', description: 'Required on the New → In Progress move: sha256 of requirements.md (Custom.PlanHash)' },
162
+ baselineSha: { type: 'string', description: 'Required on the New → In Progress move: the integration commit the plan was written against (Custom.BaselineSha)' },
163
+ },
164
+ },
165
+ },
166
+ {
167
+ name: 'workitem_create_or_update_pbi',
168
+ description: "Create a PBI (Product Backlog Item) under a Feature, or update an existing PBI. Mandatory on create: title, parentId (a Feature), a description carrying the item file's What it delivers / Verification sections, and effort in days. A comment is mandatory on every update. Predecessor links are refused as a staged warning (L3); the Feature plan fields do not exist on a PBI.",
169
+ inputSchema: {
170
+ type: 'object',
171
+ properties: {
172
+ id: { type: 'number' },
173
+ title: { type: 'string' },
174
+ description: { type: 'string', description: "Must carry the item file's sections ## What it delivers and ## Verification" },
175
+ parentId: { type: 'number', description: 'The Feature this PBI hangs under' },
176
+ related: { type: 'array', items: { type: 'number' } },
177
+ removeRelated: { type: 'number', description: 'az698: drop a single related link by id; idempotent on a missing link. Update path only.' },
178
+ fields: { type: 'object' },
179
+ state: { type: 'string' },
180
+ comment: { type: 'string', description: 'Mandatory on every update' },
181
+ assignedTo: { type: 'string' },
182
+ acceptanceCriteria: { type: 'string' },
183
+ priority: { type: 'number', enum: [1, 2, 3, 4], description: '1=Very High, 2=High, 3=Medium, 4=Low' },
184
+ effort: { type: 'number', description: 'Mandatory on create: engineering days, >= 0, fractions accepted (Microsoft.VSTS.Scheduling.Effort)' },
185
+ startDate: { type: 'string' },
186
+ targetDate: { type: 'string' },
187
+ },
188
+ },
189
+ },
190
+ {
191
+ name: 'workitem_create_or_update_bug',
192
+ description: "Create a Bug under a Feature, or update an existing Bug. Mandatory on create: title, parentId (a Feature — usually a 'Fixes to closed Features' container), a description carrying the bug item file's Current behaviour / Expected behaviour / Behaviour that must not change / Responsible Feature / Verification sections, and effort in days. A Bug under the Fixes Feature also needs a related link to its Responsible Feature and a predecessor that is a sibling Bug (L4). A comment is mandatory on every update.",
193
+ inputSchema: {
194
+ type: 'object',
195
+ properties: {
196
+ id: { type: 'number' },
143
197
  title: { type: 'string' },
144
- description: { type: 'string' }, parentId: { type: 'number' }, predecessors: { type: 'array', items: { type: 'number' } }, related: { type: 'array', items: { type: 'number' } }, fields: { type: 'object' }, state: { type: 'string' }, comment: { type: 'string' }, assignedTo: { type: 'string' }, acceptanceCriteria: { type: 'string' }, priority: { type: 'number' }, startDate: { type: 'string' }, targetDate: { type: 'string' },
145
- planRevision: { type: 'number', description: 'Feature only: which plan revision G1 approves (Custom.PlanRevision)' },
146
- planHash: { type: 'string', description: 'Feature only: sha256 of requirements.md (Custom.PlanHash)' },
147
- baselineSha: { type: 'string', description: 'Feature only: the integration commit the plan was written against (Custom.BaselineSha)' },
198
+ description: { type: 'string', description: "Must carry the bug item file's sections ## Current behaviour, ## Expected behaviour, ## Behaviour that must not change, ## Responsible Feature, ## Verification" },
199
+ parentId: { type: 'number', description: 'The Feature this Bug hangs under' },
200
+ predecessors: { type: 'array', items: { type: 'number' }, description: 'At most one, and only a sibling Bug under a Fixes Feature (L4)' },
201
+ related: { type: 'array', items: { type: 'number' }, description: 'Required under a Fixes Feature: the Responsible Feature (L4)' },
202
+ removePredecessor: { type: 'number', description: 'az698: drop a single predecessor link by id; idempotent on a missing link. Update path only.' },
203
+ removeRelated: { type: 'number', description: 'az698: drop a single related link by id; idempotent on a missing link. Update path only.' },
204
+ fields: { type: 'object' },
205
+ state: { type: 'string' },
206
+ comment: { type: 'string', description: 'Mandatory on every update' },
207
+ assignedTo: { type: 'string' },
208
+ acceptanceCriteria: { type: 'string' },
209
+ priority: { type: 'number', enum: [1, 2, 3, 4], description: '1=Very High, 2=High, 3=Medium, 4=Low' },
210
+ effort: { type: 'number', description: 'Mandatory on create: engineering days, >= 0, fractions accepted (Microsoft.VSTS.Scheduling.Effort)' },
211
+ startDate: { type: 'string' },
212
+ targetDate: { type: 'string' },
148
213
  },
149
214
  },
150
215
  },
@@ -10,7 +10,7 @@ import { prReview, VOTE_NAMES, type PrReviewArgs, type VoteName } from './azdo-p
10
10
  import { buildListOrGet, buildRun } from './azdo-pl.ts';
11
11
  import { workitemAttachments } from './azdo-attachments.ts';
12
12
  import { activityAdd, activityList } from './activity-log.ts';
13
- import { workitemCommentsRead, workitemCreateOrUpdate, workitemListOrGet } from './azdo-wit.ts';
13
+ import { workitemCommentsRead, workitemCreateOrUpdateBug, workitemCreateOrUpdateFeature, workitemCreateOrUpdatePbi, workitemListOrGet, type WorkItemWriteArgs } from './azdo-wit.ts';
14
14
 
15
15
  type PullRequestWriteArguments = {
16
16
  action?: unknown;
@@ -21,6 +21,8 @@ type PullRequestWriteArguments = {
21
21
  mergeStrategy?: unknown;
22
22
  deleteSourceBranch?: unknown;
23
23
  attachmentContent?: unknown;
24
+ addWorkItems?: unknown;
25
+ removeWorkItems?: unknown;
24
26
  repository?: unknown;
25
27
  sourceBranch?: unknown;
26
28
  targetBranch?: unknown;
@@ -48,12 +50,16 @@ export function pullRequestWriteArgs(args: PullRequestWriteArguments): PrCreateO
48
50
  if (!Number.isInteger(args.pullRequestId)) errors.push('pullRequestId is required for update');
49
51
  if (typeof args.title === 'string') errors.push(...validatePullRequestTitle(args.title));
50
52
  if (typeof args.description === 'string') errors.push(...validatePullRequestDescription(args.description));
53
+ const hasAdd = Array.isArray(args.addWorkItems) && (args.addWorkItems as unknown[]).length > 0;
54
+ const hasRemove = Array.isArray(args.removeWorkItems) && (args.removeWorkItems as unknown[]).length > 0;
51
55
  if (
52
56
  args.title === undefined &&
53
57
  args.description === undefined &&
54
58
  args.autoComplete !== true &&
55
59
  args.mergeStrategy === undefined &&
56
- args.deleteSourceBranch !== true
60
+ args.deleteSourceBranch !== true &&
61
+ !hasAdd &&
62
+ !hasRemove
57
63
  ) {
58
64
  errors.push('update requires metadata or completion options');
59
65
  }
@@ -64,6 +70,12 @@ export function pullRequestWriteArgs(args: PullRequestWriteArguments): PrCreateO
64
70
  if (args.attachmentContent !== undefined && typeof args.attachmentContent !== 'string') {
65
71
  errors.push('attachmentContent must be a string');
66
72
  }
73
+ if (args.addWorkItems !== undefined && (!Array.isArray(args.addWorkItems) || (args.addWorkItems as unknown[]).some((value) => !Number.isInteger(value)))) {
74
+ errors.push('addWorkItems must be an array of integers');
75
+ }
76
+ if (args.removeWorkItems !== undefined && (!Array.isArray(args.removeWorkItems) || (args.removeWorkItems as unknown[]).some((value) => !Number.isInteger(value)))) {
77
+ errors.push('removeWorkItems must be an array of integers');
78
+ }
67
79
  if (errors.length > 0) throw new Error(`Invalid pull request write:\n- ${errors.join('\n- ')}`);
68
80
 
69
81
  return {
@@ -76,6 +88,8 @@ export function pullRequestWriteArgs(args: PullRequestWriteArguments): PrCreateO
76
88
  ...(args.mergeStrategy === 'Squash' ? { mergeStrategy: args.mergeStrategy } : {}),
77
89
  ...(args.deleteSourceBranch === true ? { deleteSourceBranch: true } : {}),
78
90
  ...(typeof args.attachmentContent === 'string' ? { attachmentContent: args.attachmentContent } : {}),
91
+ ...(Array.isArray(args.addWorkItems) ? { addWorkItems: args.addWorkItems as number[] } : {}),
92
+ ...(Array.isArray(args.removeWorkItems) ? { removeWorkItems: args.removeWorkItems as number[] } : {}),
79
93
  ...(typeof args.repository === 'string' ? { repository: args.repository } : {}),
80
94
  };
81
95
  }
@@ -123,6 +137,33 @@ export function pullRequestReviewArgs(args: PullRequestReviewArguments): PrRevie
123
137
 
124
138
  export type ToolArguments = Record<string, unknown>;
125
139
 
140
+ /** The three per-type work-item write tools take the same arguments — only the
141
+ * name differs — so the raw MCP/CLI arguments are narrowed once, here. */
142
+ function workItemWriteArgs(args: ToolArguments): WorkItemWriteArgs {
143
+ return {
144
+ ...(Number.isInteger(args.id) ? { id: args.id as number } : {}),
145
+ ...(typeof args.title === 'string' ? { title: args.title } : {}),
146
+ ...(typeof args.description === 'string' ? { description: args.description } : {}),
147
+ ...(Number.isInteger(args.parentId) ? { parentId: args.parentId as number } : {}),
148
+ ...(Array.isArray(args.predecessors) ? { predecessors: args.predecessors as number[] } : {}),
149
+ ...(Array.isArray(args.related) ? { related: args.related as number[] } : {}),
150
+ ...(args.fields && typeof args.fields === 'object' && !Array.isArray(args.fields) ? { fields: args.fields as Record<string, unknown> } : {}),
151
+ ...(typeof args.state === 'string' ? { state: args.state } : {}),
152
+ ...(typeof args.comment === 'string' ? { comment: args.comment } : {}),
153
+ ...(typeof args.assignedTo === 'string' ? { assignedTo: args.assignedTo } : {}),
154
+ ...(typeof args.acceptanceCriteria === 'string' ? { acceptanceCriteria: args.acceptanceCriteria } : {}),
155
+ ...(Number.isInteger(args.priority) ? { priority: args.priority as number } : {}),
156
+ ...(typeof args.effort === 'number' ? { effort: args.effort } : {}),
157
+ ...(typeof args.startDate === 'string' ? { startDate: args.startDate } : {}),
158
+ ...(typeof args.targetDate === 'string' ? { targetDate: args.targetDate } : {}),
159
+ ...(Number.isInteger(args.planRevision) ? { planRevision: args.planRevision as number } : {}),
160
+ ...(typeof args.planHash === 'string' ? { planHash: args.planHash } : {}),
161
+ ...(typeof args.baselineSha === 'string' ? { baselineSha: args.baselineSha } : {}),
162
+ ...(Number.isInteger(args.removePredecessor) ? { removePredecessor: args.removePredecessor as number } : {}),
163
+ ...(Number.isInteger(args.removeRelated) ? { removeRelated: args.removeRelated as number } : {}),
164
+ };
165
+ }
166
+
126
167
  function assertString(args: ToolArguments, key: string, allowNumber = false): void {
127
168
  const value = args[key];
128
169
  if (value !== undefined && typeof value !== 'string' && !(allowNumber && typeof value === 'number')) {
@@ -142,12 +183,21 @@ function validateToolArguments(name: string, args: ToolArguments): void {
142
183
  switch (name) {
143
184
  case 'build_list_or_get': ['buildId', 'top', 'tail'].forEach((key) => assertString(args, key, true)); assertString(args, 'branch'); return;
144
185
  case 'build_run': assertString(args, 'id', true); assertString(args, 'branch'); return;
186
+ case 'pr_create_or_update':
187
+ ['pullRequestId'].forEach((key) => assertInteger(args, key));
188
+ ['addWorkItems', 'removeWorkItems'].forEach((key) => assertIntegerArray(args, key));
189
+ return;
145
190
  case 'workitem_list_or_get':
146
191
  ['id', 'limit'].forEach((key) => assertInteger(args, key)); assertIntegerArray(args, 'ids');
147
192
  ['type', 'state', 'assignedTo'].forEach((key) => assertString(args, key)); assertBoolean(args, 'raw'); return;
148
- case 'workitem_create_or_update':
149
- ['id', 'parentId', 'priority', 'planRevision'].forEach((key) => assertInteger(args, key)); ['predecessors', 'related'].forEach((key) => assertIntegerArray(args, key));
150
- ['type', 'title', 'description', 'state', 'comment', 'assignedTo', 'acceptanceCriteria', 'startDate', 'targetDate', 'planHash', 'baselineSha'].forEach((key) => assertString(args, key));
193
+ case 'workitem_create_or_update_feature':
194
+ case 'workitem_create_or_update_pbi':
195
+ case 'workitem_create_or_update_bug':
196
+ ['id', 'parentId', 'priority', 'planRevision', 'removePredecessor', 'removeRelated'].forEach((key) => assertInteger(args, key));
197
+ ['predecessors', 'related'].forEach((key) => assertIntegerArray(args, key));
198
+ ['title', 'description', 'state', 'comment', 'assignedTo', 'acceptanceCriteria', 'startDate', 'targetDate', 'planHash', 'baselineSha'].forEach((key) => assertString(args, key));
199
+ // Effort is days and accepts fractions, so it is a plain number, not an integer.
200
+ if (args.effort !== undefined && typeof args.effort !== 'number') throw new Error('effort must be a number (days)');
151
201
  if (args.fields !== undefined && (typeof args.fields !== 'object' || args.fields === null || Array.isArray(args.fields))) throw new Error('fields must be an object');
152
202
  return;
153
203
  case 'workitem_comments_read':
@@ -202,27 +252,12 @@ export async function dispatchTool(name: string, args: ToolArguments = {}): Prom
202
252
  ...(Number.isInteger(args.limit) ? { limit: args.limit as number } : {}),
203
253
  ...(typeof args.raw === 'boolean' ? { raw: args.raw } : {}),
204
254
  });
205
- case 'workitem_create_or_update':
206
- return workitemCreateOrUpdate({
207
- ...(Number.isInteger(args.id) ? { id: args.id as number } : {}),
208
- ...(typeof args.type === 'string' ? { type: args.type } : {}),
209
- ...(typeof args.title === 'string' ? { title: args.title } : {}),
210
- ...(typeof args.description === 'string' ? { description: args.description } : {}),
211
- ...(Number.isInteger(args.parentId) ? { parentId: args.parentId as number } : {}),
212
- ...(Array.isArray(args.predecessors) ? { predecessors: args.predecessors as number[] } : {}),
213
- ...(Array.isArray(args.related) ? { related: args.related as number[] } : {}),
214
- ...(args.fields && typeof args.fields === 'object' && !Array.isArray(args.fields) ? { fields: args.fields as Record<string, unknown> } : {}),
215
- ...(typeof args.state === 'string' ? { state: args.state } : {}),
216
- ...(typeof args.comment === 'string' ? { comment: args.comment } : {}),
217
- ...(typeof args.assignedTo === 'string' ? { assignedTo: args.assignedTo } : {}),
218
- ...(typeof args.acceptanceCriteria === 'string' ? { acceptanceCriteria: args.acceptanceCriteria } : {}),
219
- ...(Number.isInteger(args.priority) ? { priority: args.priority as number } : {}),
220
- ...(typeof args.startDate === 'string' ? { startDate: args.startDate } : {}),
221
- ...(typeof args.targetDate === 'string' ? { targetDate: args.targetDate } : {}),
222
- ...(Number.isInteger(args.planRevision) ? { planRevision: args.planRevision as number } : {}),
223
- ...(typeof args.planHash === 'string' ? { planHash: args.planHash } : {}),
224
- ...(typeof args.baselineSha === 'string' ? { baselineSha: args.baselineSha } : {}),
225
- });
255
+ case 'workitem_create_or_update_feature':
256
+ return workitemCreateOrUpdateFeature(workItemWriteArgs(args));
257
+ case 'workitem_create_or_update_pbi':
258
+ return workitemCreateOrUpdatePbi(workItemWriteArgs(args));
259
+ case 'workitem_create_or_update_bug':
260
+ return workitemCreateOrUpdateBug(workItemWriteArgs(args));
226
261
  case 'workitem_comments_read':
227
262
  if (!Number.isInteger(args.id)) throw new Error('id is required (a work item id)');
228
263
  return workitemCommentsRead({ id: args.id as number });