@arii/boomtick-mcp 0.6.1 → 0.7.0
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/.mcp/schemas/boomtick.health.json +22 -3
- package/.mcp/schemas/boomtick_health.json +22 -3
- package/.mcp/schemas/github.checkout_branch.json +16 -3
- package/.mcp/schemas/github.comment_triage_summary.json +16 -3
- package/.mcp/schemas/github.create_issue.json +72 -3
- package/.mcp/schemas/github.create_pull_request.json +16 -3
- package/.mcp/schemas/github.get_merge_conflict_files.json +40 -3
- package/.mcp/schemas/github.get_pr.json +12 -3
- package/.mcp/schemas/github.get_pr_commits.json +12 -3
- package/.mcp/schemas/github.get_pr_diff.json +30 -3
- package/.mcp/schemas/github.get_pr_reviews.json +24 -3
- package/.mcp/schemas/github.issue_comment.json +19 -3
- package/.mcp/schemas/github.issue_update.json +72 -3
- package/.mcp/schemas/github.issue_view.json +17 -3
- package/.mcp/schemas/github.open_replacement_pr.json +16 -3
- package/.mcp/schemas/github.search_open_prs.json +130 -3
- package/.mcp/schemas/github_checkout_branch.json +16 -3
- package/.mcp/schemas/github_comment_triage_summary.json +16 -3
- package/.mcp/schemas/github_create_issue.json +72 -3
- package/.mcp/schemas/github_create_pull_request.json +16 -3
- package/.mcp/schemas/github_get_merge_conflict_files.json +40 -3
- package/.mcp/schemas/github_get_pr.json +12 -3
- package/.mcp/schemas/github_get_pr_commits.json +12 -3
- package/.mcp/schemas/github_get_pr_diff.json +30 -3
- package/.mcp/schemas/github_get_pr_reviews.json +24 -3
- package/.mcp/schemas/github_issue_comment.json +19 -3
- package/.mcp/schemas/github_issue_update.json +72 -3
- package/.mcp/schemas/github_issue_view.json +17 -3
- package/.mcp/schemas/github_open_replacement_pr.json +16 -3
- package/.mcp/schemas/github_search_open_prs.json +130 -3
- package/.mcp/schemas/jules.cancel_session.json +38 -3
- package/.mcp/schemas/jules.create_session.json +52 -3
- package/.mcp/schemas/jules.get_messages.json +19 -3
- package/.mcp/schemas/jules.get_pr.json +23 -3
- package/.mcp/schemas/jules.get_session.json +38 -3
- package/.mcp/schemas/jules.list_sessions.json +53 -3
- package/.mcp/schemas/jules.send_message.json +31 -3
- package/.mcp/schemas/jules.trigger_feedback.json +28 -3
- package/.mcp/schemas/jules_cancel_session.json +38 -3
- package/.mcp/schemas/jules_create_session.json +52 -3
- package/.mcp/schemas/jules_get_messages.json +19 -3
- package/.mcp/schemas/jules_get_pr.json +23 -3
- package/.mcp/schemas/jules_get_session.json +38 -3
- package/.mcp/schemas/jules_list_sessions.json +53 -3
- package/.mcp/schemas/jules_send_message.json +31 -3
- package/.mcp/schemas/jules_trigger_feedback.json +28 -3
- package/.mcp/schemas/repo.check_jules_status.json +128 -0
- package/.mcp/schemas/repo.commit_patch.json +23 -3
- package/.mcp/schemas/repo.create_branch.json +28 -3
- package/.mcp/schemas/repo.create_repair_branch.json +31 -3
- package/.mcp/schemas/repo.get_changed_files.json +11 -3
- package/.mcp/schemas/repo.get_command_schema.json +29 -3
- package/.mcp/schemas/repo.get_package_scripts.json +11 -3
- package/.mcp/schemas/repo.get_workflow_runs.json +12 -3
- package/.mcp/schemas/repo.logs.json +14 -3
- package/.mcp/schemas/repo.multi_exec.json +89 -0
- package/.mcp/schemas/repo.read_agent_context.json +117 -5
- package/.mcp/schemas/repo.read_ci_logs.json +46 -3
- package/.mcp/schemas/repo.run_tests.json +31 -3
- package/.mcp/schemas/repo_check_jules_status.json +128 -0
- package/.mcp/schemas/repo_commit_patch.json +23 -3
- package/.mcp/schemas/repo_create_branch.json +28 -3
- package/.mcp/schemas/repo_create_repair_branch.json +31 -3
- package/.mcp/schemas/repo_get_changed_files.json +11 -3
- package/.mcp/schemas/repo_get_command_schema.json +29 -3
- package/.mcp/schemas/repo_get_package_scripts.json +11 -3
- package/.mcp/schemas/repo_get_workflow_runs.json +12 -3
- package/.mcp/schemas/repo_logs.json +14 -3
- package/.mcp/schemas/repo_multi_exec.json +89 -0
- package/.mcp/schemas/repo_read_agent_context.json +117 -5
- package/.mcp/schemas/repo_read_ci_logs.json +46 -3
- package/.mcp/schemas/repo_run_tests.json +31 -3
- package/CHANGELOG.md +22 -0
- package/README.md +29 -6
- package/actions/run-project-gate/action.yml +1 -1
- package/dist/config/dynamic.js +34 -0
- package/dist/config/github.js +16 -0
- package/dist/config/repo.js +28 -0
- package/dist/config.js +16 -81
- package/dist/config.test.js +10 -3
- package/dist/http.js +10 -0
- package/dist/mcp/definitions.js +76 -41
- package/dist/mcp/http.js +32 -9
- package/dist/mcp/http.test.js +62 -4
- package/dist/mcp/server.js +69 -10
- package/dist/mcp/server.test.js +59 -1
- package/dist/tools/contract.js +1079 -45
- package/dist/tools/github.comment_triage_summary.js +3 -10
- package/dist/tools/github.get_merge_conflict_files.js +4 -2
- package/dist/tools/github.get_pr_diff.js +3 -11
- package/dist/tools/jules/cancel-session.js +2 -9
- package/dist/tools/jules/cli.js +31 -0
- package/dist/tools/jules/create-session.js +12 -2
- package/dist/tools/jules/create-session.test.js +7 -0
- package/dist/tools/jules/get-messages.js +2 -9
- package/dist/tools/jules/list-sessions.js +4 -35
- package/dist/tools/jules/send-message.js +2 -9
- package/dist/tools/jules/session.js +26 -0
- package/dist/tools/jules/shared.js +2 -50
- package/dist/tools/jules/trigger-feedback.js +2 -10
- package/dist/tools/repo.check_jules_status.js +148 -0
- package/dist/tools/repo.check_jules_status.test.js +47 -0
- package/dist/tools/repo.multi_exec.js +21 -0
- package/dist/tools/repo.multi_exec.test.js +13 -0
- package/dist/tools/repo.read_agent_context.js +65 -3
- package/dist/tools/repo.read_agent_context.test.js +30 -1
- package/dist/tools/repo.run_tests.js +13 -1
- package/package.json +3 -2
- package/scripts/sync-contracts.ts +11 -2
- package/src/config/dynamic.ts +37 -0
- package/src/config/github.ts +16 -0
- package/src/config/repo.ts +36 -0
- package/src/config.test.ts +11 -3
- package/src/config.ts +16 -89
- package/src/http.ts +13 -0
- package/src/mcp/definitions.ts +110 -41
- package/src/mcp/http.test.ts +75 -5
- package/src/mcp/http.ts +40 -14
- package/src/mcp/server.test.ts +72 -1
- package/src/mcp/server.ts +72 -11
- package/src/tools/contract.ts +1194 -36
- package/src/tools/github.comment_triage_summary.ts +3 -12
- package/src/tools/github.get_merge_conflict_files.ts +5 -2
- package/src/tools/github.get_pr_diff.ts +3 -13
- package/src/tools/jules/cancel-session.ts +2 -11
- package/src/tools/jules/cli.ts +42 -0
- package/src/tools/jules/create-session.test.ts +9 -0
- package/src/tools/jules/create-session.ts +13 -2
- package/src/tools/jules/get-messages.ts +2 -11
- package/src/tools/jules/list-sessions.ts +5 -39
- package/src/tools/jules/send-message.ts +2 -11
- package/src/tools/jules/session.ts +28 -0
- package/src/tools/jules/shared.ts +2 -63
- package/src/tools/jules/trigger-feedback.ts +2 -13
- package/src/tools/repo.check_jules_status.test.ts +55 -0
- package/src/tools/repo.check_jules_status.ts +176 -0
- package/src/tools/repo.multi_exec.test.ts +15 -0
- package/src/tools/repo.multi_exec.ts +23 -0
- package/src/tools/repo.read_agent_context.test.ts +35 -1
- package/src/tools/repo.read_agent_context.ts +68 -3
- package/src/tools/repo.run_tests.ts +14 -1
|
@@ -3,6 +3,7 @@ import { config } from "../config.js";
|
|
|
3
3
|
import { execSync } from "child_process";
|
|
4
4
|
import * as fs from "fs";
|
|
5
5
|
import * as path from "path";
|
|
6
|
+
import { runTDCli } from "./jules/shared.js";
|
|
6
7
|
export const loadStandaloneAgentContext = (repoRoot = process.cwd()) => {
|
|
7
8
|
const configPath = path.join(repoRoot, "project_config.json");
|
|
8
9
|
let projectConfig = {};
|
|
@@ -27,13 +28,74 @@ export const loadStandaloneAgentContext = (repoRoot = process.cwd()) => {
|
|
|
27
28
|
config: projectConfig,
|
|
28
29
|
};
|
|
29
30
|
};
|
|
30
|
-
export const ReadAgentContextInputSchema = z.object({
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
export const ReadAgentContextInputSchema = z.object({
|
|
32
|
+
path: z.string().optional().describe("Local filesystem path to target repository root"),
|
|
33
|
+
repoPath: z.string().optional().describe("Alias for path to target repository root"),
|
|
34
|
+
repo: z.string().optional().describe("Repository name override (e.g. arii/portfolio or portfolio)"),
|
|
35
|
+
repos: z.array(z.string()).optional().describe("List of repository names to fetch context for"),
|
|
36
|
+
all: z.boolean().optional().describe("Whether to fetch context across all discovered ecosystem repositories"),
|
|
37
|
+
});
|
|
38
|
+
export async function readAgentContextHandler(args) {
|
|
39
|
+
// 1. Ecosystem / Multi-repo request
|
|
40
|
+
if (args.all || (args.repos && args.repos.length > 0)) {
|
|
41
|
+
const cliArgs = ["multi-repo", "status"];
|
|
42
|
+
if (args.repos) {
|
|
43
|
+
for (const r of args.repos) {
|
|
44
|
+
cliArgs.push("-r", r);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const output = await runTDCli(cliArgs, "fetch multi-repo context");
|
|
48
|
+
return {
|
|
49
|
+
status: "success",
|
|
50
|
+
ecosystem: true,
|
|
51
|
+
repos: output.repos ?? [],
|
|
52
|
+
total: output.total ?? (output.repos?.length ?? 0)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// 2. Specific repository name override
|
|
56
|
+
if (args.repo) {
|
|
57
|
+
let matchedPath;
|
|
58
|
+
try {
|
|
59
|
+
const output = await runTDCli(["multi-repo", "status", "-r", args.repo], "find target repository");
|
|
60
|
+
const matched = output.repos?.[0];
|
|
61
|
+
if (matched?.path) {
|
|
62
|
+
matchedPath = matched.path;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Fallback to directory inspection if CLI is unavailable or not in TD workspace
|
|
67
|
+
}
|
|
68
|
+
if (!matchedPath) {
|
|
69
|
+
const repoName = args.repo.includes("/") ? args.repo.split("/")[1] : args.repo;
|
|
70
|
+
const cwdParent = path.resolve(process.cwd(), "..", repoName);
|
|
71
|
+
const configParent = path.resolve(config.repoPath, "..", repoName);
|
|
72
|
+
if (fs.existsSync(cwdParent)) {
|
|
73
|
+
matchedPath = cwdParent;
|
|
74
|
+
}
|
|
75
|
+
else if (fs.existsSync(configParent)) {
|
|
76
|
+
matchedPath = configParent;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (matchedPath) {
|
|
80
|
+
const context = loadStandaloneAgentContext(matchedPath);
|
|
81
|
+
return {
|
|
82
|
+
status: "success",
|
|
83
|
+
repo: args.repo,
|
|
84
|
+
path: matchedPath,
|
|
85
|
+
...context
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 3. Specific filesystem path
|
|
90
|
+
const targetPath = (args.path || args.repoPath) ? path.resolve(args.path || args.repoPath) : config.repoPath;
|
|
91
|
+
const context = loadStandaloneAgentContext(targetPath);
|
|
33
92
|
const result = {
|
|
34
93
|
status: "success",
|
|
35
94
|
...context
|
|
36
95
|
};
|
|
96
|
+
if (args?.repo) {
|
|
97
|
+
result.repo = args.repo;
|
|
98
|
+
}
|
|
37
99
|
delete result.submodules;
|
|
38
100
|
if (result.config) {
|
|
39
101
|
delete result.config.submodules;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { loadStandaloneAgentContext } from "./repo.read_agent_context.js";
|
|
2
|
+
import { loadStandaloneAgentContext, readAgentContextHandler } from "./repo.read_agent_context.js";
|
|
3
3
|
describe("Standalone Agent Context Loader", () => {
|
|
4
4
|
it("should load agent context without submodule references", () => {
|
|
5
5
|
const context = loadStandaloneAgentContext(process.cwd());
|
|
@@ -7,4 +7,33 @@ describe("Standalone Agent Context Loader", () => {
|
|
|
7
7
|
expect(context).toHaveProperty("gitCommit");
|
|
8
8
|
expect(context).not.toHaveProperty("submodules");
|
|
9
9
|
});
|
|
10
|
+
it("should handle readAgentContextHandler with default workspace", async () => {
|
|
11
|
+
const res = await readAgentContextHandler({});
|
|
12
|
+
expect(res.status).toBe("success");
|
|
13
|
+
expect(res).toHaveProperty("packageName");
|
|
14
|
+
expect(res).toHaveProperty("gitCommit");
|
|
15
|
+
});
|
|
16
|
+
it("readAgentContextHandler should accept optional repoPath argument", async () => {
|
|
17
|
+
const res = await readAgentContextHandler({ repoPath: process.cwd() });
|
|
18
|
+
expect(res).toHaveProperty("status", "success");
|
|
19
|
+
expect(res).toHaveProperty("packageName");
|
|
20
|
+
expect(res).toHaveProperty("gitCommit");
|
|
21
|
+
});
|
|
22
|
+
it("readAgentContextHandler should accept path argument", async () => {
|
|
23
|
+
const res = await readAgentContextHandler({ path: process.cwd() });
|
|
24
|
+
expect(res).toHaveProperty("status", "success");
|
|
25
|
+
expect(res).toHaveProperty("packageName");
|
|
26
|
+
});
|
|
27
|
+
it("readAgentContextHandler should accept repo argument", async () => {
|
|
28
|
+
const res = await readAgentContextHandler({ repo: "arii/tech-dancer" });
|
|
29
|
+
expect(res).toHaveProperty("status", "success");
|
|
30
|
+
expect(res).toHaveProperty("repo", "arii/tech-dancer");
|
|
31
|
+
});
|
|
32
|
+
it("should handle multi-repo context querying when all: true is passed", async () => {
|
|
33
|
+
const res = await readAgentContextHandler({ all: true });
|
|
34
|
+
expect(res.status).toBe("success");
|
|
35
|
+
expect(res.ecosystem).toBe(true);
|
|
36
|
+
expect(Array.isArray(res.repos)).toBe(true);
|
|
37
|
+
expect(res.repos.length).toBeGreaterThan(0);
|
|
38
|
+
});
|
|
10
39
|
});
|
|
@@ -61,5 +61,17 @@ export async function runTestsHandler(args) {
|
|
|
61
61
|
break;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
|
|
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.
|
|
3
|
+
"version": "0.7.0",
|
|
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
|
-
|
|
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,37 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { findRepoRoot } from "./repo.js";
|
|
3
|
+
|
|
4
|
+
let cachedDynamicConfig: any = null;
|
|
5
|
+
|
|
6
|
+
export function initializeConfig(targetCwd?: string) {
|
|
7
|
+
if (cachedDynamicConfig !== null) {
|
|
8
|
+
return cachedDynamicConfig;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const cwd = findRepoRoot(targetCwd);
|
|
13
|
+
const cmd = `td config view`;
|
|
14
|
+
const output = execSync(cmd, {
|
|
15
|
+
cwd,
|
|
16
|
+
encoding: "utf-8",
|
|
17
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
18
|
+
});
|
|
19
|
+
cachedDynamicConfig = JSON.parse(output);
|
|
20
|
+
return cachedDynamicConfig;
|
|
21
|
+
} catch (e) {
|
|
22
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
23
|
+
const errorPrefix = `CRITICAL: Failed to load dynamic config from Python CLI. Ensure Python 3 is installed and cli is in your PYTHONPATH. Details: ${message}`;
|
|
24
|
+
|
|
25
|
+
if (process.env.NODE_ENV === "development" || process.env.CI === "true") {
|
|
26
|
+
throw new Error(errorPrefix);
|
|
27
|
+
} else {
|
|
28
|
+
console.warn(errorPrefix);
|
|
29
|
+
}
|
|
30
|
+
cachedDynamicConfig = {};
|
|
31
|
+
}
|
|
32
|
+
return cachedDynamicConfig;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getCachedDynamicConfig(): any {
|
|
36
|
+
return cachedDynamicConfig;
|
|
37
|
+
}
|
|
@@ -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(startDir?: string): string {
|
|
8
|
+
let current = startDir || process.cwd();
|
|
9
|
+
while (current !== path.parse(current).root) {
|
|
10
|
+
if (
|
|
11
|
+
fs.existsSync(path.join(current, "project_config.json")) ||
|
|
12
|
+
fs.existsSync(path.join(current, "workspace.json")) ||
|
|
13
|
+
fs.existsSync(path.join(current, ".git"))
|
|
14
|
+
) {
|
|
15
|
+
return current;
|
|
16
|
+
}
|
|
17
|
+
current = path.dirname(current);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (process.env.BOOMTICK_REPO_PATH) {
|
|
21
|
+
return process.env.BOOMTICK_REPO_PATH;
|
|
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.test.ts
CHANGED
|
@@ -25,10 +25,11 @@ describe("config", () => {
|
|
|
25
25
|
vi.mocked(execSync).mockReturnValue(JSON.stringify(mockConfig));
|
|
26
26
|
|
|
27
27
|
const { initializeConfig } = await import("./config.js");
|
|
28
|
+
const { findRepoRoot } = await import("./config/repo.js");
|
|
28
29
|
const result = initializeConfig();
|
|
29
30
|
|
|
30
31
|
expect(execSync).toHaveBeenCalledWith("td config view", expect.objectContaining({
|
|
31
|
-
cwd:
|
|
32
|
+
cwd: findRepoRoot()
|
|
32
33
|
}));
|
|
33
34
|
expect(result).toEqual(mockConfig);
|
|
34
35
|
});
|
|
@@ -133,10 +134,17 @@ describe("config", () => {
|
|
|
133
134
|
expect(() => config.githubRepo).toThrow("GITHUB_REPO must be set via environment variable or project_config.json");
|
|
134
135
|
});
|
|
135
136
|
|
|
136
|
-
it("should resolve
|
|
137
|
+
it("should resolve active workspace from process.cwd() over BOOMTICK_REPO_PATH when inside a repo", async () => {
|
|
137
138
|
process.env.BOOMTICK_REPO_PATH = "/custom/repo/path";
|
|
138
139
|
const { config } = await import("./config.js");
|
|
139
|
-
expect(config.repoPath).toBe("
|
|
140
|
+
expect(config.repoPath).toBe(process.cwd().replace(/\/mcp$/, ""));
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should fallback to BOOMTICK_REPO_PATH when process.cwd() is outside any repo", async () => {
|
|
144
|
+
process.env.BOOMTICK_REPO_PATH = "/custom/repo/path";
|
|
145
|
+
const { findRepoRoot } = await import("./config/repo.js");
|
|
146
|
+
const rootDir = process.platform === "win32" ? "C:\\" : "/";
|
|
147
|
+
expect(findRepoRoot(rootDir)).toBe("/custom/repo/path");
|
|
140
148
|
});
|
|
141
149
|
|
|
142
150
|
it("should resolve defaultBaseBranch, viteBasePath, and ghPath", async () => {
|
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 {
|
|
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
|
-
|
|
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() {
|
|
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
|
|
135
|
-
|
|
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
|
|
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
|
+
});
|