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,188 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx ts-node
|
|
2
|
-
/**
|
|
3
|
-
* setup-mcps.test.ts
|
|
4
|
-
* Unit tests for the pure logic in setup-mcps.ts.
|
|
5
|
-
* No external dependencies — uses Node's assert module.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* npx ts-node .ai-assistance/scripts/setup-mcps.test.ts
|
|
9
|
-
*
|
|
10
|
-
* Tests cover:
|
|
11
|
-
* - parseEnvFile: key=value parsing, comments, blank lines, edge cases
|
|
12
|
-
* - buildMcpEntries: correct shape for GitKraken + GitLab entries
|
|
13
|
-
* - mergeConfig: merges, preserves unrelated keys, overwrites same-named entries
|
|
14
|
-
* - getConfigPath: returns a string containing "Claude" on current platform
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import assert from "assert";
|
|
18
|
-
import { parseEnvFile, buildMcpEntries, mergeConfig, getConfigPath } from "./setup-mcps";
|
|
19
|
-
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
// Test runner
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
let passed = 0;
|
|
25
|
-
let failed = 0;
|
|
26
|
-
|
|
27
|
-
function test(name: string, fn: () => void) {
|
|
28
|
-
try {
|
|
29
|
-
fn();
|
|
30
|
-
console.log(` ✓ ${name}`);
|
|
31
|
-
passed++;
|
|
32
|
-
} catch (err: unknown) {
|
|
33
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
34
|
-
console.log(` ✗ ${name}`);
|
|
35
|
-
console.log(` ${message}`);
|
|
36
|
-
failed++;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
// parseEnvFile
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
console.log("\nparseEnvFile");
|
|
45
|
-
|
|
46
|
-
test("parses simple key=value pairs", () => {
|
|
47
|
-
const result = parseEnvFile("FOO=bar\nBAZ=qux");
|
|
48
|
-
assert.strictEqual(result["FOO"], "bar");
|
|
49
|
-
assert.strictEqual(result["BAZ"], "qux");
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("ignores comment lines", () => {
|
|
53
|
-
const result = parseEnvFile("# this is a comment\nFOO=bar");
|
|
54
|
-
assert.strictEqual(Object.keys(result).length, 1);
|
|
55
|
-
assert.strictEqual(result["FOO"], "bar");
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("ignores blank lines", () => {
|
|
59
|
-
const result = parseEnvFile("\n\nFOO=bar\n\n");
|
|
60
|
-
assert.strictEqual(Object.keys(result).length, 1);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("handles values containing = signs", () => {
|
|
64
|
-
const result = parseEnvFile("URL=https://example.com?foo=bar");
|
|
65
|
-
assert.strictEqual(result["URL"], "https://example.com?foo=bar");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("returns empty object for empty input", () => {
|
|
69
|
-
const result = parseEnvFile("");
|
|
70
|
-
assert.deepStrictEqual(result, {});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("returns empty object for comment-only input", () => {
|
|
74
|
-
const result = parseEnvFile("# just a comment\n# another");
|
|
75
|
-
assert.deepStrictEqual(result, {});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// ---------------------------------------------------------------------------
|
|
79
|
-
// buildMcpEntries
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
|
-
|
|
82
|
-
console.log("\nbuildMcpEntries");
|
|
83
|
-
|
|
84
|
-
test("includes gitkraken entry with correct command", () => {
|
|
85
|
-
const entries = buildMcpEntries("tok", "https://gitlab.example.com");
|
|
86
|
-
const gk = entries["gitkraken"] as Record<string, unknown>;
|
|
87
|
-
assert.strictEqual(gk.command, "gk");
|
|
88
|
-
assert.deepStrictEqual(gk.args, ["mcp"]);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("includes gitlab-mr entry with correct command", () => {
|
|
92
|
-
const entries = buildMcpEntries("tok", "https://gitlab.example.com");
|
|
93
|
-
const gl = entries["gitlab-mr"] as Record<string, unknown>;
|
|
94
|
-
assert.strictEqual(gl.command, "npx");
|
|
95
|
-
assert.deepStrictEqual(gl.args, ["-y", "@kopfrechner/gitlab-mr-mcp"]);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("injects GITLAB_TOKEN into gitlab-mr env", () => {
|
|
99
|
-
const entries = buildMcpEntries("my-token", "https://gitlab.example.com");
|
|
100
|
-
const gl = entries["gitlab-mr"] as { env: Record<string, string> };
|
|
101
|
-
assert.strictEqual(gl.env["GITLAB_TOKEN"], "my-token");
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("injects GITLAB_URL into gitlab-mr env", () => {
|
|
105
|
-
const entries = buildMcpEntries("tok", "https://gitlab.example.com");
|
|
106
|
-
const gl = entries["gitlab-mr"] as { env: Record<string, string> };
|
|
107
|
-
assert.strictEqual(gl.env["GITLAB_URL"], "https://gitlab.example.com");
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test("returns exactly two top-level keys", () => {
|
|
111
|
-
const entries = buildMcpEntries("tok", "https://gitlab.example.com");
|
|
112
|
-
assert.strictEqual(Object.keys(entries).length, 2);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// ---------------------------------------------------------------------------
|
|
116
|
-
// mergeConfig
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
|
|
119
|
-
console.log("\nmergeConfig");
|
|
120
|
-
|
|
121
|
-
test("adds mcpServers to empty config", () => {
|
|
122
|
-
const result = mergeConfig({}, { foo: { command: "foo" } });
|
|
123
|
-
assert.ok(result.mcpServers);
|
|
124
|
-
assert.ok((result.mcpServers as Record<string, unknown>)["foo"]);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("preserves existing top-level keys", () => {
|
|
128
|
-
const result = mergeConfig({ theme: "dark" }, { foo: {} });
|
|
129
|
-
assert.strictEqual(result.theme, "dark");
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test("preserves existing mcpServers entries not in newEntries", () => {
|
|
133
|
-
const existing = { mcpServers: { existing: { command: "old" } } };
|
|
134
|
-
const result = mergeConfig(existing, { newone: { command: "new" } });
|
|
135
|
-
const servers = result.mcpServers as Record<string, unknown>;
|
|
136
|
-
assert.ok(servers["existing"]);
|
|
137
|
-
assert.ok(servers["newone"]);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("overwrites same-named mcpServers entries", () => {
|
|
141
|
-
const existing = {
|
|
142
|
-
mcpServers: { "gitlab-mr": { command: "old", env: { GITLAB_TOKEN: "old-tok" } } },
|
|
143
|
-
};
|
|
144
|
-
const result = mergeConfig(existing, {
|
|
145
|
-
"gitlab-mr": { command: "npx", env: { GITLAB_TOKEN: "new-tok" } },
|
|
146
|
-
});
|
|
147
|
-
const gl = (result.mcpServers as Record<string, unknown>)["gitlab-mr"] as {
|
|
148
|
-
env: Record<string, string>;
|
|
149
|
-
};
|
|
150
|
-
assert.strictEqual(gl.env["GITLAB_TOKEN"], "new-tok");
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("handles existing config with no mcpServers key", () => {
|
|
154
|
-
const result = mergeConfig({ theme: "light" }, { foo: {} });
|
|
155
|
-
assert.ok((result.mcpServers as Record<string, unknown>)["foo"]);
|
|
156
|
-
assert.strictEqual(result.theme, "light");
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// ---------------------------------------------------------------------------
|
|
160
|
-
// getConfigPath
|
|
161
|
-
// ---------------------------------------------------------------------------
|
|
162
|
-
|
|
163
|
-
console.log("\ngetConfigPath");
|
|
164
|
-
|
|
165
|
-
test("returns a non-empty string", () => {
|
|
166
|
-
const p = getConfigPath();
|
|
167
|
-
assert.ok(typeof p === "string" && p.length > 0);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test("path contains 'Claude'", () => {
|
|
171
|
-
const p = getConfigPath();
|
|
172
|
-
assert.ok(p.includes("Claude"), `Expected path to contain 'Claude', got: ${p}`);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test("path ends with claude_desktop_config.json", () => {
|
|
176
|
-
const p = getConfigPath();
|
|
177
|
-
assert.ok(
|
|
178
|
-
p.endsWith("claude_desktop_config.json"),
|
|
179
|
-
`Expected path to end with filename, got: ${p}`
|
|
180
|
-
);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// ---------------------------------------------------------------------------
|
|
184
|
-
// Summary
|
|
185
|
-
// ---------------------------------------------------------------------------
|
|
186
|
-
|
|
187
|
-
console.log(`\n${passed + failed} tests: ${passed} passed, ${failed} failed\n`);
|
|
188
|
-
if (failed > 0) process.exit(1);
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx ts-node
|
|
2
|
-
/**
|
|
3
|
-
* setup-mcps.ts
|
|
4
|
-
* One-time setup: writes GitKraken and GitLab MCP entries into Claude Desktop's
|
|
5
|
-
* global config (claude_desktop_config.json). Safe to re-run — merges into any
|
|
6
|
-
* existing config without overwriting unrelated entries.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* npx ts-node .ai-assistance/scripts/setup-mcps.ts
|
|
10
|
-
*
|
|
11
|
-
* What it does:
|
|
12
|
-
* 1. Detects OS and locates claude_desktop_config.json
|
|
13
|
-
* 2. Reads GitLab credentials from .env as defaults (no re-entry needed)
|
|
14
|
-
* 3. Prompts to confirm or override each value
|
|
15
|
-
* 4. Backs up the existing config before writing
|
|
16
|
-
* 5. Merges GitKraken + GitLab MCP entries and writes the config
|
|
17
|
-
* 6. Prints verification steps
|
|
18
|
-
*
|
|
19
|
-
* Requirements:
|
|
20
|
-
* - GitKraken CLI (gk) installed + authenticated:
|
|
21
|
-
* https://www.gitkraken.com/cli
|
|
22
|
-
* Run: gk auth login
|
|
23
|
-
* - GitLab PAT with scopes: read_api, read_repository, create_runner, ai_features
|
|
24
|
-
* https://gitlab.ecoportal.co.nz/-/user_settings/personal_access_tokens
|
|
25
|
-
* - Node.js + ts-node:
|
|
26
|
-
* npm install -g ts-node typescript
|
|
27
|
-
*
|
|
28
|
-
* Expiry note:
|
|
29
|
-
* Current PAT expires April 2027. Token has rotation enabled — rotate before
|
|
30
|
-
* expiry and re-run this script with the new token.
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
import {
|
|
34
|
-
readFileSync,
|
|
35
|
-
writeFileSync,
|
|
36
|
-
existsSync,
|
|
37
|
-
copyFileSync,
|
|
38
|
-
mkdirSync,
|
|
39
|
-
} from "fs";
|
|
40
|
-
import { join, dirname } from "path";
|
|
41
|
-
import { createInterface } from "readline";
|
|
42
|
-
import { homedir, platform } from "os";
|
|
43
|
-
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
// Pure logic (exported for testing)
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
/** Detect OS-appropriate path to claude_desktop_config.json */
|
|
49
|
-
export function getConfigPath(): string {
|
|
50
|
-
const p = platform();
|
|
51
|
-
if (p === "win32") {
|
|
52
|
-
const appData =
|
|
53
|
-
process.env.APPDATA ?? join(homedir(), "AppData", "Roaming");
|
|
54
|
-
return join(appData, "Claude", "claude_desktop_config.json");
|
|
55
|
-
} else if (p === "darwin") {
|
|
56
|
-
return join(
|
|
57
|
-
homedir(),
|
|
58
|
-
"Library",
|
|
59
|
-
"Application Support",
|
|
60
|
-
"Claude",
|
|
61
|
-
"claude_desktop_config.json"
|
|
62
|
-
);
|
|
63
|
-
} else {
|
|
64
|
-
// Linux fallback
|
|
65
|
-
return join(homedir(), ".config", "Claude", "claude_desktop_config.json");
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Parse KEY=VALUE lines from a .env file. Ignores comments and blank lines. */
|
|
70
|
-
export function parseEnvFile(content: string): Record<string, string> {
|
|
71
|
-
const env: Record<string, string> = {};
|
|
72
|
-
for (const line of content.split("\n")) {
|
|
73
|
-
const trimmed = line.trim();
|
|
74
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
75
|
-
const eq = trimmed.indexOf("=");
|
|
76
|
-
if (eq === -1) continue;
|
|
77
|
-
const key = trimmed.slice(0, eq).trim();
|
|
78
|
-
const value = trimmed.slice(eq + 1).trim();
|
|
79
|
-
if (key) env[key] = value;
|
|
80
|
-
}
|
|
81
|
-
return env;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** Build the mcpServers block for GitKraken + GitLab. */
|
|
85
|
-
export function buildMcpEntries(
|
|
86
|
-
gitlabToken: string,
|
|
87
|
-
gitlabUrl: string
|
|
88
|
-
): Record<string, unknown> {
|
|
89
|
-
return {
|
|
90
|
-
gitkraken: {
|
|
91
|
-
command: "gk",
|
|
92
|
-
args: ["mcp"],
|
|
93
|
-
},
|
|
94
|
-
"gitlab-mr": {
|
|
95
|
-
command: "npx",
|
|
96
|
-
args: ["-y", "@kopfrechner/gitlab-mr-mcp"],
|
|
97
|
-
env: {
|
|
98
|
-
GITLAB_TOKEN: gitlabToken,
|
|
99
|
-
GITLAB_URL: gitlabUrl,
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Merge new MCP entries into an existing config object.
|
|
107
|
-
* Existing mcpServers entries not in newEntries are preserved.
|
|
108
|
-
* Entries in newEntries overwrite same-named existing entries.
|
|
109
|
-
*/
|
|
110
|
-
export function mergeConfig(
|
|
111
|
-
existing: Record<string, unknown>,
|
|
112
|
-
newEntries: Record<string, unknown>
|
|
113
|
-
): Record<string, unknown> {
|
|
114
|
-
return {
|
|
115
|
-
...existing,
|
|
116
|
-
mcpServers: {
|
|
117
|
-
...((existing.mcpServers as Record<string, unknown>) ?? {}),
|
|
118
|
-
...newEntries,
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ---------------------------------------------------------------------------
|
|
124
|
-
// I/O helpers
|
|
125
|
-
// ---------------------------------------------------------------------------
|
|
126
|
-
|
|
127
|
-
function ask(question: string): Promise<string> {
|
|
128
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
129
|
-
return new Promise((resolve) => {
|
|
130
|
-
rl.question(question, (answer) => {
|
|
131
|
-
rl.close();
|
|
132
|
-
resolve(answer.trim());
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function loadEnvDefaults(repoRoot: string): Record<string, string> {
|
|
138
|
-
const envPath = join(repoRoot, ".env");
|
|
139
|
-
if (!existsSync(envPath)) return {};
|
|
140
|
-
return parseEnvFile(readFileSync(envPath, "utf8"));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ---------------------------------------------------------------------------
|
|
144
|
-
// Main
|
|
145
|
-
// ---------------------------------------------------------------------------
|
|
146
|
-
|
|
147
|
-
async function main() {
|
|
148
|
-
// Resolve repo root relative to this script's location
|
|
149
|
-
const scriptDir = dirname(
|
|
150
|
-
process.argv[1]
|
|
151
|
-
);
|
|
152
|
-
const repoRoot = join(scriptDir, "../..");
|
|
153
|
-
const configPath = getConfigPath();
|
|
154
|
-
const env = loadEnvDefaults(repoRoot);
|
|
155
|
-
|
|
156
|
-
console.log("\n🔧 Claude Desktop MCP Setup");
|
|
157
|
-
console.log("============================");
|
|
158
|
-
console.log(`Config: ${configPath}\n`);
|
|
159
|
-
|
|
160
|
-
// Load existing config
|
|
161
|
-
let existing: Record<string, unknown> = {};
|
|
162
|
-
if (existsSync(configPath)) {
|
|
163
|
-
existing = JSON.parse(readFileSync(configPath, "utf8"));
|
|
164
|
-
const count = Object.keys(
|
|
165
|
-
(existing.mcpServers as Record<string, unknown>) ?? {}
|
|
166
|
-
).length;
|
|
167
|
-
console.log(`Found existing config with ${count} MCP server(s) — will merge.\n`);
|
|
168
|
-
} else {
|
|
169
|
-
console.log("No existing config — will create fresh.\n");
|
|
170
|
-
mkdirSync(dirname(configPath), { recursive: true });
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// --- GitLab PAT ---
|
|
174
|
-
const defaultToken = env["GITLAB_TOKEN"] ?? "";
|
|
175
|
-
const tokenHint = defaultToken
|
|
176
|
-
? `from .env, ends …${defaultToken.slice(-6)}`
|
|
177
|
-
: "required";
|
|
178
|
-
const tokenInput = await ask(
|
|
179
|
-
`GitLab PAT [${tokenHint}] (press Enter to keep): `
|
|
180
|
-
);
|
|
181
|
-
const gitlabToken = tokenInput || defaultToken;
|
|
182
|
-
|
|
183
|
-
if (!gitlabToken) {
|
|
184
|
-
console.error(
|
|
185
|
-
"\n❌ GitLab PAT is required.\n" +
|
|
186
|
-
" Create one at: https://gitlab.ecoportal.co.nz/-/user_settings/personal_access_tokens\n" +
|
|
187
|
-
" Required scopes: read_api, read_repository, create_runner, ai_features\n" +
|
|
188
|
-
" Enable token rotation. Current expiry: April 2027.\n"
|
|
189
|
-
);
|
|
190
|
-
process.exit(1);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// --- GitLab URL ---
|
|
194
|
-
const defaultUrl =
|
|
195
|
-
env["GITLAB_URL"] ?? "https://gitlab.ecoportal.co.nz";
|
|
196
|
-
const urlInput = await ask(`GitLab URL [${defaultUrl}]: `);
|
|
197
|
-
const gitlabUrl = urlInput || defaultUrl;
|
|
198
|
-
|
|
199
|
-
// --- Build + merge ---
|
|
200
|
-
const newEntries = buildMcpEntries(gitlabToken, gitlabUrl);
|
|
201
|
-
const merged = mergeConfig(existing, newEntries);
|
|
202
|
-
|
|
203
|
-
// --- Backup ---
|
|
204
|
-
if (existsSync(configPath)) {
|
|
205
|
-
const backupPath = configPath + ".backup";
|
|
206
|
-
copyFileSync(configPath, backupPath);
|
|
207
|
-
console.log(`\n✓ Backed up to ${backupPath}`);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// --- Write ---
|
|
211
|
-
writeFileSync(configPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
212
|
-
console.log(`✓ Written to ${configPath}`);
|
|
213
|
-
|
|
214
|
-
console.log(`
|
|
215
|
-
✅ Done! Next steps:
|
|
216
|
-
|
|
217
|
-
1. Restart Claude Desktop (fully quit and reopen)
|
|
218
|
-
|
|
219
|
-
2. Verify GitKraken — ask Claude:
|
|
220
|
-
"What branch am I on in ecoportal-api-graphql?"
|
|
221
|
-
First use opens a browser for GitKraken OAuth — approve it.
|
|
222
|
-
|
|
223
|
-
3. Verify GitLab — ask Claude:
|
|
224
|
-
"List open merge requests in ecoportal-api-graphql"
|
|
225
|
-
|
|
226
|
-
Note: if GitKraken (gk) isn't installed yet:
|
|
227
|
-
https://www.gitkraken.com/cli → gk auth login
|
|
228
|
-
`);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
main().catch((err) => {
|
|
232
|
-
console.error("\n❌ Setup failed:", err.message);
|
|
233
|
-
process.exit(1);
|
|
234
|
-
});
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx ts-node
|
|
2
|
-
/**
|
|
3
|
-
* task-complete.ts
|
|
4
|
-
* Writes an outbox file and archives the inbox+outbox pair.
|
|
5
|
-
* Called by Code after finishing a bridge task.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* npx ts-node .ai-assistance/scripts/task-complete.ts \
|
|
9
|
-
* --task oscar-a3f2b1c-list-open-mrs.md \
|
|
10
|
-
* --status done \
|
|
11
|
-
* --result "Branch: feature/auth. Open MRs: 3 ..." \
|
|
12
|
-
* --notes "GitLab plugin authenticated OK"
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { writeFileSync, renameSync, readFileSync, mkdirSync, existsSync } from "fs";
|
|
16
|
-
import { join, dirname } from "path";
|
|
17
|
-
|
|
18
|
-
const args = process.argv.slice(2);
|
|
19
|
-
const get = (flag: string, required = true): string | undefined => {
|
|
20
|
-
const i = args.indexOf(flag);
|
|
21
|
-
if (i === -1 || i + 1 >= args.length) {
|
|
22
|
-
if (required) { console.error(`Missing required argument: ${flag}`); process.exit(1); }
|
|
23
|
-
return undefined;
|
|
24
|
-
}
|
|
25
|
-
return args[i + 1];
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const taskFile = get("--task")!;
|
|
29
|
-
const status = (get("--status")! as string).toUpperCase(); // DONE or FAILED
|
|
30
|
-
const result = get("--result")!;
|
|
31
|
-
const notes = get("--notes", false) ?? "";
|
|
32
|
-
|
|
33
|
-
const scriptDir = dirname(process.argv[1]);
|
|
34
|
-
const bridgeDir = join(scriptDir, "../bridge");
|
|
35
|
-
const inboxPath = join(bridgeDir, "inbox", taskFile);
|
|
36
|
-
const outboxDir = join(bridgeDir, "outbox");
|
|
37
|
-
const archiveDir = join(bridgeDir, "archive");
|
|
38
|
-
|
|
39
|
-
if (!existsSync(inboxPath)) {
|
|
40
|
-
console.error(`Inbox task not found: ${inboxPath}`);
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
mkdirSync(outboxDir, { recursive: true });
|
|
45
|
-
mkdirSync(archiveDir, { recursive: true });
|
|
46
|
-
|
|
47
|
-
// Read title from inbox
|
|
48
|
-
const inboxContent = readFileSync(inboxPath, "utf8");
|
|
49
|
-
const titleMatch = inboxContent.match(/^# TASK: (.+)$/m);
|
|
50
|
-
const title = titleMatch?.[1] ?? taskFile;
|
|
51
|
-
|
|
52
|
-
// Write outbox
|
|
53
|
-
const timestamp = new Date().toISOString();
|
|
54
|
-
const outboxContent = `# TASK: ${title}
|
|
55
|
-
STATUS: ${status}
|
|
56
|
-
COMPLETED: ${timestamp}
|
|
57
|
-
FROM: code
|
|
58
|
-
TO: cowork
|
|
59
|
-
|
|
60
|
-
## Result
|
|
61
|
-
${result}
|
|
62
|
-
|
|
63
|
-
## Notes
|
|
64
|
-
${notes || "(none)"}
|
|
65
|
-
`;
|
|
66
|
-
|
|
67
|
-
const outboxPath = join(outboxDir, taskFile);
|
|
68
|
-
writeFileSync(outboxPath, outboxContent, "utf8");
|
|
69
|
-
|
|
70
|
-
// Archive both files
|
|
71
|
-
renameSync(inboxPath, join(archiveDir, `inbox-${taskFile}`));
|
|
72
|
-
renameSync(outboxPath, join(archiveDir, `outbox-${taskFile}`));
|
|
73
|
-
|
|
74
|
-
console.log(JSON.stringify({ archived: taskFile, status, timestamp }));
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx ts-node
|
|
2
|
-
/**
|
|
3
|
-
* task-create.ts
|
|
4
|
-
* Creates a new bridge inbox task file with correct naming and format.
|
|
5
|
-
* Keeps token usage low — agents call this instead of composing files manually.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* npx ts-node .ai-assistance/scripts/task-create.ts \
|
|
9
|
-
* --title "List open MRs" \
|
|
10
|
-
* --request "Run git branch --show-current, then list open MRs via GitLab plugin" \
|
|
11
|
-
* --expected "Branch name + MR table" \
|
|
12
|
-
* --from cowork \
|
|
13
|
-
* --to code \
|
|
14
|
-
* --user oscar
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { writeFileSync, mkdirSync } from "fs";
|
|
18
|
-
import { join, dirname } from "path";
|
|
19
|
-
import { randomUUID } from "crypto";
|
|
20
|
-
|
|
21
|
-
// --- Parse args ---
|
|
22
|
-
const args = process.argv.slice(2);
|
|
23
|
-
const get = (flag: string): string => {
|
|
24
|
-
const i = args.indexOf(flag);
|
|
25
|
-
if (i === -1 || i + 1 >= args.length) {
|
|
26
|
-
console.error(`Missing required argument: ${flag}`);
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
return args[i + 1];
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const title = get("--title");
|
|
33
|
-
const request = get("--request");
|
|
34
|
-
const expected = get("--expected");
|
|
35
|
-
const from = get("--from");
|
|
36
|
-
const to = get("--to");
|
|
37
|
-
const user = get("--user");
|
|
38
|
-
const context = args.includes("--context") ? get("--context") : "";
|
|
39
|
-
|
|
40
|
-
// --- Generate filename ---
|
|
41
|
-
const uuid7 = randomUUID().replace(/-/g, "").slice(0, 7);
|
|
42
|
-
const slug = title
|
|
43
|
-
.toLowerCase()
|
|
44
|
-
.replace(/[^a-z0-9\s-]/g, "")
|
|
45
|
-
.trim()
|
|
46
|
-
.replace(/\s+/g, "-")
|
|
47
|
-
.slice(0, 40);
|
|
48
|
-
const filename = `${user}-${uuid7}-${slug}.md`;
|
|
49
|
-
|
|
50
|
-
// --- Build file content ---
|
|
51
|
-
const timestamp = new Date().toISOString();
|
|
52
|
-
const content = `# TASK: ${title}
|
|
53
|
-
STATUS: PENDING
|
|
54
|
-
CREATED: ${timestamp}
|
|
55
|
-
FROM: ${from}
|
|
56
|
-
TO: ${to}
|
|
57
|
-
|
|
58
|
-
## Context
|
|
59
|
-
${context || "(none provided)"}
|
|
60
|
-
|
|
61
|
-
## Request
|
|
62
|
-
${request}
|
|
63
|
-
|
|
64
|
-
## Expected output
|
|
65
|
-
${expected}
|
|
66
|
-
`;
|
|
67
|
-
|
|
68
|
-
// --- Write file ---
|
|
69
|
-
const scriptDir = dirname(process.argv[1]);
|
|
70
|
-
const inboxDir = join(scriptDir, "../bridge/inbox");
|
|
71
|
-
mkdirSync(inboxDir, { recursive: true });
|
|
72
|
-
const filePath = join(inboxDir, filename);
|
|
73
|
-
writeFileSync(filePath, content, "utf8");
|
|
74
|
-
|
|
75
|
-
console.log(JSON.stringify({ created: filename, path: filePath }));
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx ts-node
|
|
2
|
-
/**
|
|
3
|
-
* task-read.ts
|
|
4
|
-
* Parse a bridge task file (inbox, outbox, or archive) and output structured JSON.
|
|
5
|
-
* Saves agents from reading raw Markdown — reduces token overhead for every task fetch.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* npx ts-node .ai-assistance/scripts/task-read.ts --task oscar-a3f2b1c-list-open-mrs.md
|
|
9
|
-
* npx ts-node .ai-assistance/scripts/task-read.ts --file /absolute/path/to/file.md
|
|
10
|
-
* npx ts-node .ai-assistance/scripts/task-read.ts --task oscar-abc.md --dir outbox
|
|
11
|
-
*
|
|
12
|
-
* Output (always to stdout):
|
|
13
|
-
* {
|
|
14
|
-
* "title": "List open MRs",
|
|
15
|
-
* "status": "DONE",
|
|
16
|
-
* "created": "2026-06-07T10:00:00Z",
|
|
17
|
-
* "from": "cowork",
|
|
18
|
-
* "to": "code",
|
|
19
|
-
* "context": "...",
|
|
20
|
-
* "request": "...",
|
|
21
|
-
* "expected": "...",
|
|
22
|
-
* "result": "...",
|
|
23
|
-
* "notes": "...",
|
|
24
|
-
* "raw_path": "/absolute/path/to/file.md"
|
|
25
|
-
* }
|
|
26
|
-
*
|
|
27
|
-
* Missing sections are returned as empty strings, not null.
|
|
28
|
-
* Exits 1 with {"error":"..."} if file not found or unreadable.
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
import { readFileSync, existsSync } from "fs";
|
|
32
|
-
import { join, dirname, resolve } from "path";
|
|
33
|
-
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
// Argument parsing
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
const args = process.argv.slice(2);
|
|
38
|
-
const get = (flag: string): string | undefined => {
|
|
39
|
-
const i = args.indexOf(flag);
|
|
40
|
-
return i !== -1 && i + 1 < args.length ? args[i + 1] : undefined;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const taskFile = get("--task");
|
|
44
|
-
const filePath = get("--file");
|
|
45
|
-
const dir = get("--dir") ?? "inbox"; // inbox | outbox | archive
|
|
46
|
-
|
|
47
|
-
const scriptDir = dirname(process.argv[1]);
|
|
48
|
-
const bridgeDir = join(scriptDir, "../bridge");
|
|
49
|
-
|
|
50
|
-
let resolvedPath: string;
|
|
51
|
-
|
|
52
|
-
if (filePath) {
|
|
53
|
-
resolvedPath = resolve(filePath);
|
|
54
|
-
} else if (taskFile) {
|
|
55
|
-
// Try the specified dir first, then inbox, outbox, and archive variants
|
|
56
|
-
const seen = new Set<string>();
|
|
57
|
-
const unique = (p: string) => { seen.add(p); return p; };
|
|
58
|
-
const candidates = [
|
|
59
|
-
unique(join(bridgeDir, dir, taskFile)),
|
|
60
|
-
unique(join(bridgeDir, "inbox", taskFile)),
|
|
61
|
-
unique(join(bridgeDir, "outbox", taskFile)),
|
|
62
|
-
unique(join(bridgeDir, "archive", taskFile)),
|
|
63
|
-
unique(join(bridgeDir, "archive", `inbox-${taskFile}`)),
|
|
64
|
-
unique(join(bridgeDir, "archive", `outbox-${taskFile}`)),
|
|
65
|
-
].filter((p, i, arr) => arr.indexOf(p) === i); // deduplicate
|
|
66
|
-
|
|
67
|
-
const found = candidates.find(p => existsSync(p));
|
|
68
|
-
if (!found) {
|
|
69
|
-
console.log(JSON.stringify({ error: `Task file not found: ${taskFile}`, searched: candidates }));
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
resolvedPath = found;
|
|
73
|
-
} else {
|
|
74
|
-
console.log(JSON.stringify({ error: "Provide --task <filename> or --file <path>" }));
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (!existsSync(resolvedPath)) {
|
|
79
|
-
console.log(JSON.stringify({ error: `File not found: ${resolvedPath}` }));
|
|
80
|
-
process.exit(1);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
// Parse
|
|
85
|
-
// ---------------------------------------------------------------------------
|
|
86
|
-
const raw = readFileSync(resolvedPath, "utf8");
|
|
87
|
-
|
|
88
|
-
/** Extract a header field like "STATUS: PENDING" → "PENDING" */
|
|
89
|
-
function header(key: string): string {
|
|
90
|
-
const m = raw.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
|
|
91
|
-
return m ? m[1].trim() : "";
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Extract a Markdown section body (between ## Heading and the next ## or EOF) */
|
|
95
|
-
function section(heading: string): string {
|
|
96
|
-
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
97
|
-
// Match the heading line (## Heading...\n) then capture everything until the next ## or EOF
|
|
98
|
-
const headingRe = new RegExp(`^##\\s+${escaped}[^\\n]*\\n`, "m");
|
|
99
|
-
const headingMatch = headingRe.exec(raw);
|
|
100
|
-
if (!headingMatch) return "";
|
|
101
|
-
const contentStart = headingMatch.index + headingMatch[0].length;
|
|
102
|
-
const rest = raw.slice(contentStart);
|
|
103
|
-
const nextSection = /^## /m.exec(rest);
|
|
104
|
-
return rest.slice(0, nextSection ? nextSection.index : rest.length).trim();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Title: "# TASK: My title"
|
|
108
|
-
const titleMatch = raw.match(/^#\s+TASK:\s+(.+)$/m);
|
|
109
|
-
const title = titleMatch ? titleMatch[1].trim() : "";
|
|
110
|
-
|
|
111
|
-
const result = {
|
|
112
|
-
title,
|
|
113
|
-
status: header("STATUS"),
|
|
114
|
-
created: header("CREATED"),
|
|
115
|
-
from: header("FROM"),
|
|
116
|
-
to: header("TO"),
|
|
117
|
-
context: section("Context"),
|
|
118
|
-
request: section("Request"),
|
|
119
|
-
expected: section("Expected output"),
|
|
120
|
-
result: section("Result"),
|
|
121
|
-
notes: section("Notes"),
|
|
122
|
-
raw_path: resolvedPath,
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
console.log(JSON.stringify(result, null, 2));
|