@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,55 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { createBranchHandler, CreateBranchInput } from "./repo.create_branch.js";
|
|
3
|
+
import * as shell from "../lib/shell.js";
|
|
4
|
+
|
|
5
|
+
vi.mock("../lib/shell.js", () => ({
|
|
6
|
+
runCommand: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
describe("repo.create_branch", () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
vi.clearAllMocks();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should create a new branch from a base branch", async () => {
|
|
15
|
+
const args: CreateBranchInput = {
|
|
16
|
+
branchName: "new-feature",
|
|
17
|
+
baseBranch: "main",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
(shell.runCommand as any).mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" });
|
|
21
|
+
|
|
22
|
+
const result = await createBranchHandler(args);
|
|
23
|
+
|
|
24
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["fetch", "origin", "main"]);
|
|
25
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["checkout", "-b", "new-feature", "origin/main"]);
|
|
26
|
+
expect(result).toEqual({
|
|
27
|
+
success: true,
|
|
28
|
+
branchName: "new-feature",
|
|
29
|
+
baseBranch: "main",
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should use default base branch 'main' if not provided", async () => {
|
|
34
|
+
const args: CreateBranchInput = {
|
|
35
|
+
branchName: "another-feature",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
(shell.runCommand as any).mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" });
|
|
39
|
+
|
|
40
|
+
const result = await createBranchHandler(args);
|
|
41
|
+
|
|
42
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["fetch", "origin", "main"]);
|
|
43
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["checkout", "-b", "another-feature", "origin/main"]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should throw error if git command fails", async () => {
|
|
47
|
+
const args: CreateBranchInput = {
|
|
48
|
+
branchName: "fail-branch",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
(shell.runCommand as any).mockResolvedValue({ exitCode: 1, stdout: "", stderr: "Error creating branch" });
|
|
52
|
+
|
|
53
|
+
await expect(createBranchHandler(args)).rejects.toThrow("Failed to create branch fail-branch: Error creating branch");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
|
|
4
|
+
export const CreateBranchInputSchema = z.object({
|
|
5
|
+
branchName: z.string(),
|
|
6
|
+
baseBranch: z.string().optional().default("main"),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export type CreateBranchInput = z.input<typeof CreateBranchInputSchema>;
|
|
10
|
+
|
|
11
|
+
export async function createBranchHandler(args: CreateBranchInput) {
|
|
12
|
+
const params = CreateBranchInputSchema.parse(args);
|
|
13
|
+
|
|
14
|
+
// Ensure latest base branch
|
|
15
|
+
await runCommand("git", ["fetch", "origin", params.baseBranch]);
|
|
16
|
+
|
|
17
|
+
// Create branch from origin base branch
|
|
18
|
+
const result = await runCommand("git", ["checkout", "-b", params.branchName, `origin/${params.baseBranch}`]);
|
|
19
|
+
|
|
20
|
+
if (result.exitCode !== 0) {
|
|
21
|
+
throw new Error(`Failed to create branch ${params.branchName}: ${result.stderr}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
success: true,
|
|
26
|
+
branchName: params.branchName,
|
|
27
|
+
baseBranch: params.baseBranch
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
import { createWorktree } from "../lib/git.js";
|
|
4
|
+
|
|
5
|
+
export const CreateRepairBranchInputSchema = z.object({
|
|
6
|
+
prNumber: z.number(),
|
|
7
|
+
repairBranchName: z.string().optional(),
|
|
8
|
+
writeMode: z.boolean().optional().default(false),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export async function createRepairBranchHandler(args: z.infer<typeof CreateRepairBranchInputSchema>) {
|
|
12
|
+
if (!args.writeMode) {
|
|
13
|
+
throw new Error("writeMode must be true to create a repair branch.");
|
|
14
|
+
}
|
|
15
|
+
const prResult = await runCommand("gh", [
|
|
16
|
+
"pr",
|
|
17
|
+
"view",
|
|
18
|
+
args.prNumber.toString(),
|
|
19
|
+
"--json", "headRefName,baseRefName,title"
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
if (prResult.exitCode !== 0) {
|
|
23
|
+
throw new Error(`Failed to get PR info: ${prResult.stderr}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { headRefName, baseRefName, title } = JSON.parse(prResult.stdout);
|
|
27
|
+
|
|
28
|
+
const sanitizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "-").substring(0, 30);
|
|
29
|
+
const repairBranch = args.repairBranchName || `agent/repair-pr-${args.prNumber}-${sanitizedTitle}`;
|
|
30
|
+
|
|
31
|
+
// Ensure latest
|
|
32
|
+
await runCommand("git", ["fetch", "origin", headRefName]);
|
|
33
|
+
await runCommand("git", ["fetch", "origin", baseRefName]);
|
|
34
|
+
|
|
35
|
+
const worktreePath = await createWorktree(`origin/${headRefName}`, args.prNumber);
|
|
36
|
+
|
|
37
|
+
// Create the actual repair branch in the worktree
|
|
38
|
+
await runCommand("git", ["checkout", "-b", repairBranch], { cwd: worktreePath });
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
prNumber: args.prNumber,
|
|
42
|
+
originalBranch: headRefName,
|
|
43
|
+
repairBranch,
|
|
44
|
+
baseBranch: baseRefName,
|
|
45
|
+
worktreePath
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
|
|
4
|
+
export const GetChangedFilesInputSchema = z.object({
|
|
5
|
+
base: z.string().optional().default("main"),
|
|
6
|
+
head: z.string().optional().default("HEAD"),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export async function getChangedFilesHandler(args: z.infer<typeof GetChangedFilesInputSchema>) {
|
|
10
|
+
const result = await runCommand("git", [
|
|
11
|
+
"diff",
|
|
12
|
+
"--name-status",
|
|
13
|
+
`${args.base}...${args.head}`
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
if (result.exitCode !== 0) {
|
|
17
|
+
throw new Error(`Failed to get changed files: ${result.stderr}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const files = result.stdout.trim().split("\n").filter(line => line.length > 0).map(line => {
|
|
21
|
+
const [status, path] = line.split(/\s+/);
|
|
22
|
+
return { status, path };
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return { files };
|
|
26
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
|
|
4
|
+
export const GetCommandSchemaInputSchema = z.object({
|
|
5
|
+
commandPath: z.string().describe("The CLI command path to retrieve the schema for (e.g. 'gh audit-pr')"),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export async function getCommandSchemaHandler(args: z.infer<typeof GetCommandSchemaInputSchema>) {
|
|
9
|
+
// Sanitize input to prevent command injection (allow only alphanumeric, spaces, hyphens, and underscores)
|
|
10
|
+
const sanitizedPath = args.commandPath.replace(/[^a-zA-Z0-9-_ ]/g, "");
|
|
11
|
+
const result = await runCommand("td-cli", ["schema", sanitizedPath]);
|
|
12
|
+
|
|
13
|
+
if (result.exitCode !== 0) {
|
|
14
|
+
// Log stderr for debugging but don't expose it to the user
|
|
15
|
+
// Using comma-separated arguments to satisfy semgrep unsafe-formatstring
|
|
16
|
+
console.error("td-cli schema failed for", sanitizedPath, ":", result.stderr);
|
|
17
|
+
throw new Error(`Failed to get command schema for '${sanitizedPath}'`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(result.stdout);
|
|
22
|
+
if (parsed.status === "error") {
|
|
23
|
+
throw new Error(parsed.message || "Failed to get command schema");
|
|
24
|
+
}
|
|
25
|
+
// Return the full payload (which contains the 'schema' object)
|
|
26
|
+
return parsed;
|
|
27
|
+
} catch (e) {
|
|
28
|
+
throw new Error(`Failed to parse command schema output: ${e}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { getPackageScriptsHandler } from "./repo.get_package_scripts.js";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
import { config } from "../config.js";
|
|
5
|
+
|
|
6
|
+
vi.mock("fs/promises");
|
|
7
|
+
|
|
8
|
+
describe("repo.get_package_scripts", () => {
|
|
9
|
+
it("should return scripts from package.json", async () => {
|
|
10
|
+
const mockPkg = {
|
|
11
|
+
scripts: { test: "vitest" }
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockPkg));
|
|
15
|
+
|
|
16
|
+
const result = await getPackageScriptsHandler({});
|
|
17
|
+
expect(result.scripts.test).toBe("vitest");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should handle missing scripts", async () => {
|
|
21
|
+
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify({}));
|
|
22
|
+
|
|
23
|
+
const result = await getPackageScriptsHandler({});
|
|
24
|
+
expect(result.scripts).toEqual({});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should throw error when file not found", async () => {
|
|
28
|
+
vi.mocked(fs.readFile).mockRejectedValue(new Error("File not found"));
|
|
29
|
+
|
|
30
|
+
await expect(getPackageScriptsHandler({})).rejects.toThrow("Failed to read package.json: File not found");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should filter scripts by pattern", async () => {
|
|
34
|
+
const mockPkg = {
|
|
35
|
+
scripts: {
|
|
36
|
+
test: "vitest",
|
|
37
|
+
build: "vite build",
|
|
38
|
+
"test:ui": "vitest --ui"
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockPkg));
|
|
43
|
+
|
|
44
|
+
const result = await getPackageScriptsHandler({ filter: "test" });
|
|
45
|
+
expect(result.scripts).toEqual({
|
|
46
|
+
test: "vitest",
|
|
47
|
+
"test:ui": "vitest --ui"
|
|
48
|
+
});
|
|
49
|
+
expect(result.scripts.build).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { config } from "../config.js";
|
|
6
|
+
|
|
7
|
+
export const GetPackageScriptsInputSchema = z.object({
|
|
8
|
+
filter: z.string().optional(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export async function getPackageScriptsHandler(args: z.infer<typeof GetPackageScriptsInputSchema>) {
|
|
12
|
+
GetPackageScriptsInputSchema.parse(args);
|
|
13
|
+
const packageJsonPath = path.join(config.repoPath, "package.json");
|
|
14
|
+
try {
|
|
15
|
+
const content = await fs.readFile(packageJsonPath, "utf-8");
|
|
16
|
+
let pkg;
|
|
17
|
+
try {
|
|
18
|
+
pkg = JSON.parse(content);
|
|
19
|
+
} catch (e) {
|
|
20
|
+
throw new Error(`Malformed package.json: ${e instanceof Error ? e.message : String(e)}`);
|
|
21
|
+
}
|
|
22
|
+
let scripts = pkg.scripts || {};
|
|
23
|
+
if (args.filter) {
|
|
24
|
+
const filtered: Record<string, string> = {};
|
|
25
|
+
for (const [name, cmd] of Object.entries(scripts)) {
|
|
26
|
+
if (name.includes(args.filter)) {
|
|
27
|
+
filtered[name] = cmd as string;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
scripts = filtered;
|
|
31
|
+
}
|
|
32
|
+
return { scripts };
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new Error(`Failed to read package.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
import { config } from "../config.js";
|
|
4
|
+
import path from "path";
|
|
5
|
+
|
|
6
|
+
export const GetRouteMapInputSchema = z.object({
|
|
7
|
+
includeStatic: z.boolean().optional(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export async function getRouteMapHandler(args: z.infer<typeof GetRouteMapInputSchema>) {
|
|
11
|
+
GetRouteMapInputSchema.parse(args);
|
|
12
|
+
// Logic based on tech-dancer repo structure: src/config/routes.ts and content/
|
|
13
|
+
const routesPath = path.join(config.repoPath, "src/config/routes.ts");
|
|
14
|
+
|
|
15
|
+
// For simplicity in MVP, we'll try to find routes by listing content files
|
|
16
|
+
// and reading the main routes file if it exists.
|
|
17
|
+
let routeMap: Record<string, string> = {};
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const gitFiles = await runCommand("git", ["ls-files", "content/"]);
|
|
21
|
+
const contentFiles = gitFiles.stdout.trim().split("\n");
|
|
22
|
+
|
|
23
|
+
for (const file of contentFiles) {
|
|
24
|
+
if (file.endsWith(".md")) {
|
|
25
|
+
const slug = path.basename(file, ".md");
|
|
26
|
+
if (file.includes("/posts/")) {
|
|
27
|
+
routeMap[`/blog/${slug}`] = file;
|
|
28
|
+
} else if (file.includes("/resources/")) {
|
|
29
|
+
routeMap[`/resources/${slug}`] = file;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
// If no content files, that's fine
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (args.includeStatic) {
|
|
38
|
+
routeMap["/favicon.ico"] = "public/favicon.ico";
|
|
39
|
+
}
|
|
40
|
+
return { routeMap };
|
|
41
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runTDCli } from "../lib/td-cli.js";
|
|
3
|
+
|
|
4
|
+
export const RepoLogsInputSchema = z.object({
|
|
5
|
+
prNumber: z.number(),
|
|
6
|
+
grep: z.string().optional(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export async function repoLogsHandler(args: z.infer<typeof RepoLogsInputSchema>) {
|
|
10
|
+
const params = ["repo", "logs", args.prNumber.toString()];
|
|
11
|
+
|
|
12
|
+
if (args.grep) {
|
|
13
|
+
params.push("--grep", args.grep);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const output = await runTDCli(params);
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
logs: output.logs || (output.data ? output.data.logs : undefined)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { config } from "../config.js";
|
|
5
|
+
|
|
6
|
+
export const ReadAgentContextInputSchema = z.object({});
|
|
7
|
+
|
|
8
|
+
export async function readAgentContextHandler(_args: z.infer<typeof ReadAgentContextInputSchema>) {
|
|
9
|
+
const contextPath = path.join(config.repoPath, ".agent-context.json");
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const content = await fs.readFile(contextPath, "utf-8");
|
|
13
|
+
const parsed = JSON.parse(content);
|
|
14
|
+
return {
|
|
15
|
+
status: "success",
|
|
16
|
+
...parsed
|
|
17
|
+
};
|
|
18
|
+
} catch (e: any) {
|
|
19
|
+
console.error("Failed to read agent context:", e.message);
|
|
20
|
+
throw new Error(`Failed to read agent context from ${contextPath}. Ensure it exists by running 'td-cli context-warm'.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runTDCli } from "../lib/td-cli.js";
|
|
3
|
+
|
|
4
|
+
export const ReadCiLogsInputSchema = z.object({
|
|
5
|
+
prNumber: z.number(),
|
|
6
|
+
all: z.boolean().optional(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export async function readCiLogsHandler(args: z.infer<typeof ReadCiLogsInputSchema>) {
|
|
10
|
+
const params = ["repo", "ci-logs", args.prNumber.toString()];
|
|
11
|
+
if (args.all) {
|
|
12
|
+
params.push("--all");
|
|
13
|
+
}
|
|
14
|
+
const output = await runTDCli(params);
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
checks: output.checks,
|
|
18
|
+
failedChecks: output.failedChecks,
|
|
19
|
+
logs: output.logs
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
|
|
4
|
+
export const RunLighthouseInputSchema = z.object({
|
|
5
|
+
route: z.string().optional().default("/"),
|
|
6
|
+
worktreePath: z.string().optional(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export async function runLighthouseHandler(args: z.infer<typeof RunLighthouseInputSchema>) {
|
|
10
|
+
const results = await runCommand("pnpm", ["lhci", "autorun", "--json"], { cwd: args.worktreePath });
|
|
11
|
+
|
|
12
|
+
let summary = {};
|
|
13
|
+
const failures: string[] = [];
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// Attempt to parse summary from LHCI output if available
|
|
17
|
+
// For MVP, we'll provide a structured placeholder if actual JSON isn't present
|
|
18
|
+
if (results.stdout.includes("{")) {
|
|
19
|
+
const possibleJson = results.stdout.substring(results.stdout.indexOf("{"));
|
|
20
|
+
summary = JSON.parse(possibleJson);
|
|
21
|
+
} else {
|
|
22
|
+
summary = {
|
|
23
|
+
performance: 0,
|
|
24
|
+
accessibility: 0,
|
|
25
|
+
bestPractices: 0,
|
|
26
|
+
seo: 0
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
} catch (e) {
|
|
30
|
+
failures.push("Failed to parse Lighthouse report");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
success: results.exitCode === 0,
|
|
35
|
+
route: args.route,
|
|
36
|
+
summary,
|
|
37
|
+
failures,
|
|
38
|
+
command: results.command
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
|
|
4
|
+
export const RunPlaywrightInputSchema = z.object({
|
|
5
|
+
grep: z.string().optional(),
|
|
6
|
+
worktreePath: z.string().optional(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export async function runPlaywrightHandler(args: z.infer<typeof RunPlaywrightInputSchema>) {
|
|
10
|
+
const tdArgs = ["repo", "run-playwright"];
|
|
11
|
+
if (args.grep) {
|
|
12
|
+
tdArgs.push("--grep", args.grep);
|
|
13
|
+
}
|
|
14
|
+
if (args.worktreePath) {
|
|
15
|
+
tdArgs.push("--worktree", args.worktreePath);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const result = await runCommand("td-cli", tdArgs);
|
|
19
|
+
|
|
20
|
+
if (result.exitCode !== 0) {
|
|
21
|
+
// Playwright might exit with non-zero if tests fail, but our td-cli should ideally handle it
|
|
22
|
+
// and return a JSON with success: false.
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const output = JSON.parse(result.stdout);
|
|
26
|
+
if (output.status === "error") {
|
|
27
|
+
throw new Error(`Failed to run Playwright: ${output.message}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
success: output.success,
|
|
32
|
+
command: output.command,
|
|
33
|
+
failedTests: output.failedTests
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand, ShellResult } from "../lib/shell.js";
|
|
3
|
+
|
|
4
|
+
export const RunTestsInputSchema = z.object({
|
|
5
|
+
commands: z.array(z.string()).optional(),
|
|
6
|
+
timeoutSeconds: z.number().optional().default(300),
|
|
7
|
+
worktreePath: z.string().optional(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export async function runTestsHandler(args: z.infer<typeof RunTestsInputSchema>) {
|
|
11
|
+
const commands = args.commands || [
|
|
12
|
+
"pnpm install --frozen-lockfile",
|
|
13
|
+
"pnpm lint",
|
|
14
|
+
"pnpm test",
|
|
15
|
+
"pnpm build"
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const results: ShellResult[] = [];
|
|
19
|
+
let success = true;
|
|
20
|
+
|
|
21
|
+
for (const fullCmd of commands) {
|
|
22
|
+
const [cmd, ...cmdArgs] = fullCmd.split(" ");
|
|
23
|
+
try {
|
|
24
|
+
const res = await runCommand(cmd, cmdArgs, {
|
|
25
|
+
cwd: args.worktreePath,
|
|
26
|
+
timeout: args.timeoutSeconds * 1000
|
|
27
|
+
});
|
|
28
|
+
results.push(res);
|
|
29
|
+
if (res.exitCode !== 0) {
|
|
30
|
+
success = false;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
} catch (e) {
|
|
34
|
+
success = false;
|
|
35
|
+
results.push({
|
|
36
|
+
command: fullCmd,
|
|
37
|
+
stdout: "",
|
|
38
|
+
stderr: e instanceof Error ? e.message : String(e),
|
|
39
|
+
exitCode: 1,
|
|
40
|
+
durationMs: 0
|
|
41
|
+
});
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { success, results };
|
|
47
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Start a dummy listener on port 9009 so that lsof -ti:9009 doesn't return empty and crash the server
|
|
3
|
+
python3 -c "
|
|
4
|
+
import socket, time
|
|
5
|
+
try:
|
|
6
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
7
|
+
s.bind(('127.0.0.1', 9009))
|
|
8
|
+
s.listen(1)
|
|
9
|
+
time.sleep(2)
|
|
10
|
+
except Exception:
|
|
11
|
+
pass
|
|
12
|
+
" &
|
|
13
|
+
sleep 0.2
|
|
14
|
+
exec npx @browsermcp/mcp@latest "$@"
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Set the token and run the Docker container
|
|
2
|
+
export GITHUB_TOKEN=$(gh auth token)
|
|
3
|
+
docker run -i --rm \
|
|
4
|
+
-e GITHUB_PERSONAL_ACCESS_TOKEN="$GITHUB_TOKEN" \
|
|
5
|
+
-e GITHUB_TOOLSETS="actions,code_security,dependabot,issues,pull_requests,repos,search,security_advisories" \
|
|
6
|
+
ghcr.io/github/github-mcp-server:latest
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"],
|
|
14
|
+
"exclude": ["node_modules", "dist"]
|
|
15
|
+
}
|