@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.
Files changed (187) hide show
  1. package/.env.example +6 -0
  2. package/.mcp/schemas/agent.search_ddgs.json +1 -0
  3. package/.mcp/schemas/boomtick.health.json +1 -0
  4. package/.mcp/schemas/github.checkout_branch.json +1 -0
  5. package/.mcp/schemas/github.comment_triage_summary.json +1 -0
  6. package/.mcp/schemas/github.create_issue.json +1 -0
  7. package/.mcp/schemas/github.create_pull_request.json +1 -0
  8. package/.mcp/schemas/github.get_merge_conflict_files.json +1 -0
  9. package/.mcp/schemas/github.get_pr.json +1 -0
  10. package/.mcp/schemas/github.get_pr_diff.json +1 -0
  11. package/.mcp/schemas/github.issue_comment.json +1 -0
  12. package/.mcp/schemas/github.issue_update.json +1 -0
  13. package/.mcp/schemas/github.issue_view.json +1 -0
  14. package/.mcp/schemas/github.open_replacement_pr.json +1 -0
  15. package/.mcp/schemas/github.search_open_prs.json +1 -0
  16. package/.mcp/schemas/jules.cancel_session.json +1 -0
  17. package/.mcp/schemas/jules.create_session.json +1 -0
  18. package/.mcp/schemas/jules.get_messages.json +1 -0
  19. package/.mcp/schemas/jules.get_pr.json +1 -0
  20. package/.mcp/schemas/jules.get_session.json +1 -0
  21. package/.mcp/schemas/jules.list_sessions.json +1 -0
  22. package/.mcp/schemas/jules.send_message.json +1 -0
  23. package/.mcp/schemas/jules.trigger_feedback.json +1 -0
  24. package/.mcp/schemas/repo.commit_patch.json +1 -0
  25. package/.mcp/schemas/repo.create_branch.json +1 -0
  26. package/.mcp/schemas/repo.create_repair_branch.json +1 -0
  27. package/.mcp/schemas/repo.get_changed_files.json +1 -0
  28. package/.mcp/schemas/repo.get_command_schema.json +1 -0
  29. package/.mcp/schemas/repo.get_package_scripts.json +1 -0
  30. package/.mcp/schemas/repo.get_route_map.json +1 -0
  31. package/.mcp/schemas/repo.logs.json +1 -0
  32. package/.mcp/schemas/repo.read_agent_context.json +1 -0
  33. package/.mcp/schemas/repo.read_ci_logs.json +1 -0
  34. package/.mcp/schemas/repo.run_lighthouse.json +1 -0
  35. package/.mcp/schemas/repo.run_playwright.json +1 -0
  36. package/.mcp/schemas/repo.run_tests.json +1 -0
  37. package/CHANGELOG.md +50 -0
  38. package/README.md +108 -0
  39. package/actions/ai-review/action.yml +15 -0
  40. package/actions/audit/action.yml +37 -0
  41. package/actions/ci-validate/action.yml +9 -0
  42. package/actions/impact-analysis/action.yml +385 -0
  43. package/actions/lint-typecheck/action.yml +19 -0
  44. package/actions/run-project-gate/action.yml +163 -0
  45. package/actions/setup/action.yml +15 -0
  46. package/actions/setup-workspace/action.yml +179 -0
  47. package/actions/test-build/action.yml +71 -0
  48. package/actions/update-pr-comment/action.yml +101 -0
  49. package/dist/config.js +110 -0
  50. package/dist/evals/run-evals.js +38 -0
  51. package/dist/index.js +10 -0
  52. package/dist/lib/error_utils.js +8 -0
  53. package/dist/lib/git.js +33 -0
  54. package/dist/lib/result.js +21 -0
  55. package/dist/lib/shell.js +91 -0
  56. package/dist/lib/shell.test.js +10 -0
  57. package/dist/lib/td-cli.js +27 -0
  58. package/dist/lib/test-utils.js +20 -0
  59. package/dist/mcp/definitions.js +242 -0
  60. package/dist/mcp/server.js +317 -0
  61. package/dist/mcp/tools.js +18 -0
  62. package/dist/tools/contract.js +2069 -0
  63. package/dist/tools/ddgs.search.js +50 -0
  64. package/dist/tools/ddgs.search.test.js +67 -0
  65. package/dist/tools/ddgs_search.py +23 -0
  66. package/dist/tools/github.checkout_branch.js +20 -0
  67. package/dist/tools/github.comment_triage_summary.js +25 -0
  68. package/dist/tools/github.create_issue.js +28 -0
  69. package/dist/tools/github.create_issue.test.js +54 -0
  70. package/dist/tools/github.create_pull_request.js +44 -0
  71. package/dist/tools/github.get_merge_conflict_files.js +26 -0
  72. package/dist/tools/github.get_pr.js +18 -0
  73. package/dist/tools/github.get_pr_diff.js +24 -0
  74. package/dist/tools/github.issue_comment.js +24 -0
  75. package/dist/tools/github.issue_update.js +29 -0
  76. package/dist/tools/github.issue_view.js +28 -0
  77. package/dist/tools/github.open_replacement_pr.js +41 -0
  78. package/dist/tools/github.search_open_prs.js +31 -0
  79. package/dist/tools/github.search_open_prs.test.js +51 -0
  80. package/dist/tools/jules/cancel-session.js +20 -0
  81. package/dist/tools/jules/create-session.js +20 -0
  82. package/dist/tools/jules/create-session.test.js +99 -0
  83. package/dist/tools/jules/get-messages.js +19 -0
  84. package/dist/tools/jules/get-messages.test.js +45 -0
  85. package/dist/tools/jules/get-pr.js +14 -0
  86. package/dist/tools/jules/get-pr.test.js +37 -0
  87. package/dist/tools/jules/get-session.js +10 -0
  88. package/dist/tools/jules/list-sessions.js +43 -0
  89. package/dist/tools/jules/send-message.js +22 -0
  90. package/dist/tools/jules/send-message.test.js +56 -0
  91. package/dist/tools/jules/shared.js +26 -0
  92. package/dist/tools/jules/trigger-feedback.js +16 -0
  93. package/dist/tools/jules/trigger-feedback.test.js +42 -0
  94. package/dist/tools/repo.commit_patch.js +33 -0
  95. package/dist/tools/repo.create_branch.js +21 -0
  96. package/dist/tools/repo.create_branch.test.js +42 -0
  97. package/dist/tools/repo.create_repair_branch.js +38 -0
  98. package/dist/tools/repo.get_changed_files.js +21 -0
  99. package/dist/tools/repo.get_command_schema.js +27 -0
  100. package/dist/tools/repo.get_package_scripts.js +35 -0
  101. package/dist/tools/repo.get_package_scripts.test.js +39 -0
  102. package/dist/tools/repo.get_route_map.js +37 -0
  103. package/dist/tools/repo.logs.js +16 -0
  104. package/dist/tools/repo.read_agent_context.js +20 -0
  105. package/dist/tools/repo.read_ci_logs.js +18 -0
  106. package/dist/tools/repo.run_lighthouse.js +37 -0
  107. package/dist/tools/repo.run_playwright.js +29 -0
  108. package/dist/tools/repo.run_tests.js +43 -0
  109. package/dist/tools/types.js +1 -0
  110. package/docs/testing.md +73 -0
  111. package/package.json +45 -0
  112. package/scripts/create_instructions.sh +42 -0
  113. package/scripts/export-mcp-schema.ts +7 -0
  114. package/scripts/sync-contracts.ts +74 -0
  115. package/scripts/sync-mcp-schemas.ts +97 -0
  116. package/src/agents/conflict-scout.prompt.md +20 -0
  117. package/src/agents/pr-consolidation.prompt.md +32 -0
  118. package/src/agents/pr-writer.prompt.md +13 -0
  119. package/src/agents/repair-agent.prompt.md +18 -0
  120. package/src/agents/repo-context.prompt.md +20 -0
  121. package/src/agents/verifier-agent.prompt.md +22 -0
  122. package/src/config.ts +113 -0
  123. package/src/evals/fixtures/merge-conflict-simple/README.md +0 -0
  124. package/src/evals/run-evals.ts +40 -0
  125. package/src/index.ts +12 -0
  126. package/src/lib/error_utils.ts +8 -0
  127. package/src/lib/git.ts +41 -0
  128. package/src/lib/result.ts +28 -0
  129. package/src/lib/shell.test.ts +11 -0
  130. package/src/lib/shell.ts +118 -0
  131. package/src/lib/td-cli.ts +38 -0
  132. package/src/lib/test-utils.ts +23 -0
  133. package/src/mcp/definitions.ts +278 -0
  134. package/src/mcp/server.ts +359 -0
  135. package/src/mcp/tools.ts +20 -0
  136. package/src/tools/contract.ts +2245 -0
  137. package/src/tools/ddgs.search.test.ts +92 -0
  138. package/src/tools/ddgs.search.ts +58 -0
  139. package/src/tools/ddgs_search.py +23 -0
  140. package/src/tools/github.checkout_branch.ts +25 -0
  141. package/src/tools/github.comment_triage_summary.ts +30 -0
  142. package/src/tools/github.create_issue.test.ts +63 -0
  143. package/src/tools/github.create_issue.ts +34 -0
  144. package/src/tools/github.create_pull_request.ts +50 -0
  145. package/src/tools/github.get_merge_conflict_files.ts +31 -0
  146. package/src/tools/github.get_pr.ts +24 -0
  147. package/src/tools/github.get_pr_diff.ts +29 -0
  148. package/src/tools/github.issue_comment.ts +31 -0
  149. package/src/tools/github.issue_update.ts +31 -0
  150. package/src/tools/github.issue_view.ts +35 -0
  151. package/src/tools/github.open_replacement_pr.ts +48 -0
  152. package/src/tools/github.search_open_prs.test.ts +60 -0
  153. package/src/tools/github.search_open_prs.ts +40 -0
  154. package/src/tools/jules/cancel-session.ts +26 -0
  155. package/src/tools/jules/create-session.test.ts +114 -0
  156. package/src/tools/jules/create-session.ts +24 -0
  157. package/src/tools/jules/get-messages.test.ts +52 -0
  158. package/src/tools/jules/get-messages.ts +24 -0
  159. package/src/tools/jules/get-pr.test.ts +43 -0
  160. package/src/tools/jules/get-pr.ts +17 -0
  161. package/src/tools/jules/get-session.ts +12 -0
  162. package/src/tools/jules/list-sessions.ts +50 -0
  163. package/src/tools/jules/send-message.test.ts +66 -0
  164. package/src/tools/jules/send-message.ts +27 -0
  165. package/src/tools/jules/shared.ts +28 -0
  166. package/src/tools/jules/trigger-feedback.test.ts +50 -0
  167. package/src/tools/jules/trigger-feedback.ts +21 -0
  168. package/src/tools/repo.commit_patch.ts +41 -0
  169. package/src/tools/repo.create_branch.test.ts +55 -0
  170. package/src/tools/repo.create_branch.ts +29 -0
  171. package/src/tools/repo.create_repair_branch.ts +47 -0
  172. package/src/tools/repo.get_changed_files.ts +26 -0
  173. package/src/tools/repo.get_command_schema.ts +30 -0
  174. package/src/tools/repo.get_package_scripts.test.ts +51 -0
  175. package/src/tools/repo.get_package_scripts.ts +36 -0
  176. package/src/tools/repo.get_route_map.ts +41 -0
  177. package/src/tools/repo.logs.ts +21 -0
  178. package/src/tools/repo.read_agent_context.ts +22 -0
  179. package/src/tools/repo.read_ci_logs.ts +21 -0
  180. package/src/tools/repo.run_lighthouse.ts +40 -0
  181. package/src/tools/repo.run_playwright.ts +35 -0
  182. package/src/tools/repo.run_tests.ts +47 -0
  183. package/src/tools/types.ts +8 -0
  184. package/start_browsermcp.sh +14 -0
  185. package/start_github_mcp.sh +6 -0
  186. package/tsconfig.json +15 -0
  187. 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,8 @@
1
+ export type JulesStatus = "PENDING" | "IN_PROGRESS" | "COMPLETED" | "FAILED";
2
+
3
+ export interface JulesSession {
4
+ id: string;
5
+ status: JulesStatus;
6
+ createdAt: Date;
7
+ pullRequestUrl?: string;
8
+ }
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: "node",
7
+ exclude: ["node_modules", "dist"],
8
+ },
9
+ });