boomtick-cli 0.2.1__py3-none-any.whl
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.
- boomtick_cli-0.2.1.dist-info/METADATA +20 -0
- boomtick_cli-0.2.1.dist-info/RECORD +103 -0
- boomtick_cli-0.2.1.dist-info/WHEEL +5 -0
- boomtick_cli-0.2.1.dist-info/entry_points.txt +3 -0
- boomtick_cli-0.2.1.dist-info/top_level.txt +1 -0
- dev_tools/__init__.py +1 -0
- dev_tools/cli-schema.json +2028 -0
- dev_tools/cli.py +1542 -0
- dev_tools/config.py +235 -0
- dev_tools/constants.py +14 -0
- dev_tools/daemon.py +146 -0
- dev_tools/dist/config.js +108 -0
- dev_tools/dist/index.js +10 -0
- dev_tools/dist/lib/error_utils.js +8 -0
- dev_tools/dist/lib/git.js +31 -0
- dev_tools/dist/lib/result.js +21 -0
- dev_tools/dist/lib/shell.js +91 -0
- dev_tools/dist/lib/shell.test.js +10 -0
- dev_tools/dist/lib/td-cli.js +27 -0
- dev_tools/dist/lib/test-utils.js +20 -0
- dev_tools/dist/mcp/definitions.js +242 -0
- dev_tools/dist/mcp/server.js +317 -0
- dev_tools/dist/mcp/tools.js +18 -0
- dev_tools/dist/tools/contract.js +2069 -0
- dev_tools/dist/tools/ddgs.search.js +50 -0
- dev_tools/dist/tools/ddgs.search.test.js +67 -0
- dev_tools/dist/tools/ddgs_search.py +23 -0
- dev_tools/dist/tools/github.checkout_branch.js +20 -0
- dev_tools/dist/tools/github.comment_triage_summary.js +25 -0
- dev_tools/dist/tools/github.create_issue.js +28 -0
- dev_tools/dist/tools/github.create_issue.test.js +54 -0
- dev_tools/dist/tools/github.create_pull_request.js +44 -0
- dev_tools/dist/tools/github.get_merge_conflict_files.js +26 -0
- dev_tools/dist/tools/github.get_pr.js +18 -0
- dev_tools/dist/tools/github.get_pr_diff.js +24 -0
- dev_tools/dist/tools/github.issue_comment.js +24 -0
- dev_tools/dist/tools/github.issue_update.js +29 -0
- dev_tools/dist/tools/github.issue_view.js +28 -0
- dev_tools/dist/tools/github.open_replacement_pr.js +41 -0
- dev_tools/dist/tools/github.search_open_prs.js +31 -0
- dev_tools/dist/tools/github.search_open_prs.test.js +51 -0
- dev_tools/dist/tools/jules/cancel-session.js +20 -0
- dev_tools/dist/tools/jules/create-session.js +20 -0
- dev_tools/dist/tools/jules/create-session.test.js +99 -0
- dev_tools/dist/tools/jules/get-messages.js +19 -0
- dev_tools/dist/tools/jules/get-messages.test.js +45 -0
- dev_tools/dist/tools/jules/get-pr.js +14 -0
- dev_tools/dist/tools/jules/get-pr.test.js +37 -0
- dev_tools/dist/tools/jules/get-session.js +10 -0
- dev_tools/dist/tools/jules/list-sessions.js +43 -0
- dev_tools/dist/tools/jules/send-message.js +22 -0
- dev_tools/dist/tools/jules/send-message.test.js +56 -0
- dev_tools/dist/tools/jules/shared.js +26 -0
- dev_tools/dist/tools/jules/trigger-feedback.js +16 -0
- dev_tools/dist/tools/jules/trigger-feedback.test.js +42 -0
- dev_tools/dist/tools/repo.commit_patch.js +33 -0
- dev_tools/dist/tools/repo.create_branch.js +21 -0
- dev_tools/dist/tools/repo.create_branch.test.js +42 -0
- dev_tools/dist/tools/repo.create_repair_branch.js +38 -0
- dev_tools/dist/tools/repo.get_changed_files.js +21 -0
- dev_tools/dist/tools/repo.get_command_schema.js +27 -0
- dev_tools/dist/tools/repo.get_package_scripts.js +35 -0
- dev_tools/dist/tools/repo.get_package_scripts.test.js +39 -0
- dev_tools/dist/tools/repo.get_route_map.js +37 -0
- dev_tools/dist/tools/repo.logs.js +16 -0
- dev_tools/dist/tools/repo.read_agent_context.js +20 -0
- dev_tools/dist/tools/repo.read_ci_logs.js +18 -0
- dev_tools/dist/tools/repo.run_lighthouse.js +37 -0
- dev_tools/dist/tools/repo.run_playwright.js +29 -0
- dev_tools/dist/tools/repo.run_tests.js +43 -0
- dev_tools/dist/tools/types.js +1 -0
- dev_tools/get_ai_context.py +63 -0
- dev_tools/handlers/__init__.py +1 -0
- dev_tools/handlers/command_handler.py +69 -0
- dev_tools/mcp_server.py +36 -0
- dev_tools/models.py +354 -0
- dev_tools/orchestrator.py +2841 -0
- dev_tools/pr_overlap.py +139 -0
- dev_tools/resources/__init__.py +1 -0
- dev_tools/resources/build-repo-context.py +182 -0
- dev_tools/resources/prompt_constants.json +5 -0
- dev_tools/resources/review_template.md +41 -0
- dev_tools/resources/ux-audit.config.json +15 -0
- dev_tools/resources/visual_guidelines.json +3 -0
- dev_tools/review_read_pass.py +232 -0
- dev_tools/schema_gen.py +55 -0
- dev_tools/schema_utils.py +125 -0
- dev_tools/scope_check.py +85 -0
- dev_tools/services/__init__.py +1 -0
- dev_tools/services/ai_service.py +816 -0
- dev_tools/services/dependency_graph.py +123 -0
- dev_tools/services/github.py +783 -0
- dev_tools/services/jules.py +181 -0
- dev_tools/services/repair_service.py +199 -0
- dev_tools/services/vector_store.py +82 -0
- dev_tools/services/vision_service.py +91 -0
- dev_tools/td_cli.py +28 -0
- dev_tools/utils/__init__.py +1035 -0
- dev_tools/utils/git.py +68 -0
- dev_tools/ux_report.py +213 -0
- dev_tools/verify_infra.py +91 -0
- dev_tools/verify_versions.py +191 -0
- dev_tools/version_utils.py +175 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { createJulesSessionHandler } from "./create-session.js";
|
|
3
|
+
import * as shell from "../../lib/shell.js";
|
|
4
|
+
import { setupTestEnv, teardownTestEnv } from "../../lib/test-utils.js";
|
|
5
|
+
vi.mock("../../lib/shell.js", () => ({
|
|
6
|
+
runCommand: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
describe("createJulesSessionHandler", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
setupTestEnv();
|
|
11
|
+
vi.mocked(shell.runCommand).mockResolvedValue({
|
|
12
|
+
stdout: JSON.stringify({
|
|
13
|
+
status: "success",
|
|
14
|
+
name: "sessions/1234567890",
|
|
15
|
+
state: "IN_PROGRESS",
|
|
16
|
+
createTime: new Date().toISOString(),
|
|
17
|
+
}),
|
|
18
|
+
stderr: "",
|
|
19
|
+
exitCode: 0,
|
|
20
|
+
durationMs: 10,
|
|
21
|
+
command: "td-cli agent dispatch"
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
teardownTestEnv();
|
|
26
|
+
vi.clearAllMocks();
|
|
27
|
+
});
|
|
28
|
+
it("should create a session with just task", async () => {
|
|
29
|
+
const result = await createJulesSessionHandler({ task: "do work" });
|
|
30
|
+
expect(result.id).toBe("1234567890");
|
|
31
|
+
expect(result.status).toBe("IN_PROGRESS");
|
|
32
|
+
expect(result.createdAt).toBeInstanceOf(Date);
|
|
33
|
+
expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
|
|
34
|
+
"agent", "dispatch", "main", "do work"
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
it("should create a session with specific branch", async () => {
|
|
38
|
+
const result = await createJulesSessionHandler({ task: "do work", branch: "feature-x" });
|
|
39
|
+
expect(result.id).toBe("1234567890");
|
|
40
|
+
expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
|
|
41
|
+
"agent", "dispatch", "feature-x", "do work"
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
it("should create a session with PR number", async () => {
|
|
45
|
+
vi.mocked(shell.runCommand).mockImplementation(async (cmd, args) => {
|
|
46
|
+
if (args[1] === "view") {
|
|
47
|
+
return {
|
|
48
|
+
stdout: JSON.stringify({ status: "success", pr: { headRefName: "pr-branch" } }),
|
|
49
|
+
stderr: "",
|
|
50
|
+
exitCode: 0,
|
|
51
|
+
durationMs: 10,
|
|
52
|
+
command: "td-cli gh view"
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
stdout: JSON.stringify({
|
|
57
|
+
status: "success",
|
|
58
|
+
name: "sessions/1234567890",
|
|
59
|
+
state: "IN_PROGRESS",
|
|
60
|
+
createTime: new Date().toISOString(),
|
|
61
|
+
}),
|
|
62
|
+
stderr: "",
|
|
63
|
+
exitCode: 0,
|
|
64
|
+
durationMs: 10,
|
|
65
|
+
command: "td-cli agent dispatch"
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
const result = await createJulesSessionHandler({ task: "do work", pr: 42 });
|
|
69
|
+
expect(result.id).toBe("1234567890");
|
|
70
|
+
expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
|
|
71
|
+
"gh", "view", "42"
|
|
72
|
+
]);
|
|
73
|
+
expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
|
|
74
|
+
"agent", "dispatch", "pr-branch", "do work"
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
it("should throw error if PR view fails", async () => {
|
|
78
|
+
vi.mocked(shell.runCommand).mockResolvedValue({
|
|
79
|
+
stdout: JSON.stringify({ status: "error", message: "PR not found" }),
|
|
80
|
+
stderr: "Original error",
|
|
81
|
+
exitCode: 1,
|
|
82
|
+
durationMs: 10,
|
|
83
|
+
command: "td-cli gh view"
|
|
84
|
+
});
|
|
85
|
+
await expect(createJulesSessionHandler({ task: "do work", pr: 999 }))
|
|
86
|
+
.rejects.toThrow("td-cli returned error: PR not found");
|
|
87
|
+
});
|
|
88
|
+
it("should throw descriptive error if PR JSON is invalid", async () => {
|
|
89
|
+
vi.mocked(shell.runCommand).mockResolvedValue({
|
|
90
|
+
stdout: "invalid json",
|
|
91
|
+
stderr: "",
|
|
92
|
+
exitCode: 0,
|
|
93
|
+
durationMs: 10,
|
|
94
|
+
command: "td-cli gh view"
|
|
95
|
+
});
|
|
96
|
+
await expect(createJulesSessionHandler({ task: "do work", pr: 42 }))
|
|
97
|
+
.rejects.toThrow("td-cli returned non-JSON output with exit code 0: invalid json");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../../lib/shell.js";
|
|
3
|
+
export const GetJulesMessagesInputSchema = z.object({
|
|
4
|
+
sessionId: z.string(),
|
|
5
|
+
});
|
|
6
|
+
export async function getJulesMessagesHandler(input) {
|
|
7
|
+
const result = await runCommand("td-cli", ["agent", "messages", input.sessionId]);
|
|
8
|
+
if (result.exitCode !== 0) {
|
|
9
|
+
throw new Error(`Failed to get messages: ${result.stderr}`);
|
|
10
|
+
}
|
|
11
|
+
const output = JSON.parse(result.stdout);
|
|
12
|
+
if (output.status === "error") {
|
|
13
|
+
throw new Error(`Failed to get messages: ${output.message}`);
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
id: input.sessionId.replace("sessions/", ""),
|
|
17
|
+
messages: output.messages,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { getJulesMessagesHandler } from "./get-messages.js";
|
|
3
|
+
import * as shell from "../../lib/shell.js";
|
|
4
|
+
import { setupTestEnv, teardownTestEnv } from "../../lib/test-utils.js";
|
|
5
|
+
vi.mock("../../lib/shell.js", () => ({
|
|
6
|
+
runCommand: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
describe("getJulesMessagesHandler", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
setupTestEnv();
|
|
11
|
+
});
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
teardownTestEnv();
|
|
14
|
+
vi.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
it("should return messages from td-cli", async () => {
|
|
17
|
+
const mockMessages = [
|
|
18
|
+
{
|
|
19
|
+
role: "user",
|
|
20
|
+
content: "Hello Jules",
|
|
21
|
+
time: new Date().toISOString(),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
role: "jules",
|
|
25
|
+
content: "Applying fixes...",
|
|
26
|
+
time: new Date().toISOString(),
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
vi.mocked(shell.runCommand).mockResolvedValue({
|
|
30
|
+
stdout: JSON.stringify({ status: "success", messages: mockMessages }),
|
|
31
|
+
stderr: "",
|
|
32
|
+
exitCode: 0,
|
|
33
|
+
durationMs: 0,
|
|
34
|
+
command: "td-cli agent messages"
|
|
35
|
+
});
|
|
36
|
+
const result = await getJulesMessagesHandler({ sessionId: "123" });
|
|
37
|
+
expect(result.id).toBe("123");
|
|
38
|
+
expect(result.messages.length).toBe(2);
|
|
39
|
+
expect(result.messages[0].role).toBe("user");
|
|
40
|
+
expect(result.messages[0].content).toBe("Hello Jules");
|
|
41
|
+
expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
|
|
42
|
+
"agent", "messages", "123"
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runTDCli } from "../../lib/td-cli.js";
|
|
3
|
+
import { parseJulesSession } from "./shared.js";
|
|
4
|
+
export const GetJulesPullRequestInputSchema = z.object({
|
|
5
|
+
sessionId: z.string(),
|
|
6
|
+
});
|
|
7
|
+
export async function getJulesPullRequestHandler(input) {
|
|
8
|
+
const output = await runTDCli(["agent", "get-session", input.sessionId]);
|
|
9
|
+
const session = parseJulesSession(output.session);
|
|
10
|
+
return {
|
|
11
|
+
id: session.id,
|
|
12
|
+
pullRequestUrl: session.pullRequestUrl,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { getJulesPullRequestHandler } from "./get-pr.js";
|
|
3
|
+
import * as shell from "../../lib/shell.js";
|
|
4
|
+
import { setupTestEnv, teardownTestEnv } from "../../lib/test-utils.js";
|
|
5
|
+
vi.mock("../../lib/shell.js", () => ({
|
|
6
|
+
runCommand: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
describe("getJulesPullRequestHandler", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
setupTestEnv();
|
|
11
|
+
});
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
teardownTestEnv();
|
|
14
|
+
vi.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
it("should get PR from td-cli", async () => {
|
|
17
|
+
vi.mocked(shell.runCommand).mockResolvedValue({
|
|
18
|
+
stdout: JSON.stringify({
|
|
19
|
+
status: "success",
|
|
20
|
+
session: {
|
|
21
|
+
name: "sessions/123",
|
|
22
|
+
outputs: [{ pullRequest: { url: "https://github.com/pr/1" } }]
|
|
23
|
+
}
|
|
24
|
+
}),
|
|
25
|
+
stderr: "",
|
|
26
|
+
exitCode: 0,
|
|
27
|
+
durationMs: 0,
|
|
28
|
+
command: "td-cli agent get-session"
|
|
29
|
+
});
|
|
30
|
+
const result = await getJulesPullRequestHandler({ sessionId: "123" });
|
|
31
|
+
expect(result.id).toBe("123");
|
|
32
|
+
expect(result.pullRequestUrl).toBe("https://github.com/pr/1");
|
|
33
|
+
expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
|
|
34
|
+
"agent", "get-session", "123"
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runTDCli } from "../../lib/td-cli.js";
|
|
3
|
+
import { parseJulesSession } from "./shared.js";
|
|
4
|
+
export const GetJulesSessionInputSchema = z.object({
|
|
5
|
+
sessionId: z.string(),
|
|
6
|
+
});
|
|
7
|
+
export async function getJulesSessionHandler(input) {
|
|
8
|
+
const output = await runTDCli(["agent", "get-session", input.sessionId]);
|
|
9
|
+
return parseJulesSession(output.session);
|
|
10
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../../lib/shell.js";
|
|
3
|
+
export const ListJulesSessionsInputSchema = z.object({
|
|
4
|
+
pageSize: z.number().optional(),
|
|
5
|
+
pageToken: z.string().optional(),
|
|
6
|
+
});
|
|
7
|
+
export async function listJulesSessionsHandler(input) {
|
|
8
|
+
const result = await runCommand("td-cli", ["agent", "sync"]);
|
|
9
|
+
if (result.exitCode !== 0) {
|
|
10
|
+
throw new Error(`Failed to list sessions: ${result.stderr}`);
|
|
11
|
+
}
|
|
12
|
+
const output = JSON.parse(result.stdout);
|
|
13
|
+
if (output.status === "error") {
|
|
14
|
+
throw new Error(`Failed to list sessions: ${output.message}`);
|
|
15
|
+
}
|
|
16
|
+
const sessions = output.sessions || [];
|
|
17
|
+
return sessions.map((session) => {
|
|
18
|
+
const name = session.name || "";
|
|
19
|
+
const id = name.startsWith("sessions/") ? name.substring(9) : name;
|
|
20
|
+
let status = "PENDING";
|
|
21
|
+
if (session.state === "SUCCEEDED" || session.state === "COMPLETED")
|
|
22
|
+
status = "COMPLETED";
|
|
23
|
+
else if (session.state === "FAILED" || session.state === "CANCELLED" || session.state === "TERMINATED")
|
|
24
|
+
status = "FAILED";
|
|
25
|
+
else if (session.state === "IN_PROGRESS")
|
|
26
|
+
status = "IN_PROGRESS";
|
|
27
|
+
let pullRequestUrl;
|
|
28
|
+
if (session.outputs && Array.isArray(session.outputs)) {
|
|
29
|
+
for (const out of session.outputs) {
|
|
30
|
+
if (out.pullRequest && out.pullRequest.url) {
|
|
31
|
+
pullRequestUrl = out.pullRequest.url;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
id,
|
|
38
|
+
status,
|
|
39
|
+
createdAt: session.createTime ? new Date(session.createTime) : new Date(),
|
|
40
|
+
pullRequestUrl,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../../lib/shell.js";
|
|
3
|
+
export const SendJulesMessageInputSchema = z.object({
|
|
4
|
+
sessionId: z.union([z.string(), z.array(z.string())]),
|
|
5
|
+
message: z.string(),
|
|
6
|
+
});
|
|
7
|
+
export async function sendJulesMessageHandler(input) {
|
|
8
|
+
const sessionIds = Array.isArray(input.sessionId) ? input.sessionId.join(",") : input.sessionId;
|
|
9
|
+
const result = await runCommand("td-cli", ["agent", "send", sessionIds, input.message]);
|
|
10
|
+
if (result.exitCode !== 0) {
|
|
11
|
+
throw new Error(`Failed to send message: ${result.stderr}`);
|
|
12
|
+
}
|
|
13
|
+
const output = JSON.parse(result.stdout);
|
|
14
|
+
if (output.status === "error") {
|
|
15
|
+
throw new Error(`Failed to send message: ${output.message}`);
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
id: Array.isArray(input.sessionId) ? input.sessionId.map(id => id.replace("sessions/", "")) : input.sessionId.replace("sessions/", ""),
|
|
19
|
+
status: "success",
|
|
20
|
+
message: "Message sent successfully",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { sendJulesMessageHandler } from "./send-message.js";
|
|
3
|
+
import * as shell from "../../lib/shell.js";
|
|
4
|
+
import { setupTestEnv, teardownTestEnv } from "../../lib/test-utils.js";
|
|
5
|
+
vi.mock("../../lib/shell.js", () => ({
|
|
6
|
+
runCommand: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
describe("sendJulesMessageHandler", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
setupTestEnv();
|
|
11
|
+
});
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
teardownTestEnv();
|
|
14
|
+
vi.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
it("should send a message via td-cli", async () => {
|
|
17
|
+
vi.mocked(shell.runCommand).mockResolvedValue({
|
|
18
|
+
stdout: JSON.stringify({ status: "success", message: "Message sent successfully" }),
|
|
19
|
+
stderr: "",
|
|
20
|
+
exitCode: 0,
|
|
21
|
+
durationMs: 0,
|
|
22
|
+
command: "td-cli agent send"
|
|
23
|
+
});
|
|
24
|
+
const result = await sendJulesMessageHandler({ sessionId: "123", message: "hi" });
|
|
25
|
+
expect(result.id).toBe("123");
|
|
26
|
+
expect(result.status).toBe("success");
|
|
27
|
+
expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
|
|
28
|
+
"agent", "send", "123", "hi"
|
|
29
|
+
]);
|
|
30
|
+
});
|
|
31
|
+
it("should support batch message sending", async () => {
|
|
32
|
+
vi.mocked(shell.runCommand).mockResolvedValue({
|
|
33
|
+
stdout: JSON.stringify({
|
|
34
|
+
status: "success",
|
|
35
|
+
message: "Batch send completed. 2/2 successful.",
|
|
36
|
+
results: [
|
|
37
|
+
{ sessionId: "s1", status: "success" },
|
|
38
|
+
{ sessionId: "s2", status: "success" }
|
|
39
|
+
]
|
|
40
|
+
}),
|
|
41
|
+
stderr: "",
|
|
42
|
+
exitCode: 0,
|
|
43
|
+
durationMs: 0,
|
|
44
|
+
command: "td-cli agent send"
|
|
45
|
+
});
|
|
46
|
+
const result = await sendJulesMessageHandler({
|
|
47
|
+
sessionId: ["sessions/s1", "sessions/s2"],
|
|
48
|
+
message: "hi batch"
|
|
49
|
+
});
|
|
50
|
+
expect(result.id).toEqual(["s1", "s2"]);
|
|
51
|
+
expect(result.status).toBe("success");
|
|
52
|
+
expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
|
|
53
|
+
"agent", "send", "sessions/s1,sessions/s2", "hi batch"
|
|
54
|
+
]);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function parseJulesSession(data) {
|
|
2
|
+
const name = data.name || "";
|
|
3
|
+
const id = name.startsWith("sessions/") ? name.substring(9) : name;
|
|
4
|
+
let status = "PENDING";
|
|
5
|
+
if (data.state === "SUCCEEDED" || data.state === "COMPLETED")
|
|
6
|
+
status = "COMPLETED";
|
|
7
|
+
else if (data.state === "FAILED" || data.state === "CANCELLED" || data.state === "TERMINATED")
|
|
8
|
+
status = "FAILED";
|
|
9
|
+
else if (data.state === "IN_PROGRESS")
|
|
10
|
+
status = "IN_PROGRESS";
|
|
11
|
+
let pullRequestUrl;
|
|
12
|
+
if (data.outputs && Array.isArray(data.outputs)) {
|
|
13
|
+
for (const out of data.outputs) {
|
|
14
|
+
if (out.pullRequest && out.pullRequest.url) {
|
|
15
|
+
pullRequestUrl = out.pullRequest.url;
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
id,
|
|
22
|
+
status,
|
|
23
|
+
createdAt: data.createTime ? new Date(data.createTime) : new Date(),
|
|
24
|
+
pullRequestUrl,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../../lib/shell.js";
|
|
3
|
+
export const TriggerJulesFeedbackInputSchema = z.object({
|
|
4
|
+
sessionId: z.string(),
|
|
5
|
+
});
|
|
6
|
+
export async function triggerJulesFeedbackHandler(input) {
|
|
7
|
+
const result = await runCommand("td-cli", ["agent", "trigger-feedback", input.sessionId]);
|
|
8
|
+
if (result.exitCode !== 0) {
|
|
9
|
+
throw new Error(`Failed to trigger feedback: ${result.stderr}`);
|
|
10
|
+
}
|
|
11
|
+
const output = JSON.parse(result.stdout);
|
|
12
|
+
if (output.status === "error") {
|
|
13
|
+
throw new Error(`Failed to trigger feedback: ${output.message}`);
|
|
14
|
+
}
|
|
15
|
+
return output;
|
|
16
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { triggerJulesFeedbackHandler } from "./trigger-feedback.js";
|
|
3
|
+
import * as shell from "../../lib/shell.js";
|
|
4
|
+
import { setupTestEnv, teardownTestEnv } from "../../lib/test-utils.js";
|
|
5
|
+
vi.mock("../../lib/shell.js", () => ({
|
|
6
|
+
runCommand: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
describe("triggerJulesFeedbackHandler", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
setupTestEnv();
|
|
11
|
+
});
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
teardownTestEnv();
|
|
14
|
+
vi.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
it("should return success output from td-cli", async () => {
|
|
17
|
+
vi.mocked(shell.runCommand).mockResolvedValue({
|
|
18
|
+
stdout: JSON.stringify({ status: "success", feedback: "All checks passed successfully. You may proceed." }),
|
|
19
|
+
stderr: "",
|
|
20
|
+
exitCode: 0,
|
|
21
|
+
durationMs: 0,
|
|
22
|
+
command: "td-cli agent trigger-feedback"
|
|
23
|
+
});
|
|
24
|
+
const result = await triggerJulesFeedbackHandler({ sessionId: "123" });
|
|
25
|
+
expect(result.status).toBe("success");
|
|
26
|
+
expect(result.feedback).toBe("All checks passed successfully. You may proceed.");
|
|
27
|
+
expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
|
|
28
|
+
"agent", "trigger-feedback", "123"
|
|
29
|
+
]);
|
|
30
|
+
});
|
|
31
|
+
it("should throw error if td-cli returns error", async () => {
|
|
32
|
+
vi.mocked(shell.runCommand).mockResolvedValue({
|
|
33
|
+
stdout: JSON.stringify({ status: "error", message: "Session not found" }),
|
|
34
|
+
stderr: "",
|
|
35
|
+
exitCode: 0,
|
|
36
|
+
durationMs: 0,
|
|
37
|
+
command: "td-cli agent trigger-feedback"
|
|
38
|
+
});
|
|
39
|
+
await expect(triggerJulesFeedbackHandler({ sessionId: "123" }))
|
|
40
|
+
.rejects.toThrow("Failed to trigger feedback: Session not found");
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
export const CommitPatchInputSchema = z.object({
|
|
4
|
+
worktreePath: z.string(),
|
|
5
|
+
message: z.string(),
|
|
6
|
+
allowedFiles: z.array(z.string()),
|
|
7
|
+
writeMode: z.boolean().optional().default(false),
|
|
8
|
+
});
|
|
9
|
+
export async function commitPatchHandler(args) {
|
|
10
|
+
if (!args.writeMode) {
|
|
11
|
+
throw new Error("writeMode must be true to commit changes.");
|
|
12
|
+
}
|
|
13
|
+
const statusResult = await runCommand("git", ["status", "--porcelain"], { cwd: args.worktreePath });
|
|
14
|
+
const changedFiles = statusResult.stdout.trim().split("\n").filter(l => l.length > 0).map(l => l.substring(3));
|
|
15
|
+
const forbiddenFiles = changedFiles.filter(f => !args.allowedFiles.includes(f));
|
|
16
|
+
if (forbiddenFiles.length > 0) {
|
|
17
|
+
throw new Error(`The following files are not in the allowed list: ${forbiddenFiles.join(", ")}`);
|
|
18
|
+
}
|
|
19
|
+
if (changedFiles.length === 0) {
|
|
20
|
+
throw new Error("No changes to commit.");
|
|
21
|
+
}
|
|
22
|
+
await runCommand("git", ["add", "."], { cwd: args.worktreePath });
|
|
23
|
+
const commitResult = await runCommand("git", ["commit", "-m", args.message], { cwd: args.worktreePath });
|
|
24
|
+
if (commitResult.exitCode !== 0) {
|
|
25
|
+
throw new Error(`Failed to commit: ${commitResult.stderr}`);
|
|
26
|
+
}
|
|
27
|
+
const shaResult = await runCommand("git", ["rev-parse", "HEAD"], { cwd: args.worktreePath });
|
|
28
|
+
return {
|
|
29
|
+
success: true,
|
|
30
|
+
commitSha: shaResult.stdout.trim(),
|
|
31
|
+
changedFiles
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
export const CreateBranchInputSchema = z.object({
|
|
4
|
+
branchName: z.string(),
|
|
5
|
+
baseBranch: z.string().optional().default("main"),
|
|
6
|
+
});
|
|
7
|
+
export async function createBranchHandler(args) {
|
|
8
|
+
const params = CreateBranchInputSchema.parse(args);
|
|
9
|
+
// Ensure latest base branch
|
|
10
|
+
await runCommand("git", ["fetch", "origin", params.baseBranch]);
|
|
11
|
+
// Create branch from origin base branch
|
|
12
|
+
const result = await runCommand("git", ["checkout", "-b", params.branchName, `origin/${params.baseBranch}`]);
|
|
13
|
+
if (result.exitCode !== 0) {
|
|
14
|
+
throw new Error(`Failed to create branch ${params.branchName}: ${result.stderr}`);
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
success: true,
|
|
18
|
+
branchName: params.branchName,
|
|
19
|
+
baseBranch: params.baseBranch
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { createBranchHandler } from "./repo.create_branch.js";
|
|
3
|
+
import * as shell from "../lib/shell.js";
|
|
4
|
+
vi.mock("../lib/shell.js", () => ({
|
|
5
|
+
runCommand: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
describe("repo.create_branch", () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
vi.clearAllMocks();
|
|
10
|
+
});
|
|
11
|
+
it("should create a new branch from a base branch", async () => {
|
|
12
|
+
const args = {
|
|
13
|
+
branchName: "new-feature",
|
|
14
|
+
baseBranch: "main",
|
|
15
|
+
};
|
|
16
|
+
shell.runCommand.mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" });
|
|
17
|
+
const result = await createBranchHandler(args);
|
|
18
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["fetch", "origin", "main"]);
|
|
19
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["checkout", "-b", "new-feature", "origin/main"]);
|
|
20
|
+
expect(result).toEqual({
|
|
21
|
+
success: true,
|
|
22
|
+
branchName: "new-feature",
|
|
23
|
+
baseBranch: "main",
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
it("should use default base branch 'main' if not provided", async () => {
|
|
27
|
+
const args = {
|
|
28
|
+
branchName: "another-feature",
|
|
29
|
+
};
|
|
30
|
+
shell.runCommand.mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" });
|
|
31
|
+
const result = await createBranchHandler(args);
|
|
32
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["fetch", "origin", "main"]);
|
|
33
|
+
expect(shell.runCommand).toHaveBeenCalledWith("git", ["checkout", "-b", "another-feature", "origin/main"]);
|
|
34
|
+
});
|
|
35
|
+
it("should throw error if git command fails", async () => {
|
|
36
|
+
const args = {
|
|
37
|
+
branchName: "fail-branch",
|
|
38
|
+
};
|
|
39
|
+
shell.runCommand.mockResolvedValue({ exitCode: 1, stdout: "", stderr: "Error creating branch" });
|
|
40
|
+
await expect(createBranchHandler(args)).rejects.toThrow("Failed to create branch fail-branch: Error creating branch");
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
import { createWorktree } from "../lib/git.js";
|
|
4
|
+
export const CreateRepairBranchInputSchema = z.object({
|
|
5
|
+
prNumber: z.number(),
|
|
6
|
+
repairBranchName: z.string().optional(),
|
|
7
|
+
writeMode: z.boolean().optional().default(false),
|
|
8
|
+
});
|
|
9
|
+
export async function createRepairBranchHandler(args) {
|
|
10
|
+
if (!args.writeMode) {
|
|
11
|
+
throw new Error("writeMode must be true to create a repair branch.");
|
|
12
|
+
}
|
|
13
|
+
const prResult = await runCommand("gh", [
|
|
14
|
+
"pr",
|
|
15
|
+
"view",
|
|
16
|
+
args.prNumber.toString(),
|
|
17
|
+
"--json", "headRefName,baseRefName,title"
|
|
18
|
+
]);
|
|
19
|
+
if (prResult.exitCode !== 0) {
|
|
20
|
+
throw new Error(`Failed to get PR info: ${prResult.stderr}`);
|
|
21
|
+
}
|
|
22
|
+
const { headRefName, baseRefName, title } = JSON.parse(prResult.stdout);
|
|
23
|
+
const sanitizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "-").substring(0, 30);
|
|
24
|
+
const repairBranch = args.repairBranchName || `agent/repair-pr-${args.prNumber}-${sanitizedTitle}`;
|
|
25
|
+
// Ensure latest
|
|
26
|
+
await runCommand("git", ["fetch", "origin", headRefName]);
|
|
27
|
+
await runCommand("git", ["fetch", "origin", baseRefName]);
|
|
28
|
+
const worktreePath = await createWorktree(`origin/${headRefName}`, args.prNumber);
|
|
29
|
+
// Create the actual repair branch in the worktree
|
|
30
|
+
await runCommand("git", ["checkout", "-b", repairBranch], { cwd: worktreePath });
|
|
31
|
+
return {
|
|
32
|
+
prNumber: args.prNumber,
|
|
33
|
+
originalBranch: headRefName,
|
|
34
|
+
repairBranch,
|
|
35
|
+
baseBranch: baseRefName,
|
|
36
|
+
worktreePath
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
export const GetChangedFilesInputSchema = z.object({
|
|
4
|
+
base: z.string().optional().default("main"),
|
|
5
|
+
head: z.string().optional().default("HEAD"),
|
|
6
|
+
});
|
|
7
|
+
export async function getChangedFilesHandler(args) {
|
|
8
|
+
const result = await runCommand("git", [
|
|
9
|
+
"diff",
|
|
10
|
+
"--name-status",
|
|
11
|
+
`${args.base}...${args.head}`
|
|
12
|
+
]);
|
|
13
|
+
if (result.exitCode !== 0) {
|
|
14
|
+
throw new Error(`Failed to get changed files: ${result.stderr}`);
|
|
15
|
+
}
|
|
16
|
+
const files = result.stdout.trim().split("\n").filter(line => line.length > 0).map(line => {
|
|
17
|
+
const [status, path] = line.split(/\s+/);
|
|
18
|
+
return { status, path };
|
|
19
|
+
});
|
|
20
|
+
return { files };
|
|
21
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runCommand } from "../lib/shell.js";
|
|
3
|
+
export const GetCommandSchemaInputSchema = z.object({
|
|
4
|
+
commandPath: z.string().describe("The CLI command path to retrieve the schema for (e.g. 'gh audit-pr')"),
|
|
5
|
+
});
|
|
6
|
+
export async function getCommandSchemaHandler(args) {
|
|
7
|
+
// Sanitize input to prevent command injection (allow only alphanumeric, spaces, hyphens, and underscores)
|
|
8
|
+
const sanitizedPath = args.commandPath.replace(/[^a-zA-Z0-9-_ ]/g, "");
|
|
9
|
+
const result = await runCommand("td-cli", ["schema", sanitizedPath]);
|
|
10
|
+
if (result.exitCode !== 0) {
|
|
11
|
+
// Log stderr for debugging but don't expose it to the user
|
|
12
|
+
// Using comma-separated arguments to satisfy semgrep unsafe-formatstring
|
|
13
|
+
console.error("td-cli schema failed for", sanitizedPath, ":", result.stderr);
|
|
14
|
+
throw new Error(`Failed to get command schema for '${sanitizedPath}'`);
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(result.stdout);
|
|
18
|
+
if (parsed.status === "error") {
|
|
19
|
+
throw new Error(parsed.message || "Failed to get command schema");
|
|
20
|
+
}
|
|
21
|
+
// Return the full payload (which contains the 'schema' object)
|
|
22
|
+
return parsed;
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
throw new Error(`Failed to parse command schema output: ${e}`);
|
|
26
|
+
}
|
|
27
|
+
}
|