@arii/boomtick-mcp 0.4.2 → 0.5.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/github.get_pr_commits.json +32 -0
- package/.mcp/schemas/github.get_pr_reviews.json +32 -0
- package/.mcp/schemas/github_get_pr_commits.json +32 -0
- package/.mcp/schemas/github_get_pr_reviews.json +32 -0
- package/.mcp/schemas/repo.get_workflow_runs.json +50 -0
- package/.mcp/schemas/repo.logs.json +14 -0
- package/.mcp/schemas/repo.read_ci_logs.json +14 -0
- package/.mcp/schemas/repo_get_workflow_runs.json +50 -0
- package/.mcp/schemas/repo_logs.json +14 -0
- package/.mcp/schemas/repo_read_ci_logs.json +14 -0
- package/CHANGELOG.md +7 -0
- package/README.md +0 -1
- package/actions/impact-analysis/action.yml +4 -16
- package/actions/run-project-gate/action.yml +2 -9
- package/actions/test-build/action.yml +1 -19
- package/dist/lib/git.js +44 -0
- package/dist/lib/git.test.js +82 -0
- package/dist/mcp/definitions.js +16 -39
- package/dist/mcp/server.js +0 -46
- package/dist/mcp/server.test.js +0 -33
- package/dist/tools/contract.js +135 -2
- package/dist/tools/github.get_merge_conflict_files.js +10 -18
- package/dist/tools/github.get_pr_commits.js +21 -0
- package/dist/tools/github.get_pr_reviews.js +21 -0
- package/dist/tools/github.get_pr_reviews.test.js +99 -0
- package/dist/tools/jules/create-session.js +1 -2
- package/dist/tools/jules/get-pr.js +1 -2
- package/dist/tools/jules/get-session.js +1 -2
- package/dist/tools/jules/shared.js +24 -0
- package/dist/tools/repo.get_changed_files.js +2 -13
- package/dist/tools/repo.get_workflow_runs.js +29 -0
- package/dist/tools/repo.logs.js +5 -1
- package/dist/tools/repo.read_agent_context.js +27 -1
- package/dist/tools/repo.read_agent_context.test.js +1 -1
- package/dist/tools/repo.read_ci_logs.js +5 -1
- package/dist/tools/repo.run_tests.js +1 -1
- package/package.json +3 -6
- package/src/agents/verifier-agent.prompt.md +0 -4
- package/src/lib/git.test.ts +110 -0
- package/src/lib/git.ts +75 -0
- package/src/mcp/definitions.ts +18 -42
- package/src/mcp/server.test.ts +0 -42
- package/src/mcp/server.ts +0 -45
- package/src/tools/contract.ts +147 -2
- package/src/tools/github.get_merge_conflict_files.ts +10 -20
- package/src/tools/github.get_pr_commits.ts +27 -0
- package/src/tools/github.get_pr_reviews.test.ts +110 -0
- package/src/tools/github.get_pr_reviews.ts +27 -0
- package/src/tools/jules/create-session.ts +1 -2
- package/src/tools/jules/get-pr.ts +1 -2
- package/src/tools/jules/get-session.ts +1 -2
- package/src/tools/jules/shared.ts +35 -0
- package/src/tools/repo.get_changed_files.ts +2 -16
- package/src/tools/repo.get_workflow_runs.ts +36 -0
- package/src/tools/repo.logs.ts +5 -1
- package/src/tools/repo.read_agent_context.test.ts +1 -1
- package/src/tools/repo.read_agent_context.ts +35 -1
- package/src/tools/repo.read_ci_logs.ts +5 -1
- package/src/tools/repo.run_tests.ts +1 -1
- package/.mcp/schemas/agent.search_ddgs.json +0 -31
- package/.mcp/schemas/agent_search_ddgs.json +0 -31
- package/.mcp/schemas/repo.get_route_map.json +0 -23
- package/.mcp/schemas/repo.run_lighthouse.json +0 -29
- package/.mcp/schemas/repo.run_playwright.json +0 -36
- package/.mcp/schemas/repo_get_route_map.json +0 -23
- package/.mcp/schemas/repo_run_lighthouse.json +0 -29
- package/.mcp/schemas/repo_run_playwright.json +0 -36
- package/dist/lib/td-cli.js +0 -54
- package/dist/tools/ddgs.search.js +0 -50
- package/dist/tools/ddgs.search.test.js +0 -67
- package/dist/tools/ddgs_search.py +0 -23
- package/dist/tools/repo.get_route_map.js +0 -37
- package/dist/tools/repo.run_lighthouse.js +0 -37
- package/dist/tools/repo.run_playwright.js +0 -34
- package/scripts/export-mcp-schema.ts +0 -7
- package/src/lib/td-cli.ts +0 -73
- package/src/tools/ddgs.search.test.ts +0 -92
- package/src/tools/ddgs.search.ts +0 -58
- package/src/tools/ddgs_search.py +0 -23
- package/src/tools/repo.get_route_map.ts +0 -41
- package/src/tools/repo.run_lighthouse.ts +0 -40
- package/src/tools/repo.run_playwright.ts +0 -42
package/src/tools/contract.ts
CHANGED
|
@@ -1597,6 +1597,39 @@ export const GetPackageScriptsInputJsonSchema = {
|
|
|
1597
1597
|
"type": "object"
|
|
1598
1598
|
} as const;
|
|
1599
1599
|
|
|
1600
|
+
export const GetPrCommitsInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the PR to list commits for."), "repo": z.union([z.string().regex(new RegExp("^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$")), z.null()]).describe("The target repository override (e.g. org/repo).").default(null) });
|
|
1601
|
+
|
|
1602
|
+
export type GetPrCommitsInput = z.infer<typeof GetPrCommitsInputSchema>;
|
|
1603
|
+
|
|
1604
|
+
export const GetPrCommitsInputJsonSchema = {
|
|
1605
|
+
"properties": {
|
|
1606
|
+
"prNumber": {
|
|
1607
|
+
"description": "The number of the PR to list commits for.",
|
|
1608
|
+
"title": "Prnumber",
|
|
1609
|
+
"type": "integer"
|
|
1610
|
+
},
|
|
1611
|
+
"repo": {
|
|
1612
|
+
"anyOf": [
|
|
1613
|
+
{
|
|
1614
|
+
"pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
|
|
1615
|
+
"type": "string"
|
|
1616
|
+
},
|
|
1617
|
+
{
|
|
1618
|
+
"type": "null"
|
|
1619
|
+
}
|
|
1620
|
+
],
|
|
1621
|
+
"default": null,
|
|
1622
|
+
"description": "The target repository override (e.g. org/repo).",
|
|
1623
|
+
"title": "Repo"
|
|
1624
|
+
}
|
|
1625
|
+
},
|
|
1626
|
+
"required": [
|
|
1627
|
+
"prNumber"
|
|
1628
|
+
],
|
|
1629
|
+
"title": "GetPrCommitsInput",
|
|
1630
|
+
"type": "object"
|
|
1631
|
+
} as const;
|
|
1632
|
+
|
|
1600
1633
|
export const GetPrDiffInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the pull request to get the diff for."), "repo": z.union([z.string().regex(new RegExp("^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$")), z.null()]).describe("The target repository override (e.g. org/repo).").default(null) });
|
|
1601
1634
|
|
|
1602
1635
|
export type GetPrDiffInput = z.infer<typeof GetPrDiffInputSchema>;
|
|
@@ -1663,6 +1696,39 @@ export const GetPrInputJsonSchema = {
|
|
|
1663
1696
|
"type": "object"
|
|
1664
1697
|
} as const;
|
|
1665
1698
|
|
|
1699
|
+
export const GetPrReviewsInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the PR to view reviews for."), "repo": z.union([z.string().regex(new RegExp("^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$")), z.null()]).describe("The target repository override (e.g. org/repo).").default(null) });
|
|
1700
|
+
|
|
1701
|
+
export type GetPrReviewsInput = z.infer<typeof GetPrReviewsInputSchema>;
|
|
1702
|
+
|
|
1703
|
+
export const GetPrReviewsInputJsonSchema = {
|
|
1704
|
+
"properties": {
|
|
1705
|
+
"prNumber": {
|
|
1706
|
+
"description": "The number of the PR to view reviews for.",
|
|
1707
|
+
"title": "Prnumber",
|
|
1708
|
+
"type": "integer"
|
|
1709
|
+
},
|
|
1710
|
+
"repo": {
|
|
1711
|
+
"anyOf": [
|
|
1712
|
+
{
|
|
1713
|
+
"pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
|
|
1714
|
+
"type": "string"
|
|
1715
|
+
},
|
|
1716
|
+
{
|
|
1717
|
+
"type": "null"
|
|
1718
|
+
}
|
|
1719
|
+
],
|
|
1720
|
+
"default": null,
|
|
1721
|
+
"description": "The target repository override (e.g. org/repo).",
|
|
1722
|
+
"title": "Repo"
|
|
1723
|
+
}
|
|
1724
|
+
},
|
|
1725
|
+
"required": [
|
|
1726
|
+
"prNumber"
|
|
1727
|
+
],
|
|
1728
|
+
"title": "GetPrReviewsInput",
|
|
1729
|
+
"type": "object"
|
|
1730
|
+
} as const;
|
|
1731
|
+
|
|
1666
1732
|
export const GetRouteMapInputSchema = z.object({ "includeStatic": z.union([z.boolean(), z.null()]).describe("Whether to include static assets in the route map.").default(null) });
|
|
1667
1733
|
|
|
1668
1734
|
export type GetRouteMapInput = z.infer<typeof GetRouteMapInputSchema>;
|
|
@@ -1687,6 +1753,57 @@ export const GetRouteMapInputJsonSchema = {
|
|
|
1687
1753
|
"type": "object"
|
|
1688
1754
|
} as const;
|
|
1689
1755
|
|
|
1756
|
+
export const GetWorkflowRunsInputSchema = z.object({ "branch": z.union([z.string(), z.null()]).describe("Filter workflow runs by branch name.").default(null), "event": z.union([z.string(), z.null()]).describe("Filter workflow runs by event (e.g. push, pull_request).").default(null), "repo": z.union([z.string().regex(new RegExp("^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$")), z.null()]).describe("The target repository override (e.g. org/repo).").default(null) });
|
|
1757
|
+
|
|
1758
|
+
export type GetWorkflowRunsInput = z.infer<typeof GetWorkflowRunsInputSchema>;
|
|
1759
|
+
|
|
1760
|
+
export const GetWorkflowRunsInputJsonSchema = {
|
|
1761
|
+
"properties": {
|
|
1762
|
+
"branch": {
|
|
1763
|
+
"anyOf": [
|
|
1764
|
+
{
|
|
1765
|
+
"type": "string"
|
|
1766
|
+
},
|
|
1767
|
+
{
|
|
1768
|
+
"type": "null"
|
|
1769
|
+
}
|
|
1770
|
+
],
|
|
1771
|
+
"default": null,
|
|
1772
|
+
"description": "Filter workflow runs by branch name.",
|
|
1773
|
+
"title": "Branch"
|
|
1774
|
+
},
|
|
1775
|
+
"event": {
|
|
1776
|
+
"anyOf": [
|
|
1777
|
+
{
|
|
1778
|
+
"type": "string"
|
|
1779
|
+
},
|
|
1780
|
+
{
|
|
1781
|
+
"type": "null"
|
|
1782
|
+
}
|
|
1783
|
+
],
|
|
1784
|
+
"default": null,
|
|
1785
|
+
"description": "Filter workflow runs by event (e.g. push, pull_request).",
|
|
1786
|
+
"title": "Event"
|
|
1787
|
+
},
|
|
1788
|
+
"repo": {
|
|
1789
|
+
"anyOf": [
|
|
1790
|
+
{
|
|
1791
|
+
"pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
|
|
1792
|
+
"type": "string"
|
|
1793
|
+
},
|
|
1794
|
+
{
|
|
1795
|
+
"type": "null"
|
|
1796
|
+
}
|
|
1797
|
+
],
|
|
1798
|
+
"default": null,
|
|
1799
|
+
"description": "The target repository override (e.g. org/repo).",
|
|
1800
|
+
"title": "Repo"
|
|
1801
|
+
}
|
|
1802
|
+
},
|
|
1803
|
+
"title": "GetWorkflowRunsInput",
|
|
1804
|
+
"type": "object"
|
|
1805
|
+
} as const;
|
|
1806
|
+
|
|
1690
1807
|
export const HealthCheckInputSchema = z.object({ "checkDeep": z.boolean().describe("Whether to perform a deep health check including external dependencies.").default(false) });
|
|
1691
1808
|
|
|
1692
1809
|
export type HealthCheckInput = z.infer<typeof HealthCheckInputSchema>;
|
|
@@ -2342,7 +2459,7 @@ export const ReadAgentContextInputJsonSchema = {
|
|
|
2342
2459
|
"type": "object"
|
|
2343
2460
|
} as const;
|
|
2344
2461
|
|
|
2345
|
-
export const ReadCiLogsInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the pull request to read logs for."), "all": z.boolean().describe("Include logs for successful runs (default: false).").default(false) });
|
|
2462
|
+
export const ReadCiLogsInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the pull request to read logs for."), "all": z.boolean().describe("Include logs for successful runs (default: false).").default(false), "repo": z.union([z.string().regex(new RegExp("^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$")), z.null()]).describe("The target repository override (e.g. org/repo).").default(null) });
|
|
2346
2463
|
|
|
2347
2464
|
export type ReadCiLogsInput = z.infer<typeof ReadCiLogsInputSchema>;
|
|
2348
2465
|
|
|
@@ -2358,6 +2475,20 @@ export const ReadCiLogsInputJsonSchema = {
|
|
|
2358
2475
|
"description": "Include logs for successful runs (default: false).",
|
|
2359
2476
|
"title": "All",
|
|
2360
2477
|
"type": "boolean"
|
|
2478
|
+
},
|
|
2479
|
+
"repo": {
|
|
2480
|
+
"anyOf": [
|
|
2481
|
+
{
|
|
2482
|
+
"pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
|
|
2483
|
+
"type": "string"
|
|
2484
|
+
},
|
|
2485
|
+
{
|
|
2486
|
+
"type": "null"
|
|
2487
|
+
}
|
|
2488
|
+
],
|
|
2489
|
+
"default": null,
|
|
2490
|
+
"description": "The target repository override (e.g. org/repo).",
|
|
2491
|
+
"title": "Repo"
|
|
2361
2492
|
}
|
|
2362
2493
|
},
|
|
2363
2494
|
"required": [
|
|
@@ -2538,7 +2669,7 @@ export const ReadPRCommentsResponseJsonSchema = {
|
|
|
2538
2669
|
"type": "object"
|
|
2539
2670
|
} as const;
|
|
2540
2671
|
|
|
2541
|
-
export const RepoLogsInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the pull request to stream logs for."), "grep": z.union([z.string(), z.null()]).describe("Optional pattern to filter log lines.").default(null) });
|
|
2672
|
+
export const RepoLogsInputSchema = z.object({ "prNumber": z.number().int().describe("The number of the pull request to stream logs for."), "grep": z.union([z.string(), z.null()]).describe("Optional pattern to filter log lines.").default(null), "repo": z.union([z.string().regex(new RegExp("^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$")), z.null()]).describe("The target repository override (e.g. org/repo).").default(null) });
|
|
2542
2673
|
|
|
2543
2674
|
export type RepoLogsInput = z.infer<typeof RepoLogsInputSchema>;
|
|
2544
2675
|
|
|
@@ -2561,6 +2692,20 @@ export const RepoLogsInputJsonSchema = {
|
|
|
2561
2692
|
"default": null,
|
|
2562
2693
|
"description": "Optional pattern to filter log lines.",
|
|
2563
2694
|
"title": "Grep"
|
|
2695
|
+
},
|
|
2696
|
+
"repo": {
|
|
2697
|
+
"anyOf": [
|
|
2698
|
+
{
|
|
2699
|
+
"pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$",
|
|
2700
|
+
"type": "string"
|
|
2701
|
+
},
|
|
2702
|
+
{
|
|
2703
|
+
"type": "null"
|
|
2704
|
+
}
|
|
2705
|
+
],
|
|
2706
|
+
"default": null,
|
|
2707
|
+
"description": "The target repository override (e.g. org/repo).",
|
|
2708
|
+
"title": "Repo"
|
|
2564
2709
|
}
|
|
2565
2710
|
},
|
|
2566
2711
|
"required": [
|
|
@@ -1,31 +1,21 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { getMergeConflictFiles } from "../lib/git.js";
|
|
3
3
|
|
|
4
4
|
export const GetMergeConflictFilesInputSchema = z.object({
|
|
5
|
-
prNumber: z.number(),
|
|
5
|
+
prNumber: z.number().optional(),
|
|
6
6
|
baseBranch: z.string().optional().default("main"),
|
|
7
|
+
headRef: z.string().optional().default("HEAD"),
|
|
7
8
|
});
|
|
8
9
|
|
|
9
10
|
export async function getMergeConflictFilesHandler(args: z.infer<typeof GetMergeConflictFilesInputSchema>) {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
]);
|
|
14
|
-
|
|
15
|
-
if (result.exitCode !== 0) {
|
|
16
|
-
throw new Error(`Failed to get merge conflicts: ${result.stderr}`);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const output = JSON.parse(result.stdout);
|
|
20
|
-
if (output.status === "error") {
|
|
21
|
-
throw new Error(`Failed to get merge conflicts: ${output.message}`);
|
|
22
|
-
}
|
|
11
|
+
const baseBranch = args.baseBranch || "main";
|
|
12
|
+
const headRef = args.headRef || "HEAD";
|
|
13
|
+
const conflictFiles = await getMergeConflictFiles(baseBranch, headRef);
|
|
23
14
|
|
|
24
15
|
return {
|
|
25
|
-
prNumber:
|
|
26
|
-
baseBranch
|
|
27
|
-
headRef
|
|
28
|
-
conflictFiles
|
|
29
|
-
commandLog: output.commandLog
|
|
16
|
+
prNumber: args.prNumber,
|
|
17
|
+
baseBranch,
|
|
18
|
+
headRef,
|
|
19
|
+
conflictFiles,
|
|
30
20
|
};
|
|
31
21
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
import { resolveRepo } from "../lib/git.js";
|
|
4
|
+
import { sanitizeError } from "../lib/error_utils.js";
|
|
5
|
+
|
|
6
|
+
const GetPrCommitsInputSchema = z.object({
|
|
7
|
+
prNumber: z.number().describe("The number of the PR to list commits for."),
|
|
8
|
+
repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export async function getPrCommitsHandler(args: z.infer<typeof GetPrCommitsInputSchema>) {
|
|
12
|
+
const params = GetPrCommitsInputSchema.parse(args);
|
|
13
|
+
const targetRepo = resolveRepo(params.repo);
|
|
14
|
+
|
|
15
|
+
const result = await runCommand("td-cli", ["gh", "list-pr-commits", "--pr-number", params.prNumber.toString(), "--repo", targetRepo]);
|
|
16
|
+
|
|
17
|
+
if (result.exitCode !== 0) {
|
|
18
|
+
throw new Error(`Failed to list PR commits: ${sanitizeError(result.stderr)}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const output = JSON.parse(result.stdout);
|
|
22
|
+
if (output.status === "error") {
|
|
23
|
+
throw new Error(`Failed to list PR commits: ${output.message}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return { commits: output.commits };
|
|
27
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { getPrReviewsHandler } from "./github.get_pr_reviews.js";
|
|
3
|
+
import { getPrCommitsHandler } from "./github.get_pr_commits.js";
|
|
4
|
+
import { getWorkflowRunsHandler } from "./repo.get_workflow_runs.js";
|
|
5
|
+
import * as shell from "../lib/shell.js";
|
|
6
|
+
|
|
7
|
+
vi.mock("../lib/shell.js", () => ({
|
|
8
|
+
runCommand: vi.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
describe("MCP GitHub tools", () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("getPrReviewsHandler", () => {
|
|
17
|
+
it("should fetch PR reviews via td-cli gh read-pr-reviews", async () => {
|
|
18
|
+
vi.mocked(shell.runCommand).mockResolvedValueOnce({
|
|
19
|
+
stdout: JSON.stringify({
|
|
20
|
+
status: "success",
|
|
21
|
+
reviews: [{ id: 1, state: "APPROVED", body: "LGTM" }],
|
|
22
|
+
review_count: 1,
|
|
23
|
+
}),
|
|
24
|
+
stderr: "",
|
|
25
|
+
exitCode: 0,
|
|
26
|
+
durationMs: 10,
|
|
27
|
+
command: "td-cli gh read-pr-reviews",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const res = await getPrReviewsHandler({ prNumber: 819, repo: "arii/boomtick" });
|
|
31
|
+
|
|
32
|
+
expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
|
|
33
|
+
"gh",
|
|
34
|
+
"read-pr-reviews",
|
|
35
|
+
"--pr-number",
|
|
36
|
+
"819",
|
|
37
|
+
"--repo",
|
|
38
|
+
"arii/boomtick",
|
|
39
|
+
]);
|
|
40
|
+
expect(res).toEqual({
|
|
41
|
+
reviews: [{ id: 1, state: "APPROVED", body: "LGTM" }],
|
|
42
|
+
reviewCount: 1,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("getPrCommitsHandler", () => {
|
|
48
|
+
it("should list PR commits via td-cli gh list-pr-commits", async () => {
|
|
49
|
+
vi.mocked(shell.runCommand).mockResolvedValueOnce({
|
|
50
|
+
stdout: JSON.stringify({
|
|
51
|
+
status: "success",
|
|
52
|
+
commits: [{ sha: "abc1234", author: "Dev", message: "feat: test" }],
|
|
53
|
+
}),
|
|
54
|
+
stderr: "",
|
|
55
|
+
exitCode: 0,
|
|
56
|
+
durationMs: 10,
|
|
57
|
+
command: "td-cli gh list-pr-commits",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const res = await getPrCommitsHandler({ prNumber: 819, repo: "arii/boomtick" });
|
|
61
|
+
|
|
62
|
+
expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
|
|
63
|
+
"gh",
|
|
64
|
+
"list-pr-commits",
|
|
65
|
+
"--pr-number",
|
|
66
|
+
"819",
|
|
67
|
+
"--repo",
|
|
68
|
+
"arii/boomtick",
|
|
69
|
+
]);
|
|
70
|
+
expect(res).toEqual({
|
|
71
|
+
commits: [{ sha: "abc1234", author: "Dev", message: "feat: test" }],
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("getWorkflowRunsHandler", () => {
|
|
77
|
+
it("should fetch workflow runs via td-cli repo workflow-runs", async () => {
|
|
78
|
+
vi.mocked(shell.runCommand).mockResolvedValueOnce({
|
|
79
|
+
stdout: JSON.stringify({
|
|
80
|
+
status: "success",
|
|
81
|
+
workflow_runs: [{ id: 10, name: "CI" }],
|
|
82
|
+
}),
|
|
83
|
+
stderr: "",
|
|
84
|
+
exitCode: 0,
|
|
85
|
+
durationMs: 10,
|
|
86
|
+
command: "td-cli repo workflow-runs",
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const res = await getWorkflowRunsHandler({
|
|
90
|
+
branch: "main",
|
|
91
|
+
event: "push",
|
|
92
|
+
repo: "arii/boomtick",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
|
|
96
|
+
"repo",
|
|
97
|
+
"workflow-runs",
|
|
98
|
+
"--repo",
|
|
99
|
+
"arii/boomtick",
|
|
100
|
+
"--branch",
|
|
101
|
+
"main",
|
|
102
|
+
"--event",
|
|
103
|
+
"push",
|
|
104
|
+
]);
|
|
105
|
+
expect(res).toEqual({
|
|
106
|
+
workflowRuns: [{ id: 10, name: "CI" }],
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
import { resolveRepo } from "../lib/git.js";
|
|
4
|
+
import { sanitizeError } from "../lib/error_utils.js";
|
|
5
|
+
|
|
6
|
+
const GetPrReviewsInputSchema = z.object({
|
|
7
|
+
prNumber: z.number().describe("The number of the PR to view reviews for."),
|
|
8
|
+
repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export async function getPrReviewsHandler(args: z.infer<typeof GetPrReviewsInputSchema>) {
|
|
12
|
+
const params = GetPrReviewsInputSchema.parse(args);
|
|
13
|
+
const targetRepo = resolveRepo(params.repo);
|
|
14
|
+
|
|
15
|
+
const result = await runCommand("td-cli", ["gh", "read-pr-reviews", "--pr-number", params.prNumber.toString(), "--repo", targetRepo]);
|
|
16
|
+
|
|
17
|
+
if (result.exitCode !== 0) {
|
|
18
|
+
throw new Error(`Failed to read PR reviews: ${sanitizeError(result.stderr)}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const output = JSON.parse(result.stdout);
|
|
22
|
+
if (output.status === "error") {
|
|
23
|
+
throw new Error(`Failed to read PR reviews: ${output.message}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return { reviews: output.reviews, reviewCount: output.review_count };
|
|
27
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { runTDCli } from "
|
|
3
|
-
import { parseJulesSession } from "./shared.js";
|
|
2
|
+
import { runTDCli, parseJulesSession } from "./shared.js";
|
|
4
3
|
|
|
5
4
|
export const GetJulesPullRequestInputSchema = z.object({
|
|
6
5
|
sessionId: z.string(),
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { runTDCli } from "
|
|
3
|
-
import { parseJulesSession } from "./shared.js";
|
|
2
|
+
import { runTDCli, parseJulesSession } from "./shared.js";
|
|
4
3
|
|
|
5
4
|
export const GetJulesSessionInputSchema = z.object({
|
|
6
5
|
sessionId: z.string(),
|
|
@@ -1,4 +1,39 @@
|
|
|
1
1
|
import { JulesSession, JulesStatus } from "../types.js";
|
|
2
|
+
import { runCommand } from "../../lib/shell.js";
|
|
3
|
+
|
|
4
|
+
export interface TDCliResponse {
|
|
5
|
+
status?: string;
|
|
6
|
+
message?: string;
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function tryParseJson(text: string): TDCliResponse | null {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(text) as TDCliResponse;
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function runTDCli(args: string[]): Promise<TDCliResponse> {
|
|
19
|
+
const result = await runCommand("td-cli", args);
|
|
20
|
+
const stdout = result.stdout.trim();
|
|
21
|
+
const output = stdout.startsWith("{") ? tryParseJson(stdout) : null;
|
|
22
|
+
|
|
23
|
+
if (output?.status === "error") {
|
|
24
|
+
throw new Error(`td-cli returned error: ${output.message ?? "Unknown error"}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (result.exitCode !== 0) {
|
|
28
|
+
throw new Error(`td-cli command failed (${args.join(" ")}): ${result.stderr || stdout}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!output) {
|
|
32
|
+
throw new Error(`td-cli returned non-JSON output with exit code 0: ${stdout}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return output;
|
|
36
|
+
}
|
|
2
37
|
|
|
3
38
|
export function parseJulesSession(data: any): JulesSession {
|
|
4
39
|
const name = data.name || "";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { getChangedFiles } from "../lib/git.js";
|
|
3
3
|
|
|
4
4
|
export const GetChangedFilesInputSchema = z.object({
|
|
5
5
|
base: z.string().optional().default("main"),
|
|
@@ -7,20 +7,6 @@ export const GetChangedFilesInputSchema = z.object({
|
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
export async function getChangedFilesHandler(args: z.infer<typeof GetChangedFilesInputSchema>) {
|
|
10
|
-
const
|
|
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
|
-
|
|
10
|
+
const files = await getChangedFiles(args.base || "main", args.head || "HEAD");
|
|
25
11
|
return { files };
|
|
26
12
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
import { resolveRepo } from "../lib/git.js";
|
|
4
|
+
import { sanitizeError } from "../lib/error_utils.js";
|
|
5
|
+
|
|
6
|
+
const GetWorkflowRunsInputSchema = z.object({
|
|
7
|
+
branch: z.string().optional().describe("Filter workflow runs by branch name."),
|
|
8
|
+
event: z.string().optional().describe("Filter workflow runs by event (e.g. push, pull_request)."),
|
|
9
|
+
repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export async function getWorkflowRunsHandler(args: z.infer<typeof GetWorkflowRunsInputSchema>) {
|
|
13
|
+
const params = GetWorkflowRunsInputSchema.parse(args);
|
|
14
|
+
const targetRepo = resolveRepo(params.repo);
|
|
15
|
+
|
|
16
|
+
const cliArgs = ["repo", "workflow-runs", "--repo", targetRepo];
|
|
17
|
+
if (params.branch) {
|
|
18
|
+
cliArgs.push("--branch", params.branch);
|
|
19
|
+
}
|
|
20
|
+
if (params.event) {
|
|
21
|
+
cliArgs.push("--event", params.event);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const result = await runCommand("td-cli", cliArgs);
|
|
25
|
+
|
|
26
|
+
if (result.exitCode !== 0) {
|
|
27
|
+
throw new Error(`Failed to fetch workflow runs: ${sanitizeError(result.stderr)}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const output = JSON.parse(result.stdout);
|
|
31
|
+
if (output.status === "error") {
|
|
32
|
+
throw new Error(`Failed to fetch workflow runs: ${output.message}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { workflowRuns: output.workflow_runs };
|
|
36
|
+
}
|
package/src/tools/repo.logs.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { runTDCli } from "
|
|
2
|
+
import { runTDCli } from "./jules/shared.js";
|
|
3
3
|
|
|
4
4
|
export const RepoLogsInputSchema = z.object({
|
|
5
5
|
prNumber: z.number(),
|
|
6
6
|
grep: z.string().optional(),
|
|
7
|
+
repo: z.string().optional(),
|
|
7
8
|
});
|
|
8
9
|
|
|
9
10
|
export async function repoLogsHandler(args: z.infer<typeof RepoLogsInputSchema>) {
|
|
@@ -12,6 +13,9 @@ export async function repoLogsHandler(args: z.infer<typeof RepoLogsInputSchema>)
|
|
|
12
13
|
if (args.grep) {
|
|
13
14
|
params.push("--grep", args.grep);
|
|
14
15
|
}
|
|
16
|
+
if (args.repo) {
|
|
17
|
+
params.push("--repo", args.repo);
|
|
18
|
+
}
|
|
15
19
|
|
|
16
20
|
const output = await runTDCli(params);
|
|
17
21
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { loadStandaloneAgentContext } from "
|
|
2
|
+
import { loadStandaloneAgentContext } from "./repo.read_agent_context.js";
|
|
3
3
|
|
|
4
4
|
describe("Standalone Agent Context Loader", () => {
|
|
5
5
|
it("should load agent context without submodule references", () => {
|
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { config } from "../config.js";
|
|
3
|
-
import {
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
|
|
7
|
+
export interface AgentContext {
|
|
8
|
+
packageName: string;
|
|
9
|
+
gitCommit: string;
|
|
10
|
+
config: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const loadStandaloneAgentContext = (repoRoot: string = process.cwd()): AgentContext => {
|
|
14
|
+
const configPath = path.join(repoRoot, "project_config.json");
|
|
15
|
+
let projectConfig: Record<string, unknown> = {};
|
|
16
|
+
|
|
17
|
+
if (fs.existsSync(configPath)) {
|
|
18
|
+
try {
|
|
19
|
+
projectConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.warn(`[MCP Warning] Failed to parse ${configPath}:`, e);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let gitCommit = "unknown";
|
|
26
|
+
try {
|
|
27
|
+
gitCommit = execSync("git rev-parse HEAD", { cwd: repoRoot, encoding: "utf-8" }).trim();
|
|
28
|
+
} catch {
|
|
29
|
+
// Fallback if git is unavailable
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
packageName: (projectConfig.packageName as string) ?? "boomtick",
|
|
34
|
+
gitCommit,
|
|
35
|
+
config: projectConfig,
|
|
36
|
+
};
|
|
37
|
+
};
|
|
4
38
|
|
|
5
39
|
export const ReadAgentContextInputSchema = z.object({});
|
|
6
40
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { runTDCli } from "
|
|
2
|
+
import { runTDCli } from "./jules/shared.js";
|
|
3
3
|
|
|
4
4
|
export const ReadCiLogsInputSchema = z.object({
|
|
5
5
|
prNumber: z.number(),
|
|
6
6
|
all: z.boolean().optional(),
|
|
7
|
+
repo: z.string().optional(),
|
|
7
8
|
});
|
|
8
9
|
|
|
9
10
|
export async function readCiLogsHandler(args: z.infer<typeof ReadCiLogsInputSchema>) {
|
|
@@ -11,6 +12,9 @@ export async function readCiLogsHandler(args: z.infer<typeof ReadCiLogsInputSche
|
|
|
11
12
|
if (args.all) {
|
|
12
13
|
params.push("--all");
|
|
13
14
|
}
|
|
15
|
+
if (args.repo) {
|
|
16
|
+
params.push("--repo", args.repo);
|
|
17
|
+
}
|
|
14
18
|
const output = await runTDCli(params);
|
|
15
19
|
|
|
16
20
|
return {
|
|
@@ -9,7 +9,7 @@ export const RunTestsInputSchema = z.object({
|
|
|
9
9
|
|
|
10
10
|
export type RunTestsInput = z.input<typeof RunTestsInputSchema>;
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
function parseCommandArguments(fullCmd: string): [string, string[]] {
|
|
13
13
|
const tokens: string[] = [];
|
|
14
14
|
const regex = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|(\S+)/g;
|
|
15
15
|
let match: RegExpExecArray | null;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "agent.search_ddgs",
|
|
3
|
-
"description": "Search the web using DuckDuckGo (via ddgs python library).",
|
|
4
|
-
"parameters": {
|
|
5
|
-
"properties": {
|
|
6
|
-
"query": {
|
|
7
|
-
"description": "The search query.",
|
|
8
|
-
"title": "Query",
|
|
9
|
-
"type": "string"
|
|
10
|
-
},
|
|
11
|
-
"maxResults": {
|
|
12
|
-
"anyOf": [
|
|
13
|
-
{
|
|
14
|
-
"type": "integer"
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"type": "null"
|
|
18
|
-
}
|
|
19
|
-
],
|
|
20
|
-
"default": null,
|
|
21
|
-
"description": "Maximum number of results to return.",
|
|
22
|
-
"title": "Maxresults"
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
"required": [
|
|
26
|
-
"query"
|
|
27
|
-
],
|
|
28
|
-
"title": "SearchDdgsInput",
|
|
29
|
-
"type": "object"
|
|
30
|
-
}
|
|
31
|
-
}
|