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,86 @@
1
+ #!/bin/bash
2
+ # bridge-init.sh
3
+ # Detects environment, scans bridge/inbox for pending tasks belonging to current user.
4
+ # Output: single JSON line for the agent to consume.
5
+ # Usage: source .ai-assistance/scripts/bridge-init.sh
6
+
7
+ set -euo pipefail
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ BRIDGE_DIR="$SCRIPT_DIR/../bridge"
11
+ INBOX_DIR="$BRIDGE_DIR/inbox"
12
+
13
+ # --- Environment detection ---
14
+ # Prefer explicit env var; fall back to "code" (this script is most useful in Code)
15
+ ENV="${CLAUDE_ENV:-code}"
16
+
17
+ # --- Username detection ---
18
+ # Prefer explicit env var; fall back to git config
19
+ if [ -n "${BRIDGE_USER:-}" ]; then
20
+ USER_SLUG="$BRIDGE_USER"
21
+ else
22
+ GIT_USER=$(git config user.name 2>/dev/null || echo "unknown")
23
+ USER_SLUG=$(echo "$GIT_USER" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd '[:alnum:]-')
24
+ fi
25
+
26
+ # --- Stale threshold (seconds) ---
27
+ # IN_PROGRESS tasks older than this are flagged as stale (likely a crashed session).
28
+ STALE_THRESHOLD_SECONDS=3600 # 1 hour
29
+
30
+ # --- Scan inbox ---
31
+ pending_tasks=()
32
+ stale_tasks=()
33
+
34
+ if [ -d "$INBOX_DIR" ]; then
35
+ while IFS= read -r -d '' file; do
36
+ filename=$(basename "$file")
37
+ # Only files prefixed with this user's slug
38
+ if [[ "$filename" == ${USER_SLUG}-* ]]; then
39
+ if grep -q "^STATUS: PENDING" "$file" 2>/dev/null; then
40
+ pending_tasks+=("$filename")
41
+ elif grep -q "^STATUS: IN_PROGRESS" "$file" 2>/dev/null; then
42
+ # Check how old the file is
43
+ if command -v stat &>/dev/null; then
44
+ # GNU stat (Linux/Git-Bash): -c %Y gives mtime as epoch seconds
45
+ # macOS stat uses -f %m — try GNU first, fall back gracefully
46
+ mtime=$(stat -c %Y "$file" 2>/dev/null || stat -f %m "$file" 2>/dev/null || echo 0)
47
+ now=$(date +%s)
48
+ age=$(( now - mtime ))
49
+ if [ "$age" -ge "$STALE_THRESHOLD_SECONDS" ]; then
50
+ stale_tasks+=("$filename")
51
+ fi
52
+ fi
53
+ # If stat is unavailable we silently skip stale detection for this file
54
+ fi
55
+ fi
56
+ done < <(find "$INBOX_DIR" -maxdepth 1 -name "*.md" \
57
+ -not -name "*.draft.md" \
58
+ -not -name "*.local.md" \
59
+ -not -name ".gitkeep" \
60
+ -print0 2>/dev/null)
61
+ fi
62
+
63
+ # --- Build JSON output ---
64
+ count=${#pending_tasks[@]}
65
+ stale_count=${#stale_tasks[@]}
66
+
67
+ tasks_json="["
68
+ for i in "${!pending_tasks[@]}"; do
69
+ tasks_json+="\"${pending_tasks[$i]}\""
70
+ [ $i -lt $((count - 1)) ] && tasks_json+=","
71
+ done
72
+ tasks_json+="]"
73
+
74
+ stale_json="["
75
+ for i in "${!stale_tasks[@]}"; do
76
+ stale_json+="\"${stale_tasks[$i]}\""
77
+ [ $i -lt $((stale_count - 1)) ] && stale_json+=","
78
+ done
79
+ stale_json+="]"
80
+
81
+ output="{\"env\":\"$ENV\",\"user\":\"$USER_SLUG\",\"pending\":$count,\"tasks\":$tasks_json,\"stale\":$stale_count,\"stale_tasks\":$stale_json}"
82
+
83
+ # Write to status file so Code can read it on startup (hook stdout is not injected into context)
84
+ echo "$output" > "$BRIDGE_DIR/STATUS"
85
+
86
+ echo "$output"
@@ -0,0 +1,44 @@
1
+ #!/bin/bash
2
+ # bridge-status.sh
3
+ # Quick dashboard of bridge state. One line output for agent consumption.
4
+ #
5
+ # Usage:
6
+ # bash .ai-assistance/scripts/bridge-status.sh
7
+ #
8
+ # Output:
9
+ # pending:2 in_progress:1 done:0 failed:0 archived:12 last_activity:2026-06-03T14:32Z
10
+
11
+ set -euo pipefail
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ BRIDGE_DIR="$SCRIPT_DIR/../bridge"
15
+
16
+ count_status() {
17
+ local dir="$1"
18
+ local pattern="$2"
19
+ if [ ! -d "$dir" ]; then echo 0; return; fi
20
+ grep -rl "^STATUS: $pattern" "$dir" --include="*.md" 2>/dev/null | wc -l | tr -d ' '
21
+ }
22
+
23
+ PENDING=$(count_status "$BRIDGE_DIR/inbox" "PENDING")
24
+ IN_PROGRESS=$(count_status "$BRIDGE_DIR/inbox" "IN_PROGRESS")
25
+ DONE=$(count_status "$BRIDGE_DIR/outbox" "DONE")
26
+ FAILED=$(count_status "$BRIDGE_DIR/outbox" "FAILED")
27
+
28
+ ARCHIVED=0
29
+ if [ -d "$BRIDGE_DIR/archive" ]; then
30
+ ARCHIVED=$(find "$BRIDGE_DIR/archive" -name "outbox-*.md" 2>/dev/null | wc -l | tr -d ' ')
31
+ fi
32
+
33
+ # Last activity: most recently modified file in bridge/
34
+ LAST_ACTIVITY="none"
35
+ if command -v stat &>/dev/null; then
36
+ LAST_FILE=$(find "$BRIDGE_DIR" -name "*.md" -not -name ".gitkeep" \
37
+ -exec stat -f "%m %N" {} \; 2>/dev/null \
38
+ | sort -rn | head -1 | awk '{print $2}' || true)
39
+ if [ -n "${LAST_FILE:-}" ]; then
40
+ LAST_ACTIVITY=$(date -r "$LAST_FILE" -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "unknown")
41
+ fi
42
+ fi
43
+
44
+ echo "pending:$PENDING in_progress:$IN_PROGRESS done:$DONE failed:$FAILED archived:$ARCHIVED last_activity:$LAST_ACTIVITY"
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env npx ts-node
2
+ /**
3
+ * capabilities-check.ts
4
+ * Periodic assumptions review agent.
5
+ * Uses Claude Haiku (cheap + fast) to verify stale entries in assumptions-log.md.
6
+ * Updates log with verification results and timestamps.
7
+ *
8
+ * Usage:
9
+ * npx ts-node .ai-assistance/scripts/capabilities-check.ts
10
+ *
11
+ * Requires:
12
+ * ANTHROPIC_API_KEY env var
13
+ * npm install @anthropic-ai/sdk
14
+ */
15
+
16
+ import Anthropic from "@anthropic-ai/sdk";
17
+ import { readFileSync, writeFileSync } from "fs";
18
+ import { join, dirname } from "path";
19
+
20
+ const STALE_DAYS = 30;
21
+
22
+ const scriptDir = dirname(process.argv[1]);
23
+ const logPath = join(scriptDir, "../capabilities/assumptions-log.md");
24
+
25
+ // --- Read log ---
26
+ const log = readFileSync(logPath, "utf8");
27
+
28
+ // --- Find stale or NEEDS_REVIEW entries ---
29
+ const entryPattern = /### (\d{4}-\d{2}-\d{2}) — (.+?)\nSTATUS: (CONFIRMED|NEEDS_REVIEW|RESOLVED|INVALIDATED)\nLast checked: (\d{4}-\d{2}-\d{2})/g;
30
+
31
+ const now = new Date();
32
+ const staleEntries: { title: string; date: string; status: string }[] = [];
33
+
34
+ let match;
35
+ while ((match = entryPattern.exec(log)) !== null) {
36
+ const [, , title, status, lastChecked] = match;
37
+ const checkedDate = new Date(lastChecked);
38
+ const daysSince = (now.getTime() - checkedDate.getTime()) / (1000 * 60 * 60 * 24);
39
+
40
+ if (status === "NEEDS_REVIEW" || daysSince > STALE_DAYS) {
41
+ staleEntries.push({ title, date: lastChecked, status });
42
+ }
43
+ }
44
+
45
+ if (staleEntries.length === 0) {
46
+ console.log("All assumptions current. No review needed.");
47
+ process.exit(0);
48
+ }
49
+
50
+ console.log(`Found ${staleEntries.length} stale/flagged assumption(s). Reviewing...`);
51
+
52
+ // --- Use Haiku to check each ---
53
+ const client = new Anthropic();
54
+
55
+ async function checkAssumption(title: string): Promise<string> {
56
+ const message = await client.messages.create({
57
+ model: "claude-haiku-4-5-20251001",
58
+ max_tokens: 300,
59
+ messages: [
60
+ {
61
+ role: "user",
62
+ content: `You are verifying a technical assumption about Claude/Anthropic products.
63
+ Assumption: "${title}"
64
+ Today's date: ${now.toISOString().slice(0, 10)}
65
+
66
+ Search your knowledge for whether this is still accurate.
67
+ Reply in this format exactly:
68
+ STATUS: CONFIRMED | NEEDS_REVIEW | RESOLVED | INVALIDATED
69
+ NOTES: <one sentence explaining current state>`,
70
+ },
71
+ ],
72
+ });
73
+
74
+ const text = message.content[0].type === "text" ? message.content[0].text : "";
75
+ return text.trim();
76
+ }
77
+
78
+ // --- Update log entries ---
79
+ let updatedLog = log;
80
+ const todayStr = now.toISOString().slice(0, 10);
81
+
82
+ for (const entry of staleEntries) {
83
+ const result = await checkAssumption(entry.title);
84
+ const statusLine = result.match(/^STATUS: (.+)$/m)?.[1]?.trim() ?? "NEEDS_REVIEW";
85
+ const notesLine = result.match(/^NOTES: (.+)$/m)?.[1]?.trim() ?? "Auto-check inconclusive";
86
+
87
+ console.log(` [${entry.title}] → ${statusLine}: ${notesLine}`);
88
+
89
+ // Update the entry in the log
90
+ updatedLog = updatedLog.replace(
91
+ new RegExp(`(### ${entry.date} — ${entry.title}\\nSTATUS:) [A-Z_]+\\n(Last checked:) [0-9-]+`),
92
+ `$1 ${statusLine}\n$2 ${todayStr}`
93
+ );
94
+ }
95
+
96
+ // Append a review run summary
97
+ updatedLog += `\n### ${todayStr} — Automated review run
98
+ STATUS: CONFIRMED
99
+ Last checked: ${todayStr}
100
+ Source: capabilities-check.ts
101
+ Notes: Reviewed ${staleEntries.length} stale entries. See individual entries for results.\n`;
102
+
103
+ writeFileSync(logPath, updatedLog, "utf8");
104
+ console.log(`Updated assumptions-log.md`);
@@ -0,0 +1,43 @@
1
+ #!/bin/bash
2
+ # check-outbox.sh
3
+ # Check if Code has responded to a specific bridge task.
4
+ # Used by CoWork to confirm handoff completion without reading raw files.
5
+ #
6
+ # Usage:
7
+ # bash .ai-assistance/scripts/check-outbox.sh oscar-a3f2b1c-list-open-mrs.md
8
+ #
9
+ # Output:
10
+ # DONE | <first 120 chars of result>
11
+ # PENDING | no response yet
12
+ # FAILED | <notes>
13
+ # ERROR | <reason>
14
+
15
+ set -euo pipefail
16
+
17
+ TASK_FILE="${1:-}"
18
+ if [ -z "$TASK_FILE" ]; then
19
+ echo "ERROR | usage: check-outbox.sh <task-filename>"
20
+ exit 1
21
+ fi
22
+
23
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24
+ OUTBOX_DIR="$SCRIPT_DIR/../bridge/outbox"
25
+ ARCHIVE_DIR="$SCRIPT_DIR/../bridge/archive"
26
+ OUTBOX_PATH="$OUTBOX_DIR/$TASK_FILE"
27
+ ARCHIVE_OUTBOX_PATH="$ARCHIVE_DIR/outbox-$TASK_FILE"
28
+
29
+ # Check outbox first, then archive
30
+ if [ -f "$OUTBOX_PATH" ]; then
31
+ TARGET="$OUTBOX_PATH"
32
+ elif [ -f "$ARCHIVE_OUTBOX_PATH" ]; then
33
+ TARGET="$ARCHIVE_OUTBOX_PATH"
34
+ else
35
+ echo "PENDING | no response yet"
36
+ exit 0
37
+ fi
38
+
39
+ STATUS=$(grep "^STATUS:" "$TARGET" | head -1 | awk '{print $2}')
40
+ RESULT=$(awk '/^## Result/{found=1; next} found && /^## /{exit} found{print}' "$TARGET" \
41
+ | head -3 | tr '\n' ' ' | cut -c1-120)
42
+
43
+ echo "$STATUS | $RESULT"
@@ -0,0 +1,91 @@
1
+ # .ai-assistance/scripts/dep_graph.rb
2
+ #
3
+ # Generates a JSON structural map of the gem's class/module hierarchy and
4
+ # inter-file relationships. No gem dependencies — Ruby stdlib only.
5
+ #
6
+ # Usage (via rake dep_graph):
7
+ # DepGraph.run(lib_dir: 'lib', output_path: '.ai-assistance/code/dep-graph.json')
8
+ #
9
+ # Output per file:
10
+ # defines — fully-qualified module/class names defined in the file
11
+ # requires — require_relative paths
12
+ # includes — include/extend/prepend targets
13
+
14
+ require 'json'
15
+
16
+ module DepGraph
17
+ module_function
18
+
19
+ def run(lib_dir: 'lib', output_path: '.ai-assistance/code/dep-graph.json')
20
+ files = Dir.glob(File.join(lib_dir, '**', '*.rb')).sort
21
+ result = files.each_with_object({}) do |file, map|
22
+ entry = analyse(file)
23
+ map[file] = entry unless entry.values.all?(&:empty?)
24
+ end
25
+
26
+ File.write(output_path, JSON.pretty_generate(result))
27
+ result
28
+ end
29
+
30
+ def analyse(file)
31
+ src = File.read(file, encoding: 'utf-8')
32
+ lines = src.lines
33
+
34
+ defines = extract_defines(lines)
35
+ requires = extract_requires(lines)
36
+ includes = extract_includes(lines)
37
+
38
+ { 'defines' => defines, 'requires' => requires, 'includes' => includes }
39
+ end
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # Extract fully-qualified module/class names by tracking a nesting stack.
43
+ # Handles:
44
+ # module Foo; module Bar (inline)
45
+ # class Baz::Qux (compact namespace)
46
+ # end (pops one level per bare `end`)
47
+ # ---------------------------------------------------------------------------
48
+ def extract_defines(lines)
49
+ stack = []
50
+ defines = []
51
+
52
+ lines.each do |line|
53
+ stripped = line.strip
54
+
55
+ # module Foo or class Bar (or class Bar < Baz)
56
+ if (m = stripped.match(/\A(module|class)\s+([\w:]+)/))
57
+ parts = m[2].split('::')
58
+ new_name = (stack + parts).join('::')
59
+ defines << new_name
60
+ stack.push(*parts)
61
+
62
+ # bare `end` — pop one stack level
63
+ elsif stripped == 'end'
64
+ stack.pop unless stack.empty?
65
+ end
66
+ end
67
+
68
+ defines.uniq
69
+ end
70
+
71
+ # ---------------------------------------------------------------------------
72
+ # Extract require_relative paths (strips quotes and .rb suffix).
73
+ # ---------------------------------------------------------------------------
74
+ def extract_requires(lines)
75
+ lines.filter_map do |line|
76
+ m = line.strip.match(/\Arequire_relative\s+['"](.+?)['"]/)
77
+ m && m[1].delete_suffix('.rb')
78
+ end.uniq
79
+ end
80
+
81
+ # ---------------------------------------------------------------------------
82
+ # Extract include / extend / prepend targets (module names only).
83
+ # Ignores method calls with arguments beyond the module name.
84
+ # ---------------------------------------------------------------------------
85
+ def extract_includes(lines)
86
+ lines.filter_map do |line|
87
+ m = line.strip.match(/\A(?:include|extend|prepend)\s+([\w:]+)/)
88
+ m && m[1]
89
+ end.uniq
90
+ end
91
+ end
@@ -0,0 +1,103 @@
1
+ #!/bin/bash
2
+ # lock-acquire.sh
3
+ # Atomically acquire the bridge LOCK file.
4
+ #
5
+ # Atomicity: writes content to a temp file, then hard-links it to LOCK.
6
+ # Hard-link creation is atomic on POSIX filesystems (ext4, APFS, NTFS via Git Bash).
7
+ # Only one of two concurrent callers can win the ln race — the loser gets exit 1.
8
+ # Falls back to bash noclobber if hard links are unavailable (cross-filesystem edge case).
9
+ #
10
+ # Usage:
11
+ # bash lock-acquire.sh \
12
+ # --agent code \
13
+ # --user oscar \
14
+ # --intent "Implementing Phase D mutations" \
15
+ # [--expiry-minutes 60] # default: 60
16
+ # [--lock-file /custom/path] # default: bridge/LOCK
17
+ #
18
+ # Exit codes:
19
+ # 0 — lock acquired; JSON: {"acquired":true,"lock_file":"..."}
20
+ # 1 — lock already held or contention; JSON: {"acquired":false,"reason":"...","held_by":"..."}
21
+
22
+ set -euo pipefail
23
+
24
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
25
+ LOCK_FILE="${LOCK_FILE:-$SCRIPT_DIR/../bridge/LOCK}"
26
+ EXPIRY_MINUTES=60
27
+ AGENT=""
28
+ USER_SLUG=""
29
+ INTENT=""
30
+
31
+ # --- Parse args ---
32
+ while [[ $# -gt 0 ]]; do
33
+ case "$1" in
34
+ --agent) AGENT="$2"; shift 2 ;;
35
+ --user) USER_SLUG="$2"; shift 2 ;;
36
+ --intent) INTENT="$2"; shift 2 ;;
37
+ --expiry-minutes) EXPIRY_MINUTES="$2"; shift 2 ;;
38
+ --lock-file) LOCK_FILE="$2"; shift 2 ;;
39
+ *) echo "{\"acquired\":false,\"reason\":\"unknown argument: $1\"}"; exit 1 ;;
40
+ esac
41
+ done
42
+
43
+ if [ -z "$AGENT" ] || [ -z "$USER_SLUG" ] || [ -z "$INTENT" ]; then
44
+ echo '{"acquired":false,"reason":"missing required argument (--agent, --user, --intent)"}'
45
+ exit 1
46
+ fi
47
+
48
+ # --- Check for existing lock ---
49
+ if [ -f "$LOCK_FILE" ]; then
50
+ # Determine age via stat (GNU stat on Linux/Git Bash: -c %Y; BSD/macOS: -f %m)
51
+ mtime=$(stat -c %Y "$LOCK_FILE" 2>/dev/null || stat -f %m "$LOCK_FILE" 2>/dev/null || echo 0)
52
+ now=$(date +%s)
53
+ age=$(( now - mtime ))
54
+ max_age=$(( EXPIRY_MINUTES * 60 ))
55
+
56
+ if [ "$age" -lt "$max_age" ]; then
57
+ # Lock is active — report who holds it
58
+ held_agent=$(grep "^AGENT:" "$LOCK_FILE" 2>/dev/null | sed 's/^AGENT: //' || echo "unknown")
59
+ held_user=$(grep "^USER:" "$LOCK_FILE" 2>/dev/null | sed 's/^USER: //' || echo "unknown")
60
+ held_intent=$(grep "^INTENT:" "$LOCK_FILE" 2>/dev/null | sed 's/^INTENT: //' || echo "")
61
+ echo "{\"acquired\":false,\"reason\":\"locked\",\"held_by\":\"$held_agent/$held_user\",\"intent\":\"$held_intent\",\"age_seconds\":$age}"
62
+ exit 1
63
+ fi
64
+ # Lock is stale — fall through and overwrite it
65
+ rm -f "$LOCK_FILE"
66
+ fi
67
+
68
+ # --- Build lock content ---
69
+ NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
70
+ # Compute expiry — GNU date: -d "+N minutes"; BSD/macOS: -v +NM
71
+ EXPIRES=$(date -u -d "+${EXPIRY_MINUTES} minutes" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
72
+ || date -u -v +${EXPIRY_MINUTES}M +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
73
+ || echo "unknown")
74
+
75
+ CONTENT="AGENT: ${AGENT}
76
+ USER: ${USER_SLUG}
77
+ ACQUIRED: ${NOW}
78
+ EXPIRES: ${EXPIRES}
79
+ INTENT: ${INTENT}"
80
+
81
+ # --- Atomic acquisition: temp file + hard link ---
82
+ LOCK_DIR="$(dirname "$LOCK_FILE")"
83
+ TMPFILE=$(mktemp "$LOCK_DIR/.lock-tmp.XXXXXX")
84
+ printf '%s\n' "$CONTENT" > "$TMPFILE"
85
+
86
+ if ln "$TMPFILE" "$LOCK_FILE" 2>/dev/null; then
87
+ # Hard link succeeded — we own the lock
88
+ rm -f "$TMPFILE"
89
+ echo "{\"acquired\":true,\"lock_file\":\"$LOCK_FILE\",\"expires\":\"$EXPIRES\"}"
90
+ exit 0
91
+ fi
92
+
93
+ # Hard link failed (LOCK re-appeared in the race window, or cross-fs)
94
+ rm -f "$TMPFILE"
95
+
96
+ # Fallback: bash noclobber (not fully atomic, but good enough for single-user scenarios)
97
+ if ( set -o noclobber; printf '%s\n' "$CONTENT" > "$LOCK_FILE" ) 2>/dev/null; then
98
+ echo "{\"acquired\":true,\"lock_file\":\"$LOCK_FILE\",\"expires\":\"$EXPIRES\",\"method\":\"noclobber\"}"
99
+ exit 0
100
+ fi
101
+
102
+ echo "{\"acquired\":false,\"reason\":\"contention\"}"
103
+ exit 1
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env bash
2
+ # lock-multi.sh
3
+ # Acquire the bridge LOCK across multiple repos atomically.
4
+ # Locks repos in order; rolls back already-acquired locks on failure.
5
+ # Checks queue for higher-priority waiters before acquiring.
6
+ #
7
+ # Cross-repo lock timeout policy:
8
+ # --expiry-minutes sets a hard LOCK expiry (default: 60).
9
+ # --estimated-minutes sets the ESTIMATED_RELEASE field in each LOCK —
10
+ # a human-readable "I expect to be done in N minutes" hint for other agents.
11
+ # If the acquiring agent finishes early, it should release all locks immediately.
12
+ # If it crashes, lock-acquire.sh on the next attempt will detect stale locks
13
+ # (mtime > expiry) and overwrite them automatically.
14
+ #
15
+ # Usage:
16
+ # bash lock-multi.sh \
17
+ # --agent code \
18
+ # --user oscar \
19
+ # --intent "Cross-repo refactor" \
20
+ # --repo /path/to/repo1 \
21
+ # --repo /path/to/repo2 \
22
+ # [--expiry-minutes 60] # hard timeout (default: 60)
23
+ # [--estimated-minutes 20] # expected work duration (default: = expiry)
24
+ #
25
+ # Exit 0: all locks acquired. Prints JSON with acquired lock paths.
26
+ # Exit 1: one or more locks could not be acquired. All acquired locks rolled back.
27
+
28
+ set -euo pipefail
29
+
30
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
31
+ LOCK_SCRIPT="$SCRIPT_DIR/lock-acquire.sh"
32
+
33
+ AGENT="" USER_SLUG="" INTENT="" EXPIRY_MINUTES=60 ESTIMATED_MINUTES=""
34
+ REPOS=()
35
+
36
+ while [[ $# -gt 0 ]]; do
37
+ case "$1" in
38
+ --agent) AGENT="$2"; shift 2 ;;
39
+ --user) USER_SLUG="$2"; shift 2 ;;
40
+ --intent) INTENT="$2"; shift 2 ;;
41
+ --repo) REPOS+=("$2"); shift 2 ;;
42
+ --expiry-minutes) EXPIRY_MINUTES="$2"; shift 2 ;;
43
+ --estimated-minutes) ESTIMATED_MINUTES="$2"; shift 2 ;;
44
+ *) echo "{\"acquired\":false,\"reason\":\"unknown argument: $1\"}"; exit 1 ;;
45
+ esac
46
+ done
47
+
48
+ [[ -z "$AGENT" || -z "$USER_SLUG" || -z "$INTENT" ]] && {
49
+ echo '{"acquired":false,"reason":"missing --agent, --user, or --intent"}'; exit 1; }
50
+ [[ ${#REPOS[@]} -eq 0 ]] && REPOS=("$(pwd)")
51
+
52
+ # Default estimated = expiry (conservative)
53
+ [[ -z "$ESTIMATED_MINUTES" ]] && ESTIMATED_MINUTES="$EXPIRY_MINUTES"
54
+
55
+ NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
56
+ ESTIMATED_RELEASE=$(date -u -d "+${ESTIMATED_MINUTES} minutes" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
57
+ || date -u -v +${ESTIMATED_MINUTES}M +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
58
+ || echo "unknown")
59
+
60
+ ACQUIRED=()
61
+ FAILED=""
62
+ QUEUE_NOTICES=()
63
+
64
+ for repo in "${REPOS[@]}"; do
65
+ LOCK_FILE="$repo/.ai-assistance/bridge/LOCK"
66
+
67
+ # ── Stale lock enforcement: explicitly check mtime before delegating ──────
68
+ # lock-acquire.sh handles this too, but we do it here for cross-repo clarity.
69
+ if [[ -f "$LOCK_FILE" ]]; then
70
+ mtime=$(stat -c %Y "$LOCK_FILE" 2>/dev/null || stat -f %m "$LOCK_FILE" 2>/dev/null || echo 0)
71
+ age=$(( $(date +%s) - mtime ))
72
+ max_age=$(( EXPIRY_MINUTES * 60 ))
73
+ if [[ "$age" -ge "$max_age" ]]; then
74
+ rm -f "$LOCK_FILE"
75
+ echo " [lock-multi] stale lock removed at $repo (age: ${age}s > ${max_age}s)" >&2
76
+ fi
77
+ fi
78
+
79
+ # ── Check queue for higher-priority waiters ───────────────────────────────
80
+ QUEUE_DIR="$repo/.ai-assistance/bridge/queue"
81
+ if [[ -d "$QUEUE_DIR" ]]; then
82
+ for qf in "$QUEUE_DIR"/1-critical-*.json "$QUEUE_DIR"/2-high-*.json; do
83
+ [[ -f "$qf" ]] || continue
84
+ pri=$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print(d.get('priority','?'))" "$qf" 2>/dev/null || echo "?")
85
+ agt=$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print(d.get('agent','?'))" "$qf" 2>/dev/null || echo "?")
86
+ intent_q=$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print(d.get('intent','?'))" "$qf" 2>/dev/null || echo "?")
87
+ QUEUE_NOTICES+=("repo=$(basename "$repo") priority=$pri agent=$agt intent=\"$intent_q\"")
88
+ done
89
+ fi
90
+
91
+ # ── Acquire lock via lock-acquire.sh ─────────────────────────────────────
92
+ # Include ESTIMATED_RELEASE in the intent so it appears in the LOCK file text
93
+ FULL_INTENT="${INTENT} | estimated_release:${ESTIMATED_RELEASE} | multi-repo:$(printf '%s,' "${REPOS[@]}" | sed 's/,$//')"
94
+
95
+ result=$(bash "$LOCK_SCRIPT" \
96
+ --agent "$AGENT" --user "$USER_SLUG" \
97
+ --intent "$FULL_INTENT" \
98
+ --expiry-minutes "$EXPIRY_MINUTES" \
99
+ --lock-file "$LOCK_FILE" 2>/dev/null || true)
100
+
101
+ ok=$(echo "$result" | python3 -c "import json,sys; d=json.load(sys.stdin); print('yes' if d.get('acquired') else 'no')" 2>/dev/null || echo "no")
102
+
103
+ if [[ "$ok" == "yes" ]]; then
104
+ ACQUIRED+=("$LOCK_FILE")
105
+ else
106
+ FAILED="$repo — $(echo "$result" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('reason','unknown'))" 2>/dev/null || echo 'unknown')"
107
+ break
108
+ fi
109
+ done
110
+
111
+ # ── Rollback all acquired locks if any failed ─────────────────────────────
112
+ if [[ -n "$FAILED" ]]; then
113
+ for lf in "${ACQUIRED[@]}"; do rm -f "$lf"; done
114
+ echo "{\"acquired\":false,\"reason\":\"$FAILED\",\"rolled_back\":${#ACQUIRED[@]}}"
115
+ exit 1
116
+ fi
117
+
118
+ # ── Output ────────────────────────────────────────────────────────────────
119
+ FILES_JSON=$(printf '"%s",' "${ACQUIRED[@]}" | sed 's/,$//')
120
+ NOTICES_JSON=$(printf '"%s",' "${QUEUE_NOTICES[@]:-}" | sed 's/,$//')
121
+ echo "{\"acquired\":true,\"locks\":[$FILES_JSON],\"estimated_release\":\"$ESTIMATED_RELEASE\",\"expiry_minutes\":$EXPIRY_MINUTES,\"queue_notices\":[$NOTICES_JSON]}"
122
+ echo "" >&2
123
+ [[ ${#QUEUE_NOTICES[@]} -gt 0 ]] && \
124
+ echo " [lock-multi] NOTE: higher-priority waiters in queue — wrap up within ${ESTIMATED_MINUTES} min" >&2