@arii/boomtick-mcp 0.3.3
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.
- package/.env.example +6 -0
- package/.mcp/schemas/agent.search_ddgs.json +1 -0
- package/.mcp/schemas/boomtick.health.json +1 -0
- package/.mcp/schemas/github.checkout_branch.json +1 -0
- package/.mcp/schemas/github.comment_triage_summary.json +1 -0
- package/.mcp/schemas/github.create_issue.json +1 -0
- package/.mcp/schemas/github.create_pull_request.json +1 -0
- package/.mcp/schemas/github.get_merge_conflict_files.json +1 -0
- package/.mcp/schemas/github.get_pr.json +1 -0
- package/.mcp/schemas/github.get_pr_diff.json +1 -0
- package/.mcp/schemas/github.issue_comment.json +1 -0
- package/.mcp/schemas/github.issue_update.json +1 -0
- package/.mcp/schemas/github.issue_view.json +1 -0
- package/.mcp/schemas/github.open_replacement_pr.json +1 -0
- package/.mcp/schemas/github.search_open_prs.json +1 -0
- package/.mcp/schemas/jules.cancel_session.json +1 -0
- package/.mcp/schemas/jules.create_session.json +1 -0
- package/.mcp/schemas/jules.get_messages.json +1 -0
- package/.mcp/schemas/jules.get_pr.json +1 -0
- package/.mcp/schemas/jules.get_session.json +1 -0
- package/.mcp/schemas/jules.list_sessions.json +1 -0
- package/.mcp/schemas/jules.send_message.json +1 -0
- package/.mcp/schemas/jules.trigger_feedback.json +1 -0
- package/.mcp/schemas/repo.commit_patch.json +1 -0
- package/.mcp/schemas/repo.create_branch.json +1 -0
- package/.mcp/schemas/repo.create_repair_branch.json +1 -0
- package/.mcp/schemas/repo.get_changed_files.json +1 -0
- package/.mcp/schemas/repo.get_command_schema.json +1 -0
- package/.mcp/schemas/repo.get_package_scripts.json +1 -0
- package/.mcp/schemas/repo.get_route_map.json +1 -0
- package/.mcp/schemas/repo.logs.json +1 -0
- package/.mcp/schemas/repo.read_agent_context.json +1 -0
- package/.mcp/schemas/repo.read_ci_logs.json +1 -0
- package/.mcp/schemas/repo.run_lighthouse.json +1 -0
- package/.mcp/schemas/repo.run_playwright.json +1 -0
- package/.mcp/schemas/repo.run_tests.json +1 -0
- package/CHANGELOG.md +50 -0
- package/README.md +108 -0
- package/actions/ai-review/action.yml +15 -0
- package/actions/audit/action.yml +37 -0
- package/actions/ci-validate/action.yml +9 -0
- package/actions/impact-analysis/action.yml +385 -0
- package/actions/lint-typecheck/action.yml +19 -0
- package/actions/run-project-gate/action.yml +163 -0
- package/actions/setup/action.yml +15 -0
- package/actions/setup-workspace/action.yml +179 -0
- package/actions/test-build/action.yml +71 -0
- package/actions/update-pr-comment/action.yml +101 -0
- package/dist/config.js +110 -0
- package/dist/evals/run-evals.js +38 -0
- package/dist/index.js +10 -0
- package/dist/lib/error_utils.js +8 -0
- package/dist/lib/git.js +33 -0
- package/dist/lib/result.js +21 -0
- package/dist/lib/shell.js +91 -0
- package/dist/lib/shell.test.js +10 -0
- package/dist/lib/td-cli.js +27 -0
- package/dist/lib/test-utils.js +20 -0
- package/dist/mcp/definitions.js +242 -0
- package/dist/mcp/server.js +317 -0
- package/dist/mcp/tools.js +18 -0
- package/dist/tools/contract.js +2069 -0
- package/dist/tools/ddgs.search.js +50 -0
- package/dist/tools/ddgs.search.test.js +67 -0
- package/dist/tools/ddgs_search.py +23 -0
- package/dist/tools/github.checkout_branch.js +20 -0
- package/dist/tools/github.comment_triage_summary.js +25 -0
- package/dist/tools/github.create_issue.js +28 -0
- package/dist/tools/github.create_issue.test.js +54 -0
- package/dist/tools/github.create_pull_request.js +44 -0
- package/dist/tools/github.get_merge_conflict_files.js +26 -0
- package/dist/tools/github.get_pr.js +18 -0
- package/dist/tools/github.get_pr_diff.js +24 -0
- package/dist/tools/github.issue_comment.js +24 -0
- package/dist/tools/github.issue_update.js +29 -0
- package/dist/tools/github.issue_view.js +28 -0
- package/dist/tools/github.open_replacement_pr.js +41 -0
- package/dist/tools/github.search_open_prs.js +31 -0
- package/dist/tools/github.search_open_prs.test.js +51 -0
- package/dist/tools/jules/cancel-session.js +20 -0
- package/dist/tools/jules/create-session.js +20 -0
- package/dist/tools/jules/create-session.test.js +99 -0
- package/dist/tools/jules/get-messages.js +19 -0
- package/dist/tools/jules/get-messages.test.js +45 -0
- package/dist/tools/jules/get-pr.js +14 -0
- package/dist/tools/jules/get-pr.test.js +37 -0
- package/dist/tools/jules/get-session.js +10 -0
- package/dist/tools/jules/list-sessions.js +43 -0
- package/dist/tools/jules/send-message.js +22 -0
- package/dist/tools/jules/send-message.test.js +56 -0
- package/dist/tools/jules/shared.js +26 -0
- package/dist/tools/jules/trigger-feedback.js +16 -0
- package/dist/tools/jules/trigger-feedback.test.js +42 -0
- package/dist/tools/repo.commit_patch.js +33 -0
- package/dist/tools/repo.create_branch.js +21 -0
- package/dist/tools/repo.create_branch.test.js +42 -0
- package/dist/tools/repo.create_repair_branch.js +38 -0
- package/dist/tools/repo.get_changed_files.js +21 -0
- package/dist/tools/repo.get_command_schema.js +27 -0
- package/dist/tools/repo.get_package_scripts.js +35 -0
- package/dist/tools/repo.get_package_scripts.test.js +39 -0
- package/dist/tools/repo.get_route_map.js +37 -0
- package/dist/tools/repo.logs.js +16 -0
- package/dist/tools/repo.read_agent_context.js +20 -0
- package/dist/tools/repo.read_ci_logs.js +18 -0
- package/dist/tools/repo.run_lighthouse.js +37 -0
- package/dist/tools/repo.run_playwright.js +29 -0
- package/dist/tools/repo.run_tests.js +43 -0
- package/dist/tools/types.js +1 -0
- package/docs/testing.md +73 -0
- package/package.json +45 -0
- package/scripts/create_instructions.sh +42 -0
- package/scripts/export-mcp-schema.ts +7 -0
- package/scripts/sync-contracts.ts +74 -0
- package/scripts/sync-mcp-schemas.ts +97 -0
- package/src/agents/conflict-scout.prompt.md +20 -0
- package/src/agents/pr-consolidation.prompt.md +32 -0
- package/src/agents/pr-writer.prompt.md +13 -0
- package/src/agents/repair-agent.prompt.md +18 -0
- package/src/agents/repo-context.prompt.md +20 -0
- package/src/agents/verifier-agent.prompt.md +22 -0
- package/src/config.ts +113 -0
- package/src/evals/fixtures/merge-conflict-simple/README.md +0 -0
- package/src/evals/run-evals.ts +40 -0
- package/src/index.ts +12 -0
- package/src/lib/error_utils.ts +8 -0
- package/src/lib/git.ts +41 -0
- package/src/lib/result.ts +28 -0
- package/src/lib/shell.test.ts +11 -0
- package/src/lib/shell.ts +118 -0
- package/src/lib/td-cli.ts +38 -0
- package/src/lib/test-utils.ts +23 -0
- package/src/mcp/definitions.ts +278 -0
- package/src/mcp/server.ts +359 -0
- package/src/mcp/tools.ts +20 -0
- package/src/tools/contract.ts +2245 -0
- package/src/tools/ddgs.search.test.ts +92 -0
- package/src/tools/ddgs.search.ts +58 -0
- package/src/tools/ddgs_search.py +23 -0
- package/src/tools/github.checkout_branch.ts +25 -0
- package/src/tools/github.comment_triage_summary.ts +30 -0
- package/src/tools/github.create_issue.test.ts +63 -0
- package/src/tools/github.create_issue.ts +34 -0
- package/src/tools/github.create_pull_request.ts +50 -0
- package/src/tools/github.get_merge_conflict_files.ts +31 -0
- package/src/tools/github.get_pr.ts +24 -0
- package/src/tools/github.get_pr_diff.ts +29 -0
- package/src/tools/github.issue_comment.ts +31 -0
- package/src/tools/github.issue_update.ts +31 -0
- package/src/tools/github.issue_view.ts +35 -0
- package/src/tools/github.open_replacement_pr.ts +48 -0
- package/src/tools/github.search_open_prs.test.ts +60 -0
- package/src/tools/github.search_open_prs.ts +40 -0
- package/src/tools/jules/cancel-session.ts +26 -0
- package/src/tools/jules/create-session.test.ts +114 -0
- package/src/tools/jules/create-session.ts +24 -0
- package/src/tools/jules/get-messages.test.ts +52 -0
- package/src/tools/jules/get-messages.ts +24 -0
- package/src/tools/jules/get-pr.test.ts +43 -0
- package/src/tools/jules/get-pr.ts +17 -0
- package/src/tools/jules/get-session.ts +12 -0
- package/src/tools/jules/list-sessions.ts +50 -0
- package/src/tools/jules/send-message.test.ts +66 -0
- package/src/tools/jules/send-message.ts +27 -0
- package/src/tools/jules/shared.ts +28 -0
- package/src/tools/jules/trigger-feedback.test.ts +50 -0
- package/src/tools/jules/trigger-feedback.ts +21 -0
- package/src/tools/repo.commit_patch.ts +41 -0
- package/src/tools/repo.create_branch.test.ts +55 -0
- package/src/tools/repo.create_branch.ts +29 -0
- package/src/tools/repo.create_repair_branch.ts +47 -0
- package/src/tools/repo.get_changed_files.ts +26 -0
- package/src/tools/repo.get_command_schema.ts +30 -0
- package/src/tools/repo.get_package_scripts.test.ts +51 -0
- package/src/tools/repo.get_package_scripts.ts +36 -0
- package/src/tools/repo.get_route_map.ts +41 -0
- package/src/tools/repo.logs.ts +21 -0
- package/src/tools/repo.read_agent_context.ts +22 -0
- package/src/tools/repo.read_ci_logs.ts +21 -0
- package/src/tools/repo.run_lighthouse.ts +40 -0
- package/src/tools/repo.run_playwright.ts +35 -0
- package/src/tools/repo.run_tests.ts +47 -0
- package/src/tools/types.ts +8 -0
- package/start_browsermcp.sh +14 -0
- package/start_github_mcp.sh +6 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
name: 'Setup Workspace'
|
|
2
|
+
description: 'Bootstraps the workspace by installing Node.js, Python, and dev-tools'
|
|
3
|
+
inputs:
|
|
4
|
+
setup-node:
|
|
5
|
+
description: "Install Node.js and pnpm"
|
|
6
|
+
required: false
|
|
7
|
+
default: 'true'
|
|
8
|
+
setup-python:
|
|
9
|
+
description: "Install dev-tools (Python)"
|
|
10
|
+
required: false
|
|
11
|
+
default: 'true'
|
|
12
|
+
node-version-file:
|
|
13
|
+
description: "Path to .node-version file"
|
|
14
|
+
required: false
|
|
15
|
+
default: ".node-version"
|
|
16
|
+
pnpm-version:
|
|
17
|
+
description: "Version of pnpm to activate"
|
|
18
|
+
required: false
|
|
19
|
+
default: "10.28.2"
|
|
20
|
+
|
|
21
|
+
runs:
|
|
22
|
+
using: 'composite'
|
|
23
|
+
steps:
|
|
24
|
+
- name: Setup pnpm
|
|
25
|
+
if: inputs.setup-node == 'true'
|
|
26
|
+
uses: pnpm/action-setup@v6 # nosemgrep: yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
|
|
27
|
+
with:
|
|
28
|
+
version: ${{ inputs.pnpm-version }}
|
|
29
|
+
|
|
30
|
+
- name: Get pnpm store directory
|
|
31
|
+
if: inputs.setup-node == 'true'
|
|
32
|
+
id: pnpm-cache
|
|
33
|
+
shell: bash
|
|
34
|
+
run: |
|
|
35
|
+
echo "STORE_PATH=$(pnpm store path)" >> "$GITHUB_OUTPUT"
|
|
36
|
+
|
|
37
|
+
- name: Cache pnpm store
|
|
38
|
+
if: inputs.setup-node == 'true'
|
|
39
|
+
uses: actions/cache@v4.2.0 # nosemgrep: yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
|
|
40
|
+
with:
|
|
41
|
+
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
|
42
|
+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
43
|
+
restore-keys: |
|
|
44
|
+
${{ runner.os }}-pnpm-store-
|
|
45
|
+
|
|
46
|
+
- name: Setup Node.js
|
|
47
|
+
if: inputs.setup-node == 'true'
|
|
48
|
+
uses: actions/setup-node@v7 # nosemgrep: yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
|
|
49
|
+
with:
|
|
50
|
+
node-version-file: ${{ inputs.node-version-file }}
|
|
51
|
+
|
|
52
|
+
- name: Enable Corepack
|
|
53
|
+
if: inputs.setup-node == 'true'
|
|
54
|
+
run: corepack enable
|
|
55
|
+
shell: bash
|
|
56
|
+
|
|
57
|
+
- name: Activate pinned pnpm
|
|
58
|
+
if: inputs.setup-node == 'true'
|
|
59
|
+
env:
|
|
60
|
+
PNPM_VERSION: ${{ inputs.pnpm-version }}
|
|
61
|
+
run: corepack prepare pnpm@"$PNPM_VERSION" --activate
|
|
62
|
+
shell: bash
|
|
63
|
+
|
|
64
|
+
- name: Resolve Paths
|
|
65
|
+
id: resolve-paths
|
|
66
|
+
shell: bash
|
|
67
|
+
run: |
|
|
68
|
+
# Use centralized script for CLI path resolution
|
|
69
|
+
if [ -f "scripts/resolve-cli.sh" ]; then
|
|
70
|
+
CLI_DIR=$(bash scripts/resolve-cli.sh | sed "s|$(pwd)/||")
|
|
71
|
+
elif [ -f "boomtick-pkg/scripts/resolve-cli.sh" ]; then
|
|
72
|
+
CLI_DIR=$(bash boomtick-pkg/scripts/resolve-cli.sh | sed "s|$(pwd)/||")
|
|
73
|
+
elif [ -f "boomtick/scripts/resolve-cli.sh" ]; then
|
|
74
|
+
CLI_DIR=$(bash boomtick/scripts/resolve-cli.sh | sed "s|$(pwd)/||")
|
|
75
|
+
elif [ -f "${{ github.action_path }}/../../../scripts/resolve-cli.sh" ]; then
|
|
76
|
+
CLI_DIR=$(REPO_ROOT="${{ github.action_path }}/../../../" bash "${{ github.action_path }}/../../../scripts/resolve-cli.sh" | sed "s|$(pwd)/||")
|
|
77
|
+
elif [ -d "cli" ]; then
|
|
78
|
+
CLI_DIR="cli"
|
|
79
|
+
elif [ -d "boomtick-pkg/cli" ]; then
|
|
80
|
+
CLI_DIR="boomtick-pkg/cli"
|
|
81
|
+
elif [ -d "boomtick/cli" ]; then
|
|
82
|
+
CLI_DIR="boomtick/cli"
|
|
83
|
+
else
|
|
84
|
+
CLI_DIR="cli"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Validate that the resolved CLI directory exists
|
|
88
|
+
if [ -z "$CLI_DIR" ] || { [ ! -d "$CLI_DIR" ] && [ ! -d "${{ github.action_path }}/../../../$CLI_DIR" ]; }; then
|
|
89
|
+
echo "::error::Could not resolve a valid CLI directory. Resolved: '$CLI_DIR'"
|
|
90
|
+
exit 1
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
echo "CLI_DIR=$CLI_DIR" >> "$GITHUB_OUTPUT"
|
|
94
|
+
|
|
95
|
+
# Consolidate GITHUB_PATH updates
|
|
96
|
+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
97
|
+
echo "/github/home/.local/bin" >> "$GITHUB_PATH"
|
|
98
|
+
|
|
99
|
+
- name: Get pip cache directory
|
|
100
|
+
if: inputs.setup-python == 'true'
|
|
101
|
+
id: pip-cache
|
|
102
|
+
shell: bash
|
|
103
|
+
run: |
|
|
104
|
+
echo "DIR=$(pip cache dir)" >> "$GITHUB_OUTPUT"
|
|
105
|
+
|
|
106
|
+
- name: Cache pip
|
|
107
|
+
if: inputs.setup-python == 'true'
|
|
108
|
+
uses: actions/cache@v4.2.0 # nosemgrep: yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
|
|
109
|
+
with:
|
|
110
|
+
path: ${{ steps.pip-cache.outputs.DIR }}
|
|
111
|
+
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
|
|
112
|
+
restore-keys: |
|
|
113
|
+
${{ runner.os }}-pip-
|
|
114
|
+
|
|
115
|
+
- name: Install dev-tools
|
|
116
|
+
if: inputs.setup-python == 'true'
|
|
117
|
+
env:
|
|
118
|
+
CLI_DIR: ${{ steps.resolve-paths.outputs.CLI_DIR }}
|
|
119
|
+
run: |
|
|
120
|
+
echo "::group::Install dev-tools"
|
|
121
|
+
if [ -f "requirements-dev.txt" ]; then
|
|
122
|
+
pip install -r requirements-dev.txt --break-system-packages
|
|
123
|
+
fi
|
|
124
|
+
pip install -e "$CLI_DIR" --break-system-packages
|
|
125
|
+
|
|
126
|
+
echo "::endgroup::"
|
|
127
|
+
shell: bash
|
|
128
|
+
|
|
129
|
+
- name: Export Environment Variables
|
|
130
|
+
if: inputs.setup-python == 'true'
|
|
131
|
+
id: export-env
|
|
132
|
+
env:
|
|
133
|
+
CLI_DIR: ${{ steps.resolve-paths.outputs.CLI_DIR }}
|
|
134
|
+
run: |
|
|
135
|
+
# Use $(pwd) to get the container-internal path, avoiding host-path leakage from $GITHUB_WORKSPACE
|
|
136
|
+
REPO_DIR=$(pwd)
|
|
137
|
+
if [[ "$CLI_DIR" = /* ]]; then
|
|
138
|
+
CLI_DIR_ABS="$CLI_DIR"
|
|
139
|
+
else
|
|
140
|
+
CLI_DIR_ABS="$REPO_DIR/$CLI_DIR"
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# 1. Update PYTHONPATH for subsequent steps
|
|
144
|
+
# Ensure it works even if PYTHONPATH is initially empty
|
|
145
|
+
if [ -z "$PYTHONPATH" ]; then
|
|
146
|
+
echo "PYTHONPATH=$CLI_DIR_ABS" >> "$GITHUB_ENV"
|
|
147
|
+
else
|
|
148
|
+
echo "PYTHONPATH=$CLI_DIR_ABS:$PYTHONPATH" >> "$GITHUB_ENV"
|
|
149
|
+
fi
|
|
150
|
+
echo "cli_dir_abs=$CLI_DIR_ABS" >> "$GITHUB_OUTPUT"
|
|
151
|
+
|
|
152
|
+
echo "✅ Environment configured: PYTHONPATH includes $CLI_DIR_ABS"
|
|
153
|
+
shell: bash
|
|
154
|
+
|
|
155
|
+
- name: Initialize Schemas and Contracts
|
|
156
|
+
if: inputs.setup-node == 'true' && inputs.setup-python == 'true'
|
|
157
|
+
env:
|
|
158
|
+
CLI_DIR_ABS: ${{ steps.export-env.outputs.cli_dir_abs }}
|
|
159
|
+
run: |
|
|
160
|
+
echo "::group::Initialize Schemas and Contracts"
|
|
161
|
+
export PYTHONPATH="$CLI_DIR_ABS:$PYTHONPATH"
|
|
162
|
+
python3 "$CLI_DIR_ABS/dev_tools/schema_gen.py"
|
|
163
|
+
|
|
164
|
+
MCP_DIR="mcp"
|
|
165
|
+
|
|
166
|
+
# Check for sync-contracts script in the resolved MCP directory
|
|
167
|
+
if [ -f "$MCP_DIR/package.json" ] && grep -q "sync-contracts" "$MCP_DIR/package.json"; then
|
|
168
|
+
echo "::group::Run sync-contracts in $MCP_DIR"
|
|
169
|
+
# Ensure dependencies are installed if we're in the MCP directory
|
|
170
|
+
if [ ! -d "$MCP_DIR/node_modules" ]; then
|
|
171
|
+
(cd "$MCP_DIR" && pnpm install --frozen-lockfile --prefer-offline)
|
|
172
|
+
fi
|
|
173
|
+
pnpm --filter "@arii/boomtick-mcp" run sync-contracts || (cd "$MCP_DIR" && pnpm run sync-contracts)
|
|
174
|
+
echo "::endgroup::"
|
|
175
|
+
else
|
|
176
|
+
echo "⚠️ Skipping: sync-contracts script not found in $MCP_DIR/package.json"
|
|
177
|
+
fi
|
|
178
|
+
echo "::endgroup::"
|
|
179
|
+
shell: bash
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
name: BoomTick Build & Test
|
|
2
|
+
description: Encapsulates build, bundle size check, Playwright E2E, and Lighthouse CI
|
|
3
|
+
inputs:
|
|
4
|
+
github_token:
|
|
5
|
+
description: 'GitHub token'
|
|
6
|
+
required: true
|
|
7
|
+
run_lighthouse:
|
|
8
|
+
description: 'Whether to run Lighthouse CI'
|
|
9
|
+
required: false
|
|
10
|
+
default: 'true'
|
|
11
|
+
runs:
|
|
12
|
+
using: composite
|
|
13
|
+
steps:
|
|
14
|
+
- uses: ./mcp/actions/setup
|
|
15
|
+
|
|
16
|
+
- name: pnpm install
|
|
17
|
+
run: pnpm install --frozen-lockfile --prefer-offline
|
|
18
|
+
shell: bash
|
|
19
|
+
|
|
20
|
+
- name: Build App
|
|
21
|
+
run: |
|
|
22
|
+
VITE_BASE_PATH=/; export VITE_BASE_PATH
|
|
23
|
+
pnpm run build
|
|
24
|
+
shell: bash
|
|
25
|
+
|
|
26
|
+
- name: Bundle Size Check
|
|
27
|
+
env:
|
|
28
|
+
GITHUB_TOKEN: ${{ inputs.github_token }}
|
|
29
|
+
run: |
|
|
30
|
+
export PATH="$HOME/.local/bin:/github/home/.local/bin:$PATH"
|
|
31
|
+
python3 -m dev_tools.cli gh bundle-size
|
|
32
|
+
shell: bash
|
|
33
|
+
|
|
34
|
+
- name: Run Playwright Smoke Test
|
|
35
|
+
run: |
|
|
36
|
+
VITE_BASE_PATH=/ pnpm run test:e2e
|
|
37
|
+
shell: bash
|
|
38
|
+
|
|
39
|
+
- name: Run Lighthouse CI
|
|
40
|
+
if: inputs.run_lighthouse == 'true'
|
|
41
|
+
run: |
|
|
42
|
+
set -eu
|
|
43
|
+
VITE_BASE_PATH="/"
|
|
44
|
+
CHROME_PATH="$(node -e "console.log(require('playwright').chromium.executablePath())")"
|
|
45
|
+
export VITE_BASE_PATH
|
|
46
|
+
export CHROME_PATH
|
|
47
|
+
echo "CHROME_PATH=$CHROME_PATH"
|
|
48
|
+
pnpm run lighthouse --collect.url="http://localhost:4173${VITE_BASE_PATH}"
|
|
49
|
+
env:
|
|
50
|
+
LHCI_GITHUB_TOKEN: ${{ inputs.github_token }}
|
|
51
|
+
shell: bash
|
|
52
|
+
|
|
53
|
+
- name: Check Playwright Report Exists
|
|
54
|
+
id: check_report
|
|
55
|
+
if: always()
|
|
56
|
+
run: |
|
|
57
|
+
if [ -d "playwright-report/" ]; then
|
|
58
|
+
echo "exists=true" >> "$GITHUB_OUTPUT"
|
|
59
|
+
else
|
|
60
|
+
echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
61
|
+
fi
|
|
62
|
+
shell: bash
|
|
63
|
+
|
|
64
|
+
- name: Upload Test Results
|
|
65
|
+
if: always() && steps.check_report.outputs.exists == 'true'
|
|
66
|
+
uses: actions/upload-artifact@v7 # nosemgrep: yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
|
|
67
|
+
with:
|
|
68
|
+
name: playwright-report-${{ github.run_id }}
|
|
69
|
+
path: playwright-report/
|
|
70
|
+
retention-days: 7
|
|
71
|
+
if-no-files-found: ignore
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
name: 'Update PR Comment'
|
|
2
|
+
description: 'Updates or creates a PR comment with the given status and body'
|
|
3
|
+
|
|
4
|
+
inputs:
|
|
5
|
+
github_token:
|
|
6
|
+
description: 'GitHub Token'
|
|
7
|
+
required: true
|
|
8
|
+
status_line:
|
|
9
|
+
description: 'Status line (e.g., ⏳ **Impact analysis running...**)'
|
|
10
|
+
required: true
|
|
11
|
+
impact_body:
|
|
12
|
+
description: 'Body text (leave empty for initial state)'
|
|
13
|
+
required: false
|
|
14
|
+
default: ''
|
|
15
|
+
|
|
16
|
+
runs:
|
|
17
|
+
using: 'composite'
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/github-script@v8 # nosemgrep: yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
|
|
20
|
+
env:
|
|
21
|
+
INPUT_STATUS_LINE: ${{ inputs.status_line }}
|
|
22
|
+
INPUT_IMPACT_BODY: ${{ inputs.impact_body }}
|
|
23
|
+
with:
|
|
24
|
+
github-token: ${{ inputs.github_token }}
|
|
25
|
+
script: |
|
|
26
|
+
const owner = context.repo.owner;
|
|
27
|
+
const repo = context.repo.repo;
|
|
28
|
+
const branchName = context.ref.replace('refs/heads/', '');
|
|
29
|
+
|
|
30
|
+
const pstTime = new Date().toLocaleString('en-US', {
|
|
31
|
+
timeZone: 'America/Los_Angeles',
|
|
32
|
+
dateStyle: 'medium',
|
|
33
|
+
timeStyle: 'short'
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (branchName === 'main') return;
|
|
37
|
+
|
|
38
|
+
const pullRequests = await github.paginate(github.rest.pulls.list, {
|
|
39
|
+
owner,
|
|
40
|
+
repo,
|
|
41
|
+
state: 'open'
|
|
42
|
+
});
|
|
43
|
+
const matchingPRs = pullRequests.filter(pr => pr.head.ref === branchName);
|
|
44
|
+
|
|
45
|
+
const statusLine = process.env.INPUT_STATUS_LINE;
|
|
46
|
+
// Simple sanitization to satisfy XSS concerns in GitHub comments
|
|
47
|
+
let impactBodyText = (process.env.INPUT_IMPACT_BODY || '')
|
|
48
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '[REMOVED SCRIPT]');
|
|
49
|
+
|
|
50
|
+
for (const pr of matchingPRs) {
|
|
51
|
+
const commentTag = `<!-- impact-analysis-comment-${branchName} -->`;
|
|
52
|
+
const bodyParts = [
|
|
53
|
+
`🚀 **Impact Analysis Details** (Last updated: ${pstTime} PST)`,
|
|
54
|
+
'',
|
|
55
|
+
statusLine,
|
|
56
|
+
`- **Commit SHA:** \`${process.env.GITHUB_SHA.slice(0, 7)}\``,
|
|
57
|
+
`- [Link to artifacts](https://github.com/${owner}/${repo}/actions/runs/${context.runId})`,
|
|
58
|
+
''
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
if (impactBodyText) {
|
|
62
|
+
bodyParts.push(impactBodyText, '');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const MAX_LENGTH = 65536;
|
|
66
|
+
const truncationNotice = '\n\n... (truncated due to GitHub comment size limits)\n';
|
|
67
|
+
// Determine length of string we are about to join
|
|
68
|
+
let currentContent = bodyParts.join('\n');
|
|
69
|
+
// Adding 1 because joining `bodyParts` with `\n` and `commentTag` will add a newline
|
|
70
|
+
let projectedLength = currentContent.length + commentTag.length + 1;
|
|
71
|
+
|
|
72
|
+
if (projectedLength > MAX_LENGTH) {
|
|
73
|
+
// subtracting 2 to account for the newlines joining bodyParts
|
|
74
|
+
const availableLength = MAX_LENGTH - commentTag.length - truncationNotice.length - 2;
|
|
75
|
+
// Truncate safely, avoiding cutting midway through a unicode character or making things weird
|
|
76
|
+
let safeContent = currentContent.slice(0, availableLength);
|
|
77
|
+
// Simple attempt to cut at last newline if possible
|
|
78
|
+
let lastNewline = safeContent.lastIndexOf('\n');
|
|
79
|
+
if (lastNewline > availableLength * 0.9) {
|
|
80
|
+
safeContent = safeContent.slice(0, lastNewline);
|
|
81
|
+
}
|
|
82
|
+
bodyParts = [safeContent, truncationNotice];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
bodyParts.push(commentTag);
|
|
86
|
+
const body = bodyParts.join('\n');
|
|
87
|
+
|
|
88
|
+
const comments = await github.paginate(github.rest.issues.listComments, {
|
|
89
|
+
owner,
|
|
90
|
+
repo,
|
|
91
|
+
issue_number: pr.number,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const existingComment = comments.find(c => c.body.includes(commentTag));
|
|
95
|
+
|
|
96
|
+
if (existingComment) {
|
|
97
|
+
await github.rest.issues.updateComment({ owner, repo, comment_id: existingComment.id, body });
|
|
98
|
+
} else {
|
|
99
|
+
await github.rest.issues.createComment({ issue_number: pr.number, owner, repo, body });
|
|
100
|
+
}
|
|
101
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
dotenv.config({
|
|
8
|
+
path: path.resolve(__dirname, "../.env"),
|
|
9
|
+
quiet: true
|
|
10
|
+
});
|
|
11
|
+
function getGithubToken() {
|
|
12
|
+
if (process.env.GITHUB_TOKEN) {
|
|
13
|
+
return process.env.GITHUB_TOKEN;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const token = execSync("gh auth token", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
17
|
+
if (token) {
|
|
18
|
+
return token;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
throw e;
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
let cachedDynamicConfig = null;
|
|
27
|
+
/**
|
|
28
|
+
* Explicitly initializes dynamic configuration from the Python CLI.
|
|
29
|
+
* Should be called once during server startup.
|
|
30
|
+
*/
|
|
31
|
+
export function initializeConfig() {
|
|
32
|
+
if (cachedDynamicConfig !== null) {
|
|
33
|
+
return cachedDynamicConfig;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
// Attempt to load core properties from the Python CLI to avoid duplication
|
|
37
|
+
const cmd = `td-cli config view`;
|
|
38
|
+
const output = execSync(cmd, {
|
|
39
|
+
encoding: "utf-8",
|
|
40
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
41
|
+
});
|
|
42
|
+
cachedDynamicConfig = JSON.parse(output);
|
|
43
|
+
return cachedDynamicConfig;
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
47
|
+
const errorPrefix = `CRITICAL: Failed to load dynamic config from Python CLI. Ensure Python 3 is installed and boomtick-pkg/cli is in your PYTHONPATH. Details: ${message}`;
|
|
48
|
+
if (process.env.NODE_ENV === "development" || process.env.CI === "true") {
|
|
49
|
+
throw new Error(errorPrefix);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.warn(errorPrefix);
|
|
53
|
+
}
|
|
54
|
+
// Set an empty object to prevent repeated attempts if initialization fails in non-blocking mode
|
|
55
|
+
cachedDynamicConfig = {};
|
|
56
|
+
}
|
|
57
|
+
return cachedDynamicConfig;
|
|
58
|
+
}
|
|
59
|
+
function findRepoRoot() {
|
|
60
|
+
if (process.env.BOOMTICK_REPO_PATH) {
|
|
61
|
+
return process.env.BOOMTICK_REPO_PATH;
|
|
62
|
+
}
|
|
63
|
+
// Traverse up to find workspace.json or .git
|
|
64
|
+
let current = __dirname;
|
|
65
|
+
while (current !== path.parse(current).root) {
|
|
66
|
+
if (fs.existsSync(path.join(current, "workspace.json")) || fs.existsSync(path.join(current, ".git"))) {
|
|
67
|
+
return current;
|
|
68
|
+
}
|
|
69
|
+
current = path.dirname(current);
|
|
70
|
+
}
|
|
71
|
+
// Fallback to the monolithic relative path if discovery fails
|
|
72
|
+
return path.resolve(__dirname, "../../../");
|
|
73
|
+
}
|
|
74
|
+
export const config = {
|
|
75
|
+
get githubToken() { return getGithubToken(); },
|
|
76
|
+
get githubOwner() {
|
|
77
|
+
if (process.env.GITHUB_OWNER)
|
|
78
|
+
return process.env.GITHUB_OWNER;
|
|
79
|
+
const repoString = cachedDynamicConfig?.github_repo;
|
|
80
|
+
if (typeof repoString !== "string" || !repoString.includes("/")) {
|
|
81
|
+
throw new Error("GITHUB_OWNER must be set via environment variable or project_config.json");
|
|
82
|
+
}
|
|
83
|
+
return repoString.split("/")[0];
|
|
84
|
+
},
|
|
85
|
+
get githubRepo() {
|
|
86
|
+
if (process.env.GITHUB_REPO)
|
|
87
|
+
return process.env.GITHUB_REPO;
|
|
88
|
+
const repoString = cachedDynamicConfig?.github_repo;
|
|
89
|
+
if (typeof repoString !== "string" || !repoString.includes("/")) {
|
|
90
|
+
throw new Error("GITHUB_REPO must be set via environment variable or project_config.json");
|
|
91
|
+
}
|
|
92
|
+
return repoString.split("/")[1];
|
|
93
|
+
},
|
|
94
|
+
get repoPath() {
|
|
95
|
+
return findRepoRoot();
|
|
96
|
+
},
|
|
97
|
+
get defaultBaseBranch() {
|
|
98
|
+
return process.env.DEFAULT_BASE_BRANCH || cachedDynamicConfig?.base_branch?.split("/").pop() || "main";
|
|
99
|
+
},
|
|
100
|
+
get viteBasePath() {
|
|
101
|
+
const path = process.env.VITE_BASE_PATH || cachedDynamicConfig?.vite_base_path;
|
|
102
|
+
if (!path) {
|
|
103
|
+
throw new Error("VITE_BASE_PATH must be set via environment variable or project_config.json");
|
|
104
|
+
}
|
|
105
|
+
return path;
|
|
106
|
+
},
|
|
107
|
+
get ghPath() {
|
|
108
|
+
return process.env.GH_PATH || cachedDynamicConfig?.gh_path || "gh";
|
|
109
|
+
}
|
|
110
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { healthHandler } from "../mcp/tools.js";
|
|
2
|
+
import { searchOpenPrsHandler } from "../tools/github.search_open_prs.js";
|
|
3
|
+
import { getPackageScriptsHandler } from "../tools/repo.get_package_scripts.js";
|
|
4
|
+
async function runEvals() {
|
|
5
|
+
console.log("Starting Boomtick MCP Evaluations...");
|
|
6
|
+
try {
|
|
7
|
+
console.log("\n1. Health Check:");
|
|
8
|
+
try {
|
|
9
|
+
const health = await healthHandler({ checkDeep: false });
|
|
10
|
+
console.log(JSON.stringify(health, null, 2));
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
console.log("Health check failed:", e instanceof Error ? e.message : String(e));
|
|
14
|
+
}
|
|
15
|
+
console.log("\n2. Repository Scripts:");
|
|
16
|
+
try {
|
|
17
|
+
const scripts = await getPackageScriptsHandler({ filter: "" });
|
|
18
|
+
console.log(`Found ${Object.keys(scripts.scripts).length} scripts.`);
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
console.log("Repository scripts fetch failed:", e instanceof Error ? e.message : String(e));
|
|
22
|
+
}
|
|
23
|
+
console.log("\n3. GitHub PR Search (Dry Run):");
|
|
24
|
+
try {
|
|
25
|
+
const prs = await searchOpenPrsHandler({ state: "open", includeDrafts: true, limit: 100 });
|
|
26
|
+
console.log(`Found ${prs.prs.length} open PRs.`);
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
console.log("GitHub search failed (likely due to environment/auth):", e instanceof Error ? e.message : String(e));
|
|
30
|
+
}
|
|
31
|
+
console.log("\nEvaluations complete.");
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.error("\nEvaluation failed:", error);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
runEvals();
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { BoomtickMCPServer } from "./mcp/server.js";
|
|
3
|
+
import { initializeConfig } from "./config.js";
|
|
4
|
+
// Initialize configuration once at startup
|
|
5
|
+
initializeConfig();
|
|
6
|
+
const server = new BoomtickMCPServer();
|
|
7
|
+
server.run().catch((error) => {
|
|
8
|
+
console.error("Fatal error running server:", error);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitizes stderr from CLI commands to prevent implementation detail leakage
|
|
3
|
+
* in error messages returned to the AI agent.
|
|
4
|
+
*/
|
|
5
|
+
export function sanitizeError(stderr) {
|
|
6
|
+
// Take first line and truncate to 200 chars to balance detail vs security
|
|
7
|
+
return (stderr.split("\n")[0] || "Unknown error").slice(0, 200);
|
|
8
|
+
}
|
package/dist/lib/git.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { runCommand } from "./shell.js";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
function validatePrNumber(value) {
|
|
5
|
+
const prNumber = Number(value);
|
|
6
|
+
if (!Number.isInteger(prNumber) || prNumber <= 0) {
|
|
7
|
+
throw new Error("Invalid PR number");
|
|
8
|
+
}
|
|
9
|
+
return prNumber;
|
|
10
|
+
}
|
|
11
|
+
export async function createWorktree(branch, prNumber) {
|
|
12
|
+
const safePrNumber = validatePrNumber(prNumber);
|
|
13
|
+
const workspaceRoot = "/tmp/boomtick-worktrees";
|
|
14
|
+
// nosemgrep
|
|
15
|
+
const worktreePath = path.join(workspaceRoot, `boomtick-mcp-rescue-${safePrNumber}`);
|
|
16
|
+
// Clean up if exists
|
|
17
|
+
try {
|
|
18
|
+
await fs.rm(worktreePath, { recursive: true, force: true });
|
|
19
|
+
await runCommand("git", ["worktree", "prune"]);
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
throw e;
|
|
23
|
+
}
|
|
24
|
+
const result = await runCommand("git", ["worktree", "add", "-b", `repair-pr-${prNumber}-${Date.now()}`, worktreePath, branch]);
|
|
25
|
+
if (result.exitCode !== 0) {
|
|
26
|
+
throw new Error(`Failed to create worktree: ${result.stderr}`);
|
|
27
|
+
}
|
|
28
|
+
return worktreePath;
|
|
29
|
+
}
|
|
30
|
+
export async function removeWorktree(worktreePath) {
|
|
31
|
+
await runCommand("git", ["worktree", "remove", "--force", worktreePath]);
|
|
32
|
+
await runCommand("git", ["worktree", "prune"]);
|
|
33
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function createSuccessResult(data) {
|
|
2
|
+
return {
|
|
3
|
+
content: [
|
|
4
|
+
{
|
|
5
|
+
type: "text",
|
|
6
|
+
text: typeof data === "string" ? data : JSON.stringify(data, null, 2),
|
|
7
|
+
},
|
|
8
|
+
],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function createErrorResult(message) {
|
|
12
|
+
return {
|
|
13
|
+
content: [
|
|
14
|
+
{
|
|
15
|
+
type: "text",
|
|
16
|
+
text: message,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
isError: true,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { config } from "../config.js";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
export const ALLOWED_COMMANDS = {
|
|
6
|
+
git: "git",
|
|
7
|
+
gh: "gh",
|
|
8
|
+
"td-cli": "td-cli",
|
|
9
|
+
pnpm: "pnpm",
|
|
10
|
+
ls: "ls",
|
|
11
|
+
rm: "rm",
|
|
12
|
+
mkdir: "mkdir",
|
|
13
|
+
cp: "cp",
|
|
14
|
+
python3: "python3"
|
|
15
|
+
};
|
|
16
|
+
export async function runCommand(cmd, args, options = {}) {
|
|
17
|
+
if (!(cmd in ALLOWED_COMMANDS)) {
|
|
18
|
+
throw new Error(`Command not allowed: ${cmd}`);
|
|
19
|
+
}
|
|
20
|
+
const safeCmd = cmd;
|
|
21
|
+
const start = Date.now();
|
|
22
|
+
const timeout = options.timeout || 60000;
|
|
23
|
+
// Resolve path for 'gh' or 'td-cli' if specified
|
|
24
|
+
let finalCmd = ALLOWED_COMMANDS[safeCmd];
|
|
25
|
+
if (safeCmd === "gh") {
|
|
26
|
+
finalCmd = config.ghPath;
|
|
27
|
+
}
|
|
28
|
+
else if (safeCmd === "td-cli") {
|
|
29
|
+
// If we have a repo-relative .venv, prioritize it
|
|
30
|
+
const venvBin = path.join(config.repoPath, ".venv", "bin", "td-cli");
|
|
31
|
+
const localVenvBin = path.join(process.cwd(), ".venv", "bin", "td-cli");
|
|
32
|
+
const parentVenvBin = path.join(config.repoPath, "..", ".venv", "bin", "td-cli");
|
|
33
|
+
// In standalone mode, config.repoPath is the boomtick-pkg root.
|
|
34
|
+
// We check common venv locations to ensure we call the isolated binary.
|
|
35
|
+
if (fs.existsSync(venvBin)) {
|
|
36
|
+
finalCmd = venvBin;
|
|
37
|
+
}
|
|
38
|
+
else if (fs.existsSync(localVenvBin)) {
|
|
39
|
+
finalCmd = localVenvBin;
|
|
40
|
+
}
|
|
41
|
+
else if (fs.existsSync(parentVenvBin)) {
|
|
42
|
+
finalCmd = parentVenvBin;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const env = {
|
|
46
|
+
...process.env,
|
|
47
|
+
...options.env,
|
|
48
|
+
GH_TOKEN: config.githubToken,
|
|
49
|
+
GITHUB_TOKEN: config.githubToken
|
|
50
|
+
};
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
// nosemgrep
|
|
53
|
+
const child = spawn(finalCmd, args, {
|
|
54
|
+
cwd: options.cwd || config.repoPath,
|
|
55
|
+
env
|
|
56
|
+
});
|
|
57
|
+
let stdout = "";
|
|
58
|
+
let stderr = "";
|
|
59
|
+
child.stdout.on("data", (data) => {
|
|
60
|
+
stdout += data.toString();
|
|
61
|
+
});
|
|
62
|
+
child.stderr.on("data", (data) => {
|
|
63
|
+
stderr += data.toString();
|
|
64
|
+
});
|
|
65
|
+
const timer = setTimeout(() => {
|
|
66
|
+
child.kill();
|
|
67
|
+
reject(new Error(`Command timed out: ${cmd} ${args.join(" ")}`));
|
|
68
|
+
}, timeout);
|
|
69
|
+
child.on("close", (code) => {
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
const durationMs = Date.now() - start;
|
|
72
|
+
// Redact token from output
|
|
73
|
+
if (config.githubToken) {
|
|
74
|
+
const redact = (str) => str.replace(new RegExp(config.githubToken, "g"), "REDACTED");
|
|
75
|
+
stdout = redact(stdout);
|
|
76
|
+
stderr = redact(stderr);
|
|
77
|
+
}
|
|
78
|
+
resolve({
|
|
79
|
+
stdout,
|
|
80
|
+
stderr,
|
|
81
|
+
exitCode: code,
|
|
82
|
+
durationMs,
|
|
83
|
+
command: `${cmd} ${args.join(" ")}`
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
child.on("error", (err) => {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
reject(err);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import path from "path";
|
|
3
|
+
// Simple test to verify environment and vitest setup
|
|
4
|
+
describe("Basic Environment", () => {
|
|
5
|
+
it("should have a valid repo path", () => {
|
|
6
|
+
const repoPath = process.env.BOOMTICK_REPO_PATH || "/app";
|
|
7
|
+
expect(repoPath).toBeDefined();
|
|
8
|
+
expect(path.isAbsolute(repoPath)).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
});
|