@arii/boomtick-mcp 0.6.1 → 0.6.2

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.
Files changed (69) hide show
  1. package/.mcp/schemas/github.create_issue.json +72 -3
  2. package/.mcp/schemas/github.get_merge_conflict_files.json +40 -3
  3. package/.mcp/schemas/github.get_pr_diff.json +30 -3
  4. package/.mcp/schemas/github.issue_update.json +72 -3
  5. package/.mcp/schemas/github.search_open_prs.json +130 -3
  6. package/.mcp/schemas/github_create_issue.json +72 -3
  7. package/.mcp/schemas/github_get_merge_conflict_files.json +40 -3
  8. package/.mcp/schemas/github_get_pr_diff.json +30 -3
  9. package/.mcp/schemas/github_issue_update.json +72 -3
  10. package/.mcp/schemas/github_search_open_prs.json +130 -3
  11. package/.mcp/schemas/repo.check_jules_status.json +38 -0
  12. package/.mcp/schemas/repo.run_tests.json +31 -3
  13. package/.mcp/schemas/repo_check_jules_status.json +38 -0
  14. package/.mcp/schemas/repo_run_tests.json +31 -3
  15. package/CHANGELOG.md +8 -0
  16. package/README.md +22 -1
  17. package/actions/run-project-gate/action.yml +1 -1
  18. package/dist/config/dynamic.js +33 -0
  19. package/dist/config/github.js +16 -0
  20. package/dist/config/repo.js +28 -0
  21. package/dist/config.js +16 -81
  22. package/dist/http.js +10 -0
  23. package/dist/mcp/definitions.js +29 -7
  24. package/dist/mcp/http.js +18 -10
  25. package/dist/mcp/http.test.js +54 -4
  26. package/dist/mcp/server.js +35 -10
  27. package/dist/mcp/server.test.js +28 -1
  28. package/dist/tools/contract.js +77 -0
  29. package/dist/tools/github.comment_triage_summary.js +3 -10
  30. package/dist/tools/github.get_merge_conflict_files.js +4 -2
  31. package/dist/tools/github.get_pr_diff.js +3 -11
  32. package/dist/tools/jules/cancel-session.js +2 -9
  33. package/dist/tools/jules/cli.js +31 -0
  34. package/dist/tools/jules/get-messages.js +2 -9
  35. package/dist/tools/jules/list-sessions.js +4 -35
  36. package/dist/tools/jules/send-message.js +2 -9
  37. package/dist/tools/jules/session.js +26 -0
  38. package/dist/tools/jules/shared.js +2 -50
  39. package/dist/tools/jules/trigger-feedback.js +2 -10
  40. package/dist/tools/repo.check_jules_status.js +148 -0
  41. package/dist/tools/repo.check_jules_status.test.js +47 -0
  42. package/dist/tools/repo.run_tests.js +13 -1
  43. package/package.json +3 -2
  44. package/scripts/sync-contracts.ts +11 -2
  45. package/src/config/dynamic.ts +36 -0
  46. package/src/config/github.ts +16 -0
  47. package/src/config/repo.ts +36 -0
  48. package/src/config.ts +16 -89
  49. package/src/http.ts +13 -0
  50. package/src/mcp/definitions.ts +34 -6
  51. package/src/mcp/http.test.ts +65 -5
  52. package/src/mcp/http.ts +20 -14
  53. package/src/mcp/server.test.ts +34 -1
  54. package/src/mcp/server.ts +36 -11
  55. package/src/tools/contract.ts +85 -0
  56. package/src/tools/github.comment_triage_summary.ts +3 -12
  57. package/src/tools/github.get_merge_conflict_files.ts +5 -2
  58. package/src/tools/github.get_pr_diff.ts +3 -13
  59. package/src/tools/jules/cancel-session.ts +2 -11
  60. package/src/tools/jules/cli.ts +42 -0
  61. package/src/tools/jules/get-messages.ts +2 -11
  62. package/src/tools/jules/list-sessions.ts +5 -39
  63. package/src/tools/jules/send-message.ts +2 -11
  64. package/src/tools/jules/session.ts +28 -0
  65. package/src/tools/jules/shared.ts +2 -63
  66. package/src/tools/jules/trigger-feedback.ts +2 -13
  67. package/src/tools/repo.check_jules_status.test.ts +55 -0
  68. package/src/tools/repo.check_jules_status.ts +176 -0
  69. package/src/tools/repo.run_tests.ts +14 -1
@@ -1,50 +1,2 @@
1
- import { runCommand } from "../../lib/shell.js";
2
- function tryParseJson(text) {
3
- try {
4
- return JSON.parse(text);
5
- }
6
- catch {
7
- return null;
8
- }
9
- }
10
- export async function runTDCli(args) {
11
- const result = await runCommand("td", args);
12
- const stdout = result.stdout.trim();
13
- const output = stdout.startsWith("{") ? tryParseJson(stdout) : null;
14
- if (output?.status === "error") {
15
- throw new Error(`td returned error: ${output.message ?? "Unknown error"}`);
16
- }
17
- if (result.exitCode !== 0) {
18
- throw new Error(`td command failed (${args.join(" ")}): ${result.stderr || stdout}`);
19
- }
20
- if (!output) {
21
- throw new Error(`td returned non-JSON output with exit code 0: ${stdout}`);
22
- }
23
- return output;
24
- }
25
- export function parseJulesSession(data) {
26
- const name = data.name || "";
27
- const id = name.startsWith("sessions/") ? name.substring(9) : name;
28
- let status = "PENDING";
29
- if (data.state === "SUCCEEDED" || data.state === "COMPLETED")
30
- status = "COMPLETED";
31
- else if (data.state === "FAILED" || data.state === "CANCELLED" || data.state === "TERMINATED")
32
- status = "FAILED";
33
- else if (data.state === "IN_PROGRESS")
34
- status = "IN_PROGRESS";
35
- let pullRequestUrl;
36
- if (data.outputs && Array.isArray(data.outputs)) {
37
- for (const out of data.outputs) {
38
- if (out.pullRequest && out.pullRequest.url) {
39
- pullRequestUrl = out.pullRequest.url;
40
- break;
41
- }
42
- }
43
- }
44
- return {
45
- id,
46
- status,
47
- createdAt: data.createTime ? new Date(data.createTime) : new Date(),
48
- pullRequestUrl,
49
- };
50
- }
1
+ export * from "./cli.js";
2
+ export * from "./session.js";
@@ -1,16 +1,8 @@
1
1
  import { z } from "zod";
2
- import { runCommand } from "../../lib/shell.js";
2
+ import { runTDCli } from "./cli.js";
3
3
  export const TriggerJulesFeedbackInputSchema = z.object({
4
4
  sessionId: z.string(),
5
5
  });
6
6
  export async function triggerJulesFeedbackHandler(input) {
7
- const result = await runCommand("td", ["agent", "trigger-feedback", input.sessionId]);
8
- if (result.exitCode !== 0) {
9
- throw new Error(`Failed to trigger feedback: ${result.stderr}`);
10
- }
11
- const output = JSON.parse(result.stdout);
12
- if (output.status === "error") {
13
- throw new Error(`Failed to trigger feedback: ${output.message}`);
14
- }
15
- return output;
7
+ return await runTDCli(["agent", "trigger-feedback", input.sessionId], "trigger feedback");
16
8
  }
@@ -0,0 +1,148 @@
1
+ import { z } from "zod";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import * as crypto from "crypto";
5
+ import { config } from "../config.js";
6
+ export const CheckJulesStatusInputSchema = z.object({
7
+ sessionId: z.string().optional().describe("The unique ID or task ID of the Jules session."),
8
+ commitSha: z.string().optional().describe("Commit SHA for idempotency key verification."),
9
+ failureLog: z.string().optional().describe("Failure log content for idempotency key calculation."),
10
+ poll: z.boolean().optional().describe("Whether to poll until session completion or timeout."),
11
+ timeoutMs: z.number().int().optional().describe("Timeout in milliseconds when polling."),
12
+ intervalMs: z.number().int().optional().describe("Polling interval in milliseconds."),
13
+ });
14
+ function calculateHash(commitSha, failureLog) {
15
+ const hash = crypto.createHash("sha256");
16
+ return hash.update(`${commitSha}:${failureLog.trim()}`).digest("hex");
17
+ }
18
+ export async function checkJulesStatusHandler(input) {
19
+ let resolvedTaskId = input.sessionId || process.env.TASK_ID;
20
+ if (!resolvedTaskId) {
21
+ return {
22
+ sessionId: "",
23
+ status: "NOT_FOUND",
24
+ completed: false,
25
+ message: "No session ID provided.",
26
+ };
27
+ }
28
+ const sessionId = resolvedTaskId.startsWith("sessions/") ? resolvedTaskId : `sessions/${resolvedTaskId}`;
29
+ const apiKey = process.env.JULES_API_KEY;
30
+ if (!apiKey) {
31
+ return {
32
+ sessionId,
33
+ status: "NOT_FOUND",
34
+ completed: false,
35
+ message: "JULES_API_KEY environment variable not set.",
36
+ };
37
+ }
38
+ const commitSha = input.commitSha || process.env.GITHUB_SHA || process.env.COMMIT_SHA;
39
+ let idempotencyHash;
40
+ let isDuplicate = false;
41
+ if (commitSha && input.failureLog) {
42
+ idempotencyHash = calculateHash(commitSha, input.failureLog);
43
+ const artifactsDir = path.join(config.repoPath || process.cwd(), "artifacts");
44
+ const hashTrackingFile = path.join(artifactsDir, `.jules-dispatch-${sessionId.replace(/[^a-zA-Z0-9_-]/g, "_")}.hash`);
45
+ if (fs.existsSync(hashTrackingFile)) {
46
+ try {
47
+ const existingHash = fs.readFileSync(hashTrackingFile, "utf8").trim();
48
+ if (existingHash === idempotencyHash) {
49
+ isDuplicate = true;
50
+ }
51
+ }
52
+ catch (e) {
53
+ console.warn("⚠️ Could not read previous Jules dispatch hash file during status check:", e);
54
+ }
55
+ }
56
+ }
57
+ const url = `https://jules.googleapis.com/v1alpha/${sessionId}`;
58
+ const poll = input.poll ?? false;
59
+ const timeoutMs = input.timeoutMs ?? 60000;
60
+ const intervalMs = input.intervalMs ?? 5000;
61
+ const startTime = Date.now();
62
+ const fetchSession = async () => {
63
+ const response = await fetch(url, {
64
+ method: "GET",
65
+ headers: {
66
+ "Content-Type": "application/json",
67
+ "x-goog-api-key": apiKey,
68
+ },
69
+ });
70
+ if (!response.ok) {
71
+ return null;
72
+ }
73
+ return await response.json();
74
+ };
75
+ while (true) {
76
+ let sessionData = null;
77
+ try {
78
+ sessionData = await fetchSession();
79
+ }
80
+ catch (e) {
81
+ console.warn(`⚠️ Error fetching Jules session status for ${sessionId}:`, e);
82
+ }
83
+ if (!sessionData) {
84
+ return {
85
+ sessionId,
86
+ status: "NOT_FOUND",
87
+ completed: false,
88
+ idempotencyHash,
89
+ isDuplicate,
90
+ message: `Failed to retrieve session data for ${sessionId}`,
91
+ };
92
+ }
93
+ const rawState = sessionData.state || "UNKNOWN";
94
+ let status = "IN_PROGRESS";
95
+ if (rawState === "SUCCEEDED" || rawState === "COMPLETED") {
96
+ status = "COMPLETED";
97
+ }
98
+ else if (rawState === "FAILED" || rawState === "CANCELLED" || rawState === "TERMINATED") {
99
+ status = "FAILED";
100
+ }
101
+ else if (rawState === "IN_PROGRESS" || rawState === "PENDING") {
102
+ status = "IN_PROGRESS";
103
+ }
104
+ let remediationBranch;
105
+ let pullRequestUrl;
106
+ if (sessionData.sourceContext?.githubRepoContext?.startingBranch) {
107
+ remediationBranch = sessionData.sourceContext.githubRepoContext.startingBranch;
108
+ }
109
+ if (Array.isArray(sessionData.outputs)) {
110
+ for (const output of sessionData.outputs) {
111
+ if (output?.pullRequest?.url) {
112
+ pullRequestUrl = output.pullRequest.url;
113
+ }
114
+ if (output?.pullRequest?.branch || output?.gitBranch) {
115
+ remediationBranch = output?.pullRequest?.branch || output?.gitBranch;
116
+ }
117
+ }
118
+ }
119
+ const completed = status === "COMPLETED";
120
+ if (!poll || status === "COMPLETED" || status === "FAILED") {
121
+ return {
122
+ sessionId,
123
+ status,
124
+ rawState,
125
+ completed,
126
+ remediationBranch,
127
+ pullRequestUrl,
128
+ idempotencyHash,
129
+ isDuplicate,
130
+ message: `Session state: ${rawState}`,
131
+ };
132
+ }
133
+ if (Date.now() - startTime >= timeoutMs) {
134
+ return {
135
+ sessionId,
136
+ status: "TIMED_OUT",
137
+ rawState,
138
+ completed: false,
139
+ remediationBranch,
140
+ pullRequestUrl,
141
+ idempotencyHash,
142
+ isDuplicate,
143
+ message: `Polling timed out after ${timeoutMs}ms (current state: ${rawState})`,
144
+ };
145
+ }
146
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
147
+ }
148
+ }
@@ -0,0 +1,47 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { checkJulesStatusHandler } from "./repo.check_jules_status.js";
3
+ describe("repo.check_jules_status MCP Tool Handler", () => {
4
+ const originalEnv = process.env;
5
+ beforeEach(() => {
6
+ vi.clearAllMocks();
7
+ process.env = { ...originalEnv };
8
+ delete process.env.TASK_ID;
9
+ delete process.env.JULES_API_KEY;
10
+ global.fetch = vi.fn();
11
+ });
12
+ afterEach(() => {
13
+ process.env = originalEnv;
14
+ vi.restoreAllMocks();
15
+ });
16
+ it("returns NOT_FOUND status if no session ID or JULES_API_KEY is available", async () => {
17
+ const result = await checkJulesStatusHandler({});
18
+ expect(result.status).toBe("NOT_FOUND");
19
+ expect(result.completed).toBe(false);
20
+ });
21
+ it("checks Jules session status and returns response object", async () => {
22
+ process.env.JULES_API_KEY = "test-api-key";
23
+ vi.mocked(global.fetch).mockResolvedValue({
24
+ ok: true,
25
+ json: vi.fn().mockResolvedValue({
26
+ name: "sessions/test-session-123",
27
+ state: "SUCCEEDED",
28
+ outputs: [
29
+ {
30
+ pullRequest: {
31
+ url: "https://github.com/org/repo/pull/100",
32
+ branch: "remediation-branch-123",
33
+ },
34
+ },
35
+ ],
36
+ }),
37
+ });
38
+ const result = await checkJulesStatusHandler({
39
+ sessionId: "test-session-123",
40
+ });
41
+ expect(result.sessionId).toBe("sessions/test-session-123");
42
+ expect(result.status).toBe("COMPLETED");
43
+ expect(result.completed).toBe(true);
44
+ expect(result.remediationBranch).toBe("remediation-branch-123");
45
+ expect(result.pullRequestUrl).toBe("https://github.com/org/repo/pull/100");
46
+ });
47
+ });
@@ -61,5 +61,17 @@ export async function runTestsHandler(args) {
61
61
  break;
62
62
  }
63
63
  }
64
- return { success, results };
64
+ const exitCode = results.length > 0 ? results[results.length - 1].exitCode : 0;
65
+ const failedTests = [];
66
+ for (const res of results) {
67
+ if (res.exitCode !== 0) {
68
+ failedTests.push(res.command);
69
+ }
70
+ }
71
+ return {
72
+ passed: success,
73
+ exitCode,
74
+ failedTests,
75
+ results,
76
+ };
65
77
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arii/boomtick-mcp",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Boomtick MCP Guide: PR Rescue + Merge Conflict Agent",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,7 +13,8 @@
13
13
  "type": "module",
14
14
  "main": "dist/index.js",
15
15
  "bin": {
16
- "boomtick-mcp": "dist/index.js"
16
+ "boomtick-mcp": "dist/index.js",
17
+ "boomtick-mcp-http": "dist/http.js"
17
18
  },
18
19
  "dependencies": {
19
20
  "@modelcontextprotocol/sdk": "^1.6.0",
@@ -216,9 +216,18 @@ async function syncContracts() {
216
216
  if (!fs.existsSync(path.join(rootDir, 'pnpm-workspace.yaml'))) {
217
217
  rootDir = path.dirname(rootDir);
218
218
  }
219
- const pythonScriptPath = path.join(rootDir, 'cli/dev_tools/schema_gen.py');
220
219
  console.log(`Regenerating cli-schema.json...`);
221
- execFileSync('python3', [pythonScriptPath], { cwd: rootDir, stdio: 'inherit' });
220
+ const cliDir = path.join(rootDir, 'cli');
221
+ const existingPythonPath = process.env.PYTHONPATH || '';
222
+ const newPythonPath = existingPythonPath ? `${existingPythonPath}${path.delimiter}${cliDir}` : cliDir;
223
+ execFileSync('python3', ['-m', 'dev_tools.schema_gen'], {
224
+ cwd: rootDir,
225
+ stdio: 'inherit',
226
+ env: {
227
+ ...process.env,
228
+ PYTHONPATH: newPythonPath,
229
+ },
230
+ });
222
231
 
223
232
  // Then perform the original Zod contract generation
224
233
  let jsonSchemaToZod;
@@ -0,0 +1,36 @@
1
+ import { execSync } from "child_process";
2
+
3
+ let cachedDynamicConfig: any = null;
4
+
5
+ export function initializeConfig() {
6
+ if (cachedDynamicConfig !== null) {
7
+ return cachedDynamicConfig;
8
+ }
9
+
10
+ try {
11
+ const cwd = process.env.BOOMTICK_REPO_PATH || process.cwd();
12
+ const cmd = `td config view`;
13
+ const output = execSync(cmd, {
14
+ cwd,
15
+ encoding: "utf-8",
16
+ stdio: ["ignore", "pipe", "ignore"],
17
+ });
18
+ cachedDynamicConfig = JSON.parse(output);
19
+ return cachedDynamicConfig;
20
+ } catch (e) {
21
+ const message = e instanceof Error ? e.message : String(e);
22
+ const errorPrefix = `CRITICAL: Failed to load dynamic config from Python CLI. Ensure Python 3 is installed and cli is in your PYTHONPATH. Details: ${message}`;
23
+
24
+ if (process.env.NODE_ENV === "development" || process.env.CI === "true") {
25
+ throw new Error(errorPrefix);
26
+ } else {
27
+ console.warn(errorPrefix);
28
+ }
29
+ cachedDynamicConfig = {};
30
+ }
31
+ return cachedDynamicConfig;
32
+ }
33
+
34
+ export function getCachedDynamicConfig(): any {
35
+ return cachedDynamicConfig;
36
+ }
@@ -0,0 +1,16 @@
1
+ import { execSync } from "child_process";
2
+
3
+ export function getGithubToken(): string | undefined {
4
+ if (process.env.GITHUB_TOKEN) {
5
+ return process.env.GITHUB_TOKEN;
6
+ }
7
+ try {
8
+ const token = execSync("gh auth token", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
9
+ if (token) {
10
+ return token;
11
+ }
12
+ } catch {
13
+ /* ignore to allow fallback */
14
+ }
15
+ return undefined;
16
+ }
@@ -0,0 +1,36 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { fileURLToPath } from "url";
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+
7
+ export function findRepoRoot(): string {
8
+ if (process.env.BOOMTICK_REPO_PATH) {
9
+ return process.env.BOOMTICK_REPO_PATH;
10
+ }
11
+
12
+ let current = process.cwd();
13
+ while (current !== path.parse(current).root) {
14
+ if (
15
+ fs.existsSync(path.join(current, "project_config.json")) ||
16
+ fs.existsSync(path.join(current, "workspace.json")) ||
17
+ fs.existsSync(path.join(current, ".git"))
18
+ ) {
19
+ return current;
20
+ }
21
+ current = path.dirname(current);
22
+ }
23
+
24
+ current = __dirname;
25
+ while (current !== path.parse(current).root) {
26
+ if (
27
+ fs.existsSync(path.join(current, "project_config.json")) ||
28
+ fs.existsSync(path.join(current, "workspace.json")) ||
29
+ fs.existsSync(path.join(current, ".git"))
30
+ ) {
31
+ return current;
32
+ }
33
+ current = path.dirname(current);
34
+ }
35
+ return path.resolve(__dirname, "../../../../");
36
+ }
package/src/config.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import path from "path";
2
2
  import fs from "fs";
3
3
  import { fileURLToPath } from "url";
4
- import { execSync } from "child_process";
4
+ import { findRepoRoot } from "./config/repo.js";
5
+ import { getGithubToken } from "./config/github.js";
6
+ import { initializeConfig, getCachedDynamicConfig } from "./config/dynamic.js";
5
7
 
6
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
9
 
@@ -14,93 +16,14 @@ if (fs.existsSync(envPath) && typeof process.loadEnvFile === "function") {
14
16
  }
15
17
  }
16
18
 
17
- function getGithubToken(): string | undefined {
18
- if (process.env.GITHUB_TOKEN) {
19
- return process.env.GITHUB_TOKEN;
20
- }
21
- try {
22
- const token = execSync("gh auth token", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
23
- if (token) {
24
- return token;
25
- }
26
- } catch (e) { /* ignore to allow fallback */ }
27
- return undefined;
28
- }
29
-
30
- let cachedDynamicConfig: any = null;
31
-
32
- /**
33
- * Explicitly initializes dynamic configuration from the Python CLI.
34
- * Should be called once during server startup.
35
- */
36
- export function initializeConfig() {
37
- if (cachedDynamicConfig !== null) {
38
- return cachedDynamicConfig;
39
- }
40
-
41
- try {
42
- // Attempt to load core properties from the Python CLI in the active workspace
43
- const cwd = process.env.BOOMTICK_REPO_PATH || process.cwd();
44
- const cmd = `td config view`;
45
- const output = execSync(cmd, {
46
- cwd,
47
- encoding: "utf-8",
48
- stdio: ["ignore", "pipe", "ignore"]
49
- });
50
- cachedDynamicConfig = JSON.parse(output);
51
- return cachedDynamicConfig;
52
- } catch (e) {
53
- const message = e instanceof Error ? e.message : String(e);
54
- const errorPrefix = `CRITICAL: Failed to load dynamic config from Python CLI. Ensure Python 3 is installed and cli is in your PYTHONPATH. Details: ${message}`;
55
-
56
- if (process.env.NODE_ENV === "development" || process.env.CI === "true") {
57
- throw new Error(errorPrefix);
58
- } else {
59
- console.warn(errorPrefix);
60
- }
61
- // Set an empty object to prevent repeated attempts if initialization fails in non-blocking mode
62
- cachedDynamicConfig = {};
63
- }
64
- return cachedDynamicConfig;
65
- }
66
-
67
- function findRepoRoot() {
68
- if (process.env.BOOMTICK_REPO_PATH) {
69
- return process.env.BOOMTICK_REPO_PATH;
70
- }
71
-
72
- // Traverse up from process.cwd() to find project_config.json, workspace.json, or .git
73
- let current = process.cwd();
74
- while (current !== path.parse(current).root) {
75
- if (
76
- fs.existsSync(path.join(current, "project_config.json")) ||
77
- fs.existsSync(path.join(current, "workspace.json")) ||
78
- fs.existsSync(path.join(current, ".git"))
79
- ) {
80
- return current;
81
- }
82
- current = path.dirname(current);
83
- }
84
-
85
- // Fallback to __dirname search if discovery from cwd fails
86
- current = __dirname;
87
- while (current !== path.parse(current).root) {
88
- if (
89
- fs.existsSync(path.join(current, "project_config.json")) ||
90
- fs.existsSync(path.join(current, "workspace.json")) ||
91
- fs.existsSync(path.join(current, ".git"))
92
- ) {
93
- return current;
94
- }
95
- current = path.dirname(current);
96
- }
97
- // Fallback to the monolithic relative path if discovery fails
98
- return path.resolve(__dirname, "../../../");
99
- }
19
+ export { initializeConfig };
100
20
 
101
21
  export const config = {
102
- get githubToken() { return getGithubToken(); },
22
+ get githubToken() {
23
+ return getGithubToken();
24
+ },
103
25
  get githubOwner() {
26
+ const cachedDynamicConfig = getCachedDynamicConfig();
104
27
  const repoString = cachedDynamicConfig?.github_repo;
105
28
  if (typeof repoString === "string" && repoString.includes("/")) {
106
29
  return repoString.split("/")[0];
@@ -113,6 +36,7 @@ export const config = {
113
36
  throw new Error("GITHUB_OWNER must be set via environment variable or project_config.json");
114
37
  },
115
38
  get githubRepo() {
39
+ const cachedDynamicConfig = getCachedDynamicConfig();
116
40
  const repoString = cachedDynamicConfig?.github_repo;
117
41
  if (typeof repoString === "string" && repoString.includes("/")) {
118
42
  return repoString.split("/")[1];
@@ -128,16 +52,19 @@ export const config = {
128
52
  return findRepoRoot();
129
53
  },
130
54
  get defaultBaseBranch() {
55
+ const cachedDynamicConfig = getCachedDynamicConfig();
131
56
  return process.env.DEFAULT_BASE_BRANCH || cachedDynamicConfig?.base_branch?.split("/").pop() || "main";
132
57
  },
133
58
  get viteBasePath() {
134
- const path = process.env.VITE_BASE_PATH || cachedDynamicConfig?.vite_base_path;
135
- if (!path) {
59
+ const cachedDynamicConfig = getCachedDynamicConfig();
60
+ const pathValue = process.env.VITE_BASE_PATH || cachedDynamicConfig?.vite_base_path;
61
+ if (!pathValue) {
136
62
  throw new Error("VITE_BASE_PATH must be set via environment variable or project_config.json");
137
63
  }
138
- return path;
64
+ return pathValue;
139
65
  },
140
66
  get ghPath() {
67
+ const cachedDynamicConfig = getCachedDynamicConfig();
141
68
  return process.env.GH_PATH || cachedDynamicConfig?.gh_path || "gh";
142
- }
69
+ },
143
70
  };
package/src/http.ts ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import { BoomtickMCPServer } from "./mcp/server.js";
3
+ import { initializeConfig } from "./config.js";
4
+
5
+ initializeConfig();
6
+
7
+ process.env.MCP_TRANSPORT = "http";
8
+
9
+ const server = new BoomtickMCPServer();
10
+ server.run().catch((error) => {
11
+ console.error("Fatal error running HTTP server:", error);
12
+ process.exit(1);
13
+ });