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.
- checksums.yaml +4 -4
- data/.ai-assistance/bridge/CLAUDE.md +338 -0
- data/.ai-assistance/bridge/archive/.gitkeep +0 -0
- data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.inbox.md +29 -0
- data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.outbox.md +18 -0
- data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.inbox.md +42 -0
- data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.outbox.md +115 -0
- data/.ai-assistance/bridge/context/gemini-review-prompt.txt +48 -0
- data/.ai-assistance/bridge/context/gemini-review-response.md +104 -0
- data/.ai-assistance/bridge/context/project.md +42 -0
- data/.ai-assistance/bridge/inbox/.gitkeep +0 -0
- data/.ai-assistance/bridge/outbox/.gitkeep +0 -0
- data/.ai-assistance/bridge/outbox/request-for-standards-discovery.md +48 -0
- data/.ai-assistance/bridge/queue/.gitkeep +1 -0
- data/.ai-assistance/capabilities/CLAUDE.md +27 -0
- data/.ai-assistance/capabilities/assumptions-log.md +80 -0
- data/.ai-assistance/capabilities/code.md +47 -0
- data/.ai-assistance/capabilities/connectors.md +37 -0
- data/.ai-assistance/capabilities/cowork.md +55 -0
- data/.ai-assistance/code/OVERVIEW.md +155 -0
- data/.ai-assistance/code/data_fields.md +242 -0
- data/.ai-assistance/code/dependencies.md +151 -0
- data/.ai-assistance/code/diff_as_input.md +234 -0
- data/.ai-assistance/code/diff_service_deep_dive.md +192 -0
- data/.ai-assistance/code/ecoPortal_architecture/00_overview_and_index.md +55 -0
- data/.ai-assistance/code/ecoPortal_architecture/01_terminology_dictionary.md +181 -0
- data/.ai-assistance/code/ecoPortal_architecture/02_data_model.md +192 -0
- data/.ai-assistance/code/ecoPortal_architecture/03_api_layers.md +147 -0
- data/.ai-assistance/code/ecoPortal_architecture/04_graphql_queries_mutations.md +277 -0
- data/.ai-assistance/code/ecoPortal_architecture/05_page_workflows.md +200 -0
- data/.ai-assistance/code/ecoPortal_architecture/06_search_and_filters.md +228 -0
- data/.ai-assistance/code/ecoPortal_architecture/07_data_fields.md +197 -0
- data/.ai-assistance/code/ecoPortal_architecture/08_stages_sections.md +243 -0
- data/.ai-assistance/code/ecoPortal_architecture/09_people_contractors_locations.md +196 -0
- data/.ai-assistance/code/ecoPortal_architecture/10_forces_workflow_builder.md +132 -0
- data/.ai-assistance/code/ecoPortal_architecture/11_integration_gems.md +187 -0
- data/.ai-assistance/code/ecoPortal_architecture/12_ai_documentation_sources_gaps.md +236 -0
- data/.ai-assistance/code/ecoPortal_architecture/13_ai_infrastructure.md +183 -0
- data/.ai-assistance/code/ecoportal_schema_reference.md +240 -0
- data/.ai-assistance/code/graphql_domain_knowledge.md +230 -0
- data/.ai-assistance/code/refactoring/datafield-readwrite-shape-asymmetry.md +71 -0
- data/.ai-assistance/code/refactoring/opportunities.md +251 -0
- data/.ai-assistance/code/schema_analysis.md +321 -0
- data/.ai-assistance/code/search_filters.md +868 -0
- data/.ai-assistance/code/spec_coverage.md +73 -0
- data/.ai-assistance/code/workflow-command-guide.md +438 -0
- data/.ai-assistance/code/workflow-space.md +353 -0
- data/.ai-assistance/conventions/CLAUDE.md +30 -0
- data/.ai-assistance/conventions/code-working-tree-protocol.md +199 -0
- data/.ai-assistance/conventions/gitignore-rules.md +42 -0
- data/.ai-assistance/conventions/permission-guidance.md +120 -0
- data/.ai-assistance/integrations/README.md +70 -0
- data/.ai-assistance/integrations/gitkraken-mcp.md +107 -0
- data/.ai-assistance/integrations/gitlab-mcp.md +123 -0
- data/.ai-assistance/integrations/local-git.md +60 -0
- data/.ai-assistance/local_paths.example.md +17 -0
- data/.ai-assistance/projects/TODO.md +97 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/DECISIONS.md +168 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/INTENT.md +60 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/TODO.md +267 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/UPSTREAM.md +53 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/csv-template-pipeline-design.md +102 -0
- data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/cutover-usecase-gap-audit.md +139 -0
- data/.ai-assistance/projects/dynamic-model-generation/INTENT.md +93 -0
- data/.ai-assistance/projects/eco-helpers-compat/INTENT.md +244 -0
- data/.ai-assistance/projects/eco-helpers-compat/MIGRATION_GUIDE.md +266 -0
- data/.ai-assistance/projects/eco-helpers-compat/TODO.md +86 -0
- data/.ai-assistance/projects/ecoportal-api-v2-doublemodel-review/INTENT.md +101 -0
- data/.ai-assistance/projects/graphql-agent/GAP_ANALYSIS.md +177 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/DECISIONS.md +161 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/INTENT.md +125 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/RISKS.md +126 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/TODO.md +256 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-06-30-cutover-workflow-deep-review.md +122 -0
- data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-07-01-forces-via-workflow-commands-miss-rca.md +148 -0
- data/.ai-assistance/projects/page-model/DECISIONS.md +245 -0
- data/.ai-assistance/projects/page-model/TODO.md +190 -0
- data/.ai-assistance/projects/search-filter-builder/INTENT.md +107 -0
- data/.ai-assistance/projects/search-filter-builder/TODO.md +131 -0
- data/.ai-assistance/projects/template-maintenance/DESIGN.md +134 -0
- data/.ai-assistance/projects/workflow-space/TODO.md +213 -0
- data/.ai-assistance/reinstall-claude-desktop-windows.md +136 -0
- data/.ai-assistance/scripts/CLAUDE.md +150 -0
- data/.ai-assistance/scripts/bridge-init.sh +86 -0
- data/.ai-assistance/scripts/bridge-status.sh +44 -0
- data/.ai-assistance/scripts/capabilities-check.ts +104 -0
- data/.ai-assistance/scripts/check-outbox.sh +43 -0
- data/.ai-assistance/scripts/dep_graph.rb +91 -0
- data/.ai-assistance/scripts/lock-acquire.sh +103 -0
- data/.ai-assistance/scripts/lock-multi.sh +124 -0
- data/.ai-assistance/scripts/lock-queue.sh +94 -0
- data/.ai-assistance/scripts/setup-mcps.test.ts +188 -0
- data/.ai-assistance/scripts/setup-mcps.ts +234 -0
- data/.ai-assistance/scripts/task-complete.ts +74 -0
- data/.ai-assistance/scripts/task-create.ts +75 -0
- data/.ai-assistance/scripts/task-read.ts +125 -0
- data/.ai-assistance/scripts/token-logger.js +220 -0
- data/.ai-assistance/scripts/token-report.ts +158 -0
- data/.ai-assistance/scripts/token-session-start.js +66 -0
- data/.ai-assistance/skills/ai-instructions/SKILL.md +48 -0
- data/.ai-assistance/skills/code-specs/SKILL.md +69 -0
- data/.ai-assistance/skills/corporate-policies/SKILL.md +201 -0
- data/.ai-assistance/skills/dep-graph/SKILL.md +139 -0
- data/.ai-assistance/skills/ep-ai-manager/SKILL.md +417 -0
- data/.ai-assistance/skills/gemini-assist/SKILL.md +63 -0
- data/.ai-assistance/skills/gemini-assist/gemini-mcp-server.js +205 -0
- data/.ai-assistance/skills/gemini-assist/gemini_ask.py +1 -0
- data/.ai-assistance/skills/gemini-assist/gemini_ask.rb +240 -0
- data/.ai-assistance/skills/gemini-assist/prompts/cycle_end_review.txt +25 -0
- data/.ai-assistance/skills/graphql-schema-analysis/SKILL.md +261 -0
- data/.ai-assistance/skills/project-cycle/SKILL.md +177 -0
- data/.ai-assistance/skills/refactor/SKILL.md +62 -0
- data/.ai-assistance/skills/rubocop/SKILL.md +93 -0
- data/.ai-assistance/skills/ruby-scripting/SKILL.md +215 -0
- data/.ai-assistance/skills/spec-generation/SKILL.md +72 -0
- data/.ai-assistance/standards-version.json +21 -0
- data/.ai-assistance/token-budget.json +32 -0
- data/.ai-assistance/version.json +39 -0
- data/.claude/settings.json +146 -0
- data/.env.example +18 -0
- data/.gitattributes +15 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +121 -97
- data/CHANGELOG.md +673 -477
- data/CLAUDE.md +232 -0
- data/Gemfile +30 -6
- data/Rakefile +90 -38
- data/docs/worklog.md +574 -0
- data/ecoportal-api-graphql.gemspec +40 -40
- data/lib/ecoportal/api/common/graphql/CLAUDE.md +36 -0
- data/lib/ecoportal/api/common/graphql/client.rb +1 -1
- data/lib/ecoportal/api/common/graphql/http_client.rb +35 -3
- data/lib/ecoportal/api/common/graphql/model/CLAUDE.md +28 -0
- data/lib/ecoportal/api/common/graphql/model/as_input.rb +8 -5
- data/lib/ecoportal/api/common/graphql/model/diffable/classic_diff_service.rb +3 -1
- data/lib/ecoportal/api/common/graphql/model/diffable/diff_service.rb +31 -23
- data/lib/ecoportal/api/common/graphql/model/diffable/hash_diff_nesting.rb +54 -1
- data/lib/ecoportal/api/graphql/CLAUDE.md +37 -0
- data/lib/ecoportal/api/graphql/base/CLAUDE.md +50 -0
- data/lib/ecoportal/api/graphql/base/ai_summary_version.rb +17 -0
- data/lib/ecoportal/api/graphql/base/delta_result.rb +16 -0
- data/lib/ecoportal/api/graphql/base/force/binding.rb +19 -0
- data/lib/ecoportal/api/graphql/base/force/binding_collection.rb +62 -0
- data/lib/ecoportal/api/graphql/base/force/collection.rb +47 -0
- data/lib/ecoportal/api/graphql/base/force.rb +65 -0
- data/lib/ecoportal/api/graphql/base/kickstand/job.rb +17 -0
- data/lib/ecoportal/api/graphql/base/kickstand/workflow.rb +18 -0
- data/lib/ecoportal/api/graphql/base/kickstand.rb +13 -0
- data/lib/ecoportal/api/graphql/base/page/basic.rb +38 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/actions_list.rb +9 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/ai_summary.rb +18 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/checklist.rb +29 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/collection.rb +112 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/contractor_entities.rb +28 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/cross_reference.rb +54 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/date_field.rb +23 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/file_field.rb +25 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/gauge.rb +19 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/geo.rb +24 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/image_gallery.rb +24 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/law.rb +8 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/mailbox.rb +9 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/number.rb +20 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/people.rb +35 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/plain_text.rb +17 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/rich_text.rb +26 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/select.rb +41 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/signature.rb +9 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/smart_fill.rb +17 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/table.rb +9 -0
- data/lib/ecoportal/api/graphql/base/page/data_field/tag_field.rb +21 -0
- data/lib/ecoportal/api/graphql/base/page/data_field.rb +137 -12
- data/lib/ecoportal/api/graphql/base/page/phased/stage.rb +68 -14
- data/lib/ecoportal/api/graphql/base/page/phased.rb +1 -0
- data/lib/ecoportal/api/graphql/base/page/section.rb +36 -0
- data/lib/ecoportal/api/graphql/base/page/section_collection.rb +79 -0
- data/lib/ecoportal/api/graphql/base/page.rb +2 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/action_type_selection.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/callback_type.rb +28 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/command_change.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/command_change_message.rb +15 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/command_es_change.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/command_interface.rb +36 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/email_config.rb +18 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/escalation_level.rb +19 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/in_system_config.rb +16 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/mailbox_field_selection.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/operation_interface.rb +39 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/operations/assign_to.rb +27 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/operations/create_page.rb +21 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/operations/send_notification.rb +30 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/people_field_selection.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/recipient_config.rb +34 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/register_field.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/task_config_selection.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/time_delay_config.rb +16 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/trigger_interface.rb +26 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/triggers/conditional_logic.rb +17 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow/user_selection.rb +16 -0
- data/lib/ecoportal/api/graphql/base/pages_workflow.rb +35 -0
- data/lib/ecoportal/api/graphql/base/preset_view.rb +17 -0
- data/lib/ecoportal/api/graphql/base/preview_page.rb +23 -0
- data/lib/ecoportal/api/graphql/base/register.rb +18 -0
- data/lib/ecoportal/api/graphql/base.rb +11 -0
- data/lib/ecoportal/api/graphql/builder/CLAUDE.md +65 -0
- data/lib/ecoportal/api/graphql/builder/kickstand.rb +73 -0
- data/lib/ecoportal/api/graphql/builder/page.rb +210 -41
- data/lib/ecoportal/api/graphql/builder/register/preset_view.rb +84 -0
- data/lib/ecoportal/api/graphql/builder/register.rb +27 -19
- data/lib/ecoportal/api/graphql/builder/template.rb +80 -0
- data/lib/ecoportal/api/graphql/builder.rb +2 -0
- data/lib/ecoportal/api/graphql/compat/filter_translator.rb +107 -0
- data/lib/ecoportal/api/graphql/compat/page_reference.rb +23 -0
- data/lib/ecoportal/api/graphql/compat/pages.rb +212 -0
- data/lib/ecoportal/api/graphql/compat/registers.rb +84 -0
- data/lib/ecoportal/api/graphql/compat/response.rb +35 -0
- data/lib/ecoportal/api/graphql/compat/search_results.rb +33 -0
- data/lib/ecoportal/api/graphql/compat/stage_collection.rb +70 -0
- data/lib/ecoportal/api/graphql/compat/stage_view.rb +76 -0
- data/lib/ecoportal/api/graphql/compat.rb +17 -0
- data/lib/ecoportal/api/graphql/concerns/data_field_access.rb +71 -0
- data/lib/ecoportal/api/graphql/concerns/deprecation.rb +20 -0
- data/lib/ecoportal/api/graphql/concerns/fragment_definitions.rb +21 -28
- data/lib/ecoportal/api/graphql/concerns/page_compat.rb +51 -0
- data/lib/ecoportal/api/graphql/concerns/snake_camel_access.rb +60 -0
- data/lib/ecoportal/api/graphql/concerns.rb +4 -0
- data/lib/ecoportal/api/graphql/connection/page.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/pages_workflow_command.rb +13 -0
- data/lib/ecoportal/api/graphql/connection/preset_view.rb +11 -0
- data/lib/ecoportal/api/graphql/connection/preview_page.rb +11 -0
- data/lib/ecoportal/api/graphql/connection.rb +4 -0
- data/lib/ecoportal/api/graphql/file_upload/client.rb +181 -0
- data/lib/ecoportal/api/graphql/file_upload.rb +10 -0
- data/lib/ecoportal/api/graphql/fragment/action.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment/action_category.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment/contractor_entity.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment/force.rb +30 -0
- data/lib/ecoportal/api/graphql/fragment/location_draft.rb +2 -2
- data/lib/ecoportal/api/graphql/fragment/location_node.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment/locations_error.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment/page.rb +85 -0
- data/lib/ecoportal/api/graphql/fragment/pages/common_page_union.rb +395 -0
- data/lib/ecoportal/api/graphql/fragment/pages.rb +15 -0
- data/lib/ecoportal/api/graphql/fragment/pages_workflow.rb +172 -0
- data/lib/ecoportal/api/graphql/fragment/pagination.rb +1 -1
- data/lib/ecoportal/api/graphql/fragment.rb +37 -27
- data/lib/ecoportal/api/graphql/input/contractor_entity/update.rb +25 -0
- data/lib/ecoportal/api/graphql/input/delta_input.rb +16 -0
- data/lib/ecoportal/api/graphql/input/page/archive.rb +14 -0
- data/lib/ecoportal/api/graphql/input/page/build_from_template.rb +13 -0
- data/lib/ecoportal/api/graphql/input/page/create_draft.rb +13 -0
- data/lib/ecoportal/api/graphql/input/page/create_from_template.rb +18 -0
- data/lib/ecoportal/api/graphql/input/page/delete_draft.rb +13 -0
- data/lib/ecoportal/api/graphql/input/page/publish_draft.rb +13 -0
- data/lib/ecoportal/api/graphql/input/page/review_task.rb +14 -0
- data/lib/ecoportal/api/graphql/input/page/unarchive.rb +14 -0
- data/lib/ecoportal/api/graphql/input/page/update.rb +140 -0
- data/lib/ecoportal/api/graphql/input/page.rb +26 -0
- data/lib/ecoportal/api/graphql/input/preset_view/create.rb +18 -0
- data/lib/ecoportal/api/graphql/input/preset_view/permission.rb +16 -0
- data/lib/ecoportal/api/graphql/input/preset_view/update.rb +16 -0
- data/lib/ecoportal/api/graphql/input/preset_view.rb +14 -0
- data/lib/ecoportal/api/graphql/input/register/create.rb +18 -0
- data/lib/ecoportal/api/graphql/input/register/update.rb +15 -0
- data/lib/ecoportal/api/graphql/input/register.rb +13 -0
- data/lib/ecoportal/api/graphql/input/search_conf/ai_generator.rb +234 -0
- data/lib/ecoportal/api/graphql/input/search_conf.rb +367 -0
- data/lib/ecoportal/api/graphql/input/variable_binding.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_action_tag.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_binding.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_comment_tagging_user_group.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_default_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_default_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_field.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_force.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_gauge_field_stop.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_linked_field_config.rb +23 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_linked_helper.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_operation.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_operation_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_operation_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_action_type.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_filter.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_people_field.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_task_config.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_recipient_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_scheduled_callback.rb +23 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_scheduled_callback_action.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_select_field_option.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage_tag.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_task.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_task_assignment_user_group.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/add_workflow_callback.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/collapse_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_binding.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_creator_permissions.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_default_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_field_configuration.rb +21 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_force.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_gauge_field_stop.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_linked_field_config.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_linked_helper.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_operation.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_operation_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page.rb +28 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page_creator_permissions.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_reminder.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_required_sign_offs.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_restrict_comment_tagging.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_restrict_task_assignment.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_scheduled_callback.rb +22 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_section_header.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_select_field_option.rb +19 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_stage.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_task_due.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_trigger.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/expand_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/contractor_entities.rb +24 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/cross_reference.rb +23 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/date.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/gauge.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/image_gallery.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/location_field.rb +24 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/people.rb +24 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/plain_text.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/rich_text.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/select.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/signature.rb +20 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/table.rb +25 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/move_field.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/move_stage.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_action_tag.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_binding.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_callback.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_comment_tagging_user_group.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_default_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_default_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_field.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_force.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_gauge_field_stop.rb +17 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_linked_field_config.rb +17 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_linked_helper.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation_direct_strategy_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_operation_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_action_type.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_filter.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_people_field.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_task_config.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_recipient_user.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_scheduled_callback.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_select_field_option.rb +17 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_stage_tag.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_strategy.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_task.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_assignment_user_group.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_due.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/remove_task_priority_level.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_forces.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_section.rb +18 -0
- data/lib/ecoportal/api/graphql/input/workflow_command.rb +251 -0
- data/lib/ecoportal/api/graphql/input.rb +8 -0
- data/lib/ecoportal/api/graphql/interface/base_page.rb +100 -58
- data/lib/ecoportal/api/graphql/interface/location_structure/nodes.rb +27 -28
- data/lib/ecoportal/api/graphql/logic/base_model.rb +2 -0
- data/lib/ecoportal/api/graphql/logic/base_query.rb +45 -2
- data/lib/ecoportal/api/graphql/logic/input.rb +15 -0
- data/lib/ecoportal/api/graphql/model/ai_summary_version.rb +10 -0
- data/lib/ecoportal/api/graphql/model/organization.rb +65 -55
- data/lib/ecoportal/api/graphql/model/page/basic.rb +14 -0
- data/lib/ecoportal/api/graphql/model/page/phased.rb +82 -20
- data/lib/ecoportal/api/graphql/model/page.rb +1 -0
- data/lib/ecoportal/api/graphql/model/page_union.rb +21 -0
- data/lib/ecoportal/api/graphql/model/pages_workflow.rb +20 -0
- data/lib/ecoportal/api/graphql/model/preset_view.rb +10 -0
- data/lib/ecoportal/api/graphql/model/preview_page.rb +10 -0
- data/lib/ecoportal/api/graphql/model/register.rb +10 -0
- data/lib/ecoportal/api/graphql/model.rb +8 -0
- data/lib/ecoportal/api/graphql/mutation/ai_summary/generate.rb +45 -0
- data/lib/ecoportal/api/graphql/mutation/ai_summary/submit_feedback.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/ai_summary.rb +13 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/bulk_update_jobs.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/bulk_update_workflows.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/fail_job.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/fail_workflow.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/start_job.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/start_workflow.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand/stop_workflow.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/kickstand.rb +18 -0
- data/lib/ecoportal/api/graphql/mutation/location_structure/apply_commands.rb +3 -3
- data/lib/ecoportal/api/graphql/mutation/location_structure/draft/add_commands.rb +2 -2
- data/lib/ecoportal/api/graphql/mutation/location_structure/draft/create.rb +2 -2
- data/lib/ecoportal/api/graphql/mutation/location_structure/draft/drop_bad_commands.rb +3 -3
- data/lib/ecoportal/api/graphql/mutation/location_structure/draft/publish.rb +3 -3
- data/lib/ecoportal/api/graphql/mutation/page/approve_review_task.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/archive.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/batch_update_review_task.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/build_from_template.rb +50 -0
- data/lib/ecoportal/api/graphql/mutation/page/create_draft.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/create_from_template.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/page/delete_draft.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/execute_force_commands.rb +69 -0
- data/lib/ecoportal/api/graphql/mutation/page/execute_workflow_commands.rb +51 -0
- data/lib/ecoportal/api/graphql/mutation/page/publish_draft.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/reject_review_task.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/restart_review_task.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/unarchive.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/undo_review_task.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/update.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/page/update_variable_bindings.rb +44 -0
- data/lib/ecoportal/api/graphql/mutation/page.rb +28 -0
- data/lib/ecoportal/api/graphql/mutation/preset_view/create.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/preset_view/destroy.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/preset_view/permission.rb +37 -0
- data/lib/ecoportal/api/graphql/mutation/preset_view/update.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/preset_view.rb +15 -0
- data/lib/ecoportal/api/graphql/mutation/register/create.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/register/destroy.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/register/update.rb +35 -0
- data/lib/ecoportal/api/graphql/mutation/register.rb +14 -0
- data/lib/ecoportal/api/graphql/mutation/smart_fill/generate.rb +36 -0
- data/lib/ecoportal/api/graphql/mutation/smart_fill/submit_feedback.rb +40 -0
- data/lib/ecoportal/api/graphql/mutation/smart_fill.rb +13 -0
- data/lib/ecoportal/api/graphql/mutation/template/create.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/template/create_related_page.rb +46 -0
- data/lib/ecoportal/api/graphql/mutation/template/destroy_related_page.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/template/publish.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/template/unpublish.rb +39 -0
- data/lib/ecoportal/api/graphql/mutation/template/update.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/template/update_information.rb +43 -0
- data/lib/ecoportal/api/graphql/mutation/template.rb +18 -0
- data/lib/ecoportal/api/graphql/mutation.rb +8 -0
- data/lib/ecoportal/api/graphql/payload/ai_summary_generate.rb +12 -0
- data/lib/ecoportal/api/graphql/payload/execute_workflow_commands.rb +36 -0
- data/lib/ecoportal/api/graphql/payload/force_commands.rb +31 -0
- data/lib/ecoportal/api/graphql/payload/kickstand/bulk_update_jobs.rb +36 -0
- data/lib/ecoportal/api/graphql/payload/kickstand/bulk_update_workflows.rb +36 -0
- data/lib/ecoportal/api/graphql/payload/kickstand/job.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/kickstand/workflow.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/kickstand.rb +15 -0
- data/lib/ecoportal/api/graphql/payload/location_structure/draft/create.rb +33 -34
- data/lib/ecoportal/api/graphql/payload/ok_payload.rb +21 -0
- data/lib/ecoportal/api/graphql/payload/page/archive.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/build_from_template.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/create_from_template.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/draft.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/review_task.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/unarchive.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/update.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page/update_variable_bindings.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/page.rb +19 -0
- data/lib/ecoportal/api/graphql/payload/preset_view.rb +11 -0
- data/lib/ecoportal/api/graphql/payload/register.rb +11 -0
- data/lib/ecoportal/api/graphql/payload/template/create.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/create_related_page.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/destroy_related_page.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/publish.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/unpublish.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/update.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template/update_information.rb +13 -0
- data/lib/ecoportal/api/graphql/payload/template.rb +18 -0
- data/lib/ecoportal/api/graphql/payload.rb +11 -0
- data/lib/ecoportal/api/graphql/query/action.rb +1 -1
- data/lib/ecoportal/api/graphql/query/action_categories.rb +1 -1
- data/lib/ecoportal/api/graphql/query/actions.rb +2 -2
- data/lib/ecoportal/api/graphql/query/contractor_entities.rb +1 -1
- data/lib/ecoportal/api/graphql/query/file_upload_signature.rb +76 -0
- data/lib/ecoportal/api/graphql/query/location_structure/draft.rb +2 -2
- data/lib/ecoportal/api/graphql/query/location_structure.rb +1 -1
- data/lib/ecoportal/api/graphql/query/location_structures.rb +1 -1
- data/lib/ecoportal/api/graphql/query/page.rb +45 -0
- data/lib/ecoportal/api/graphql/query/page_delta.rb +47 -0
- data/lib/ecoportal/api/graphql/query/page_with_forces.rb +43 -0
- data/lib/ecoportal/api/graphql/query/pages.rb +59 -0
- data/lib/ecoportal/api/graphql/query/pages_workflow_commands.rb +59 -0
- data/lib/ecoportal/api/graphql/query/register_preset_views.rb +78 -0
- data/lib/ecoportal/api/graphql/query/register_preview_pages.rb +83 -0
- data/lib/ecoportal/api/graphql/query/templates.rb +53 -0
- data/lib/ecoportal/api/graphql/query.rb +11 -0
- data/lib/ecoportal/api/graphql.rb +60 -2
- data/lib/ecoportal/api/graphql_version.rb +5 -5
- data/scripts/auto-worker-scheduler.sh +386 -0
- data/tests/contractor_entity_create.rb +19 -19
- data/tests/contractor_entity_udpate.rb +20 -20
- data/tests/dump_page_model.rb +74 -0
- data/tests/loc_structure_get.rb +1 -2
- data/tests/loc_structure_update.rb +51 -51
- data/tests/loc_structures_get.rb +15 -15
- 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
|