ecoportal-api-graphql 1.3.5 → 1.3.9

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 (499) hide show
  1. checksums.yaml +4 -4
  2. data/.ai-assistance/bridge/CLAUDE.md +338 -0
  3. data/.ai-assistance/bridge/archive/.gitkeep +0 -0
  4. data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.inbox.md +29 -0
  5. data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.outbox.md +18 -0
  6. data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.inbox.md +42 -0
  7. data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.outbox.md +115 -0
  8. data/.ai-assistance/bridge/context/gemini-review-prompt.txt +48 -0
  9. data/.ai-assistance/bridge/context/gemini-review-response.md +104 -0
  10. data/.ai-assistance/bridge/context/project.md +42 -0
  11. data/.ai-assistance/bridge/inbox/.gitkeep +0 -0
  12. data/.ai-assistance/bridge/outbox/.gitkeep +0 -0
  13. data/.ai-assistance/bridge/outbox/request-for-standards-discovery.md +48 -0
  14. data/.ai-assistance/bridge/queue/.gitkeep +1 -0
  15. data/.ai-assistance/capabilities/CLAUDE.md +27 -0
  16. data/.ai-assistance/capabilities/assumptions-log.md +80 -0
  17. data/.ai-assistance/capabilities/code.md +47 -0
  18. data/.ai-assistance/capabilities/connectors.md +37 -0
  19. data/.ai-assistance/capabilities/cowork.md +55 -0
  20. data/.ai-assistance/code/OVERVIEW.md +155 -0
  21. data/.ai-assistance/code/data_fields.md +242 -0
  22. data/.ai-assistance/code/dependencies.md +151 -0
  23. data/.ai-assistance/code/diff_as_input.md +234 -0
  24. data/.ai-assistance/code/diff_service_deep_dive.md +192 -0
  25. data/.ai-assistance/code/ecoPortal_architecture/00_overview_and_index.md +55 -0
  26. data/.ai-assistance/code/ecoPortal_architecture/01_terminology_dictionary.md +181 -0
  27. data/.ai-assistance/code/ecoPortal_architecture/02_data_model.md +192 -0
  28. data/.ai-assistance/code/ecoPortal_architecture/03_api_layers.md +147 -0
  29. data/.ai-assistance/code/ecoPortal_architecture/04_graphql_queries_mutations.md +277 -0
  30. data/.ai-assistance/code/ecoPortal_architecture/05_page_workflows.md +200 -0
  31. data/.ai-assistance/code/ecoPortal_architecture/06_search_and_filters.md +228 -0
  32. data/.ai-assistance/code/ecoPortal_architecture/07_data_fields.md +197 -0
  33. data/.ai-assistance/code/ecoPortal_architecture/08_stages_sections.md +243 -0
  34. data/.ai-assistance/code/ecoPortal_architecture/09_people_contractors_locations.md +196 -0
  35. data/.ai-assistance/code/ecoPortal_architecture/10_forces_workflow_builder.md +132 -0
  36. data/.ai-assistance/code/ecoPortal_architecture/11_integration_gems.md +187 -0
  37. data/.ai-assistance/code/ecoPortal_architecture/12_ai_documentation_sources_gaps.md +236 -0
  38. data/.ai-assistance/code/ecoPortal_architecture/13_ai_infrastructure.md +183 -0
  39. data/.ai-assistance/code/ecoportal_schema_reference.md +240 -0
  40. data/.ai-assistance/code/graphql_domain_knowledge.md +230 -0
  41. data/.ai-assistance/code/refactoring/datafield-readwrite-shape-asymmetry.md +71 -0
  42. data/.ai-assistance/code/refactoring/opportunities.md +251 -0
  43. data/.ai-assistance/code/schema_analysis.md +321 -0
  44. data/.ai-assistance/code/search_filters.md +868 -0
  45. data/.ai-assistance/code/spec_coverage.md +73 -0
  46. data/.ai-assistance/code/workflow-command-guide.md +438 -0
  47. data/.ai-assistance/code/workflow-space.md +353 -0
  48. data/.ai-assistance/conventions/CLAUDE.md +30 -0
  49. data/.ai-assistance/conventions/code-working-tree-protocol.md +199 -0
  50. data/.ai-assistance/conventions/gitignore-rules.md +42 -0
  51. data/.ai-assistance/conventions/permission-guidance.md +120 -0
  52. data/.ai-assistance/integrations/README.md +70 -0
  53. data/.ai-assistance/integrations/gitkraken-mcp.md +107 -0
  54. data/.ai-assistance/integrations/gitlab-mcp.md +123 -0
  55. data/.ai-assistance/integrations/local-git.md +60 -0
  56. data/.ai-assistance/local_paths.example.md +17 -0
  57. data/.ai-assistance/projects/TODO.md +97 -0
  58. data/.ai-assistance/projects/api-v2-to-graphql-migration/DECISIONS.md +168 -0
  59. data/.ai-assistance/projects/api-v2-to-graphql-migration/INTENT.md +60 -0
  60. data/.ai-assistance/projects/api-v2-to-graphql-migration/TODO.md +267 -0
  61. data/.ai-assistance/projects/api-v2-to-graphql-migration/UPSTREAM.md +53 -0
  62. data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/csv-template-pipeline-design.md +102 -0
  63. data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/cutover-usecase-gap-audit.md +139 -0
  64. data/.ai-assistance/projects/dynamic-model-generation/INTENT.md +93 -0
  65. data/.ai-assistance/projects/eco-helpers-compat/INTENT.md +244 -0
  66. data/.ai-assistance/projects/eco-helpers-compat/MIGRATION_GUIDE.md +266 -0
  67. data/.ai-assistance/projects/eco-helpers-compat/TODO.md +86 -0
  68. data/.ai-assistance/projects/ecoportal-api-v2-doublemodel-review/INTENT.md +101 -0
  69. data/.ai-assistance/projects/graphql-agent/GAP_ANALYSIS.md +177 -0
  70. data/.ai-assistance/projects/ooze-graphql-native-migration/DECISIONS.md +161 -0
  71. data/.ai-assistance/projects/ooze-graphql-native-migration/INTENT.md +125 -0
  72. data/.ai-assistance/projects/ooze-graphql-native-migration/RISKS.md +126 -0
  73. data/.ai-assistance/projects/ooze-graphql-native-migration/TODO.md +256 -0
  74. data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-06-30-cutover-workflow-deep-review.md +122 -0
  75. data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-07-01-forces-via-workflow-commands-miss-rca.md +148 -0
  76. data/.ai-assistance/projects/page-model/DECISIONS.md +245 -0
  77. data/.ai-assistance/projects/page-model/TODO.md +190 -0
  78. data/.ai-assistance/projects/search-filter-builder/INTENT.md +107 -0
  79. data/.ai-assistance/projects/search-filter-builder/TODO.md +131 -0
  80. data/.ai-assistance/projects/template-maintenance/DESIGN.md +134 -0
  81. data/.ai-assistance/projects/workflow-space/TODO.md +213 -0
  82. data/.ai-assistance/reinstall-claude-desktop-windows.md +136 -0
  83. data/.ai-assistance/scripts/CLAUDE.md +150 -0
  84. data/.ai-assistance/scripts/bridge-init.sh +86 -0
  85. data/.ai-assistance/scripts/bridge-status.sh +44 -0
  86. data/.ai-assistance/scripts/capabilities-check.ts +104 -0
  87. data/.ai-assistance/scripts/check-outbox.sh +43 -0
  88. data/.ai-assistance/scripts/dep_graph.rb +91 -0
  89. data/.ai-assistance/scripts/lock-acquire.sh +103 -0
  90. data/.ai-assistance/scripts/lock-multi.sh +124 -0
  91. data/.ai-assistance/scripts/lock-queue.sh +94 -0
  92. data/.ai-assistance/scripts/setup-mcps.test.ts +188 -0
  93. data/.ai-assistance/scripts/setup-mcps.ts +234 -0
  94. data/.ai-assistance/scripts/task-complete.ts +74 -0
  95. data/.ai-assistance/scripts/task-create.ts +75 -0
  96. data/.ai-assistance/scripts/task-read.ts +125 -0
  97. data/.ai-assistance/scripts/token-logger.js +220 -0
  98. data/.ai-assistance/scripts/token-report.ts +158 -0
  99. data/.ai-assistance/scripts/token-session-start.js +66 -0
  100. data/.ai-assistance/skills/ai-instructions/SKILL.md +48 -0
  101. data/.ai-assistance/skills/code-specs/SKILL.md +69 -0
  102. data/.ai-assistance/skills/corporate-policies/SKILL.md +201 -0
  103. data/.ai-assistance/skills/dep-graph/SKILL.md +139 -0
  104. data/.ai-assistance/skills/ep-ai-manager/SKILL.md +417 -0
  105. data/.ai-assistance/skills/gemini-assist/SKILL.md +63 -0
  106. data/.ai-assistance/skills/gemini-assist/gemini-mcp-server.js +205 -0
  107. data/.ai-assistance/skills/gemini-assist/gemini_ask.py +1 -0
  108. data/.ai-assistance/skills/gemini-assist/gemini_ask.rb +240 -0
  109. data/.ai-assistance/skills/gemini-assist/prompts/cycle_end_review.txt +25 -0
  110. data/.ai-assistance/skills/graphql-schema-analysis/SKILL.md +261 -0
  111. data/.ai-assistance/skills/project-cycle/SKILL.md +177 -0
  112. data/.ai-assistance/skills/refactor/SKILL.md +62 -0
  113. data/.ai-assistance/skills/rubocop/SKILL.md +93 -0
  114. data/.ai-assistance/skills/ruby-scripting/SKILL.md +215 -0
  115. data/.ai-assistance/skills/spec-generation/SKILL.md +72 -0
  116. data/.ai-assistance/standards-version.json +21 -0
  117. data/.ai-assistance/token-budget.json +32 -0
  118. data/.ai-assistance/version.json +39 -0
  119. data/.claude/settings.json +146 -0
  120. data/.env.example +18 -0
  121. data/.gitattributes +15 -0
  122. data/.gitignore +13 -0
  123. data/.rubocop.yml +121 -97
  124. data/CHANGELOG.md +673 -477
  125. data/CLAUDE.md +232 -0
  126. data/Gemfile +30 -6
  127. data/Rakefile +90 -38
  128. data/docs/worklog.md +574 -0
  129. data/ecoportal-api-graphql.gemspec +40 -40
  130. data/lib/ecoportal/api/common/graphql/CLAUDE.md +36 -0
  131. data/lib/ecoportal/api/common/graphql/client.rb +1 -1
  132. data/lib/ecoportal/api/common/graphql/http_client.rb +35 -3
  133. data/lib/ecoportal/api/common/graphql/model/CLAUDE.md +28 -0
  134. data/lib/ecoportal/api/common/graphql/model/as_input.rb +8 -5
  135. data/lib/ecoportal/api/common/graphql/model/diffable/classic_diff_service.rb +3 -1
  136. data/lib/ecoportal/api/common/graphql/model/diffable/diff_service.rb +31 -23
  137. data/lib/ecoportal/api/common/graphql/model/diffable/hash_diff_nesting.rb +54 -1
  138. data/lib/ecoportal/api/graphql/CLAUDE.md +37 -0
  139. data/lib/ecoportal/api/graphql/base/CLAUDE.md +50 -0
  140. data/lib/ecoportal/api/graphql/base/ai_summary_version.rb +17 -0
  141. data/lib/ecoportal/api/graphql/base/delta_result.rb +16 -0
  142. data/lib/ecoportal/api/graphql/base/force/binding.rb +19 -0
  143. data/lib/ecoportal/api/graphql/base/force/binding_collection.rb +62 -0
  144. data/lib/ecoportal/api/graphql/base/force/collection.rb +47 -0
  145. data/lib/ecoportal/api/graphql/base/force.rb +65 -0
  146. data/lib/ecoportal/api/graphql/base/kickstand/job.rb +17 -0
  147. data/lib/ecoportal/api/graphql/base/kickstand/workflow.rb +18 -0
  148. data/lib/ecoportal/api/graphql/base/kickstand.rb +13 -0
  149. data/lib/ecoportal/api/graphql/base/page/basic.rb +38 -0
  150. data/lib/ecoportal/api/graphql/base/page/data_field/actions_list.rb +9 -0
  151. data/lib/ecoportal/api/graphql/base/page/data_field/ai_summary.rb +18 -0
  152. data/lib/ecoportal/api/graphql/base/page/data_field/checklist.rb +29 -0
  153. data/lib/ecoportal/api/graphql/base/page/data_field/collection.rb +112 -0
  154. data/lib/ecoportal/api/graphql/base/page/data_field/contractor_entities.rb +28 -0
  155. data/lib/ecoportal/api/graphql/base/page/data_field/cross_reference.rb +54 -0
  156. data/lib/ecoportal/api/graphql/base/page/data_field/date_field.rb +23 -0
  157. data/lib/ecoportal/api/graphql/base/page/data_field/file_field.rb +25 -0
  158. data/lib/ecoportal/api/graphql/base/page/data_field/gauge.rb +19 -0
  159. data/lib/ecoportal/api/graphql/base/page/data_field/geo.rb +24 -0
  160. data/lib/ecoportal/api/graphql/base/page/data_field/image_gallery.rb +24 -0
  161. data/lib/ecoportal/api/graphql/base/page/data_field/law.rb +8 -0
  162. data/lib/ecoportal/api/graphql/base/page/data_field/mailbox.rb +9 -0
  163. data/lib/ecoportal/api/graphql/base/page/data_field/number.rb +20 -0
  164. data/lib/ecoportal/api/graphql/base/page/data_field/people.rb +35 -0
  165. data/lib/ecoportal/api/graphql/base/page/data_field/plain_text.rb +17 -0
  166. data/lib/ecoportal/api/graphql/base/page/data_field/rich_text.rb +26 -0
  167. data/lib/ecoportal/api/graphql/base/page/data_field/select.rb +41 -0
  168. data/lib/ecoportal/api/graphql/base/page/data_field/signature.rb +9 -0
  169. data/lib/ecoportal/api/graphql/base/page/data_field/smart_fill.rb +17 -0
  170. data/lib/ecoportal/api/graphql/base/page/data_field/table.rb +9 -0
  171. data/lib/ecoportal/api/graphql/base/page/data_field/tag_field.rb +21 -0
  172. data/lib/ecoportal/api/graphql/base/page/data_field.rb +137 -12
  173. data/lib/ecoportal/api/graphql/base/page/phased/stage.rb +68 -14
  174. data/lib/ecoportal/api/graphql/base/page/phased.rb +1 -0
  175. data/lib/ecoportal/api/graphql/base/page/section.rb +36 -0
  176. data/lib/ecoportal/api/graphql/base/page/section_collection.rb +79 -0
  177. data/lib/ecoportal/api/graphql/base/page.rb +2 -0
  178. data/lib/ecoportal/api/graphql/base/pages_workflow/action_type_selection.rb +17 -0
  179. data/lib/ecoportal/api/graphql/base/pages_workflow/callback_type.rb +28 -0
  180. data/lib/ecoportal/api/graphql/base/pages_workflow/command_change.rb +17 -0
  181. data/lib/ecoportal/api/graphql/base/pages_workflow/command_change_message.rb +15 -0
  182. data/lib/ecoportal/api/graphql/base/pages_workflow/command_es_change.rb +17 -0
  183. data/lib/ecoportal/api/graphql/base/pages_workflow/command_interface.rb +36 -0
  184. data/lib/ecoportal/api/graphql/base/pages_workflow/email_config.rb +18 -0
  185. data/lib/ecoportal/api/graphql/base/pages_workflow/escalation_level.rb +19 -0
  186. data/lib/ecoportal/api/graphql/base/pages_workflow/in_system_config.rb +16 -0
  187. data/lib/ecoportal/api/graphql/base/pages_workflow/mailbox_field_selection.rb +17 -0
  188. data/lib/ecoportal/api/graphql/base/pages_workflow/operation_interface.rb +39 -0
  189. data/lib/ecoportal/api/graphql/base/pages_workflow/operations/assign_to.rb +27 -0
  190. data/lib/ecoportal/api/graphql/base/pages_workflow/operations/create_page.rb +21 -0
  191. data/lib/ecoportal/api/graphql/base/pages_workflow/operations/send_notification.rb +30 -0
  192. data/lib/ecoportal/api/graphql/base/pages_workflow/people_field_selection.rb +17 -0
  193. data/lib/ecoportal/api/graphql/base/pages_workflow/recipient_config.rb +34 -0
  194. data/lib/ecoportal/api/graphql/base/pages_workflow/register_field.rb +17 -0
  195. data/lib/ecoportal/api/graphql/base/pages_workflow/task_config_selection.rb +17 -0
  196. data/lib/ecoportal/api/graphql/base/pages_workflow/time_delay_config.rb +16 -0
  197. data/lib/ecoportal/api/graphql/base/pages_workflow/trigger_interface.rb +26 -0
  198. data/lib/ecoportal/api/graphql/base/pages_workflow/triggers/conditional_logic.rb +17 -0
  199. data/lib/ecoportal/api/graphql/base/pages_workflow/user_selection.rb +16 -0
  200. data/lib/ecoportal/api/graphql/base/pages_workflow.rb +35 -0
  201. data/lib/ecoportal/api/graphql/base/preset_view.rb +17 -0
  202. data/lib/ecoportal/api/graphql/base/preview_page.rb +23 -0
  203. data/lib/ecoportal/api/graphql/base/register.rb +18 -0
  204. data/lib/ecoportal/api/graphql/base.rb +11 -0
  205. data/lib/ecoportal/api/graphql/builder/CLAUDE.md +65 -0
  206. data/lib/ecoportal/api/graphql/builder/kickstand.rb +73 -0
  207. data/lib/ecoportal/api/graphql/builder/page.rb +210 -41
  208. data/lib/ecoportal/api/graphql/builder/register/preset_view.rb +84 -0
  209. data/lib/ecoportal/api/graphql/builder/register.rb +27 -19
  210. data/lib/ecoportal/api/graphql/builder/template.rb +80 -0
  211. data/lib/ecoportal/api/graphql/builder.rb +2 -0
  212. data/lib/ecoportal/api/graphql/compat/filter_translator.rb +107 -0
  213. data/lib/ecoportal/api/graphql/compat/page_reference.rb +23 -0
  214. data/lib/ecoportal/api/graphql/compat/pages.rb +212 -0
  215. data/lib/ecoportal/api/graphql/compat/registers.rb +84 -0
  216. data/lib/ecoportal/api/graphql/compat/response.rb +35 -0
  217. data/lib/ecoportal/api/graphql/compat/search_results.rb +33 -0
  218. data/lib/ecoportal/api/graphql/compat/stage_collection.rb +70 -0
  219. data/lib/ecoportal/api/graphql/compat/stage_view.rb +76 -0
  220. data/lib/ecoportal/api/graphql/compat.rb +17 -0
  221. data/lib/ecoportal/api/graphql/concerns/data_field_access.rb +71 -0
  222. data/lib/ecoportal/api/graphql/concerns/deprecation.rb +20 -0
  223. data/lib/ecoportal/api/graphql/concerns/fragment_definitions.rb +21 -28
  224. data/lib/ecoportal/api/graphql/concerns/page_compat.rb +51 -0
  225. data/lib/ecoportal/api/graphql/concerns/snake_camel_access.rb +60 -0
  226. data/lib/ecoportal/api/graphql/concerns.rb +4 -0
  227. data/lib/ecoportal/api/graphql/connection/page.rb +11 -0
  228. data/lib/ecoportal/api/graphql/connection/pages_workflow_command.rb +13 -0
  229. data/lib/ecoportal/api/graphql/connection/preset_view.rb +11 -0
  230. data/lib/ecoportal/api/graphql/connection/preview_page.rb +11 -0
  231. data/lib/ecoportal/api/graphql/connection.rb +4 -0
  232. data/lib/ecoportal/api/graphql/file_upload/client.rb +181 -0
  233. data/lib/ecoportal/api/graphql/file_upload.rb +10 -0
  234. data/lib/ecoportal/api/graphql/fragment/action.rb +1 -1
  235. data/lib/ecoportal/api/graphql/fragment/action_category.rb +1 -1
  236. data/lib/ecoportal/api/graphql/fragment/contractor_entity.rb +1 -1
  237. data/lib/ecoportal/api/graphql/fragment/force.rb +30 -0
  238. data/lib/ecoportal/api/graphql/fragment/location_draft.rb +2 -2
  239. data/lib/ecoportal/api/graphql/fragment/location_node.rb +1 -1
  240. data/lib/ecoportal/api/graphql/fragment/locations_error.rb +1 -1
  241. data/lib/ecoportal/api/graphql/fragment/page.rb +85 -0
  242. data/lib/ecoportal/api/graphql/fragment/pages/common_page_union.rb +395 -0
  243. data/lib/ecoportal/api/graphql/fragment/pages.rb +15 -0
  244. data/lib/ecoportal/api/graphql/fragment/pages_workflow.rb +172 -0
  245. data/lib/ecoportal/api/graphql/fragment/pagination.rb +1 -1
  246. data/lib/ecoportal/api/graphql/fragment.rb +37 -27
  247. data/lib/ecoportal/api/graphql/input/contractor_entity/update.rb +25 -0
  248. data/lib/ecoportal/api/graphql/input/delta_input.rb +16 -0
  249. data/lib/ecoportal/api/graphql/input/page/archive.rb +14 -0
  250. data/lib/ecoportal/api/graphql/input/page/build_from_template.rb +13 -0
  251. data/lib/ecoportal/api/graphql/input/page/create_draft.rb +13 -0
  252. data/lib/ecoportal/api/graphql/input/page/create_from_template.rb +18 -0
  253. data/lib/ecoportal/api/graphql/input/page/delete_draft.rb +13 -0
  254. data/lib/ecoportal/api/graphql/input/page/publish_draft.rb +13 -0
  255. data/lib/ecoportal/api/graphql/input/page/review_task.rb +14 -0
  256. data/lib/ecoportal/api/graphql/input/page/unarchive.rb +14 -0
  257. data/lib/ecoportal/api/graphql/input/page/update.rb +140 -0
  258. data/lib/ecoportal/api/graphql/input/page.rb +26 -0
  259. data/lib/ecoportal/api/graphql/input/preset_view/create.rb +18 -0
  260. data/lib/ecoportal/api/graphql/input/preset_view/permission.rb +16 -0
  261. data/lib/ecoportal/api/graphql/input/preset_view/update.rb +16 -0
  262. data/lib/ecoportal/api/graphql/input/preset_view.rb +14 -0
  263. data/lib/ecoportal/api/graphql/input/register/create.rb +18 -0
  264. data/lib/ecoportal/api/graphql/input/register/update.rb +15 -0
  265. data/lib/ecoportal/api/graphql/input/register.rb +13 -0
  266. data/lib/ecoportal/api/graphql/input/search_conf/ai_generator.rb +234 -0
  267. data/lib/ecoportal/api/graphql/input/search_conf.rb +367 -0
  268. data/lib/ecoportal/api/graphql/input/variable_binding.rb +20 -0
  269. data/lib/ecoportal/api/graphql/input/workflow_command/add_action_tag.rb +18 -0
  270. data/lib/ecoportal/api/graphql/input/workflow_command/add_binding.rb +18 -0
  271. data/lib/ecoportal/api/graphql/input/workflow_command/add_comment_tagging_user_group.rb +18 -0
  272. data/lib/ecoportal/api/graphql/input/workflow_command/add_default_direct_strategy_user.rb +18 -0
  273. data/lib/ecoportal/api/graphql/input/workflow_command/add_default_strategy.rb +18 -0
  274. data/lib/ecoportal/api/graphql/input/workflow_command/add_direct_strategy_user.rb +18 -0
  275. data/lib/ecoportal/api/graphql/input/workflow_command/add_field.rb +18 -0
  276. data/lib/ecoportal/api/graphql/input/workflow_command/add_force.rb +18 -0
  277. data/lib/ecoportal/api/graphql/input/workflow_command/add_gauge_field_stop.rb +19 -0
  278. data/lib/ecoportal/api/graphql/input/workflow_command/add_linked_field_config.rb +23 -0
  279. data/lib/ecoportal/api/graphql/input/workflow_command/add_linked_helper.rb +18 -0
  280. data/lib/ecoportal/api/graphql/input/workflow_command/add_operation.rb +18 -0
  281. data/lib/ecoportal/api/graphql/input/workflow_command/add_operation_direct_strategy_user.rb +18 -0
  282. data/lib/ecoportal/api/graphql/input/workflow_command/add_operation_strategy.rb +18 -0
  283. data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_action_type.rb +18 -0
  284. data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_filter.rb +18 -0
  285. data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_people_field.rb +18 -0
  286. data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_task_config.rb +18 -0
  287. data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_user.rb +18 -0
  288. data/lib/ecoportal/api/graphql/input/workflow_command/add_scheduled_callback.rb +23 -0
  289. data/lib/ecoportal/api/graphql/input/workflow_command/add_scheduled_callback_action.rb +18 -0
  290. data/lib/ecoportal/api/graphql/input/workflow_command/add_section.rb +18 -0
  291. data/lib/ecoportal/api/graphql/input/workflow_command/add_select_field_option.rb +19 -0
  292. data/lib/ecoportal/api/graphql/input/workflow_command/add_stage.rb +18 -0
  293. data/lib/ecoportal/api/graphql/input/workflow_command/add_stage_section.rb +18 -0
  294. data/lib/ecoportal/api/graphql/input/workflow_command/add_stage_tag.rb +18 -0
  295. data/lib/ecoportal/api/graphql/input/workflow_command/add_strategy.rb +18 -0
  296. data/lib/ecoportal/api/graphql/input/workflow_command/add_task.rb +18 -0
  297. data/lib/ecoportal/api/graphql/input/workflow_command/add_task_assignment_user_group.rb +18 -0
  298. data/lib/ecoportal/api/graphql/input/workflow_command/add_workflow_callback.rb +18 -0
  299. data/lib/ecoportal/api/graphql/input/workflow_command/collapse_section.rb +18 -0
  300. data/lib/ecoportal/api/graphql/input/workflow_command/edit_binding.rb +18 -0
  301. data/lib/ecoportal/api/graphql/input/workflow_command/edit_creator_permissions.rb +18 -0
  302. data/lib/ecoportal/api/graphql/input/workflow_command/edit_default_strategy.rb +18 -0
  303. data/lib/ecoportal/api/graphql/input/workflow_command/edit_field_configuration.rb +21 -0
  304. data/lib/ecoportal/api/graphql/input/workflow_command/edit_force.rb +18 -0
  305. data/lib/ecoportal/api/graphql/input/workflow_command/edit_gauge_field_stop.rb +19 -0
  306. data/lib/ecoportal/api/graphql/input/workflow_command/edit_linked_field_config.rb +19 -0
  307. data/lib/ecoportal/api/graphql/input/workflow_command/edit_linked_helper.rb +18 -0
  308. data/lib/ecoportal/api/graphql/input/workflow_command/edit_operation.rb +18 -0
  309. data/lib/ecoportal/api/graphql/input/workflow_command/edit_operation_strategy.rb +18 -0
  310. data/lib/ecoportal/api/graphql/input/workflow_command/edit_page.rb +28 -0
  311. data/lib/ecoportal/api/graphql/input/workflow_command/edit_page_creator_permissions.rb +18 -0
  312. data/lib/ecoportal/api/graphql/input/workflow_command/edit_reminder.rb +18 -0
  313. data/lib/ecoportal/api/graphql/input/workflow_command/edit_required_sign_offs.rb +18 -0
  314. data/lib/ecoportal/api/graphql/input/workflow_command/edit_restrict_comment_tagging.rb +18 -0
  315. data/lib/ecoportal/api/graphql/input/workflow_command/edit_restrict_task_assignment.rb +18 -0
  316. data/lib/ecoportal/api/graphql/input/workflow_command/edit_scheduled_callback.rb +22 -0
  317. data/lib/ecoportal/api/graphql/input/workflow_command/edit_section_header.rb +18 -0
  318. data/lib/ecoportal/api/graphql/input/workflow_command/edit_select_field_option.rb +19 -0
  319. data/lib/ecoportal/api/graphql/input/workflow_command/edit_stage.rb +18 -0
  320. data/lib/ecoportal/api/graphql/input/workflow_command/edit_strategy.rb +18 -0
  321. data/lib/ecoportal/api/graphql/input/workflow_command/edit_task_due.rb +18 -0
  322. data/lib/ecoportal/api/graphql/input/workflow_command/edit_trigger.rb +18 -0
  323. data/lib/ecoportal/api/graphql/input/workflow_command/expand_section.rb +18 -0
  324. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/contractor_entities.rb +24 -0
  325. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/cross_reference.rb +23 -0
  326. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/date.rb +20 -0
  327. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/gauge.rb +20 -0
  328. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/image_gallery.rb +20 -0
  329. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/location_field.rb +24 -0
  330. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/people.rb +24 -0
  331. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/plain_text.rb +20 -0
  332. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/rich_text.rb +20 -0
  333. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/select.rb +20 -0
  334. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/signature.rb +20 -0
  335. data/lib/ecoportal/api/graphql/input/workflow_command/field_config/table.rb +25 -0
  336. data/lib/ecoportal/api/graphql/input/workflow_command/move_field.rb +18 -0
  337. data/lib/ecoportal/api/graphql/input/workflow_command/move_stage.rb +18 -0
  338. data/lib/ecoportal/api/graphql/input/workflow_command/remove_action_tag.rb +18 -0
  339. data/lib/ecoportal/api/graphql/input/workflow_command/remove_binding.rb +18 -0
  340. data/lib/ecoportal/api/graphql/input/workflow_command/remove_callback.rb +18 -0
  341. data/lib/ecoportal/api/graphql/input/workflow_command/remove_comment_tagging_user_group.rb +18 -0
  342. data/lib/ecoportal/api/graphql/input/workflow_command/remove_default_direct_strategy_user.rb +18 -0
  343. data/lib/ecoportal/api/graphql/input/workflow_command/remove_default_strategy.rb +18 -0
  344. data/lib/ecoportal/api/graphql/input/workflow_command/remove_direct_strategy_user.rb +18 -0
  345. data/lib/ecoportal/api/graphql/input/workflow_command/remove_field.rb +18 -0
  346. data/lib/ecoportal/api/graphql/input/workflow_command/remove_force.rb +18 -0
  347. data/lib/ecoportal/api/graphql/input/workflow_command/remove_gauge_field_stop.rb +17 -0
  348. data/lib/ecoportal/api/graphql/input/workflow_command/remove_linked_field_config.rb +17 -0
  349. data/lib/ecoportal/api/graphql/input/workflow_command/remove_linked_helper.rb +18 -0
  350. data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation.rb +18 -0
  351. data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation_direct_strategy_user.rb +18 -0
  352. data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation_strategy.rb +18 -0
  353. data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_action_type.rb +18 -0
  354. data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_filter.rb +18 -0
  355. data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_people_field.rb +18 -0
  356. data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_task_config.rb +18 -0
  357. data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_user.rb +18 -0
  358. data/lib/ecoportal/api/graphql/input/workflow_command/remove_scheduled_callback.rb +18 -0
  359. data/lib/ecoportal/api/graphql/input/workflow_command/remove_section.rb +18 -0
  360. data/lib/ecoportal/api/graphql/input/workflow_command/remove_select_field_option.rb +17 -0
  361. data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage.rb +18 -0
  362. data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage_section.rb +18 -0
  363. data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage_tag.rb +18 -0
  364. data/lib/ecoportal/api/graphql/input/workflow_command/remove_strategy.rb +18 -0
  365. data/lib/ecoportal/api/graphql/input/workflow_command/remove_task.rb +18 -0
  366. data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_assignment_user_group.rb +18 -0
  367. data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_due.rb +18 -0
  368. data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_priority_level.rb +18 -0
  369. data/lib/ecoportal/api/graphql/input/workflow_command/reorder_forces.rb +18 -0
  370. data/lib/ecoportal/api/graphql/input/workflow_command/reorder_section.rb +18 -0
  371. data/lib/ecoportal/api/graphql/input/workflow_command.rb +251 -0
  372. data/lib/ecoportal/api/graphql/input.rb +8 -0
  373. data/lib/ecoportal/api/graphql/interface/base_page.rb +100 -58
  374. data/lib/ecoportal/api/graphql/interface/location_structure/nodes.rb +27 -28
  375. data/lib/ecoportal/api/graphql/logic/base_model.rb +2 -0
  376. data/lib/ecoportal/api/graphql/logic/base_query.rb +45 -2
  377. data/lib/ecoportal/api/graphql/logic/input.rb +15 -0
  378. data/lib/ecoportal/api/graphql/model/ai_summary_version.rb +10 -0
  379. data/lib/ecoportal/api/graphql/model/organization.rb +65 -55
  380. data/lib/ecoportal/api/graphql/model/page/basic.rb +14 -0
  381. data/lib/ecoportal/api/graphql/model/page/phased.rb +82 -20
  382. data/lib/ecoportal/api/graphql/model/page.rb +1 -0
  383. data/lib/ecoportal/api/graphql/model/page_union.rb +21 -0
  384. data/lib/ecoportal/api/graphql/model/pages_workflow.rb +20 -0
  385. data/lib/ecoportal/api/graphql/model/preset_view.rb +10 -0
  386. data/lib/ecoportal/api/graphql/model/preview_page.rb +10 -0
  387. data/lib/ecoportal/api/graphql/model/register.rb +10 -0
  388. data/lib/ecoportal/api/graphql/model.rb +8 -0
  389. data/lib/ecoportal/api/graphql/mutation/ai_summary/generate.rb +45 -0
  390. data/lib/ecoportal/api/graphql/mutation/ai_summary/submit_feedback.rb +40 -0
  391. data/lib/ecoportal/api/graphql/mutation/ai_summary.rb +13 -0
  392. data/lib/ecoportal/api/graphql/mutation/kickstand/bulk_update_jobs.rb +43 -0
  393. data/lib/ecoportal/api/graphql/mutation/kickstand/bulk_update_workflows.rb +43 -0
  394. data/lib/ecoportal/api/graphql/mutation/kickstand/fail_job.rb +39 -0
  395. data/lib/ecoportal/api/graphql/mutation/kickstand/fail_workflow.rb +39 -0
  396. data/lib/ecoportal/api/graphql/mutation/kickstand/start_job.rb +39 -0
  397. data/lib/ecoportal/api/graphql/mutation/kickstand/start_workflow.rb +39 -0
  398. data/lib/ecoportal/api/graphql/mutation/kickstand/stop_workflow.rb +39 -0
  399. data/lib/ecoportal/api/graphql/mutation/kickstand.rb +18 -0
  400. data/lib/ecoportal/api/graphql/mutation/location_structure/apply_commands.rb +3 -3
  401. data/lib/ecoportal/api/graphql/mutation/location_structure/draft/add_commands.rb +2 -2
  402. data/lib/ecoportal/api/graphql/mutation/location_structure/draft/create.rb +2 -2
  403. data/lib/ecoportal/api/graphql/mutation/location_structure/draft/drop_bad_commands.rb +3 -3
  404. data/lib/ecoportal/api/graphql/mutation/location_structure/draft/publish.rb +3 -3
  405. data/lib/ecoportal/api/graphql/mutation/page/approve_review_task.rb +40 -0
  406. data/lib/ecoportal/api/graphql/mutation/page/archive.rb +40 -0
  407. data/lib/ecoportal/api/graphql/mutation/page/batch_update_review_task.rb +40 -0
  408. data/lib/ecoportal/api/graphql/mutation/page/build_from_template.rb +50 -0
  409. data/lib/ecoportal/api/graphql/mutation/page/create_draft.rb +40 -0
  410. data/lib/ecoportal/api/graphql/mutation/page/create_from_template.rb +43 -0
  411. data/lib/ecoportal/api/graphql/mutation/page/delete_draft.rb +40 -0
  412. data/lib/ecoportal/api/graphql/mutation/page/execute_force_commands.rb +69 -0
  413. data/lib/ecoportal/api/graphql/mutation/page/execute_workflow_commands.rb +51 -0
  414. data/lib/ecoportal/api/graphql/mutation/page/publish_draft.rb +40 -0
  415. data/lib/ecoportal/api/graphql/mutation/page/reject_review_task.rb +40 -0
  416. data/lib/ecoportal/api/graphql/mutation/page/restart_review_task.rb +40 -0
  417. data/lib/ecoportal/api/graphql/mutation/page/unarchive.rb +40 -0
  418. data/lib/ecoportal/api/graphql/mutation/page/undo_review_task.rb +40 -0
  419. data/lib/ecoportal/api/graphql/mutation/page/update.rb +40 -0
  420. data/lib/ecoportal/api/graphql/mutation/page/update_variable_bindings.rb +44 -0
  421. data/lib/ecoportal/api/graphql/mutation/page.rb +28 -0
  422. data/lib/ecoportal/api/graphql/mutation/preset_view/create.rb +35 -0
  423. data/lib/ecoportal/api/graphql/mutation/preset_view/destroy.rb +35 -0
  424. data/lib/ecoportal/api/graphql/mutation/preset_view/permission.rb +37 -0
  425. data/lib/ecoportal/api/graphql/mutation/preset_view/update.rb +35 -0
  426. data/lib/ecoportal/api/graphql/mutation/preset_view.rb +15 -0
  427. data/lib/ecoportal/api/graphql/mutation/register/create.rb +35 -0
  428. data/lib/ecoportal/api/graphql/mutation/register/destroy.rb +35 -0
  429. data/lib/ecoportal/api/graphql/mutation/register/update.rb +35 -0
  430. data/lib/ecoportal/api/graphql/mutation/register.rb +14 -0
  431. data/lib/ecoportal/api/graphql/mutation/smart_fill/generate.rb +36 -0
  432. data/lib/ecoportal/api/graphql/mutation/smart_fill/submit_feedback.rb +40 -0
  433. data/lib/ecoportal/api/graphql/mutation/smart_fill.rb +13 -0
  434. data/lib/ecoportal/api/graphql/mutation/template/create.rb +39 -0
  435. data/lib/ecoportal/api/graphql/mutation/template/create_related_page.rb +46 -0
  436. data/lib/ecoportal/api/graphql/mutation/template/destroy_related_page.rb +43 -0
  437. data/lib/ecoportal/api/graphql/mutation/template/publish.rb +39 -0
  438. data/lib/ecoportal/api/graphql/mutation/template/unpublish.rb +39 -0
  439. data/lib/ecoportal/api/graphql/mutation/template/update.rb +43 -0
  440. data/lib/ecoportal/api/graphql/mutation/template/update_information.rb +43 -0
  441. data/lib/ecoportal/api/graphql/mutation/template.rb +18 -0
  442. data/lib/ecoportal/api/graphql/mutation.rb +8 -0
  443. data/lib/ecoportal/api/graphql/payload/ai_summary_generate.rb +12 -0
  444. data/lib/ecoportal/api/graphql/payload/execute_workflow_commands.rb +36 -0
  445. data/lib/ecoportal/api/graphql/payload/force_commands.rb +31 -0
  446. data/lib/ecoportal/api/graphql/payload/kickstand/bulk_update_jobs.rb +36 -0
  447. data/lib/ecoportal/api/graphql/payload/kickstand/bulk_update_workflows.rb +36 -0
  448. data/lib/ecoportal/api/graphql/payload/kickstand/job.rb +13 -0
  449. data/lib/ecoportal/api/graphql/payload/kickstand/workflow.rb +13 -0
  450. data/lib/ecoportal/api/graphql/payload/kickstand.rb +15 -0
  451. data/lib/ecoportal/api/graphql/payload/location_structure/draft/create.rb +33 -34
  452. data/lib/ecoportal/api/graphql/payload/ok_payload.rb +21 -0
  453. data/lib/ecoportal/api/graphql/payload/page/archive.rb +13 -0
  454. data/lib/ecoportal/api/graphql/payload/page/build_from_template.rb +13 -0
  455. data/lib/ecoportal/api/graphql/payload/page/create_from_template.rb +13 -0
  456. data/lib/ecoportal/api/graphql/payload/page/draft.rb +13 -0
  457. data/lib/ecoportal/api/graphql/payload/page/review_task.rb +13 -0
  458. data/lib/ecoportal/api/graphql/payload/page/unarchive.rb +13 -0
  459. data/lib/ecoportal/api/graphql/payload/page/update.rb +13 -0
  460. data/lib/ecoportal/api/graphql/payload/page/update_variable_bindings.rb +13 -0
  461. data/lib/ecoportal/api/graphql/payload/page.rb +19 -0
  462. data/lib/ecoportal/api/graphql/payload/preset_view.rb +11 -0
  463. data/lib/ecoportal/api/graphql/payload/register.rb +11 -0
  464. data/lib/ecoportal/api/graphql/payload/template/create.rb +13 -0
  465. data/lib/ecoportal/api/graphql/payload/template/create_related_page.rb +13 -0
  466. data/lib/ecoportal/api/graphql/payload/template/destroy_related_page.rb +13 -0
  467. data/lib/ecoportal/api/graphql/payload/template/publish.rb +13 -0
  468. data/lib/ecoportal/api/graphql/payload/template/unpublish.rb +13 -0
  469. data/lib/ecoportal/api/graphql/payload/template/update.rb +13 -0
  470. data/lib/ecoportal/api/graphql/payload/template/update_information.rb +13 -0
  471. data/lib/ecoportal/api/graphql/payload/template.rb +18 -0
  472. data/lib/ecoportal/api/graphql/payload.rb +11 -0
  473. data/lib/ecoportal/api/graphql/query/action.rb +1 -1
  474. data/lib/ecoportal/api/graphql/query/action_categories.rb +1 -1
  475. data/lib/ecoportal/api/graphql/query/actions.rb +2 -2
  476. data/lib/ecoportal/api/graphql/query/contractor_entities.rb +1 -1
  477. data/lib/ecoportal/api/graphql/query/file_upload_signature.rb +76 -0
  478. data/lib/ecoportal/api/graphql/query/location_structure/draft.rb +2 -2
  479. data/lib/ecoportal/api/graphql/query/location_structure.rb +1 -1
  480. data/lib/ecoportal/api/graphql/query/location_structures.rb +1 -1
  481. data/lib/ecoportal/api/graphql/query/page.rb +45 -0
  482. data/lib/ecoportal/api/graphql/query/page_delta.rb +47 -0
  483. data/lib/ecoportal/api/graphql/query/page_with_forces.rb +43 -0
  484. data/lib/ecoportal/api/graphql/query/pages.rb +59 -0
  485. data/lib/ecoportal/api/graphql/query/pages_workflow_commands.rb +59 -0
  486. data/lib/ecoportal/api/graphql/query/register_preset_views.rb +78 -0
  487. data/lib/ecoportal/api/graphql/query/register_preview_pages.rb +83 -0
  488. data/lib/ecoportal/api/graphql/query/templates.rb +53 -0
  489. data/lib/ecoportal/api/graphql/query.rb +11 -0
  490. data/lib/ecoportal/api/graphql.rb +60 -2
  491. data/lib/ecoportal/api/graphql_version.rb +5 -5
  492. data/scripts/auto-worker-scheduler.sh +386 -0
  493. data/tests/contractor_entity_create.rb +19 -19
  494. data/tests/contractor_entity_udpate.rb +20 -20
  495. data/tests/dump_page_model.rb +74 -0
  496. data/tests/loc_structure_get.rb +1 -2
  497. data/tests/loc_structure_update.rb +51 -51
  498. data/tests/loc_structures_get.rb +15 -15
  499. metadata +436 -5
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env bash
2
+ # lock-queue.sh
3
+ # Drop a priority turn-request into the bridge queue of one or more repos.
4
+ # Used when a lock is held and this agent wants to wait with declared priority.
5
+ #
6
+ # Usage:
7
+ # bash lock-queue.sh \
8
+ # --agent cowork \
9
+ # --user oscar \
10
+ # --intent "Review auth changes" \
11
+ # --priority high \
12
+ # --target-project ep-claude-aws-platform \
13
+ # [--repo /path/to/repo1 --repo /path/to/repo2 ...] \
14
+ # [--expiry-minutes 90]
15
+ #
16
+ # Writes: .ai-assistance/bridge/queue/<priority>-<ts>-<id>.json in each repo
17
+ # Exit 0: request(s) queued. Prints JSON with queue file paths.
18
+
19
+ set -euo pipefail
20
+
21
+ AGENT="" USER_SLUG="" INTENT="" PRIORITY="medium" TARGET_PROJECT=""
22
+ EXPIRY_MINUTES=90 ESTIMATED_WAIT_MINUTES=""
23
+ REPOS=()
24
+
25
+ while [[ $# -gt 0 ]]; do
26
+ case "$1" in
27
+ --agent) AGENT="$2"; shift 2 ;;
28
+ --user) USER_SLUG="$2"; shift 2 ;;
29
+ --intent) INTENT="$2"; shift 2 ;;
30
+ --priority) PRIORITY="$2"; shift 2 ;;
31
+ --target-project) TARGET_PROJECT="$2"; shift 2 ;;
32
+ --estimated-wait) ESTIMATED_WAIT_MINUTES="$2"; shift 2 ;;
33
+ --repo) REPOS+=("$2"); shift 2 ;;
34
+ --expiry-minutes) EXPIRY_MINUTES="$2"; shift 2 ;;
35
+ *) echo "{\"queued\":false,\"reason\":\"unknown argument: $1\"}"; exit 1 ;;
36
+ esac
37
+ done
38
+
39
+ [[ -z "$AGENT" || -z "$USER_SLUG" || -z "$INTENT" ]] && {
40
+ echo '{"queued":false,"reason":"missing --agent, --user, or --intent"}'; exit 1; }
41
+
42
+ # Default: current repo
43
+ [[ ${#REPOS[@]} -eq 0 ]] && REPOS=("$(pwd)")
44
+
45
+ NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
46
+ EXPIRES=$(date -u -d "+${EXPIRY_MINUTES} minutes" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
47
+ || date -u -v +${EXPIRY_MINUTES}M +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
48
+ || echo "unknown")
49
+ REQUEST_ID="req-$(date +%s)-$$"
50
+
51
+ # Priority sort prefix: 1=critical 2=high 3=medium 4=low
52
+ case "$PRIORITY" in
53
+ critical) PFX="1" ;; high) PFX="2" ;; medium) PFX="3" ;; low) PFX="4" ;; *) PFX="3" ;;
54
+ esac
55
+
56
+ QUEUED_FILES=()
57
+
58
+ # estimated_wait: read from the current LOCK file if present (it may contain estimated_release)
59
+ get_estimated_release() {
60
+ local lock="$1/.ai-assistance/bridge/LOCK"
61
+ [[ -f "$lock" ]] || { echo "unknown"; return; }
62
+ grep "estimated_release:" "$lock" 2>/dev/null | sed 's/.*estimated_release://' | tr -d ' ' | head -1 || echo "unknown"
63
+ }
64
+
65
+ for repo in "${REPOS[@]}"; do
66
+ QUEUE_DIR="$repo/.ai-assistance/bridge/queue"
67
+ [[ -d "$QUEUE_DIR" ]] || mkdir -p "$QUEUE_DIR"
68
+ QFILE="$QUEUE_DIR/${PFX}-${PRIORITY}-$(date +%s%3N)-${REQUEST_ID}.json"
69
+
70
+ # Read current lock's estimated release so queue entry shows the expected wait
71
+ LOCK_ESTIMATED_RELEASE=$(get_estimated_release "$repo")
72
+ EFFECTIVE_WAIT="${ESTIMATED_WAIT_MINUTES:-unknown}"
73
+
74
+ cat > "$QFILE" << JSON
75
+ {
76
+ "request_id": "$REQUEST_ID",
77
+ "agent": "$AGENT",
78
+ "user": "$USER_SLUG",
79
+ "intent": "$INTENT",
80
+ "priority": "$PRIORITY",
81
+ "target_project": "$TARGET_PROJECT",
82
+ "repo": "$repo",
83
+ "requested_at": "$NOW",
84
+ "expires_at": "$EXPIRES",
85
+ "estimated_wait_minutes": "$EFFECTIVE_WAIT",
86
+ "current_lock_estimated_release": "$LOCK_ESTIMATED_RELEASE"
87
+ }
88
+ JSON
89
+ QUEUED_FILES+=("$QFILE")
90
+ done
91
+
92
+ # Report
93
+ FILES_JSON=$(printf '"%s",' "${QUEUED_FILES[@]}" | sed 's/,$//')
94
+ echo "{\"queued\":true,\"request_id\":\"$REQUEST_ID\",\"priority\":\"$PRIORITY\",\"files\":[$FILES_JSON]}"
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env npx ts-node
2
+ /**
3
+ * setup-mcps.test.ts
4
+ * Unit tests for the pure logic in setup-mcps.ts.
5
+ * No external dependencies — uses Node's assert module.
6
+ *
7
+ * Usage:
8
+ * npx ts-node .ai-assistance/scripts/setup-mcps.test.ts
9
+ *
10
+ * Tests cover:
11
+ * - parseEnvFile: key=value parsing, comments, blank lines, edge cases
12
+ * - buildMcpEntries: correct shape for GitKraken + GitLab entries
13
+ * - mergeConfig: merges, preserves unrelated keys, overwrites same-named entries
14
+ * - getConfigPath: returns a string containing "Claude" on current platform
15
+ */
16
+
17
+ import assert from "assert";
18
+ import { parseEnvFile, buildMcpEntries, mergeConfig, getConfigPath } from "./setup-mcps";
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Test runner
22
+ // ---------------------------------------------------------------------------
23
+
24
+ let passed = 0;
25
+ let failed = 0;
26
+
27
+ function test(name: string, fn: () => void) {
28
+ try {
29
+ fn();
30
+ console.log(` ✓ ${name}`);
31
+ passed++;
32
+ } catch (err: unknown) {
33
+ const message = err instanceof Error ? err.message : String(err);
34
+ console.log(` ✗ ${name}`);
35
+ console.log(` ${message}`);
36
+ failed++;
37
+ }
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // parseEnvFile
42
+ // ---------------------------------------------------------------------------
43
+
44
+ console.log("\nparseEnvFile");
45
+
46
+ test("parses simple key=value pairs", () => {
47
+ const result = parseEnvFile("FOO=bar\nBAZ=qux");
48
+ assert.strictEqual(result["FOO"], "bar");
49
+ assert.strictEqual(result["BAZ"], "qux");
50
+ });
51
+
52
+ test("ignores comment lines", () => {
53
+ const result = parseEnvFile("# this is a comment\nFOO=bar");
54
+ assert.strictEqual(Object.keys(result).length, 1);
55
+ assert.strictEqual(result["FOO"], "bar");
56
+ });
57
+
58
+ test("ignores blank lines", () => {
59
+ const result = parseEnvFile("\n\nFOO=bar\n\n");
60
+ assert.strictEqual(Object.keys(result).length, 1);
61
+ });
62
+
63
+ test("handles values containing = signs", () => {
64
+ const result = parseEnvFile("URL=https://example.com?foo=bar");
65
+ assert.strictEqual(result["URL"], "https://example.com?foo=bar");
66
+ });
67
+
68
+ test("returns empty object for empty input", () => {
69
+ const result = parseEnvFile("");
70
+ assert.deepStrictEqual(result, {});
71
+ });
72
+
73
+ test("returns empty object for comment-only input", () => {
74
+ const result = parseEnvFile("# just a comment\n# another");
75
+ assert.deepStrictEqual(result, {});
76
+ });
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // buildMcpEntries
80
+ // ---------------------------------------------------------------------------
81
+
82
+ console.log("\nbuildMcpEntries");
83
+
84
+ test("includes gitkraken entry with correct command", () => {
85
+ const entries = buildMcpEntries("tok", "https://gitlab.example.com");
86
+ const gk = entries["gitkraken"] as Record<string, unknown>;
87
+ assert.strictEqual(gk.command, "gk");
88
+ assert.deepStrictEqual(gk.args, ["mcp"]);
89
+ });
90
+
91
+ test("includes gitlab-mr entry with correct command", () => {
92
+ const entries = buildMcpEntries("tok", "https://gitlab.example.com");
93
+ const gl = entries["gitlab-mr"] as Record<string, unknown>;
94
+ assert.strictEqual(gl.command, "npx");
95
+ assert.deepStrictEqual(gl.args, ["-y", "@kopfrechner/gitlab-mr-mcp"]);
96
+ });
97
+
98
+ test("injects GITLAB_TOKEN into gitlab-mr env", () => {
99
+ const entries = buildMcpEntries("my-token", "https://gitlab.example.com");
100
+ const gl = entries["gitlab-mr"] as { env: Record<string, string> };
101
+ assert.strictEqual(gl.env["GITLAB_TOKEN"], "my-token");
102
+ });
103
+
104
+ test("injects GITLAB_URL into gitlab-mr env", () => {
105
+ const entries = buildMcpEntries("tok", "https://gitlab.example.com");
106
+ const gl = entries["gitlab-mr"] as { env: Record<string, string> };
107
+ assert.strictEqual(gl.env["GITLAB_URL"], "https://gitlab.example.com");
108
+ });
109
+
110
+ test("returns exactly two top-level keys", () => {
111
+ const entries = buildMcpEntries("tok", "https://gitlab.example.com");
112
+ assert.strictEqual(Object.keys(entries).length, 2);
113
+ });
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // mergeConfig
117
+ // ---------------------------------------------------------------------------
118
+
119
+ console.log("\nmergeConfig");
120
+
121
+ test("adds mcpServers to empty config", () => {
122
+ const result = mergeConfig({}, { foo: { command: "foo" } });
123
+ assert.ok(result.mcpServers);
124
+ assert.ok((result.mcpServers as Record<string, unknown>)["foo"]);
125
+ });
126
+
127
+ test("preserves existing top-level keys", () => {
128
+ const result = mergeConfig({ theme: "dark" }, { foo: {} });
129
+ assert.strictEqual(result.theme, "dark");
130
+ });
131
+
132
+ test("preserves existing mcpServers entries not in newEntries", () => {
133
+ const existing = { mcpServers: { existing: { command: "old" } } };
134
+ const result = mergeConfig(existing, { newone: { command: "new" } });
135
+ const servers = result.mcpServers as Record<string, unknown>;
136
+ assert.ok(servers["existing"]);
137
+ assert.ok(servers["newone"]);
138
+ });
139
+
140
+ test("overwrites same-named mcpServers entries", () => {
141
+ const existing = {
142
+ mcpServers: { "gitlab-mr": { command: "old", env: { GITLAB_TOKEN: "old-tok" } } },
143
+ };
144
+ const result = mergeConfig(existing, {
145
+ "gitlab-mr": { command: "npx", env: { GITLAB_TOKEN: "new-tok" } },
146
+ });
147
+ const gl = (result.mcpServers as Record<string, unknown>)["gitlab-mr"] as {
148
+ env: Record<string, string>;
149
+ };
150
+ assert.strictEqual(gl.env["GITLAB_TOKEN"], "new-tok");
151
+ });
152
+
153
+ test("handles existing config with no mcpServers key", () => {
154
+ const result = mergeConfig({ theme: "light" }, { foo: {} });
155
+ assert.ok((result.mcpServers as Record<string, unknown>)["foo"]);
156
+ assert.strictEqual(result.theme, "light");
157
+ });
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // getConfigPath
161
+ // ---------------------------------------------------------------------------
162
+
163
+ console.log("\ngetConfigPath");
164
+
165
+ test("returns a non-empty string", () => {
166
+ const p = getConfigPath();
167
+ assert.ok(typeof p === "string" && p.length > 0);
168
+ });
169
+
170
+ test("path contains 'Claude'", () => {
171
+ const p = getConfigPath();
172
+ assert.ok(p.includes("Claude"), `Expected path to contain 'Claude', got: ${p}`);
173
+ });
174
+
175
+ test("path ends with claude_desktop_config.json", () => {
176
+ const p = getConfigPath();
177
+ assert.ok(
178
+ p.endsWith("claude_desktop_config.json"),
179
+ `Expected path to end with filename, got: ${p}`
180
+ );
181
+ });
182
+
183
+ // ---------------------------------------------------------------------------
184
+ // Summary
185
+ // ---------------------------------------------------------------------------
186
+
187
+ console.log(`\n${passed + failed} tests: ${passed} passed, ${failed} failed\n`);
188
+ if (failed > 0) process.exit(1);
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env npx ts-node
2
+ /**
3
+ * setup-mcps.ts
4
+ * One-time setup: writes GitKraken and GitLab MCP entries into Claude Desktop's
5
+ * global config (claude_desktop_config.json). Safe to re-run — merges into any
6
+ * existing config without overwriting unrelated entries.
7
+ *
8
+ * Usage:
9
+ * npx ts-node .ai-assistance/scripts/setup-mcps.ts
10
+ *
11
+ * What it does:
12
+ * 1. Detects OS and locates claude_desktop_config.json
13
+ * 2. Reads GitLab credentials from .env as defaults (no re-entry needed)
14
+ * 3. Prompts to confirm or override each value
15
+ * 4. Backs up the existing config before writing
16
+ * 5. Merges GitKraken + GitLab MCP entries and writes the config
17
+ * 6. Prints verification steps
18
+ *
19
+ * Requirements:
20
+ * - GitKraken CLI (gk) installed + authenticated:
21
+ * https://www.gitkraken.com/cli
22
+ * Run: gk auth login
23
+ * - GitLab PAT with scopes: read_api, read_repository, create_runner, ai_features
24
+ * https://gitlab.ecoportal.co.nz/-/user_settings/personal_access_tokens
25
+ * - Node.js + ts-node:
26
+ * npm install -g ts-node typescript
27
+ *
28
+ * Expiry note:
29
+ * Current PAT expires April 2027. Token has rotation enabled — rotate before
30
+ * expiry and re-run this script with the new token.
31
+ */
32
+
33
+ import {
34
+ readFileSync,
35
+ writeFileSync,
36
+ existsSync,
37
+ copyFileSync,
38
+ mkdirSync,
39
+ } from "fs";
40
+ import { join, dirname } from "path";
41
+ import { createInterface } from "readline";
42
+ import { homedir, platform } from "os";
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Pure logic (exported for testing)
46
+ // ---------------------------------------------------------------------------
47
+
48
+ /** Detect OS-appropriate path to claude_desktop_config.json */
49
+ export function getConfigPath(): string {
50
+ const p = platform();
51
+ if (p === "win32") {
52
+ const appData =
53
+ process.env.APPDATA ?? join(homedir(), "AppData", "Roaming");
54
+ return join(appData, "Claude", "claude_desktop_config.json");
55
+ } else if (p === "darwin") {
56
+ return join(
57
+ homedir(),
58
+ "Library",
59
+ "Application Support",
60
+ "Claude",
61
+ "claude_desktop_config.json"
62
+ );
63
+ } else {
64
+ // Linux fallback
65
+ return join(homedir(), ".config", "Claude", "claude_desktop_config.json");
66
+ }
67
+ }
68
+
69
+ /** Parse KEY=VALUE lines from a .env file. Ignores comments and blank lines. */
70
+ export function parseEnvFile(content: string): Record<string, string> {
71
+ const env: Record<string, string> = {};
72
+ for (const line of content.split("\n")) {
73
+ const trimmed = line.trim();
74
+ if (!trimmed || trimmed.startsWith("#")) continue;
75
+ const eq = trimmed.indexOf("=");
76
+ if (eq === -1) continue;
77
+ const key = trimmed.slice(0, eq).trim();
78
+ const value = trimmed.slice(eq + 1).trim();
79
+ if (key) env[key] = value;
80
+ }
81
+ return env;
82
+ }
83
+
84
+ /** Build the mcpServers block for GitKraken + GitLab. */
85
+ export function buildMcpEntries(
86
+ gitlabToken: string,
87
+ gitlabUrl: string
88
+ ): Record<string, unknown> {
89
+ return {
90
+ gitkraken: {
91
+ command: "gk",
92
+ args: ["mcp"],
93
+ },
94
+ "gitlab-mr": {
95
+ command: "npx",
96
+ args: ["-y", "@kopfrechner/gitlab-mr-mcp"],
97
+ env: {
98
+ GITLAB_TOKEN: gitlabToken,
99
+ GITLAB_URL: gitlabUrl,
100
+ },
101
+ },
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Merge new MCP entries into an existing config object.
107
+ * Existing mcpServers entries not in newEntries are preserved.
108
+ * Entries in newEntries overwrite same-named existing entries.
109
+ */
110
+ export function mergeConfig(
111
+ existing: Record<string, unknown>,
112
+ newEntries: Record<string, unknown>
113
+ ): Record<string, unknown> {
114
+ return {
115
+ ...existing,
116
+ mcpServers: {
117
+ ...((existing.mcpServers as Record<string, unknown>) ?? {}),
118
+ ...newEntries,
119
+ },
120
+ };
121
+ }
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // I/O helpers
125
+ // ---------------------------------------------------------------------------
126
+
127
+ function ask(question: string): Promise<string> {
128
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
129
+ return new Promise((resolve) => {
130
+ rl.question(question, (answer) => {
131
+ rl.close();
132
+ resolve(answer.trim());
133
+ });
134
+ });
135
+ }
136
+
137
+ function loadEnvDefaults(repoRoot: string): Record<string, string> {
138
+ const envPath = join(repoRoot, ".env");
139
+ if (!existsSync(envPath)) return {};
140
+ return parseEnvFile(readFileSync(envPath, "utf8"));
141
+ }
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // Main
145
+ // ---------------------------------------------------------------------------
146
+
147
+ async function main() {
148
+ // Resolve repo root relative to this script's location
149
+ const scriptDir = dirname(
150
+ process.argv[1]
151
+ );
152
+ const repoRoot = join(scriptDir, "../..");
153
+ const configPath = getConfigPath();
154
+ const env = loadEnvDefaults(repoRoot);
155
+
156
+ console.log("\n🔧 Claude Desktop MCP Setup");
157
+ console.log("============================");
158
+ console.log(`Config: ${configPath}\n`);
159
+
160
+ // Load existing config
161
+ let existing: Record<string, unknown> = {};
162
+ if (existsSync(configPath)) {
163
+ existing = JSON.parse(readFileSync(configPath, "utf8"));
164
+ const count = Object.keys(
165
+ (existing.mcpServers as Record<string, unknown>) ?? {}
166
+ ).length;
167
+ console.log(`Found existing config with ${count} MCP server(s) — will merge.\n`);
168
+ } else {
169
+ console.log("No existing config — will create fresh.\n");
170
+ mkdirSync(dirname(configPath), { recursive: true });
171
+ }
172
+
173
+ // --- GitLab PAT ---
174
+ const defaultToken = env["GITLAB_TOKEN"] ?? "";
175
+ const tokenHint = defaultToken
176
+ ? `from .env, ends …${defaultToken.slice(-6)}`
177
+ : "required";
178
+ const tokenInput = await ask(
179
+ `GitLab PAT [${tokenHint}] (press Enter to keep): `
180
+ );
181
+ const gitlabToken = tokenInput || defaultToken;
182
+
183
+ if (!gitlabToken) {
184
+ console.error(
185
+ "\n❌ GitLab PAT is required.\n" +
186
+ " Create one at: https://gitlab.ecoportal.co.nz/-/user_settings/personal_access_tokens\n" +
187
+ " Required scopes: read_api, read_repository, create_runner, ai_features\n" +
188
+ " Enable token rotation. Current expiry: April 2027.\n"
189
+ );
190
+ process.exit(1);
191
+ }
192
+
193
+ // --- GitLab URL ---
194
+ const defaultUrl =
195
+ env["GITLAB_URL"] ?? "https://gitlab.ecoportal.co.nz";
196
+ const urlInput = await ask(`GitLab URL [${defaultUrl}]: `);
197
+ const gitlabUrl = urlInput || defaultUrl;
198
+
199
+ // --- Build + merge ---
200
+ const newEntries = buildMcpEntries(gitlabToken, gitlabUrl);
201
+ const merged = mergeConfig(existing, newEntries);
202
+
203
+ // --- Backup ---
204
+ if (existsSync(configPath)) {
205
+ const backupPath = configPath + ".backup";
206
+ copyFileSync(configPath, backupPath);
207
+ console.log(`\n✓ Backed up to ${backupPath}`);
208
+ }
209
+
210
+ // --- Write ---
211
+ writeFileSync(configPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
212
+ console.log(`✓ Written to ${configPath}`);
213
+
214
+ console.log(`
215
+ ✅ Done! Next steps:
216
+
217
+ 1. Restart Claude Desktop (fully quit and reopen)
218
+
219
+ 2. Verify GitKraken — ask Claude:
220
+ "What branch am I on in ecoportal-api-graphql?"
221
+ First use opens a browser for GitKraken OAuth — approve it.
222
+
223
+ 3. Verify GitLab — ask Claude:
224
+ "List open merge requests in ecoportal-api-graphql"
225
+
226
+ Note: if GitKraken (gk) isn't installed yet:
227
+ https://www.gitkraken.com/cli → gk auth login
228
+ `);
229
+ }
230
+
231
+ main().catch((err) => {
232
+ console.error("\n❌ Setup failed:", err.message);
233
+ process.exit(1);
234
+ });
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env npx ts-node
2
+ /**
3
+ * task-complete.ts
4
+ * Writes an outbox file and archives the inbox+outbox pair.
5
+ * Called by Code after finishing a bridge task.
6
+ *
7
+ * Usage:
8
+ * npx ts-node .ai-assistance/scripts/task-complete.ts \
9
+ * --task oscar-a3f2b1c-list-open-mrs.md \
10
+ * --status done \
11
+ * --result "Branch: feature/auth. Open MRs: 3 ..." \
12
+ * --notes "GitLab plugin authenticated OK"
13
+ */
14
+
15
+ import { writeFileSync, renameSync, readFileSync, mkdirSync, existsSync } from "fs";
16
+ import { join, dirname } from "path";
17
+
18
+ const args = process.argv.slice(2);
19
+ const get = (flag: string, required = true): string | undefined => {
20
+ const i = args.indexOf(flag);
21
+ if (i === -1 || i + 1 >= args.length) {
22
+ if (required) { console.error(`Missing required argument: ${flag}`); process.exit(1); }
23
+ return undefined;
24
+ }
25
+ return args[i + 1];
26
+ };
27
+
28
+ const taskFile = get("--task")!;
29
+ const status = (get("--status")! as string).toUpperCase(); // DONE or FAILED
30
+ const result = get("--result")!;
31
+ const notes = get("--notes", false) ?? "";
32
+
33
+ const scriptDir = dirname(process.argv[1]);
34
+ const bridgeDir = join(scriptDir, "../bridge");
35
+ const inboxPath = join(bridgeDir, "inbox", taskFile);
36
+ const outboxDir = join(bridgeDir, "outbox");
37
+ const archiveDir = join(bridgeDir, "archive");
38
+
39
+ if (!existsSync(inboxPath)) {
40
+ console.error(`Inbox task not found: ${inboxPath}`);
41
+ process.exit(1);
42
+ }
43
+
44
+ mkdirSync(outboxDir, { recursive: true });
45
+ mkdirSync(archiveDir, { recursive: true });
46
+
47
+ // Read title from inbox
48
+ const inboxContent = readFileSync(inboxPath, "utf8");
49
+ const titleMatch = inboxContent.match(/^# TASK: (.+)$/m);
50
+ const title = titleMatch?.[1] ?? taskFile;
51
+
52
+ // Write outbox
53
+ const timestamp = new Date().toISOString();
54
+ const outboxContent = `# TASK: ${title}
55
+ STATUS: ${status}
56
+ COMPLETED: ${timestamp}
57
+ FROM: code
58
+ TO: cowork
59
+
60
+ ## Result
61
+ ${result}
62
+
63
+ ## Notes
64
+ ${notes || "(none)"}
65
+ `;
66
+
67
+ const outboxPath = join(outboxDir, taskFile);
68
+ writeFileSync(outboxPath, outboxContent, "utf8");
69
+
70
+ // Archive both files
71
+ renameSync(inboxPath, join(archiveDir, `inbox-${taskFile}`));
72
+ renameSync(outboxPath, join(archiveDir, `outbox-${taskFile}`));
73
+
74
+ console.log(JSON.stringify({ archived: taskFile, status, timestamp }));
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env npx ts-node
2
+ /**
3
+ * task-create.ts
4
+ * Creates a new bridge inbox task file with correct naming and format.
5
+ * Keeps token usage low — agents call this instead of composing files manually.
6
+ *
7
+ * Usage:
8
+ * npx ts-node .ai-assistance/scripts/task-create.ts \
9
+ * --title "List open MRs" \
10
+ * --request "Run git branch --show-current, then list open MRs via GitLab plugin" \
11
+ * --expected "Branch name + MR table" \
12
+ * --from cowork \
13
+ * --to code \
14
+ * --user oscar
15
+ */
16
+
17
+ import { writeFileSync, mkdirSync } from "fs";
18
+ import { join, dirname } from "path";
19
+ import { randomUUID } from "crypto";
20
+
21
+ // --- Parse args ---
22
+ const args = process.argv.slice(2);
23
+ const get = (flag: string): string => {
24
+ const i = args.indexOf(flag);
25
+ if (i === -1 || i + 1 >= args.length) {
26
+ console.error(`Missing required argument: ${flag}`);
27
+ process.exit(1);
28
+ }
29
+ return args[i + 1];
30
+ };
31
+
32
+ const title = get("--title");
33
+ const request = get("--request");
34
+ const expected = get("--expected");
35
+ const from = get("--from");
36
+ const to = get("--to");
37
+ const user = get("--user");
38
+ const context = args.includes("--context") ? get("--context") : "";
39
+
40
+ // --- Generate filename ---
41
+ const uuid7 = randomUUID().replace(/-/g, "").slice(0, 7);
42
+ const slug = title
43
+ .toLowerCase()
44
+ .replace(/[^a-z0-9\s-]/g, "")
45
+ .trim()
46
+ .replace(/\s+/g, "-")
47
+ .slice(0, 40);
48
+ const filename = `${user}-${uuid7}-${slug}.md`;
49
+
50
+ // --- Build file content ---
51
+ const timestamp = new Date().toISOString();
52
+ const content = `# TASK: ${title}
53
+ STATUS: PENDING
54
+ CREATED: ${timestamp}
55
+ FROM: ${from}
56
+ TO: ${to}
57
+
58
+ ## Context
59
+ ${context || "(none provided)"}
60
+
61
+ ## Request
62
+ ${request}
63
+
64
+ ## Expected output
65
+ ${expected}
66
+ `;
67
+
68
+ // --- Write file ---
69
+ const scriptDir = dirname(process.argv[1]);
70
+ const inboxDir = join(scriptDir, "../bridge/inbox");
71
+ mkdirSync(inboxDir, { recursive: true });
72
+ const filePath = join(inboxDir, filename);
73
+ writeFileSync(filePath, content, "utf8");
74
+
75
+ console.log(JSON.stringify({ created: filename, path: filePath }));