@arii/boomtick-mcp 0.6.2 → 0.7.1

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 (109) hide show
  1. package/.mcp/schemas/boomtick.health.json +22 -3
  2. package/.mcp/schemas/boomtick_health.json +22 -3
  3. package/.mcp/schemas/github.checkout_branch.json +16 -3
  4. package/.mcp/schemas/github.comment_triage_summary.json +16 -3
  5. package/.mcp/schemas/github.create_pull_request.json +16 -3
  6. package/.mcp/schemas/github.get_merge_conflict_files.json +55 -5
  7. package/.mcp/schemas/github.get_pr.json +12 -3
  8. package/.mcp/schemas/github.get_pr_commits.json +12 -3
  9. package/.mcp/schemas/github.get_pr_reviews.json +24 -3
  10. package/.mcp/schemas/github.issue_comment.json +19 -3
  11. package/.mcp/schemas/github.issue_view.json +17 -3
  12. package/.mcp/schemas/github.open_replacement_pr.json +16 -3
  13. package/.mcp/schemas/github_checkout_branch.json +16 -3
  14. package/.mcp/schemas/github_comment_triage_summary.json +16 -3
  15. package/.mcp/schemas/github_create_pull_request.json +16 -3
  16. package/.mcp/schemas/github_get_merge_conflict_files.json +55 -5
  17. package/.mcp/schemas/github_get_pr.json +12 -3
  18. package/.mcp/schemas/github_get_pr_commits.json +12 -3
  19. package/.mcp/schemas/github_get_pr_reviews.json +24 -3
  20. package/.mcp/schemas/github_issue_comment.json +19 -3
  21. package/.mcp/schemas/github_issue_view.json +17 -3
  22. package/.mcp/schemas/github_open_replacement_pr.json +16 -3
  23. package/.mcp/schemas/jules.cancel_session.json +38 -3
  24. package/.mcp/schemas/jules.create_session.json +52 -3
  25. package/.mcp/schemas/jules.get_messages.json +19 -3
  26. package/.mcp/schemas/jules.get_pr.json +23 -3
  27. package/.mcp/schemas/jules.get_session.json +38 -3
  28. package/.mcp/schemas/jules.list_sessions.json +53 -3
  29. package/.mcp/schemas/jules.send_message.json +31 -3
  30. package/.mcp/schemas/jules.trigger_feedback.json +28 -3
  31. package/.mcp/schemas/jules_cancel_session.json +38 -3
  32. package/.mcp/schemas/jules_create_session.json +52 -3
  33. package/.mcp/schemas/jules_get_messages.json +19 -3
  34. package/.mcp/schemas/jules_get_pr.json +23 -3
  35. package/.mcp/schemas/jules_get_session.json +38 -3
  36. package/.mcp/schemas/jules_list_sessions.json +53 -3
  37. package/.mcp/schemas/jules_send_message.json +31 -3
  38. package/.mcp/schemas/jules_trigger_feedback.json +28 -3
  39. package/.mcp/schemas/repo.check_jules_status.json +93 -3
  40. package/.mcp/schemas/repo.commit_patch.json +23 -3
  41. package/.mcp/schemas/repo.create_branch.json +91 -3
  42. package/.mcp/schemas/repo.create_repair_branch.json +94 -3
  43. package/.mcp/schemas/repo.get_changed_files.json +50 -3
  44. package/.mcp/schemas/repo.get_command_schema.json +29 -3
  45. package/.mcp/schemas/repo.get_package_scripts.json +11 -3
  46. package/.mcp/schemas/repo.get_workflow_runs.json +12 -3
  47. package/.mcp/schemas/repo.logs.json +14 -3
  48. package/.mcp/schemas/repo.multi_exec.json +89 -0
  49. package/.mcp/schemas/repo.read_agent_context.json +117 -5
  50. package/.mcp/schemas/repo.read_ci_logs.json +46 -3
  51. package/.mcp/schemas/repo_check_jules_status.json +93 -3
  52. package/.mcp/schemas/repo_commit_patch.json +23 -3
  53. package/.mcp/schemas/repo_create_branch.json +91 -3
  54. package/.mcp/schemas/repo_create_repair_branch.json +94 -3
  55. package/.mcp/schemas/repo_get_changed_files.json +50 -3
  56. package/.mcp/schemas/repo_get_command_schema.json +29 -3
  57. package/.mcp/schemas/repo_get_package_scripts.json +11 -3
  58. package/.mcp/schemas/repo_get_workflow_runs.json +12 -3
  59. package/.mcp/schemas/repo_logs.json +14 -3
  60. package/.mcp/schemas/repo_multi_exec.json +89 -0
  61. package/.mcp/schemas/repo_read_agent_context.json +117 -5
  62. package/.mcp/schemas/repo_read_ci_logs.json +46 -3
  63. package/CHANGELOG.md +22 -0
  64. package/README.md +7 -5
  65. package/dist/config/dynamic.js +3 -2
  66. package/dist/config/repo.js +5 -5
  67. package/dist/config.test.js +10 -3
  68. package/dist/lib/git.js +78 -6
  69. package/dist/lib/git.test.js +34 -1
  70. package/dist/mcp/definitions.js +49 -36
  71. package/dist/mcp/http.js +15 -0
  72. package/dist/mcp/http.test.js +8 -0
  73. package/dist/mcp/server.js +34 -0
  74. package/dist/mcp/server.test.js +31 -0
  75. package/dist/tools/contract.js +1264 -92
  76. package/dist/tools/github.get_merge_conflict_files.js +10 -2
  77. package/dist/tools/jules/create-session.js +12 -2
  78. package/dist/tools/jules/create-session.test.js +7 -0
  79. package/dist/tools/repo.create_branch.js +18 -4
  80. package/dist/tools/repo.create_branch.test.js +41 -5
  81. package/dist/tools/repo.create_repair_branch.js +26 -9
  82. package/dist/tools/repo.get_changed_files.js +10 -2
  83. package/dist/tools/repo.multi_exec.js +21 -0
  84. package/dist/tools/repo.multi_exec.test.js +13 -0
  85. package/dist/tools/repo.read_agent_context.js +65 -3
  86. package/dist/tools/repo.read_agent_context.test.js +30 -1
  87. package/package.json +1 -1
  88. package/src/config/dynamic.ts +3 -2
  89. package/src/config/repo.ts +6 -6
  90. package/src/config.test.ts +11 -3
  91. package/src/lib/git.test.ts +45 -1
  92. package/src/lib/git.ts +94 -6
  93. package/src/mcp/definitions.ts +77 -36
  94. package/src/mcp/http.test.ts +10 -0
  95. package/src/mcp/http.ts +20 -0
  96. package/src/mcp/server.test.ts +38 -0
  97. package/src/mcp/server.ts +36 -0
  98. package/src/tools/contract.ts +1355 -67
  99. package/src/tools/github.get_merge_conflict_files.ts +11 -2
  100. package/src/tools/jules/create-session.test.ts +9 -0
  101. package/src/tools/jules/create-session.ts +13 -2
  102. package/src/tools/repo.create_branch.test.ts +73 -5
  103. package/src/tools/repo.create_branch.ts +30 -4
  104. package/src/tools/repo.create_repair_branch.ts +31 -9
  105. package/src/tools/repo.get_changed_files.ts +10 -2
  106. package/src/tools/repo.multi_exec.test.ts +15 -0
  107. package/src/tools/repo.multi_exec.ts +23 -0
  108. package/src/tools/repo.read_agent_context.test.ts +35 -1
  109. package/src/tools/repo.read_agent_context.ts +68 -3
@@ -1,16 +1,25 @@
1
1
  import { z } from "zod";
2
- import { getMergeConflictFiles } from "../lib/git.js";
2
+ import { getMergeConflictFiles, resolveRepoPath } from "../lib/git.js";
3
3
 
4
4
  export const GetMergeConflictFilesInputSchema = z.object({
5
5
  prNumber: z.number().optional(),
6
6
  baseBranch: z.string().optional().default("main"),
7
7
  headRef: z.string().optional().default("HEAD"),
8
+ repo: z.string().optional().describe("Target GitHub repository override."),
9
+ path: z.string().optional().describe("Local filesystem path to target repository root."),
10
+ repoPath: z.string().optional().describe("Alias for path to target repository root."),
8
11
  });
9
12
 
10
13
  export async function getMergeConflictFilesHandler(args: z.infer<typeof GetMergeConflictFilesInputSchema>) {
11
14
  const baseBranch = args.baseBranch || "main";
12
15
  const headRef = args.headRef || "HEAD";
13
- const conflictFiles = await getMergeConflictFiles(baseBranch, headRef);
16
+ const targetCwd = resolveRepoPath({
17
+ path: args.path,
18
+ repoPath: args.repoPath,
19
+ repo: args.repo,
20
+ });
21
+
22
+ const conflictFiles = await getMergeConflictFiles(baseBranch, headRef, targetCwd);
14
23
 
15
24
  const hasConflicts = conflictFiles.length > 0;
16
25
 
@@ -49,6 +49,15 @@ describe("createJulesSessionHandler", () => {
49
49
  ]);
50
50
  });
51
51
 
52
+ it("should create a session with repo parameter", async () => {
53
+ const result = await createJulesSessionHandler({ task: "do work", branch: "main", repo: "arii/portfolio" });
54
+ expect(result.id).toBe("1234567890");
55
+
56
+ expect(shell.runCommand).toHaveBeenCalledWith("td", [
57
+ "agent", "dispatch", "--repo", "arii/portfolio", "main", "do work"
58
+ ]);
59
+ });
60
+
52
61
  it("should create a session with PR number", async () => {
53
62
  vi.mocked(shell.runCommand).mockImplementation(async (cmd, args) => {
54
63
  if (args[1] === "view") {
@@ -5,12 +5,17 @@ export const CreateJulesSessionInputSchema = z.object({
5
5
  task: z.string(),
6
6
  branch: z.string().optional(),
7
7
  pr: z.number().optional(),
8
+ repo: z.string().optional(),
8
9
  });
9
10
 
10
11
  export async function createJulesSessionHandler(input: z.infer<typeof CreateJulesSessionInputSchema>) {
11
12
  let branch = input.branch;
12
13
  if (!branch && input.pr) {
13
- const prData = await runTDCli(["gh", "view", input.pr.toString()]);
14
+ const ghArgs = ["gh", "view", input.pr.toString()];
15
+ if (input.repo) {
16
+ ghArgs.push("--repo", input.repo);
17
+ }
18
+ const prData = await runTDCli(ghArgs);
14
19
  branch = prData.pr.headRefName;
15
20
  }
16
21
 
@@ -18,6 +23,12 @@ export async function createJulesSessionHandler(input: z.infer<typeof CreateJule
18
23
  branch = process.env.DEFAULT_BASE_BRANCH || "main";
19
24
  }
20
25
 
21
- const output = await runTDCli(["agent", "dispatch", branch, input.task]);
26
+ const dispatchArgs = ["agent", "dispatch"];
27
+ if (input.repo) {
28
+ dispatchArgs.push("--repo", input.repo);
29
+ }
30
+ dispatchArgs.push(branch, input.task);
31
+
32
+ const output = await runTDCli(dispatchArgs);
22
33
  return parseJulesSession(output.session || output);
23
34
  }
@@ -21,13 +21,22 @@ describe("repo.create_branch", () => {
21
21
 
22
22
  const result = await createBranchHandler(args);
23
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({
24
+ expect(shell.runCommand).toHaveBeenCalledWith(
25
+ "git",
26
+ ["fetch", "origin", "main"],
27
+ { cwd: expect.any(String) }
28
+ );
29
+ expect(shell.runCommand).toHaveBeenCalledWith(
30
+ "git",
31
+ ["checkout", "-b", "new-feature", "origin/main"],
32
+ { cwd: expect.any(String) }
33
+ );
34
+ expect(result).toMatchObject({
27
35
  success: true,
28
36
  branchName: "new-feature",
29
37
  baseBranch: "main",
30
38
  });
39
+ expect(result.path).toBeDefined();
31
40
  });
32
41
 
33
42
  it("should use default base branch 'main' if not provided", async () => {
@@ -39,8 +48,67 @@ describe("repo.create_branch", () => {
39
48
 
40
49
  const result = await createBranchHandler(args);
41
50
 
42
- expect(shell.runCommand).toHaveBeenCalledWith("git", ["fetch", "origin", "main"]);
43
- expect(shell.runCommand).toHaveBeenCalledWith("git", ["checkout", "-b", "another-feature", "origin/main"]);
51
+ expect(shell.runCommand).toHaveBeenCalledWith(
52
+ "git",
53
+ ["fetch", "origin", "main"],
54
+ { cwd: expect.any(String) }
55
+ );
56
+ expect(shell.runCommand).toHaveBeenCalledWith(
57
+ "git",
58
+ ["checkout", "-b", "another-feature", "origin/main"],
59
+ { cwd: expect.any(String) }
60
+ );
61
+ expect(result.success).toBe(true);
62
+ });
63
+
64
+ it("should create branch in specified target workspace path", async () => {
65
+ const args: CreateBranchInput = {
66
+ branchName: "custom-path-feature",
67
+ baseBranch: "main",
68
+ path: "/home/ari/tech-dancer",
69
+ };
70
+
71
+ (shell.runCommand as any).mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" });
72
+
73
+ const result = await createBranchHandler(args);
74
+
75
+ expect(shell.runCommand).toHaveBeenCalledWith(
76
+ "git",
77
+ ["fetch", "origin", "main"],
78
+ { cwd: "/home/ari/tech-dancer" }
79
+ );
80
+ expect(shell.runCommand).toHaveBeenCalledWith(
81
+ "git",
82
+ ["checkout", "-b", "custom-path-feature", "origin/main"],
83
+ { cwd: "/home/ari/tech-dancer" }
84
+ );
85
+ expect(result).toEqual({
86
+ success: true,
87
+ branchName: "custom-path-feature",
88
+ baseBranch: "main",
89
+ path: "/home/ari/tech-dancer",
90
+ });
91
+ });
92
+
93
+ it("should include repo field when repo override is specified", async () => {
94
+ const args: CreateBranchInput = {
95
+ branchName: "repo-feature",
96
+ baseBranch: "main",
97
+ repo: "arii/tech-dancer",
98
+ path: "/home/ari/tech-dancer",
99
+ };
100
+
101
+ (shell.runCommand as any).mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" });
102
+
103
+ const result = await createBranchHandler(args);
104
+
105
+ expect(result).toEqual({
106
+ success: true,
107
+ branchName: "repo-feature",
108
+ baseBranch: "main",
109
+ path: "/home/ari/tech-dancer",
110
+ repo: "arii/tech-dancer",
111
+ });
44
112
  });
45
113
 
46
114
  it("should throw error if git command fails", async () => {
@@ -1,29 +1,55 @@
1
1
  import { z } from "zod";
2
2
  import { runCommand } from "../lib/shell.js";
3
+ import { resolveRepoPath } from "../lib/git.js";
3
4
 
4
5
  export const CreateBranchInputSchema = z.object({
5
6
  branchName: z.string(),
6
7
  baseBranch: z.string().optional().default("main"),
8
+ repo: z.string().optional().describe("Target repository override (e.g. org/repo or repo)."),
9
+ path: z.string().optional().describe("Local filesystem path to target repository root."),
10
+ repoPath: z.string().optional().describe("Alias for path to target repository root."),
7
11
  });
8
12
 
9
13
  export type CreateBranchInput = z.input<typeof CreateBranchInputSchema>;
10
14
 
11
15
  export async function createBranchHandler(args: CreateBranchInput) {
12
16
  const params = CreateBranchInputSchema.parse(args);
17
+ const targetCwd = resolveRepoPath({
18
+ path: params.path,
19
+ repoPath: params.repoPath,
20
+ repo: params.repo,
21
+ });
13
22
 
14
23
  // Ensure latest base branch
15
- await runCommand("git", ["fetch", "origin", params.baseBranch]);
24
+ await runCommand("git", ["fetch", "origin", params.baseBranch], { cwd: targetCwd });
16
25
 
17
26
  // Create branch from origin base branch
18
- const result = await runCommand("git", ["checkout", "-b", params.branchName, `origin/${params.baseBranch}`]);
27
+ const result = await runCommand(
28
+ "git",
29
+ ["checkout", "-b", params.branchName, `origin/${params.baseBranch}`],
30
+ { cwd: targetCwd }
31
+ );
19
32
 
20
33
  if (result.exitCode !== 0) {
21
34
  throw new Error(`Failed to create branch ${params.branchName}: ${result.stderr}`);
22
35
  }
23
36
 
24
- return {
37
+ const response: {
38
+ success: boolean;
39
+ branchName: string;
40
+ baseBranch: string;
41
+ path: string;
42
+ repo?: string;
43
+ } = {
25
44
  success: true,
26
45
  branchName: params.branchName,
27
- baseBranch: params.baseBranch
46
+ baseBranch: params.baseBranch,
47
+ path: targetCwd,
28
48
  };
49
+
50
+ if (params.repo) {
51
+ response.repo = params.repo;
52
+ }
53
+
54
+ return response;
29
55
  }
@@ -1,23 +1,38 @@
1
1
  import { z } from "zod";
2
2
  import { runCommand } from "../lib/shell.js";
3
- import { createWorktree } from "../lib/git.js";
3
+ import { createWorktree, resolveRepoPath } from "../lib/git.js";
4
4
 
5
5
  export const CreateRepairBranchInputSchema = z.object({
6
6
  prNumber: z.number(),
7
7
  repairBranchName: z.string().optional(),
8
8
  writeMode: z.boolean().optional().default(false),
9
+ repo: z.string().optional().describe("Target repository override (e.g. org/repo or repo)."),
10
+ path: z.string().optional().describe("Local filesystem path to target repository root."),
11
+ repoPath: z.string().optional().describe("Alias for path to target repository root."),
9
12
  });
10
13
 
11
14
  export async function createRepairBranchHandler(args: z.infer<typeof CreateRepairBranchInputSchema>) {
12
15
  if (!args.writeMode) {
13
16
  throw new Error("writeMode must be true to create a repair branch.");
14
17
  }
15
- const prResult = await runCommand("gh", [
18
+
19
+ const targetCwd = resolveRepoPath({
20
+ path: args.path,
21
+ repoPath: args.repoPath,
22
+ repo: args.repo,
23
+ });
24
+
25
+ const ghArgs = [
16
26
  "pr",
17
27
  "view",
18
28
  args.prNumber.toString(),
19
29
  "--json", "headRefName,baseRefName,title"
20
- ]);
30
+ ];
31
+ if (args.repo) {
32
+ ghArgs.push("--repo", args.repo);
33
+ }
34
+
35
+ const prResult = await runCommand("gh", ghArgs, { cwd: targetCwd });
21
36
 
22
37
  if (prResult.exitCode !== 0) {
23
38
  throw new Error(`Failed to get PR info: ${prResult.stderr}`);
@@ -28,20 +43,27 @@ export async function createRepairBranchHandler(args: z.infer<typeof CreateRepai
28
43
  const sanitizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "-").substring(0, 30);
29
44
  const repairBranch = args.repairBranchName || `agent/repair-pr-${args.prNumber}-${sanitizedTitle}`;
30
45
 
31
- // Ensure latest
32
- await runCommand("git", ["fetch", "origin", headRefName]);
33
- await runCommand("git", ["fetch", "origin", baseRefName]);
46
+ // Ensure latest in target repo
47
+ await runCommand("git", ["fetch", "origin", headRefName], { cwd: targetCwd });
48
+ await runCommand("git", ["fetch", "origin", baseRefName], { cwd: targetCwd });
34
49
 
35
- const worktreePath = await createWorktree(`origin/${headRefName}`, args.prNumber);
50
+ const worktreePath = await createWorktree(`origin/${headRefName}`, args.prNumber, targetCwd);
36
51
 
37
52
  // Create the actual repair branch in the worktree
38
53
  await runCommand("git", ["checkout", "-b", repairBranch], { cwd: worktreePath });
39
54
 
40
- return {
55
+ const response: Record<string, unknown> = {
41
56
  prNumber: args.prNumber,
42
57
  originalBranch: headRefName,
43
58
  repairBranch,
44
59
  baseBranch: baseRefName,
45
- worktreePath
60
+ worktreePath,
61
+ path: targetCwd,
46
62
  };
63
+
64
+ if (args.repo) {
65
+ response.repo = args.repo;
66
+ }
67
+
68
+ return response;
47
69
  }
@@ -1,12 +1,20 @@
1
1
  import { z } from "zod";
2
- import { getChangedFiles } from "../lib/git.js";
2
+ import { getChangedFiles, resolveRepoPath } from "../lib/git.js";
3
3
 
4
4
  export const GetChangedFilesInputSchema = z.object({
5
5
  base: z.string().optional().default("main"),
6
6
  head: z.string().optional().default("HEAD"),
7
+ repo: z.string().optional().describe("Target GitHub repository override."),
8
+ path: z.string().optional().describe("Local filesystem path to target repository root."),
9
+ repoPath: z.string().optional().describe("Alias for path to target repository root."),
7
10
  });
8
11
 
9
12
  export async function getChangedFilesHandler(args: z.infer<typeof GetChangedFilesInputSchema>) {
10
- const files = await getChangedFiles(args.base || "main", args.head || "HEAD");
13
+ const targetCwd = resolveRepoPath({
14
+ path: args.path,
15
+ repoPath: args.repoPath,
16
+ repo: args.repo,
17
+ });
18
+ const files = await getChangedFiles(args.base || "main", args.head || "HEAD", targetCwd);
11
19
  return { files };
12
20
  }
@@ -0,0 +1,15 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { multiExecHandler } from "./repo.multi_exec.js";
3
+
4
+ describe("MultiExec Tool", () => {
5
+ it("should execute a command sequentially across repositories", async () => {
6
+ const res = await multiExecHandler({
7
+ command: "echo test-vitest-exec",
8
+ repos: ["arii/boomtick"],
9
+ });
10
+
11
+ expect(res.status).toBe("success");
12
+ expect(res.total_repos).toBe(1);
13
+ expect(res.results[0].stdout).toContain("test-vitest-exec");
14
+ });
15
+ });
@@ -0,0 +1,23 @@
1
+ import { z } from "zod";
2
+ import { runTDCli } from "./jules/shared.js";
3
+
4
+ export const MultiExecInputSchema = z.object({
5
+ command: z.string().describe("Shell command to execute sequentially across repositories"),
6
+ repos: z.array(z.string()).optional().describe("Optional list of repository names to target"),
7
+ continueOnError: z.boolean().optional().default(true).describe("Whether to continue execution if a repo command fails"),
8
+ });
9
+
10
+ export async function multiExecHandler(input: z.input<typeof MultiExecInputSchema>) {
11
+ const args = MultiExecInputSchema.parse(input);
12
+ const cliArgs = ["multi-repo", "exec", args.command];
13
+ if (args.repos) {
14
+ for (const r of args.repos) {
15
+ cliArgs.push("-r", r);
16
+ }
17
+ }
18
+ if (args.continueOnError === false) {
19
+ cliArgs.push("--fail-fast");
20
+ }
21
+ const output = await runTDCli(cliArgs, "execute command across ecosystem repositories");
22
+ return output;
23
+ }
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { loadStandaloneAgentContext } from "./repo.read_agent_context.js";
2
+ import { loadStandaloneAgentContext, readAgentContextHandler } from "./repo.read_agent_context.js";
3
3
 
4
4
  describe("Standalone Agent Context Loader", () => {
5
5
  it("should load agent context without submodule references", () => {
@@ -8,4 +8,38 @@ describe("Standalone Agent Context Loader", () => {
8
8
  expect(context).toHaveProperty("gitCommit");
9
9
  expect(context).not.toHaveProperty("submodules");
10
10
  });
11
+
12
+ it("should handle readAgentContextHandler with default workspace", async () => {
13
+ const res = await readAgentContextHandler({});
14
+ expect(res.status).toBe("success");
15
+ expect(res).toHaveProperty("packageName");
16
+ expect(res).toHaveProperty("gitCommit");
17
+ });
18
+
19
+ it("readAgentContextHandler should accept optional repoPath argument", async () => {
20
+ const res = await readAgentContextHandler({ repoPath: process.cwd() });
21
+ expect(res).toHaveProperty("status", "success");
22
+ expect(res).toHaveProperty("packageName");
23
+ expect(res).toHaveProperty("gitCommit");
24
+ });
25
+
26
+ it("readAgentContextHandler should accept path argument", async () => {
27
+ const res = await readAgentContextHandler({ path: process.cwd() });
28
+ expect(res).toHaveProperty("status", "success");
29
+ expect(res).toHaveProperty("packageName");
30
+ });
31
+
32
+ it("readAgentContextHandler should accept repo argument", async () => {
33
+ const res = await readAgentContextHandler({ repo: "arii/tech-dancer" });
34
+ expect(res).toHaveProperty("status", "success");
35
+ expect(res).toHaveProperty("repo", "arii/tech-dancer");
36
+ });
37
+
38
+ it("should handle multi-repo context querying when all: true is passed", async () => {
39
+ const res = await readAgentContextHandler({ all: true });
40
+ expect(res.status).toBe("success");
41
+ expect(res.ecosystem).toBe(true);
42
+ expect(Array.isArray(res.repos)).toBe(true);
43
+ expect(res.repos.length).toBeGreaterThan(0);
44
+ });
11
45
  });
@@ -3,6 +3,7 @@ import { config } from "../config.js";
3
3
  import { execSync } from "child_process";
4
4
  import * as fs from "fs";
5
5
  import * as path from "path";
6
+ import { runTDCli } from "./jules/shared.js";
6
7
 
7
8
  export interface AgentContext {
8
9
  packageName: string;
@@ -36,14 +37,78 @@ export const loadStandaloneAgentContext = (repoRoot: string = process.cwd()): Ag
36
37
  };
37
38
  };
38
39
 
39
- export const ReadAgentContextInputSchema = z.object({});
40
+ export const ReadAgentContextInputSchema = z.object({
41
+ path: z.string().optional().describe("Local filesystem path to target repository root"),
42
+ repoPath: z.string().optional().describe("Alias for path to target repository root"),
43
+ repo: z.string().optional().describe("Repository name override (e.g. arii/portfolio or portfolio)"),
44
+ repos: z.array(z.string()).optional().describe("List of repository names to fetch context for"),
45
+ all: z.boolean().optional().describe("Whether to fetch context across all discovered ecosystem repositories"),
46
+ });
40
47
 
41
- export async function readAgentContextHandler(_args: z.infer<typeof ReadAgentContextInputSchema>) {
42
- const context = loadStandaloneAgentContext(config.repoPath);
48
+ export async function readAgentContextHandler(args: z.infer<typeof ReadAgentContextInputSchema>) {
49
+ // 1. Ecosystem / Multi-repo request
50
+ if (args.all || (args.repos && args.repos.length > 0)) {
51
+ const cliArgs = ["multi-repo", "status"];
52
+ if (args.repos) {
53
+ for (const r of args.repos) {
54
+ cliArgs.push("-r", r);
55
+ }
56
+ }
57
+ const output = await runTDCli(cliArgs, "fetch multi-repo context");
58
+ return {
59
+ status: "success",
60
+ ecosystem: true,
61
+ repos: output.repos ?? [],
62
+ total: output.total ?? (output.repos?.length ?? 0)
63
+ };
64
+ }
65
+
66
+ // 2. Specific repository name override
67
+ if (args.repo) {
68
+ let matchedPath: string | undefined;
69
+ try {
70
+ const output = await runTDCli(["multi-repo", "status", "-r", args.repo], "find target repository");
71
+ const matched = output.repos?.[0];
72
+ if (matched?.path) {
73
+ matchedPath = matched.path;
74
+ }
75
+ } catch {
76
+ // Fallback to directory inspection if CLI is unavailable or not in TD workspace
77
+ }
78
+
79
+ if (!matchedPath) {
80
+ const repoName = args.repo.includes("/") ? args.repo.split("/")[1] : args.repo;
81
+ const cwdParent = path.resolve(process.cwd(), "..", repoName);
82
+ const configParent = path.resolve(config.repoPath, "..", repoName);
83
+
84
+ if (fs.existsSync(cwdParent)) {
85
+ matchedPath = cwdParent;
86
+ } else if (fs.existsSync(configParent)) {
87
+ matchedPath = configParent;
88
+ }
89
+ }
90
+
91
+ if (matchedPath) {
92
+ const context = loadStandaloneAgentContext(matchedPath);
93
+ return {
94
+ status: "success",
95
+ repo: args.repo,
96
+ path: matchedPath,
97
+ ...context
98
+ };
99
+ }
100
+ }
101
+
102
+ // 3. Specific filesystem path
103
+ const targetPath = (args.path || args.repoPath) ? path.resolve(args.path || args.repoPath!) : config.repoPath;
104
+ const context = loadStandaloneAgentContext(targetPath);
43
105
  const result: any = {
44
106
  status: "success",
45
107
  ...context
46
108
  };
109
+ if (args?.repo) {
110
+ result.repo = args.repo;
111
+ }
47
112
  delete result.submodules;
48
113
  if (result.config) {
49
114
  delete result.config.submodules;