ecoportal-api-graphql 1.3.13 → 1.3.16
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/CHANGELOG.md +87 -0
- data/lib/ecoportal/api/common/graphql/client.rb +0 -2
- data/lib/ecoportal/api/common/graphql/http_client.rb +6 -0
- data/lib/ecoportal/api/graphql/base/ai_summary_version.rb +17 -17
- data/lib/ecoportal/api/graphql/base/file_container.rb +14 -14
- data/lib/ecoportal/api/graphql/base/location_classification_type.rb +15 -15
- data/lib/ecoportal/api/graphql/base/location_structure.rb +27 -27
- data/lib/ecoportal/api/graphql/base/organization.rb +15 -15
- data/lib/ecoportal/api/graphql/base/page/task.rb +24 -0
- data/lib/ecoportal/api/graphql/base/page.rb +17 -16
- data/lib/ecoportal/api/graphql/base/template.rb +35 -0
- data/lib/ecoportal/api/graphql/base.rb +37 -35
- data/lib/ecoportal/api/graphql/builder/contractor_entity.rb +41 -41
- data/lib/ecoportal/api/graphql/compat/response.rb +35 -35
- data/lib/ecoportal/api/graphql/connection/action.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/action_category.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/contractor_entity.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/page.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/person_member.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/preview_page.rb +11 -11
- data/lib/ecoportal/api/graphql/connection.rb +17 -17
- data/lib/ecoportal/api/graphql/file_upload/client.rb +21 -10
- data/lib/ecoportal/api/graphql/fragment/field_configuration.rb +38 -0
- data/lib/ecoportal/api/graphql/fragment/force.rb +14 -10
- data/lib/ecoportal/api/graphql/fragment/location_node.rb +26 -26
- data/lib/ecoportal/api/graphql/fragment/page_task.rb +39 -0
- data/lib/ecoportal/api/graphql/fragment/template.rb +51 -0
- data/lib/ecoportal/api/graphql/fragment.rb +3 -0
- data/lib/ecoportal/api/graphql/input/contractor_entity.rb +14 -14
- data/lib/ecoportal/api/graphql/input/preset_view/update.rb +16 -16
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_field_configuration.rb +21 -21
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page.rb +28 -28
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page_creator_permissions.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/cross_reference.rb +23 -23
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/date.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/image_gallery.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/people.rb +24 -24
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/plain_text.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/rich_text.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/select.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/move_field.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_forces.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_section.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command.rb +251 -251
- data/lib/ecoportal/api/graphql/input.rb +24 -24
- data/lib/ecoportal/api/graphql/logic/connection.rb +34 -34
- data/lib/ecoportal/api/graphql/model/page/task.rb +16 -0
- data/lib/ecoportal/api/graphql/model/page.rb +16 -15
- data/lib/ecoportal/api/graphql/model/person_member.rb +15 -15
- data/lib/ecoportal/api/graphql/model/template.rb +15 -0
- data/lib/ecoportal/api/graphql/model.rb +32 -30
- data/lib/ecoportal/api/graphql/mutation/ai_summary/generate.rb +45 -45
- data/lib/ecoportal/api/graphql/mutation/ai_summary/submit_feedback.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/contractor_entity.rb +14 -14
- data/lib/ecoportal/api/graphql/mutation/page/approve_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/page/batch_update_review_task.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/create_draft.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/delete_draft.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/execute_force_commands.rb +7 -7
- data/lib/ecoportal/api/graphql/mutation/page/execute_workflow_commands.rb +8 -7
- data/lib/ecoportal/api/graphql/mutation/page/publish_draft.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/reject_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/page/restart_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/page/undo_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/preset_view/destroy.rb +34 -35
- data/lib/ecoportal/api/graphql/mutation/register/destroy.rb +35 -35
- data/lib/ecoportal/api/graphql/mutation/smart_fill/generate.rb +36 -36
- data/lib/ecoportal/api/graphql/mutation/smart_fill/submit_feedback.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/smart_fill.rb +13 -13
- data/lib/ecoportal/api/graphql/mutation/template/create_related_page.rb +46 -46
- data/lib/ecoportal/api/graphql/mutation/template/destroy_related_page.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation/template/update_information.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation.rb +20 -20
- data/lib/ecoportal/api/graphql/payload/contractor_entity.rb +14 -14
- data/lib/ecoportal/api/graphql/payload/ok_payload.rb +36 -21
- data/lib/ecoportal/api/graphql/payload/page/draft.rb +26 -13
- data/lib/ecoportal/api/graphql/payload/page/review_task.rb +13 -13
- data/lib/ecoportal/api/graphql/payload/page/review_task_batch.rb +23 -0
- data/lib/ecoportal/api/graphql/payload/page.rb +20 -19
- data/lib/ecoportal/api/graphql/payload/preset_view.rb +15 -11
- data/lib/ecoportal/api/graphql/payload/register.rb +15 -11
- data/lib/ecoportal/api/graphql/payload/template/create_related_page.rb +1 -1
- data/lib/ecoportal/api/graphql/payload/template/destroy_related_page.rb +1 -1
- data/lib/ecoportal/api/graphql/payload/template/update_information.rb +1 -1
- data/lib/ecoportal/api/graphql/payload.rb +23 -23
- data/lib/ecoportal/api/graphql/query/location_structures.rb +57 -57
- data/lib/ecoportal/api/graphql/query/page.rb +45 -45
- data/lib/ecoportal/api/graphql/query/page_with_forces.rb +9 -3
- data/lib/ecoportal/api/graphql/query/pages_workflow_commands.rb +9 -3
- data/lib/ecoportal/api/graphql/query/register_preset_views.rb +78 -78
- data/lib/ecoportal/api/graphql/query.rb +27 -27
- data/lib/ecoportal/api/graphql.rb +9 -5
- data/lib/ecoportal/api/graphql_version.rb +1 -1
- metadata +9 -180
- data/.ai-assistance/bridge/CLAUDE.md +0 -338
- data/.ai-assistance/bridge/archive/.gitkeep +0 -0
- data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.inbox.md +0 -29
- data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.outbox.md +0 -18
- data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.inbox.md +0 -42
- data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.outbox.md +0 -115
- data/.ai-assistance/bridge/context/gemini-review-prompt.txt +0 -48
- data/.ai-assistance/bridge/context/gemini-review-response.md +0 -104
- data/.ai-assistance/bridge/context/project.md +0 -42
- 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 +0 -48
- data/.ai-assistance/bridge/queue/.gitkeep +0 -1
- data/.ai-assistance/capabilities/CLAUDE.md +0 -27
- data/.ai-assistance/capabilities/assumptions-log.md +0 -80
- data/.ai-assistance/capabilities/code.md +0 -47
- data/.ai-assistance/capabilities/connectors.md +0 -37
- data/.ai-assistance/capabilities/cowork.md +0 -55
- data/.ai-assistance/code/OVERVIEW.md +0 -155
- data/.ai-assistance/code/data_fields.md +0 -242
- data/.ai-assistance/code/dependencies.md +0 -151
- data/.ai-assistance/code/diff_as_input.md +0 -234
- data/.ai-assistance/code/diff_pairing_engine.md +0 -243
- data/.ai-assistance/code/diff_service_deep_dive.md +0 -192
- data/.ai-assistance/code/ecoPortal_architecture/00_overview_and_index.md +0 -55
- data/.ai-assistance/code/ecoPortal_architecture/01_terminology_dictionary.md +0 -181
- data/.ai-assistance/code/ecoPortal_architecture/02_data_model.md +0 -192
- data/.ai-assistance/code/ecoPortal_architecture/03_api_layers.md +0 -147
- data/.ai-assistance/code/ecoPortal_architecture/04_graphql_queries_mutations.md +0 -277
- data/.ai-assistance/code/ecoPortal_architecture/05_page_workflows.md +0 -200
- data/.ai-assistance/code/ecoPortal_architecture/06_search_and_filters.md +0 -228
- data/.ai-assistance/code/ecoPortal_architecture/07_data_fields.md +0 -197
- data/.ai-assistance/code/ecoPortal_architecture/08_stages_sections.md +0 -243
- data/.ai-assistance/code/ecoPortal_architecture/09_people_contractors_locations.md +0 -196
- data/.ai-assistance/code/ecoPortal_architecture/10_forces_workflow_builder.md +0 -132
- data/.ai-assistance/code/ecoPortal_architecture/11_integration_gems.md +0 -187
- data/.ai-assistance/code/ecoPortal_architecture/12_ai_documentation_sources_gaps.md +0 -236
- data/.ai-assistance/code/ecoPortal_architecture/13_ai_infrastructure.md +0 -183
- data/.ai-assistance/code/ecoportal_schema_reference.md +0 -240
- data/.ai-assistance/code/filter_contract_matrix.md +0 -177
- data/.ai-assistance/code/graphql_domain_knowledge.md +0 -240
- data/.ai-assistance/code/refactoring/datafield-readwrite-shape-asymmetry.md +0 -71
- data/.ai-assistance/code/refactoring/opportunities.md +0 -251
- data/.ai-assistance/code/schema_analysis.md +0 -321
- data/.ai-assistance/code/search_filters.md +0 -868
- data/.ai-assistance/code/spec_coverage.md +0 -73
- data/.ai-assistance/code/template_diff_pairing_domain.md +0 -175
- data/.ai-assistance/code/workflow-command-guide.md +0 -466
- data/.ai-assistance/code/workflow-space.md +0 -353
- data/.ai-assistance/conventions/CLAUDE.md +0 -30
- data/.ai-assistance/conventions/code-working-tree-protocol.md +0 -199
- data/.ai-assistance/conventions/gitignore-rules.md +0 -42
- data/.ai-assistance/conventions/permission-guidance.md +0 -120
- data/.ai-assistance/integrations/README.md +0 -70
- data/.ai-assistance/integrations/gitkraken-mcp.md +0 -107
- data/.ai-assistance/integrations/gitlab-mcp.md +0 -123
- data/.ai-assistance/integrations/local-git.md +0 -60
- data/.ai-assistance/local_paths.example.md +0 -17
- data/.ai-assistance/projects/TODO.md +0 -118
- data/.ai-assistance/projects/api-v2-to-graphql-migration/DECISIONS.md +0 -168
- data/.ai-assistance/projects/api-v2-to-graphql-migration/INTENT.md +0 -60
- data/.ai-assistance/projects/api-v2-to-graphql-migration/TODO.md +0 -267
- data/.ai-assistance/projects/api-v2-to-graphql-migration/UPSTREAM.md +0 -53
- data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/csv-template-pipeline-design.md +0 -102
- data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/cutover-usecase-gap-audit.md +0 -139
- data/.ai-assistance/projects/compat-layer-audit/COMPAT_AUDIT.md +0 -244
- data/.ai-assistance/projects/dynamic-model-generation/INTENT.md +0 -93
- data/.ai-assistance/projects/eco-helpers-compat/INTENT.md +0 -244
- data/.ai-assistance/projects/eco-helpers-compat/MIGRATION_GUIDE.md +0 -266
- data/.ai-assistance/projects/eco-helpers-compat/TODO.md +0 -86
- data/.ai-assistance/projects/ecoportal-api-v2-doublemodel-review/INTENT.md +0 -101
- data/.ai-assistance/projects/graphql-agent/GAP_ANALYSIS.md +0 -177
- data/.ai-assistance/projects/ooze-graphql-native-migration/DECISIONS.md +0 -161
- data/.ai-assistance/projects/ooze-graphql-native-migration/INTENT.md +0 -125
- data/.ai-assistance/projects/ooze-graphql-native-migration/INVENTORY.md +0 -136
- data/.ai-assistance/projects/ooze-graphql-native-migration/RISKS.md +0 -126
- data/.ai-assistance/projects/ooze-graphql-native-migration/TODO.md +0 -261
- data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-06-30-cutover-workflow-deep-review.md +0 -122
- data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-07-01-forces-via-workflow-commands-miss-rca.md +0 -148
- data/.ai-assistance/projects/page-model/DECISIONS.md +0 -245
- data/.ai-assistance/projects/page-model/TODO.md +0 -190
- data/.ai-assistance/projects/qa-services-delivery/DECISIONS.md +0 -93
- data/.ai-assistance/projects/qa-services-delivery/INTENT.md +0 -76
- data/.ai-assistance/projects/qa-services-delivery/PHASE3-SCOPE.md +0 -115
- data/.ai-assistance/projects/qa-services-delivery/ROADMAP.md +0 -99
- data/.ai-assistance/projects/qa-services-delivery/TODO.md +0 -81
- data/.ai-assistance/projects/search-filter-builder/INTENT.md +0 -107
- data/.ai-assistance/projects/search-filter-builder/TODO.md +0 -131
- data/.ai-assistance/projects/template-automatic-build-maintenance/INTENT.md +0 -87
- data/.ai-assistance/projects/template-automatic-build-maintenance/TODO.md +0 -108
- data/.ai-assistance/projects/template-diff-deploy/INTENT.md +0 -12
- data/.ai-assistance/projects/template-diff-deploy/TODO.md +0 -9
- data/.ai-assistance/projects/template-maintenance/DESIGN.md +0 -134
- data/.ai-assistance/projects/template-maintenance/PHASE0-FINDINGS.md +0 -93
- data/.ai-assistance/projects/template-maintenance/README.md +0 -14
- data/.ai-assistance/projects/template-model-logic/CATEGORY_CATALOG.md +0 -236
- data/.ai-assistance/projects/template-model-logic/CLASSIFICATION_SPIKE.md +0 -243
- data/.ai-assistance/projects/template-model-logic/DESIGN_NOTE.md +0 -154
- data/.ai-assistance/projects/workflow-space/TODO.md +0 -213
- data/.ai-assistance/reinstall-claude-desktop-windows.md +0 -136
- data/.ai-assistance/scripts/CLAUDE.md +0 -150
- data/.ai-assistance/scripts/bridge-inbox-check.sh +0 -75
- data/.ai-assistance/scripts/bridge-init.sh +0 -86
- data/.ai-assistance/scripts/bridge-status.sh +0 -44
- data/.ai-assistance/scripts/capabilities-check.ts +0 -104
- data/.ai-assistance/scripts/check-outbox.sh +0 -43
- data/.ai-assistance/scripts/dep_graph.rb +0 -91
- data/.ai-assistance/scripts/lock-acquire.sh +0 -103
- data/.ai-assistance/scripts/lock-multi.sh +0 -124
- data/.ai-assistance/scripts/lock-queue.sh +0 -94
- data/.ai-assistance/scripts/setup-mcps.test.ts +0 -188
- data/.ai-assistance/scripts/setup-mcps.ts +0 -234
- data/.ai-assistance/scripts/task-complete.ts +0 -74
- data/.ai-assistance/scripts/task-create.ts +0 -75
- data/.ai-assistance/scripts/task-read.ts +0 -125
- data/.ai-assistance/scripts/token-logger.js +0 -220
- data/.ai-assistance/scripts/token-report.ts +0 -158
- data/.ai-assistance/scripts/token-session-start.js +0 -66
- data/.ai-assistance/skills/ai-instructions/SKILL.md +0 -48
- data/.ai-assistance/skills/code-specs/SKILL.md +0 -69
- data/.ai-assistance/skills/corporate-policies/SKILL.md +0 -201
- data/.ai-assistance/skills/dep-graph/SKILL.md +0 -139
- data/.ai-assistance/skills/ep-ai-manager/SKILL.md +0 -417
- data/.ai-assistance/skills/gemini-assist/SKILL.md +0 -63
- data/.ai-assistance/skills/gemini-assist/gemini-mcp-server.js +0 -205
- data/.ai-assistance/skills/gemini-assist/gemini_ask.py +0 -1
- data/.ai-assistance/skills/gemini-assist/gemini_ask.rb +0 -240
- data/.ai-assistance/skills/gemini-assist/prompts/cycle_end_review.txt +0 -25
- data/.ai-assistance/skills/graphql-schema-analysis/SKILL.md +0 -261
- data/.ai-assistance/skills/procedural-memory/SKILL.md +0 -319
- data/.ai-assistance/skills/project-cycle/SKILL.md +0 -177
- data/.ai-assistance/skills/project-self-docs/SKILL.md +0 -181
- data/.ai-assistance/skills/project-self-docs/scripts/self_docs_scan.py +0 -378
- data/.ai-assistance/skills/refactor/SKILL.md +0 -62
- data/.ai-assistance/skills/rubocop/SKILL.md +0 -93
- data/.ai-assistance/skills/ruby-scripting/SKILL.md +0 -215
- data/.ai-assistance/skills/spec-generation/SKILL.md +0 -72
- data/.ai-assistance/standards-version.json +0 -22
- data/.ai-assistance/token-budget.json +0 -32
- data/.ai-assistance/version.json +0 -39
- data/.claude/settings.json +0 -150
- data/.env.example +0 -18
- data/.gitattributes +0 -15
- data/.gitignore +0 -37
- data/.gitlab-ci.yml +0 -45
- data/.markdownlint.json +0 -4
- data/.rspec +0 -3
- data/.rubocop.yml +0 -121
- data/.ruby-version +0 -1
- data/.yardopts +0 -10
- data/CLAUDE.md +0 -243
- data/Gemfile +0 -30
- data/Rakefile +0 -90
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/setup.ps1 +0 -6
- data/docs/self-docs/ARCHITECTURE.md +0 -88
- data/docs/self-docs/CHANGES.jsonl +0 -12
- data/docs/self-docs/COMPLIANCE.md +0 -79
- data/docs/self-docs/CONVENTIONS.md +0 -74
- data/docs/self-docs/INTEGRATIONS.md +0 -65
- data/docs/self-docs/OPERATIONS.md +0 -76
- data/docs/self-docs/OVERVIEW.md +0 -64
- data/docs/self-docs/STATUS.md +0 -73
- data/docs/self-docs/self-docs-index.json +0 -51
- data/docs/worklog.md +0 -977
- data/ecoportal-api-graphql.gemspec +0 -40
- data/scripts/auto-worker-scheduler.sh +0 -386
- data/tests/actions_get.rb +0 -7
- data/tests/contractor_entities_get.rb +0 -20
- data/tests/contractor_entity_create.rb +0 -19
- data/tests/contractor_entity_udpate.rb +0 -20
- data/tests/dump_page_model.rb +0 -74
- data/tests/dump_template_model.rb +0 -90
- data/tests/loc_structure_get.rb +0 -10
- data/tests/loc_structure_update.rb +0 -51
- data/tests/loc_structures_get.rb +0 -15
- data/tests/local_libs.rb +0 -20
- data/tests/validate_queries.rb +0 -127
|
@@ -1,44 +0,0 @@
|
|
|
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"
|
|
@@ -1,104 +0,0 @@
|
|
|
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`);
|
|
@@ -1,43 +0,0 @@
|
|
|
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"
|
|
@@ -1,91 +0,0 @@
|
|
|
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
|
|
@@ -1,103 +0,0 @@
|
|
|
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
|
|
@@ -1,124 +0,0 @@
|
|
|
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
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# lock-queue.sh
|
|
3
|
-
# Drop a priority turn-request into the bridge queue of one or more repos.
|
|
4
|
-
# Used when a lock is held and this agent wants to wait with declared priority.
|
|
5
|
-
#
|
|
6
|
-
# Usage:
|
|
7
|
-
# bash lock-queue.sh \
|
|
8
|
-
# --agent cowork \
|
|
9
|
-
# --user oscar \
|
|
10
|
-
# --intent "Review auth changes" \
|
|
11
|
-
# --priority high \
|
|
12
|
-
# --target-project ep-claude-aws-platform \
|
|
13
|
-
# [--repo /path/to/repo1 --repo /path/to/repo2 ...] \
|
|
14
|
-
# [--expiry-minutes 90]
|
|
15
|
-
#
|
|
16
|
-
# Writes: .ai-assistance/bridge/queue/<priority>-<ts>-<id>.json in each repo
|
|
17
|
-
# Exit 0: request(s) queued. Prints JSON with queue file paths.
|
|
18
|
-
|
|
19
|
-
set -euo pipefail
|
|
20
|
-
|
|
21
|
-
AGENT="" USER_SLUG="" INTENT="" PRIORITY="medium" TARGET_PROJECT=""
|
|
22
|
-
EXPIRY_MINUTES=90 ESTIMATED_WAIT_MINUTES=""
|
|
23
|
-
REPOS=()
|
|
24
|
-
|
|
25
|
-
while [[ $# -gt 0 ]]; do
|
|
26
|
-
case "$1" in
|
|
27
|
-
--agent) AGENT="$2"; shift 2 ;;
|
|
28
|
-
--user) USER_SLUG="$2"; shift 2 ;;
|
|
29
|
-
--intent) INTENT="$2"; shift 2 ;;
|
|
30
|
-
--priority) PRIORITY="$2"; shift 2 ;;
|
|
31
|
-
--target-project) TARGET_PROJECT="$2"; shift 2 ;;
|
|
32
|
-
--estimated-wait) ESTIMATED_WAIT_MINUTES="$2"; shift 2 ;;
|
|
33
|
-
--repo) REPOS+=("$2"); shift 2 ;;
|
|
34
|
-
--expiry-minutes) EXPIRY_MINUTES="$2"; shift 2 ;;
|
|
35
|
-
*) echo "{\"queued\":false,\"reason\":\"unknown argument: $1\"}"; exit 1 ;;
|
|
36
|
-
esac
|
|
37
|
-
done
|
|
38
|
-
|
|
39
|
-
[[ -z "$AGENT" || -z "$USER_SLUG" || -z "$INTENT" ]] && {
|
|
40
|
-
echo '{"queued":false,"reason":"missing --agent, --user, or --intent"}'; exit 1; }
|
|
41
|
-
|
|
42
|
-
# Default: current repo
|
|
43
|
-
[[ ${#REPOS[@]} -eq 0 ]] && REPOS=("$(pwd)")
|
|
44
|
-
|
|
45
|
-
NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
46
|
-
EXPIRES=$(date -u -d "+${EXPIRY_MINUTES} minutes" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
|
|
47
|
-
|| date -u -v +${EXPIRY_MINUTES}M +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
|
|
48
|
-
|| echo "unknown")
|
|
49
|
-
REQUEST_ID="req-$(date +%s)-$$"
|
|
50
|
-
|
|
51
|
-
# Priority sort prefix: 1=critical 2=high 3=medium 4=low
|
|
52
|
-
case "$PRIORITY" in
|
|
53
|
-
critical) PFX="1" ;; high) PFX="2" ;; medium) PFX="3" ;; low) PFX="4" ;; *) PFX="3" ;;
|
|
54
|
-
esac
|
|
55
|
-
|
|
56
|
-
QUEUED_FILES=()
|
|
57
|
-
|
|
58
|
-
# estimated_wait: read from the current LOCK file if present (it may contain estimated_release)
|
|
59
|
-
get_estimated_release() {
|
|
60
|
-
local lock="$1/.ai-assistance/bridge/LOCK"
|
|
61
|
-
[[ -f "$lock" ]] || { echo "unknown"; return; }
|
|
62
|
-
grep "estimated_release:" "$lock" 2>/dev/null | sed 's/.*estimated_release://' | tr -d ' ' | head -1 || echo "unknown"
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
for repo in "${REPOS[@]}"; do
|
|
66
|
-
QUEUE_DIR="$repo/.ai-assistance/bridge/queue"
|
|
67
|
-
[[ -d "$QUEUE_DIR" ]] || mkdir -p "$QUEUE_DIR"
|
|
68
|
-
QFILE="$QUEUE_DIR/${PFX}-${PRIORITY}-$(date +%s%3N)-${REQUEST_ID}.json"
|
|
69
|
-
|
|
70
|
-
# Read current lock's estimated release so queue entry shows the expected wait
|
|
71
|
-
LOCK_ESTIMATED_RELEASE=$(get_estimated_release "$repo")
|
|
72
|
-
EFFECTIVE_WAIT="${ESTIMATED_WAIT_MINUTES:-unknown}"
|
|
73
|
-
|
|
74
|
-
cat > "$QFILE" << JSON
|
|
75
|
-
{
|
|
76
|
-
"request_id": "$REQUEST_ID",
|
|
77
|
-
"agent": "$AGENT",
|
|
78
|
-
"user": "$USER_SLUG",
|
|
79
|
-
"intent": "$INTENT",
|
|
80
|
-
"priority": "$PRIORITY",
|
|
81
|
-
"target_project": "$TARGET_PROJECT",
|
|
82
|
-
"repo": "$repo",
|
|
83
|
-
"requested_at": "$NOW",
|
|
84
|
-
"expires_at": "$EXPIRES",
|
|
85
|
-
"estimated_wait_minutes": "$EFFECTIVE_WAIT",
|
|
86
|
-
"current_lock_estimated_release": "$LOCK_ESTIMATED_RELEASE"
|
|
87
|
-
}
|
|
88
|
-
JSON
|
|
89
|
-
QUEUED_FILES+=("$QFILE")
|
|
90
|
-
done
|
|
91
|
-
|
|
92
|
-
# Report
|
|
93
|
-
FILES_JSON=$(printf '"%s",' "${QUEUED_FILES[@]}" | sed 's/,$//')
|
|
94
|
-
echo "{\"queued\":true,\"request_id\":\"$REQUEST_ID\",\"priority\":\"$PRIORITY\",\"files\":[$FILES_JSON]}"
|