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,125 @@
1
+ #!/usr/bin/env npx ts-node
2
+ /**
3
+ * task-read.ts
4
+ * Parse a bridge task file (inbox, outbox, or archive) and output structured JSON.
5
+ * Saves agents from reading raw Markdown — reduces token overhead for every task fetch.
6
+ *
7
+ * Usage:
8
+ * npx ts-node .ai-assistance/scripts/task-read.ts --task oscar-a3f2b1c-list-open-mrs.md
9
+ * npx ts-node .ai-assistance/scripts/task-read.ts --file /absolute/path/to/file.md
10
+ * npx ts-node .ai-assistance/scripts/task-read.ts --task oscar-abc.md --dir outbox
11
+ *
12
+ * Output (always to stdout):
13
+ * {
14
+ * "title": "List open MRs",
15
+ * "status": "DONE",
16
+ * "created": "2026-06-07T10:00:00Z",
17
+ * "from": "cowork",
18
+ * "to": "code",
19
+ * "context": "...",
20
+ * "request": "...",
21
+ * "expected": "...",
22
+ * "result": "...",
23
+ * "notes": "...",
24
+ * "raw_path": "/absolute/path/to/file.md"
25
+ * }
26
+ *
27
+ * Missing sections are returned as empty strings, not null.
28
+ * Exits 1 with {"error":"..."} if file not found or unreadable.
29
+ */
30
+
31
+ import { readFileSync, existsSync } from "fs";
32
+ import { join, dirname, resolve } from "path";
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Argument parsing
36
+ // ---------------------------------------------------------------------------
37
+ const args = process.argv.slice(2);
38
+ const get = (flag: string): string | undefined => {
39
+ const i = args.indexOf(flag);
40
+ return i !== -1 && i + 1 < args.length ? args[i + 1] : undefined;
41
+ };
42
+
43
+ const taskFile = get("--task");
44
+ const filePath = get("--file");
45
+ const dir = get("--dir") ?? "inbox"; // inbox | outbox | archive
46
+
47
+ const scriptDir = dirname(process.argv[1]);
48
+ const bridgeDir = join(scriptDir, "../bridge");
49
+
50
+ let resolvedPath: string;
51
+
52
+ if (filePath) {
53
+ resolvedPath = resolve(filePath);
54
+ } else if (taskFile) {
55
+ // Try the specified dir first, then inbox, outbox, and archive variants
56
+ const seen = new Set<string>();
57
+ const unique = (p: string) => { seen.add(p); return p; };
58
+ const candidates = [
59
+ unique(join(bridgeDir, dir, taskFile)),
60
+ unique(join(bridgeDir, "inbox", taskFile)),
61
+ unique(join(bridgeDir, "outbox", taskFile)),
62
+ unique(join(bridgeDir, "archive", taskFile)),
63
+ unique(join(bridgeDir, "archive", `inbox-${taskFile}`)),
64
+ unique(join(bridgeDir, "archive", `outbox-${taskFile}`)),
65
+ ].filter((p, i, arr) => arr.indexOf(p) === i); // deduplicate
66
+
67
+ const found = candidates.find(p => existsSync(p));
68
+ if (!found) {
69
+ console.log(JSON.stringify({ error: `Task file not found: ${taskFile}`, searched: candidates }));
70
+ process.exit(1);
71
+ }
72
+ resolvedPath = found;
73
+ } else {
74
+ console.log(JSON.stringify({ error: "Provide --task <filename> or --file <path>" }));
75
+ process.exit(1);
76
+ }
77
+
78
+ if (!existsSync(resolvedPath)) {
79
+ console.log(JSON.stringify({ error: `File not found: ${resolvedPath}` }));
80
+ process.exit(1);
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Parse
85
+ // ---------------------------------------------------------------------------
86
+ const raw = readFileSync(resolvedPath, "utf8");
87
+
88
+ /** Extract a header field like "STATUS: PENDING" → "PENDING" */
89
+ function header(key: string): string {
90
+ const m = raw.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
91
+ return m ? m[1].trim() : "";
92
+ }
93
+
94
+ /** Extract a Markdown section body (between ## Heading and the next ## or EOF) */
95
+ function section(heading: string): string {
96
+ const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
97
+ // Match the heading line (## Heading...\n) then capture everything until the next ## or EOF
98
+ const headingRe = new RegExp(`^##\\s+${escaped}[^\\n]*\\n`, "m");
99
+ const headingMatch = headingRe.exec(raw);
100
+ if (!headingMatch) return "";
101
+ const contentStart = headingMatch.index + headingMatch[0].length;
102
+ const rest = raw.slice(contentStart);
103
+ const nextSection = /^## /m.exec(rest);
104
+ return rest.slice(0, nextSection ? nextSection.index : rest.length).trim();
105
+ }
106
+
107
+ // Title: "# TASK: My title"
108
+ const titleMatch = raw.match(/^#\s+TASK:\s+(.+)$/m);
109
+ const title = titleMatch ? titleMatch[1].trim() : "";
110
+
111
+ const result = {
112
+ title,
113
+ status: header("STATUS"),
114
+ created: header("CREATED"),
115
+ from: header("FROM"),
116
+ to: header("TO"),
117
+ context: section("Context"),
118
+ request: section("Request"),
119
+ expected: section("Expected output"),
120
+ result: section("Result"),
121
+ notes: section("Notes"),
122
+ raw_path: resolvedPath,
123
+ };
124
+
125
+ console.log(JSON.stringify(result, null, 2));
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * token-logger.js
4
+ *
5
+ * Claude Code Stop hook — fires after every AI response turn.
6
+ * Reads the session transcript, extracts token usage, accumulates weekly totals,
7
+ * and warns when approaching the project's budget allocation.
8
+ *
9
+ * Wired in .claude/settings.json:
10
+ * "Stop": [{ "type": "command", "command": "node .ai-assistance/scripts/token-logger.js" }]
11
+ *
12
+ * Reads: stdin (Stop event JSON with session_id, transcript_path, cwd)
13
+ * .ai-assistance/token-budget.json
14
+ * .ai-assistance/local/kpi/session-<id>.json (running session state)
15
+ * .ai-assistance/local/kpi/weekly-<YYYY-WNN>.json (weekly totals)
16
+ *
17
+ * Writes: .ai-assistance/local/kpi/session-<id>.json (updated state)
18
+ * .ai-assistance/local/kpi/weekly-<YYYY-WNN>.json (updated totals)
19
+ * .ai-assistance/local/kpi/sessions-<YYYY-WNN>.jsonl (completed turns)
20
+ */
21
+
22
+ const fs = require("fs");
23
+ const path = require("path");
24
+ const os = require("os");
25
+
26
+ // ── Helpers ────────────────────────────────────────────────────────────────
27
+
28
+ function isoWeek(d) {
29
+ const jan4 = new Date(d.getFullYear(), 0, 4);
30
+ const startOfWeek = new Date(jan4);
31
+ startOfWeek.setDate(jan4.getDate() - ((jan4.getDay() + 6) % 7));
32
+ const weekNum = Math.ceil(((d - startOfWeek) / 86400000 + 1) / 7);
33
+ return `${d.getFullYear()}-W${String(weekNum).padStart(2, "0")}`;
34
+ }
35
+
36
+ function loadJson(p, fallback) {
37
+ try { return JSON.parse(fs.readFileSync(p, "utf8")); }
38
+ catch { return fallback; }
39
+ }
40
+
41
+ function saveJson(p, data) {
42
+ fs.mkdirSync(path.dirname(p), { recursive: true });
43
+ fs.writeFileSync(p, JSON.stringify(data, null, 2) + "\n", "utf8");
44
+ }
45
+
46
+ function appendJsonl(p, obj) {
47
+ fs.mkdirSync(path.dirname(p), { recursive: true });
48
+ fs.appendFileSync(p, JSON.stringify(obj) + "\n", "utf8");
49
+ }
50
+
51
+ // ── Extract token usage from transcript JSONL ──────────────────────────────
52
+
53
+ function extractUsageFromTranscript(transcriptPath) {
54
+ if (!transcriptPath || !fs.existsSync(transcriptPath)) return null;
55
+
56
+ let inputTokens = 0, outputTokens = 0, cacheReadTokens = 0, cacheCreateTokens = 0;
57
+ let toolCalls = 0, turns = 0, found = false;
58
+
59
+ try {
60
+ const lines = fs.readFileSync(transcriptPath, "utf8").split("\n").filter(Boolean);
61
+ for (const line of lines) {
62
+ try {
63
+ const entry = JSON.parse(line);
64
+ // Extract usage from any entry that has it
65
+ const usage = entry.usage || entry.message?.usage;
66
+ if (usage) {
67
+ inputTokens += usage.input_tokens || 0;
68
+ outputTokens += usage.output_tokens || 0;
69
+ cacheReadTokens += usage.cache_read_input_tokens || 0;
70
+ cacheCreateTokens+= usage.cache_creation_input_tokens|| 0;
71
+ found = true;
72
+ }
73
+ // Count tool uses
74
+ if (entry.type === "tool_use" || entry.tool_name) toolCalls++;
75
+ // Count assistant turns
76
+ if (entry.role === "assistant" || entry.type === "assistant") turns++;
77
+ } catch { /* skip malformed lines */ }
78
+ }
79
+ } catch { return null; }
80
+
81
+ if (!found) return null;
82
+ return { inputTokens, outputTokens, cacheReadTokens, cacheCreateTokens, toolCalls, turns };
83
+ }
84
+
85
+ // ── Estimate tokens when transcript doesn't have usage data ───────────────
86
+
87
+ function estimateFromTranscript(transcriptPath) {
88
+ if (!transcriptPath || !fs.existsSync(transcriptPath)) {
89
+ return { inputTokens: 0, outputTokens: 0, toolCalls: 0, turns: 0, estimated: true };
90
+ }
91
+ let inputChars = 0, outputChars = 0, toolCalls = 0, turns = 0;
92
+ try {
93
+ const lines = fs.readFileSync(transcriptPath, "utf8").split("\n").filter(Boolean);
94
+ for (const line of lines) {
95
+ try {
96
+ const entry = JSON.parse(line);
97
+ const content = JSON.stringify(entry.content || entry.text || "");
98
+ if (entry.role === "user" || entry.type === "user") { inputChars += content.length; }
99
+ if (entry.role === "assistant" || entry.type === "assistant") { outputChars += content.length; turns++; }
100
+ if (entry.type === "tool_use" || entry.tool_name) { toolCalls++; inputChars += 500 * 4; }
101
+ } catch { /* skip */ }
102
+ }
103
+ } catch {}
104
+ return {
105
+ inputTokens: Math.round(inputChars / 4),
106
+ outputTokens: Math.round(outputChars / 4),
107
+ cacheReadTokens: 0, cacheCreateTokens: 0,
108
+ toolCalls, turns, estimated: true
109
+ };
110
+ }
111
+
112
+ // ── Main ───────────────────────────────────────────────────────────────────
113
+
114
+ async function main() {
115
+ let event = {};
116
+ try {
117
+ const raw = fs.readFileSync("/dev/stdin", "utf8");
118
+ event = JSON.parse(raw);
119
+ } catch { /* no stdin or parse error — use empty event */ }
120
+
121
+ const cwd = event.cwd || process.cwd();
122
+ const sessionId = event.session_id || `unknown-${Date.now()}`;
123
+ const transcriptPath = event.transcript_path;
124
+
125
+ const budgetFile = path.join(cwd, ".ai-assistance", "token-budget.json");
126
+ const kpiDir = path.join(cwd, ".ai-assistance", "local", "kpi");
127
+ const weekId = isoWeek(new Date());
128
+ const sessionFile = path.join(kpiDir, `session-${sessionId}.json`);
129
+ const weeklyFile = path.join(kpiDir, `weekly-${weekId}.json`);
130
+ const turnLogFile = path.join(kpiDir, `sessions-${weekId}.jsonl`);
131
+
132
+ const budget = loadJson(budgetFile, {});
133
+ const project = (budget.project?.name || path.basename(cwd));
134
+ const priority= (budget.project?.priority || "medium");
135
+ const targetPct = (budget.weekly_quota?.target_utilization_pct || 75) / 100;
136
+ const warnAt = (budget.session_logging?.warn_at_pct || 80) / 100;
137
+
138
+ // Extract usage from transcript
139
+ const transcriptUsage = extractUsageFromTranscript(transcriptPath)
140
+ || estimateFromTranscript(transcriptPath);
141
+
142
+ // Load previous session state (accumulate across turns in a session)
143
+ const prevSession = loadJson(sessionFile, {
144
+ session_id: sessionId, project, priority,
145
+ started_at: new Date().toISOString(),
146
+ week_id: weekId,
147
+ input_tokens: 0, output_tokens: 0,
148
+ cache_read_tokens: 0, cache_create_tokens: 0,
149
+ tool_calls: 0, turns: 0, estimated: false,
150
+ });
151
+
152
+ // Use transcript totals (they accumulate naturally) not deltas
153
+ const sessionNow = {
154
+ ...prevSession,
155
+ input_tokens: transcriptUsage.inputTokens,
156
+ output_tokens: transcriptUsage.outputTokens,
157
+ cache_read_tokens: transcriptUsage.cacheReadTokens || 0,
158
+ cache_create_tokens:transcriptUsage.cacheCreateTokens || 0,
159
+ tool_calls: transcriptUsage.toolCalls,
160
+ turns: transcriptUsage.turns,
161
+ estimated: transcriptUsage.estimated || false,
162
+ last_updated_at: new Date().toISOString(),
163
+ };
164
+
165
+ saveJson(sessionFile, sessionNow);
166
+
167
+ // Update weekly totals — replace session contribution (re-compute from sessions)
168
+ const weekly = loadJson(weeklyFile, { week_id: weekId, projects: {}, total_tokens: 0 });
169
+ const sessionTotal = sessionNow.input_tokens + sessionNow.output_tokens;
170
+ const prevContrib = (weekly.projects[sessionId]?.tokens || 0);
171
+ weekly.projects[sessionId] = {
172
+ project, priority, tokens: sessionTotal,
173
+ tool_calls: sessionNow.tool_calls, turns: sessionNow.turns,
174
+ updated_at: new Date().toISOString()
175
+ };
176
+ weekly.total_tokens = Object.values(weekly.projects).reduce((s, p) => s + p.tokens, 0);
177
+ saveJson(weeklyFile, weekly);
178
+
179
+ // Log the turn to the weekly JSONL (for cross-session analysis)
180
+ appendJsonl(turnLogFile, {
181
+ ts: new Date().toISOString(), session_id: sessionId, project, priority, week_id: weekId,
182
+ turn_tokens: sessionTotal - prevContrib,
183
+ session_total_tokens: sessionTotal,
184
+ tool_calls: sessionNow.tool_calls,
185
+ estimated: sessionNow.estimated,
186
+ });
187
+
188
+ // ── Budget warnings ────────────────────────────────────────────────────
189
+
190
+ const totalTokens = budget.weekly_quota?.total_tokens;
191
+ if (totalTokens) {
192
+ const usedPct = weekly.total_tokens / totalTokens;
193
+ const targetTokens = totalTokens * targetPct;
194
+
195
+ // Priority-based soft allocation
196
+ const weights = budget.project_allocation?.priority_weights || { high: 50, medium: 30, low: 20 };
197
+ const myWeight = (weights[priority] || 30) / 100;
198
+ const myBudget = totalTokens * targetPct * myWeight;
199
+ const myUsed = Object.values(weekly.projects)
200
+ .filter(p => p.project === project)
201
+ .reduce((s, p) => s + p.tokens, 0);
202
+ const myPct = myBudget > 0 ? myUsed / myBudget : 0;
203
+
204
+ if (usedPct >= warnAt) {
205
+ process.stderr.write(
206
+ `\n[token-budget] ⚠ Week ${weekId}: ${Math.round(usedPct * 100)}% of quota used` +
207
+ ` (${weekly.total_tokens.toLocaleString()}/${totalTokens.toLocaleString()} tokens)` +
208
+ ` — target was ${Math.round(targetPct * 100)}%\n`
209
+ );
210
+ }
211
+ if (myPct >= warnAt) {
212
+ process.stderr.write(
213
+ `[token-budget] ⚠ Project "${project}" (${priority}): ${Math.round(myPct * 100)}% of allocation` +
214
+ ` (${myUsed.toLocaleString()}/${Math.round(myBudget).toLocaleString()} tokens)\n`
215
+ );
216
+ }
217
+ }
218
+ }
219
+
220
+ main().catch(() => { /* never crash the hook */ });
@@ -0,0 +1,158 @@
1
+ /**
2
+ * token-report.ts
3
+ *
4
+ * Cross-project token usage report. Reads weekly session logs from all aligned
5
+ * projects (via local/paths.md) and produces a management-ready summary.
6
+ *
7
+ * Usage:
8
+ * npx ts-node .ai-assistance/scripts/token-report.ts
9
+ * npx ts-node .ai-assistance/scripts/token-report.ts --week 2026-W24
10
+ * npx ts-node .ai-assistance/scripts/token-report.ts --format json
11
+ *
12
+ * Output: Markdown table (default) or JSON (--format json)
13
+ * The report is printed to stdout — redirect to a file or pipe to your reporting tool.
14
+ */
15
+
16
+ import * as fs from "fs";
17
+ import * as path from "path";
18
+
19
+ const SCRIPTS_DIR = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1"));
20
+ const AI_DIR = path.join(SCRIPTS_DIR, "..");
21
+ const CWD = path.join(AI_DIR, "..", "..");
22
+
23
+ // ── Helpers ────────────────────────────────────────────────────────────────
24
+
25
+ function isoWeek(d: Date): string {
26
+ const jan4 = new Date(d.getFullYear(), 0, 4);
27
+ const s = new Date(jan4); s.setDate(jan4.getDate() - ((jan4.getDay() + 6) % 7));
28
+ return `${d.getFullYear()}-W${String(Math.ceil(((d.getTime() - s.getTime()) / 86400000 + 1) / 7)).padStart(2, "0")}`;
29
+ }
30
+
31
+ function loadJson<T>(p: string, fb: T): T {
32
+ try { return JSON.parse(fs.readFileSync(p, "utf8")); } catch { return fb; }
33
+ }
34
+
35
+ interface ProjectPath { alias: string; localPath: string; }
36
+
37
+ function loadProjectPaths(): ProjectPath[] {
38
+ const pathsFile = path.join(AI_DIR, "local", "paths.md");
39
+ if (!fs.existsSync(pathsFile)) return [{ alias: path.basename(CWD), localPath: CWD }];
40
+ const projects: ProjectPath[] = [];
41
+ const seen = new Set<string>();
42
+ for (const line of fs.readFileSync(pathsFile, "utf8").split("\n")) {
43
+ const m = line.match(/\|\s*`([^`]+)`\s*\|\s*`([^`]+)`/);
44
+ if (!m) continue;
45
+ const [, alias, rawPath] = m;
46
+ const localPath = rawPath.replace(/\//g, path.sep);
47
+ const real = fs.existsSync(localPath) ? fs.realpathSync(localPath) : localPath;
48
+ if (!seen.has(real) && fs.existsSync(localPath)) { projects.push({ alias, localPath }); seen.add(real); }
49
+ }
50
+ return projects.length ? projects : [{ alias: path.basename(CWD), localPath: CWD }];
51
+ }
52
+
53
+ interface WeeklyData {
54
+ week_id: string;
55
+ total_tokens: number;
56
+ projects: Record<string, { project: string; priority: string; tokens: number; tool_calls: number; turns: number; }>;
57
+ }
58
+
59
+ interface SessionTurn {
60
+ ts: string; session_id: string; project: string; priority: string;
61
+ week_id: string; turn_tokens: number; session_total_tokens: number;
62
+ tool_calls: number; estimated: boolean;
63
+ }
64
+
65
+ // ── Main ───────────────────────────────────────────────────────────────────
66
+
67
+ const args = process.argv.slice(2);
68
+ const weekArg = args.includes("--week") ? args[args.indexOf("--week") + 1] : null;
69
+ const format = args.includes("--format") ? args[args.indexOf("--format") + 1] : "text";
70
+ const weekId = weekArg || isoWeek(new Date());
71
+
72
+ const projects = loadProjectPaths();
73
+
74
+ // Aggregate across all projects
75
+ const byProject: Record<string, {
76
+ tokens: number; tool_calls: number; turns: number;
77
+ priority: string; sessions: number; estimated: number;
78
+ }> = {};
79
+
80
+ let grandTotal = 0;
81
+
82
+ for (const { localPath } of projects) {
83
+ const kpiDir = path.join(localPath, ".ai-assistance", "local", "kpi");
84
+ const weeklyFile = path.join(kpiDir, `weekly-${weekId}.json`);
85
+ const budget = loadJson<any>(path.join(localPath, ".ai-assistance", "token-budget.json"), {});
86
+ const projectName= budget.project?.name || path.basename(localPath);
87
+ const priority = budget.project?.priority || "medium";
88
+
89
+ if (!fs.existsSync(weeklyFile)) continue;
90
+
91
+ const weekly = loadJson<WeeklyData>(weeklyFile, { week_id: weekId, total_tokens: 0, projects: {} });
92
+
93
+ // Sum sessions for this project
94
+ let tokens = 0, toolCalls = 0, turns = 0, sessions = 0, estimated = 0;
95
+ for (const s of Object.values(weekly.projects)) {
96
+ if (s.project === projectName) {
97
+ tokens += s.tokens;
98
+ toolCalls += s.tool_calls || 0;
99
+ turns += s.turns || 0;
100
+ sessions++;
101
+ }
102
+ }
103
+
104
+ // Count estimated sessions from JSONL
105
+ const jsonl = path.join(kpiDir, `sessions-${weekId}.jsonl`);
106
+ if (fs.existsSync(jsonl)) {
107
+ const lines = fs.readFileSync(jsonl, "utf8").split("\n").filter(Boolean);
108
+ estimated = lines.filter(l => { try { return JSON.parse(l).estimated; } catch { return false; } }).length;
109
+ }
110
+
111
+ if (tokens > 0) {
112
+ byProject[projectName] = { tokens, tool_calls: toolCalls, turns, priority, sessions, estimated };
113
+ grandTotal += tokens;
114
+ }
115
+ }
116
+
117
+ if (format === "json") {
118
+ console.log(JSON.stringify({ week_id: weekId, grand_total_tokens: grandTotal, projects: byProject }, null, 2));
119
+ process.exit(0);
120
+ }
121
+
122
+ // ── Text report ────────────────────────────────────────────────────────────
123
+
124
+ const sortedProjects = Object.entries(byProject)
125
+ .sort(([, a], [, b]) => b.tokens - a.tokens);
126
+
127
+ console.log(`\n${"=".repeat(70)}`);
128
+ console.log(` Token Usage Report — ${weekId}`);
129
+ console.log(` Generated: ${new Date().toISOString().slice(0, 16)}`);
130
+ console.log(`${"=".repeat(70)}\n`);
131
+ console.log(` Grand total: ${grandTotal.toLocaleString()} tokens across ${sortedProjects.length} project(s)\n`);
132
+
133
+ console.log(` ${"Project".padEnd(30)} ${"Priority".padEnd(10)} ${"Tokens".padStart(10)} ${"Share".padStart(7)} ${"Tool calls".padStart(12)} ${"Turns".padStart(6)}`);
134
+ console.log(` ${"-".repeat(78)}`);
135
+
136
+ for (const [name, data] of sortedProjects) {
137
+ const share = grandTotal > 0 ? Math.round((data.tokens / grandTotal) * 100) : 0;
138
+ const est = data.estimated > 0 ? " ~" : " ";
139
+ console.log(` ${name.padEnd(30)} ${data.priority.padEnd(10)} ${est}${data.tokens.toLocaleString().padStart(8)} ${`${share}%`.padStart(7)} ${data.tool_calls.toString().padStart(12)} ${data.turns.toString().padStart(6)}`);
140
+ }
141
+
142
+ console.log(`\n ~ = some sessions used token estimation (transcript had no usage data)`);
143
+
144
+ // Priority allocation analysis
145
+ const targetPct = 75;
146
+ const weights = { high: 50, medium: 30, low: 20 };
147
+ console.log(`\n Priority allocation vs actuals (target utilization: ${targetPct}%):`);
148
+ for (const priority of ["high", "medium", "low"]) {
149
+ const w = (weights as any)[priority];
150
+ const actual = Object.values(byProject)
151
+ .filter(p => p.priority === priority)
152
+ .reduce((s, p) => s + p.tokens, 0);
153
+ const actualPct = grandTotal > 0 ? Math.round((actual / grandTotal) * 100) : 0;
154
+ const projects = Object.entries(byProject).filter(([, p]) => p.priority === priority).map(([n]) => n).join(", ") || "(none)";
155
+ console.log(` ${priority.padEnd(8)} target ~${w}% actual ${`${actualPct}%`.padStart(4)} — ${projects}`);
156
+ }
157
+
158
+ console.log(`\n${"=".repeat(70)}\n`);
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * token-session-start.js
4
+ *
5
+ * Claude Code SessionStart hook.
6
+ * Checks current week's token usage, warns if over allocation,
7
+ * and shows the budget status for this project.
8
+ *
9
+ * Wired in .claude/settings.json:
10
+ * "SessionStart": [{ "type": "command", "command": "node .ai-assistance/scripts/token-session-start.js" }]
11
+ */
12
+
13
+ const fs = require("fs");
14
+ const path = require("path");
15
+
16
+ function isoWeek(d) {
17
+ const jan4 = new Date(d.getFullYear(), 0, 4);
18
+ const s = new Date(jan4); s.setDate(jan4.getDate() - ((jan4.getDay() + 6) % 7));
19
+ return `${d.getFullYear()}-W${String(Math.ceil(((d - s) / 86400000 + 1) / 7)).padStart(2, "0")}`;
20
+ }
21
+ function loadJson(p, fb) { try { return JSON.parse(fs.readFileSync(p, "utf8")); } catch { return fb; } }
22
+
23
+ async function main() {
24
+ let event = {};
25
+ try { event = JSON.parse(fs.readFileSync("/dev/stdin", "utf8")); } catch {}
26
+
27
+ const cwd = event.cwd || process.cwd();
28
+ const weekId = isoWeek(new Date());
29
+ const budget = loadJson(path.join(cwd, ".ai-assistance", "token-budget.json"), {});
30
+ const weekly = loadJson(path.join(cwd, ".ai-assistance", "local", "kpi", `weekly-${weekId}.json`), { total_tokens: 0, projects: {} });
31
+
32
+ const project = budget.project?.name || path.basename(cwd);
33
+ const priority = budget.project?.priority || "medium";
34
+ const total = budget.weekly_quota?.total_tokens;
35
+ const targetPct= (budget.weekly_quota?.target_utilization_pct || 75);
36
+
37
+ // My project's tokens this week across all sessions
38
+ const myTokens = Object.values(weekly.projects)
39
+ .filter(p => p.project === project)
40
+ .reduce((s, p) => s + p.tokens, 0);
41
+
42
+ const lines = [`\n[token-budget] Week ${weekId} | Project: ${project} (${priority})`];
43
+ lines.push(` All projects this week: ${weekly.total_tokens.toLocaleString()} tokens`);
44
+ lines.push(` This project this week: ${myTokens.toLocaleString()} tokens`);
45
+
46
+ if (total) {
47
+ const usedPct = Math.round((weekly.total_tokens / total) * 100);
48
+ const weights = budget.project_allocation?.priority_weights || { high: 50, medium: 30, low: 20 };
49
+ const myAlloc = Math.round(total * (targetPct / 100) * ((weights[priority] || 30) / 100));
50
+ const myPct = myAlloc > 0 ? Math.round((myTokens / myAlloc) * 100) : 0;
51
+ lines.push(` Week quota: ${usedPct}% used (target ${targetPct}%) | This project: ${myPct}% of ${myAlloc.toLocaleString()} alloc`);
52
+ if (usedPct >= targetPct) lines.push(` !! Over weekly target — consider deferring low-priority work`);
53
+ if (myPct >= 90) lines.push(` !! This project near allocation limit`);
54
+ }
55
+
56
+ process.stderr.write(lines.join("\n") + "\n");
57
+
58
+ // Also run bridge-init if it exists
59
+ const bridgeInit = path.join(cwd, ".ai-assistance", "scripts", "bridge-init.sh");
60
+ if (fs.existsSync(bridgeInit)) {
61
+ const { execSync } = require("child_process");
62
+ try { execSync(`bash "${bridgeInit}"`, { stdio: "inherit" }); } catch {}
63
+ }
64
+ }
65
+
66
+ main().catch(() => {});
@@ -0,0 +1,48 @@
1
+ # SKILL: AI Instructions Generator, Reviewer & Updater
2
+
3
+ **Purpose:** Keep CLAUDE.md, skill files, and conventions accurate, tidy, and placed correctly as the project evolves.
4
+
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ - A developer makes an observation that reveals a gap, mistake, or new convention.
10
+ - A new pattern emerges in the code that future AI agents should know about.
11
+ - A skill needs to be created, updated, or split.
12
+ - CLAUDE.md needs to reflect a structural change to the repo.
13
+
14
+ ---
15
+
16
+ ## Decision: Practice/Convention vs. CLAUDE.md Instruction vs. Skill
17
+
18
+ When an observation is made, scope it:
19
+
20
+ | Type | Where it goes |
21
+ |------|--------------|
22
+ | One-off note or context | Inline comment in the relevant `.ai-assistance/code/*.md` doc |
23
+ | Convention (how we do things here) | `CLAUDE.md` → Conventions section |
24
+ | Always-do behaviour for AI agents | `CLAUDE.md` → Before Starting Any Task, or a new section |
25
+ | Reusable, invocable capability | New or updated `SKILL.md` in `.ai-assistance/skills/<skill-name>/` |
26
+
27
+ ---
28
+
29
+ ## File Placement Rules
30
+
31
+ - **CLAUDE.md** lives at repo root — one file, always.
32
+ - **Skills** live at `.ai-assistance/skills/<skill-name>/SKILL.md`.
33
+ - Additional assets (scripts, prompts) live alongside the SKILL.md in the same folder.
34
+ - **Code specs** live at `.ai-assistance/code/` (see `code-specs` skill).
35
+ - **Project files** live at `.ai-assistance/projects/<project-name>/` (see `project-cycle` skill).
36
+ - **Refactoring logs** live at `.ai-assistance/code/refactoring/` (see `refactor` skill).
37
+
38
+ **Never scatter instruction files in code directories.** All AI tooling lives under `.ai-assistance/`.
39
+
40
+ ---
41
+
42
+ ## Workflow
43
+
44
+ 1. Identify the type of observation (see table above).
45
+ 2. Locate the correct file. Create it if it doesn't exist.
46
+ 3. Make the minimal change — don't rewrite documents unnecessarily.
47
+ 4. Update the skill index table in CLAUDE.md if a new skill was added.
48
+ 5. Confirm with the developer if the change affects an existing convention that other developers rely on.