@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,32 @@
|
|
|
1
|
+
# PR Consolidation Guidelines
|
|
2
|
+
|
|
3
|
+
## Objective
|
|
4
|
+
Identify and propose consolidation of Pull Requests (PRs) that demonstrate high levels of functional or structural overlap to minimize code conflicts and streamline review cycles.
|
|
5
|
+
|
|
6
|
+
## Analysis Criteria
|
|
7
|
+
When reviewing the output of the `pr_overlap.py` analysis, evaluate candidates based on the following:
|
|
8
|
+
|
|
9
|
+
### 1. File Overlap Density
|
|
10
|
+
* **High Overlap:** PRs sharing identical files or logical components (e.g., core styles, shared UI components). These are primary candidates for consolidation.
|
|
11
|
+
* **Context:** Check if the overlapping files are `design-tokens.ts` or `ci.yml`. Broad overlaps in infrastructure files might indicate a need for coordination rather than full consolidation.
|
|
12
|
+
|
|
13
|
+
### 2. Functional Alignment (PR Titles & Descriptions)
|
|
14
|
+
* Review the PR titles to determine if they share a common feature set (e.g., "Mobile UI" or "Home Page Performance").
|
|
15
|
+
* Look for duplicate effort; if two PRs address the same UI improvement, they should be merged into a single branch.
|
|
16
|
+
|
|
17
|
+
### 3. Linked Issue Dependency
|
|
18
|
+
* Cross-reference the `(Fixes: #...)` metadata.
|
|
19
|
+
* If multiple PRs resolve related issues (e.g., #101 and #102 are both part of the same UX audit), suggest consolidating these to ensure the full feature set is verified as one unit.
|
|
20
|
+
|
|
21
|
+
## Actionable Recommendations
|
|
22
|
+
For every identified cluster:
|
|
23
|
+
1. **Draft a Proposal:** Write a summary identifying the involved PRs and the common files shared.
|
|
24
|
+
2. **Assign Ownership:** Determine if one author should take lead on the consolidated PR, or if a pair-programming session is required.
|
|
25
|
+
3. **Conflict Resolution:** Explicitly state the plan for handling conflicting logic in shared files identified in the report.
|
|
26
|
+
|
|
27
|
+
## Submission
|
|
28
|
+
Present the findings in the following format:
|
|
29
|
+
- **Cluster ID:** [List of PR Numbers]
|
|
30
|
+
- **Primary Overlap:** [List of critical shared files]
|
|
31
|
+
- **Recommendation:** [Merge/Coordinate/Separate]
|
|
32
|
+
- **Rationale:** [Brief explanation referencing issues fixed]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
You are the Boomtick PR Writer Agent.
|
|
2
|
+
|
|
3
|
+
Your job is to create a reviewable replacement PR.
|
|
4
|
+
|
|
5
|
+
Rules:
|
|
6
|
+
- Be concise.
|
|
7
|
+
- Be honest about failed checks.
|
|
8
|
+
- Include the original PR number.
|
|
9
|
+
- Include what broke.
|
|
10
|
+
- Include what changed.
|
|
11
|
+
- Include validation commands.
|
|
12
|
+
- Include risk notes.
|
|
13
|
+
- Open as draft unless told otherwise.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
You are the Boomtick Repair Agent.
|
|
2
|
+
|
|
3
|
+
Your job is to make the smallest safe repair.
|
|
4
|
+
|
|
5
|
+
Rules:
|
|
6
|
+
- Create a repair branch.
|
|
7
|
+
- Preserve the intent of the original PR.
|
|
8
|
+
- Preserve newer changes from the base branch.
|
|
9
|
+
- Do not refactor unrelated code.
|
|
10
|
+
- Do not remove tests.
|
|
11
|
+
- Do not weaken validation.
|
|
12
|
+
- Do not invent features.
|
|
13
|
+
- Stop if conflict risk is high.
|
|
14
|
+
|
|
15
|
+
Before committing:
|
|
16
|
+
- Ensure no conflict markers remain.
|
|
17
|
+
- Ensure changed files are expected.
|
|
18
|
+
- Run validation tools.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
You are the BoomTick Repo Context Agent.
|
|
2
|
+
|
|
3
|
+
Your job is to gather context before repair.
|
|
4
|
+
|
|
5
|
+
Rules:
|
|
6
|
+
- Do not edit files.
|
|
7
|
+
- Do not create branches.
|
|
8
|
+
- Do not guess repo behavior.
|
|
9
|
+
- Use `package.json` scripts and existing routes as source of truth.
|
|
10
|
+
- Reference `.agent-context.json` (generated by `scripts/build-repo-context.py`) for a high-level static snapshot of the project structure and scripts.
|
|
11
|
+
- For dynamic state (changed files, CI logs, workflows), use MCP tools (`get_changed_files`, `read_ci_logs`, `gh audit-pr`).
|
|
12
|
+
- Identify affected files, routes, tests, and likely validation commands.
|
|
13
|
+
|
|
14
|
+
Output:
|
|
15
|
+
- changed files
|
|
16
|
+
- affected routes
|
|
17
|
+
- relevant scripts (referencing `package.json` in `.agent-context.json`)
|
|
18
|
+
- CI failures (consult `read_ci_logs` or `gh audit-pr`)
|
|
19
|
+
- likely cause
|
|
20
|
+
- repair risk
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
You are the Boomtick Verifier Agent.
|
|
2
|
+
|
|
3
|
+
Your job is to prove whether the repair is safe.
|
|
4
|
+
|
|
5
|
+
Run the appropriate commands based on changed files.
|
|
6
|
+
|
|
7
|
+
Required:
|
|
8
|
+
- install
|
|
9
|
+
- lint
|
|
10
|
+
- test
|
|
11
|
+
- build
|
|
12
|
+
|
|
13
|
+
Conditional:
|
|
14
|
+
- Playwright for UI/routes/browser behavior
|
|
15
|
+
- Lighthouse for performance/accessibility/SEO-sensitive changes
|
|
16
|
+
|
|
17
|
+
Output:
|
|
18
|
+
- commands run
|
|
19
|
+
- pass/fail status
|
|
20
|
+
- error summaries
|
|
21
|
+
- report paths
|
|
22
|
+
- remaining risks
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
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
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
dotenv.config({
|
|
10
|
+
path: path.resolve(__dirname, "../.env"),
|
|
11
|
+
quiet: true
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function getGithubToken(): string | undefined {
|
|
15
|
+
if (process.env.GITHUB_TOKEN) {
|
|
16
|
+
return process.env.GITHUB_TOKEN;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const token = execSync("gh auth token", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
20
|
+
if (token) {
|
|
21
|
+
return token;
|
|
22
|
+
}
|
|
23
|
+
} catch (e) { /* ignore to allow fallback */ }
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let cachedDynamicConfig: any = null;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Explicitly initializes dynamic configuration from the Python CLI.
|
|
31
|
+
* Should be called once during server startup.
|
|
32
|
+
*/
|
|
33
|
+
export function initializeConfig() {
|
|
34
|
+
if (cachedDynamicConfig !== null) {
|
|
35
|
+
return cachedDynamicConfig;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Attempt to load core properties from the Python CLI to avoid duplication
|
|
40
|
+
const cmd = `td-cli config view`;
|
|
41
|
+
const output = execSync(cmd, {
|
|
42
|
+
encoding: "utf-8",
|
|
43
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
44
|
+
});
|
|
45
|
+
cachedDynamicConfig = JSON.parse(output);
|
|
46
|
+
return cachedDynamicConfig;
|
|
47
|
+
} catch (e) {
|
|
48
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
49
|
+
const errorPrefix = `CRITICAL: Failed to load dynamic config from Python CLI. Ensure Python 3 is installed and cli is in your PYTHONPATH. Details: ${message}`;
|
|
50
|
+
|
|
51
|
+
if (process.env.NODE_ENV === "development" || process.env.CI === "true") {
|
|
52
|
+
throw new Error(errorPrefix);
|
|
53
|
+
} else {
|
|
54
|
+
console.warn(errorPrefix);
|
|
55
|
+
}
|
|
56
|
+
// Set an empty object to prevent repeated attempts if initialization fails in non-blocking mode
|
|
57
|
+
cachedDynamicConfig = {};
|
|
58
|
+
}
|
|
59
|
+
return cachedDynamicConfig;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function findRepoRoot() {
|
|
63
|
+
if (process.env.BOOMTICK_REPO_PATH) {
|
|
64
|
+
return process.env.BOOMTICK_REPO_PATH;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Traverse up to find workspace.json or .git
|
|
68
|
+
let current = __dirname;
|
|
69
|
+
while (current !== path.parse(current).root) {
|
|
70
|
+
if (fs.existsSync(path.join(current, "workspace.json")) || fs.existsSync(path.join(current, ".git"))) {
|
|
71
|
+
return current;
|
|
72
|
+
}
|
|
73
|
+
current = path.dirname(current);
|
|
74
|
+
}
|
|
75
|
+
// Fallback to the monolithic relative path if discovery fails
|
|
76
|
+
return path.resolve(__dirname, "../../../");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const config = {
|
|
80
|
+
get githubToken() { return getGithubToken(); },
|
|
81
|
+
get githubOwner() {
|
|
82
|
+
if (process.env.GITHUB_OWNER) return process.env.GITHUB_OWNER;
|
|
83
|
+
const repoString = cachedDynamicConfig?.github_repo;
|
|
84
|
+
if (typeof repoString !== "string" || !repoString.includes("/")) {
|
|
85
|
+
throw new Error("GITHUB_OWNER must be set via environment variable or project_config.json");
|
|
86
|
+
}
|
|
87
|
+
return repoString.split("/")[0];
|
|
88
|
+
},
|
|
89
|
+
get githubRepo() {
|
|
90
|
+
if (process.env.GITHUB_REPO) return process.env.GITHUB_REPO;
|
|
91
|
+
const repoString = cachedDynamicConfig?.github_repo;
|
|
92
|
+
if (typeof repoString !== "string" || !repoString.includes("/")) {
|
|
93
|
+
throw new Error("GITHUB_REPO must be set via environment variable or project_config.json");
|
|
94
|
+
}
|
|
95
|
+
return repoString.split("/")[1];
|
|
96
|
+
},
|
|
97
|
+
get repoPath() {
|
|
98
|
+
return findRepoRoot();
|
|
99
|
+
},
|
|
100
|
+
get defaultBaseBranch() {
|
|
101
|
+
return process.env.DEFAULT_BASE_BRANCH || cachedDynamicConfig?.base_branch?.split("/").pop() || "main";
|
|
102
|
+
},
|
|
103
|
+
get viteBasePath() {
|
|
104
|
+
const path = process.env.VITE_BASE_PATH || cachedDynamicConfig?.vite_base_path;
|
|
105
|
+
if (!path) {
|
|
106
|
+
throw new Error("VITE_BASE_PATH must be set via environment variable or project_config.json");
|
|
107
|
+
}
|
|
108
|
+
return path;
|
|
109
|
+
},
|
|
110
|
+
get ghPath() {
|
|
111
|
+
return process.env.GH_PATH || cachedDynamicConfig?.gh_path || "gh";
|
|
112
|
+
}
|
|
113
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { healthHandler, HealthCheckInputSchema } from "../mcp/tools.js";
|
|
2
|
+
import { searchOpenPrsHandler, SearchOpenPrsInputSchema } from "../tools/github.search_open_prs.js";
|
|
3
|
+
import { getPackageScriptsHandler, GetPackageScriptsInputSchema } from "../tools/repo.get_package_scripts.js";
|
|
4
|
+
|
|
5
|
+
async function runEvals() {
|
|
6
|
+
console.log("Starting Boomtick MCP Evaluations...");
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
console.log("\n1. Health Check:");
|
|
10
|
+
try {
|
|
11
|
+
const health = await healthHandler({ checkDeep: false });
|
|
12
|
+
console.log(JSON.stringify(health, null, 2));
|
|
13
|
+
} catch (e) {
|
|
14
|
+
console.log("Health check failed:", e instanceof Error ? e.message : String(e));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
console.log("\n2. Repository Scripts:");
|
|
18
|
+
try {
|
|
19
|
+
const scripts = await getPackageScriptsHandler({ filter: "" });
|
|
20
|
+
console.log(`Found ${Object.keys(scripts.scripts).length} scripts.`);
|
|
21
|
+
} catch (e) {
|
|
22
|
+
console.log("Repository scripts fetch failed:", e instanceof Error ? e.message : String(e));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log("\n3. GitHub PR Search (Dry Run):");
|
|
26
|
+
try {
|
|
27
|
+
const prs = await searchOpenPrsHandler({ state: "open", includeDrafts: true, limit: 100 });
|
|
28
|
+
console.log(`Found ${prs.prs.length} open PRs.`);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.log("GitHub search failed (likely due to environment/auth):", e instanceof Error ? e.message : String(e));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log("\nEvaluations complete.");
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error("\nEvaluation failed:", error);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
runEvals();
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { BoomtickMCPServer } from "./mcp/server.js";
|
|
3
|
+
import { initializeConfig } from "./config.js";
|
|
4
|
+
|
|
5
|
+
// Initialize configuration once at startup
|
|
6
|
+
initializeConfig();
|
|
7
|
+
|
|
8
|
+
const server = new BoomtickMCPServer();
|
|
9
|
+
server.run().catch((error) => {
|
|
10
|
+
console.error("Fatal error running server:", error);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
|
@@ -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: string): string {
|
|
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/src/lib/git.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { runCommand } from "./shell.js";
|
|
2
|
+
import { config } from "../config.js";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
|
|
6
|
+
function validatePrNumber(value: unknown): number {
|
|
7
|
+
const prNumber = Number(value);
|
|
8
|
+
|
|
9
|
+
if (!Number.isInteger(prNumber) || prNumber <= 0) {
|
|
10
|
+
throw new Error("Invalid PR number");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return prNumber;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function createWorktree(branch: string, prNumber: number): Promise<string> {
|
|
17
|
+
const safePrNumber = validatePrNumber(prNumber);
|
|
18
|
+
|
|
19
|
+
const workspaceRoot = "/tmp/boomtick-worktrees";
|
|
20
|
+
// nosemgrep
|
|
21
|
+
const worktreePath = path.join(workspaceRoot, `boomtick-mcp-rescue-${safePrNumber}`);
|
|
22
|
+
|
|
23
|
+
// Clean up if exists
|
|
24
|
+
try {
|
|
25
|
+
await fs.rm(worktreePath, { recursive: true, force: true });
|
|
26
|
+
await runCommand("git", ["worktree", "prune"]);
|
|
27
|
+
} catch (e) { /* ignore if no previous worktree */ }
|
|
28
|
+
|
|
29
|
+
const result = await runCommand("git", ["worktree", "add", "-b", `repair-pr-${prNumber}-${Date.now()}`, worktreePath, branch]);
|
|
30
|
+
|
|
31
|
+
if (result.exitCode !== 0) {
|
|
32
|
+
throw new Error(`Failed to create worktree: ${result.stderr}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return worktreePath;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function removeWorktree(worktreePath: string): Promise<void> {
|
|
39
|
+
await runCommand("git", ["worktree", "remove", "--force", worktreePath]);
|
|
40
|
+
await runCommand("git", ["worktree", "prune"]);
|
|
41
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
|
|
3
|
+
export interface ToolResult extends CallToolResult {
|
|
4
|
+
isError?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function createSuccessResult(data: any): ToolResult {
|
|
8
|
+
return {
|
|
9
|
+
content: [
|
|
10
|
+
{
|
|
11
|
+
type: "text",
|
|
12
|
+
text: typeof data === "string" ? data : JSON.stringify(data, null, 2),
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createErrorResult(message: string): ToolResult {
|
|
19
|
+
return {
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: "text",
|
|
23
|
+
text: message,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
isError: true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
// Simple test to verify environment and vitest setup
|
|
5
|
+
describe("Basic Environment", () => {
|
|
6
|
+
it("should have a valid repo path", () => {
|
|
7
|
+
const repoPath = process.env.BOOMTICK_REPO_PATH || "/app";
|
|
8
|
+
expect(repoPath).toBeDefined();
|
|
9
|
+
expect(path.isAbsolute(repoPath)).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
});
|
package/src/lib/shell.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { config } from "../config.js";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
|
|
6
|
+
export const ALLOWED_COMMANDS = {
|
|
7
|
+
git: "git",
|
|
8
|
+
gh: "gh",
|
|
9
|
+
"td-cli": "td-cli",
|
|
10
|
+
pnpm: "pnpm",
|
|
11
|
+
ls: "ls",
|
|
12
|
+
rm: "rm",
|
|
13
|
+
mkdir: "mkdir",
|
|
14
|
+
cp: "cp",
|
|
15
|
+
python3: "python3"
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export type AllowedCommand = keyof typeof ALLOWED_COMMANDS;
|
|
19
|
+
|
|
20
|
+
export interface ShellResult {
|
|
21
|
+
stdout: string;
|
|
22
|
+
stderr: string;
|
|
23
|
+
exitCode: number | null;
|
|
24
|
+
durationMs: number;
|
|
25
|
+
command: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function runCommand(
|
|
29
|
+
cmd: string,
|
|
30
|
+
args: string[],
|
|
31
|
+
options: { cwd?: string; timeout?: number; env?: Record<string, string> } = {}
|
|
32
|
+
): Promise<ShellResult> {
|
|
33
|
+
if (!(cmd in ALLOWED_COMMANDS)) {
|
|
34
|
+
throw new Error(`Command not allowed: ${cmd}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const safeCmd = cmd as AllowedCommand;
|
|
38
|
+
|
|
39
|
+
const start = Date.now();
|
|
40
|
+
const timeout = options.timeout || 60000;
|
|
41
|
+
|
|
42
|
+
// Resolve path for 'gh' or 'td-cli' if specified
|
|
43
|
+
let finalCmd = ALLOWED_COMMANDS[safeCmd] as string;
|
|
44
|
+
if (safeCmd === "gh") {
|
|
45
|
+
finalCmd = config.ghPath;
|
|
46
|
+
} else if (safeCmd === "td-cli") {
|
|
47
|
+
// If we have a repo-relative .venv, prioritize it
|
|
48
|
+
const venvBin = path.join(config.repoPath, ".venv", "bin", "td-cli");
|
|
49
|
+
const localVenvBin = path.join(process.cwd(), ".venv", "bin", "td-cli");
|
|
50
|
+
const parentVenvBin = path.join(config.repoPath, "..", ".venv", "bin", "td-cli");
|
|
51
|
+
|
|
52
|
+
// In standalone mode, config.repoPath is the boomtick-pkg root.
|
|
53
|
+
// We check common venv locations to ensure we call the isolated binary.
|
|
54
|
+
if (fs.existsSync(venvBin)) {
|
|
55
|
+
finalCmd = venvBin;
|
|
56
|
+
} else if (fs.existsSync(localVenvBin)) {
|
|
57
|
+
finalCmd = localVenvBin;
|
|
58
|
+
} else if (fs.existsSync(parentVenvBin)) {
|
|
59
|
+
finalCmd = parentVenvBin;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const env = {
|
|
64
|
+
...process.env,
|
|
65
|
+
...options.env,
|
|
66
|
+
GH_TOKEN: config.githubToken,
|
|
67
|
+
GITHUB_TOKEN: config.githubToken
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
// nosemgrep
|
|
72
|
+
const child = spawn(finalCmd, args, {
|
|
73
|
+
cwd: options.cwd || config.repoPath,
|
|
74
|
+
env
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
let stdout = "";
|
|
78
|
+
let stderr = "";
|
|
79
|
+
|
|
80
|
+
child.stdout.on("data", (data) => {
|
|
81
|
+
stdout += data.toString();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
child.stderr.on("data", (data) => {
|
|
85
|
+
stderr += data.toString();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const timer = setTimeout(() => {
|
|
89
|
+
child.kill();
|
|
90
|
+
reject(new Error(`Command timed out: ${cmd} ${args.join(" ")}`));
|
|
91
|
+
}, timeout);
|
|
92
|
+
|
|
93
|
+
child.on("close", (code) => {
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
const durationMs = Date.now() - start;
|
|
96
|
+
|
|
97
|
+
// Redact token from output
|
|
98
|
+
if (config.githubToken) {
|
|
99
|
+
const redact = (str: string) => str.replace(new RegExp(config.githubToken!, "g"), "REDACTED");
|
|
100
|
+
stdout = redact(stdout);
|
|
101
|
+
stderr = redact(stderr);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
resolve({
|
|
105
|
+
stdout,
|
|
106
|
+
stderr,
|
|
107
|
+
exitCode: code,
|
|
108
|
+
durationMs,
|
|
109
|
+
command: `${cmd} ${args.join(" ")}`
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
child.on("error", (err) => {
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
reject(err);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { runCommand } from "../lib/shell.js";
|
|
2
|
+
|
|
3
|
+
export interface TDCliResponse {
|
|
4
|
+
status: string;
|
|
5
|
+
message?: string;
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function tryParseJson(text: string): TDCliResponse | null {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(text) as TDCliResponse;
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function runTDCli(args: string[]): Promise<TDCliResponse> {
|
|
18
|
+
const result = await runCommand("td-cli", args);
|
|
19
|
+
const stdout = result.stdout.trim();
|
|
20
|
+
const output = stdout.startsWith("{") ? tryParseJson(stdout) : null;
|
|
21
|
+
|
|
22
|
+
// Handle structured errors (status: "error") regardless of exit code
|
|
23
|
+
if (output?.status === "error") {
|
|
24
|
+
throw new Error(`td-cli returned error: ${output.message ?? "Unknown error"}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Handle non-zero exit codes (shell failure)
|
|
28
|
+
if (result.exitCode !== 0) {
|
|
29
|
+
throw new Error(`td-cli command failed (${args.join(" ")}): ${result.stderr || stdout}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Ensure we have a valid JSON response for successful exit
|
|
33
|
+
if (!output) {
|
|
34
|
+
throw new Error(`td-cli returned non-JSON output with exit code 0: ${stdout}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return output;
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared test utilities for boomtick-mcp
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Ensures JULES_API_KEY is set in the environment for tests that require it.
|
|
9
|
+
* Should be called in beforeEach.
|
|
10
|
+
*/
|
|
11
|
+
export function setupTestEnv() {
|
|
12
|
+
vi.stubEnv("JULES_API_KEY", "test-key");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resets the test environment.
|
|
17
|
+
* Should be called in afterEach.
|
|
18
|
+
*/
|
|
19
|
+
export function teardownTestEnv() {
|
|
20
|
+
vi.unstubAllEnvs();
|
|
21
|
+
// Explicitly delete to satisfy strict isolation requirements if vi.unstubAllEnvs is not enough
|
|
22
|
+
delete process.env.JULES_API_KEY;
|
|
23
|
+
}
|