@getforgeai/cli 0.1.0-beta.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 (221) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +80 -0
  3. package/dist/bin/forgeai.d.ts +3 -0
  4. package/dist/bin/forgeai.js +8 -0
  5. package/dist/cli.d.ts +4 -0
  6. package/dist/cli.js +180 -0
  7. package/dist/commands/auth-login.d.ts +4 -0
  8. package/dist/commands/auth-login.js +202 -0
  9. package/dist/commands/auth-logout.d.ts +2 -0
  10. package/dist/commands/auth-logout.js +41 -0
  11. package/dist/commands/auth-status.d.ts +4 -0
  12. package/dist/commands/auth-status.js +81 -0
  13. package/dist/commands/cli-list.d.ts +4 -0
  14. package/dist/commands/cli-list.js +40 -0
  15. package/dist/commands/cli-unlink.d.ts +4 -0
  16. package/dist/commands/cli-unlink.js +45 -0
  17. package/dist/commands/config-set.d.ts +5 -0
  18. package/dist/commands/config-set.js +63 -0
  19. package/dist/commands/connect.d.ts +9 -0
  20. package/dist/commands/connect.js +165 -0
  21. package/dist/commands/disconnect.d.ts +5 -0
  22. package/dist/commands/disconnect.js +126 -0
  23. package/dist/commands/journal-clear.d.ts +2 -0
  24. package/dist/commands/journal-clear.js +16 -0
  25. package/dist/commands/journal-list.d.ts +2 -0
  26. package/dist/commands/journal-list.js +20 -0
  27. package/dist/commands/journal-status.d.ts +2 -0
  28. package/dist/commands/journal-status.js +18 -0
  29. package/dist/commands/mcp-serve.d.ts +13 -0
  30. package/dist/commands/mcp-serve.js +74 -0
  31. package/dist/commands/org-list.d.ts +4 -0
  32. package/dist/commands/org-list.js +56 -0
  33. package/dist/commands/org-unlink.d.ts +4 -0
  34. package/dist/commands/org-unlink.js +63 -0
  35. package/dist/commands/reconnect.d.ts +4 -0
  36. package/dist/commands/reconnect.js +46 -0
  37. package/dist/commands/release-open.d.ts +2 -0
  38. package/dist/commands/release-open.js +43 -0
  39. package/dist/commands/session-respond.d.ts +2 -0
  40. package/dist/commands/session-respond.js +29 -0
  41. package/dist/commands/task-open.d.ts +2 -0
  42. package/dist/commands/task-open.js +42 -0
  43. package/dist/commands/vm.d.ts +21 -0
  44. package/dist/commands/vm.js +122 -0
  45. package/dist/commands/workflow-open.d.ts +2 -0
  46. package/dist/commands/workflow-open.js +43 -0
  47. package/dist/config/config-manager.d.ts +31 -0
  48. package/dist/config/config-manager.js +70 -0
  49. package/dist/config/token-manager.d.ts +11 -0
  50. package/dist/config/token-manager.js +87 -0
  51. package/dist/index.d.ts +11 -0
  52. package/dist/index.js +22 -0
  53. package/dist/lib/auth-client.d.ts +1670 -0
  54. package/dist/lib/auth-client.js +10 -0
  55. package/dist/lib/branch-manager.d.ts +79 -0
  56. package/dist/lib/branch-manager.js +620 -0
  57. package/dist/lib/clone-and-checkout.d.ts +6 -0
  58. package/dist/lib/clone-and-checkout.js +41 -0
  59. package/dist/lib/connection-manager.d.ts +58 -0
  60. package/dist/lib/connection-manager.js +436 -0
  61. package/dist/lib/daemon-entry.d.ts +2 -0
  62. package/dist/lib/daemon-entry.js +210 -0
  63. package/dist/lib/daemon.d.ts +38 -0
  64. package/dist/lib/daemon.js +148 -0
  65. package/dist/lib/event-journal.d.ts +24 -0
  66. package/dist/lib/event-journal.js +76 -0
  67. package/dist/lib/ipc-client.d.ts +10 -0
  68. package/dist/lib/ipc-client.js +70 -0
  69. package/dist/lib/ipc-server.d.ts +36 -0
  70. package/dist/lib/ipc-server.js +70 -0
  71. package/dist/lib/node-version-guard.d.ts +8 -0
  72. package/dist/lib/node-version-guard.js +60 -0
  73. package/dist/lib/open-directory.d.ts +7 -0
  74. package/dist/lib/open-directory.js +41 -0
  75. package/dist/lib/repo-manager.d.ts +29 -0
  76. package/dist/lib/repo-manager.js +103 -0
  77. package/dist/lib/resilient-emitter.d.ts +53 -0
  78. package/dist/lib/resilient-emitter.js +160 -0
  79. package/dist/lib/rest-client.d.ts +164 -0
  80. package/dist/lib/rest-client.js +139 -0
  81. package/dist/lib/skill-manager.d.ts +23 -0
  82. package/dist/lib/skill-manager.js +167 -0
  83. package/dist/lib/socket-client.d.ts +68 -0
  84. package/dist/lib/socket-client.js +160 -0
  85. package/dist/mcp/mcp-probe.d.ts +11 -0
  86. package/dist/mcp/mcp-probe.js +106 -0
  87. package/dist/mcp/mcp-response.d.ts +29 -0
  88. package/dist/mcp/mcp-response.js +67 -0
  89. package/dist/mcp/mcp-server.d.ts +19 -0
  90. package/dist/mcp/mcp-server.js +76 -0
  91. package/dist/mcp/resources/project-conventions.d.ts +4 -0
  92. package/dist/mcp/resources/project-conventions.js +50 -0
  93. package/dist/mcp/resources/task-current.d.ts +4 -0
  94. package/dist/mcp/resources/task-current.js +51 -0
  95. package/dist/mcp/tools/forge-add-group-dependency.d.ts +4 -0
  96. package/dist/mcp/tools/forge-add-group-dependency.js +20 -0
  97. package/dist/mcp/tools/forge-add-task-dependency.d.ts +4 -0
  98. package/dist/mcp/tools/forge-add-task-dependency.js +20 -0
  99. package/dist/mcp/tools/forge-ask-human.d.ts +4 -0
  100. package/dist/mcp/tools/forge-ask-human.js +80 -0
  101. package/dist/mcp/tools/forge-column-done.d.ts +4 -0
  102. package/dist/mcp/tools/forge-column-done.js +46 -0
  103. package/dist/mcp/tools/forge-complete-step.d.ts +4 -0
  104. package/dist/mcp/tools/forge-complete-step.js +139 -0
  105. package/dist/mcp/tools/forge-create-task-group.d.ts +4 -0
  106. package/dist/mcp/tools/forge-create-task-group.js +22 -0
  107. package/dist/mcp/tools/forge-create-task.d.ts +4 -0
  108. package/dist/mcp/tools/forge-create-task.js +28 -0
  109. package/dist/mcp/tools/forge-delete-task-group.d.ts +4 -0
  110. package/dist/mcp/tools/forge-delete-task-group.js +17 -0
  111. package/dist/mcp/tools/forge-delete-task.d.ts +4 -0
  112. package/dist/mcp/tools/forge-delete-task.js +17 -0
  113. package/dist/mcp/tools/forge-get-task.d.ts +4 -0
  114. package/dist/mcp/tools/forge-get-task.js +17 -0
  115. package/dist/mcp/tools/forge-merge-back.d.ts +4 -0
  116. package/dist/mcp/tools/forge-merge-back.js +179 -0
  117. package/dist/mcp/tools/forge-release-merge.d.ts +4 -0
  118. package/dist/mcp/tools/forge-release-merge.js +159 -0
  119. package/dist/mcp/tools/forge-release-notes.d.ts +4 -0
  120. package/dist/mcp/tools/forge-release-notes.js +51 -0
  121. package/dist/mcp/tools/forge-release-tag.d.ts +4 -0
  122. package/dist/mcp/tools/forge-release-tag.js +125 -0
  123. package/dist/mcp/tools/forge-remove-group-dependency.d.ts +4 -0
  124. package/dist/mcp/tools/forge-remove-group-dependency.js +19 -0
  125. package/dist/mcp/tools/forge-remove-task-dependency.d.ts +4 -0
  126. package/dist/mcp/tools/forge-remove-task-dependency.js +19 -0
  127. package/dist/mcp/tools/forge-requeue.d.ts +4 -0
  128. package/dist/mcp/tools/forge-requeue.js +39 -0
  129. package/dist/mcp/tools/forge-submit-pr.d.ts +4 -0
  130. package/dist/mcp/tools/forge-submit-pr.js +68 -0
  131. package/dist/mcp/tools/forge-update-status.d.ts +4 -0
  132. package/dist/mcp/tools/forge-update-status.js +77 -0
  133. package/dist/mcp/tools/forge-update-task-group.d.ts +4 -0
  134. package/dist/mcp/tools/forge-update-task-group.js +21 -0
  135. package/dist/mcp/tools/forge-update-task.d.ts +4 -0
  136. package/dist/mcp/tools/forge-update-task.js +25 -0
  137. package/dist/mcp/tools/forge-validate-merge.d.ts +4 -0
  138. package/dist/mcp/tools/forge-validate-merge.js +194 -0
  139. package/dist/mcp/types.d.ts +39 -0
  140. package/dist/mcp/types.js +8 -0
  141. package/dist/methodology/artifact-manager.d.ts +14 -0
  142. package/dist/methodology/artifact-manager.js +55 -0
  143. package/dist/orchestrator/agent-registry.d.ts +53 -0
  144. package/dist/orchestrator/agent-registry.js +96 -0
  145. package/dist/orchestrator/agent-spawner.d.ts +112 -0
  146. package/dist/orchestrator/agent-spawner.js +1518 -0
  147. package/dist/orchestrator/connectors/claude-code-connector.d.ts +3 -0
  148. package/dist/orchestrator/connectors/claude-code-connector.js +60 -0
  149. package/dist/orchestrator/connectors/connector.d.ts +95 -0
  150. package/dist/orchestrator/connectors/connector.js +25 -0
  151. package/dist/orchestrator/connectors/docker-connector.d.ts +14 -0
  152. package/dist/orchestrator/connectors/docker-connector.js +337 -0
  153. package/dist/orchestrator/connectors/native-vm-connector.d.ts +11 -0
  154. package/dist/orchestrator/connectors/native-vm-connector.js +162 -0
  155. package/dist/orchestrator/connectors/opencode-connector.d.ts +3 -0
  156. package/dist/orchestrator/connectors/opencode-connector.js +39 -0
  157. package/dist/orchestrator/connectors/vm-connector.d.ts +3 -0
  158. package/dist/orchestrator/connectors/vm-connector.js +139 -0
  159. package/dist/orchestrator/dispatch-queue.d.ts +43 -0
  160. package/dist/orchestrator/dispatch-queue.js +93 -0
  161. package/dist/orchestrator/resume-handler.d.ts +34 -0
  162. package/dist/orchestrator/resume-handler.js +159 -0
  163. package/dist/orchestrator/stream-batcher.d.ts +16 -0
  164. package/dist/orchestrator/stream-batcher.js +50 -0
  165. package/dist/orchestrator/stream-json-extractor.d.ts +97 -0
  166. package/dist/orchestrator/stream-json-extractor.js +579 -0
  167. package/dist/vm/claude-token-manager.d.ts +36 -0
  168. package/dist/vm/claude-token-manager.js +223 -0
  169. package/dist/vm/docker-executor.d.ts +131 -0
  170. package/dist/vm/docker-executor.js +360 -0
  171. package/dist/vm/docker-process-proxy.d.ts +31 -0
  172. package/dist/vm/docker-process-proxy.js +105 -0
  173. package/dist/vm/forge-vm-agent.d.ts +11 -0
  174. package/dist/vm/forge-vm-agent.js +159 -0
  175. package/dist/vm/git-credentials.d.ts +43 -0
  176. package/dist/vm/git-credentials.js +53 -0
  177. package/dist/vm/image-manager.d.ts +7 -0
  178. package/dist/vm/image-manager.js +61 -0
  179. package/dist/vm/log.d.ts +9 -0
  180. package/dist/vm/log.js +18 -0
  181. package/dist/vm/mcp-bridge.d.ts +63 -0
  182. package/dist/vm/mcp-bridge.js +243 -0
  183. package/dist/vm/mcp-stub.d.ts +15 -0
  184. package/dist/vm/mcp-stub.js +67 -0
  185. package/dist/vm/native-addon.d.ts +69 -0
  186. package/dist/vm/native-addon.js +68 -0
  187. package/dist/vm/provider-resolver.d.ts +7 -0
  188. package/dist/vm/provider-resolver.js +27 -0
  189. package/dist/vm/providers/lima.d.ts +17 -0
  190. package/dist/vm/providers/lima.js +191 -0
  191. package/dist/vm/providers/qemu.d.ts +28 -0
  192. package/dist/vm/providers/qemu.js +260 -0
  193. package/dist/vm/providers/vfkit.d.ts +40 -0
  194. package/dist/vm/providers/vfkit.js +371 -0
  195. package/dist/vm/providers/wsl2.d.ts +27 -0
  196. package/dist/vm/providers/wsl2.js +226 -0
  197. package/dist/vm/session-manager.d.ts +130 -0
  198. package/dist/vm/session-manager.js +494 -0
  199. package/dist/vm/session-snapshot.d.ts +31 -0
  200. package/dist/vm/session-snapshot.js +180 -0
  201. package/dist/vm/types.d.ts +73 -0
  202. package/dist/vm/types.js +2 -0
  203. package/dist/vm/vm-helper-client.d.ts +81 -0
  204. package/dist/vm/vm-helper-client.js +335 -0
  205. package/dist/vm/vm-helper.d.ts +11 -0
  206. package/dist/vm/vm-helper.js +156 -0
  207. package/dist/vm/vm-ipc-protocol.d.ts +84 -0
  208. package/dist/vm/vm-ipc-protocol.js +9 -0
  209. package/dist/vm/vm-manager-facade.d.ts +23 -0
  210. package/dist/vm/vm-manager-facade.js +43 -0
  211. package/dist/vm/vm-manager.d.ts +74 -0
  212. package/dist/vm/vm-manager.js +515 -0
  213. package/dist/vm/vm-process-proxy.d.ts +34 -0
  214. package/dist/vm/vm-process-proxy.js +64 -0
  215. package/dist/vm/vsock-bridge.d.ts +44 -0
  216. package/dist/vm/vsock-bridge.js +140 -0
  217. package/dist/vm/vsock-protocol.d.ts +98 -0
  218. package/dist/vm/vsock-protocol.js +57 -0
  219. package/package.json +65 -0
  220. package/rootfs/Dockerfile +50 -0
  221. package/rootfs/forge-mcp-relay.mjs +158 -0
@@ -0,0 +1,22 @@
1
+ import { ForgeCreateTaskGroupInputSchema } from "@getforgeai/protocol";
2
+ import { executeSocketEmit } from "../mcp-response.js";
3
+ const shape = ForgeCreateTaskGroupInputSchema.shape;
4
+ export function registerForgeCreateTaskGroup(server, context) {
5
+ server.registerTool("forge_create_task_group", {
6
+ description: "Create a task group in the current project. Returns the created group's id and name. Use this to organize tasks into logical groups before creating tasks.",
7
+ inputSchema: {
8
+ name: shape.name.describe("Group name"),
9
+ description: shape.description.describe("Group description"),
10
+ order: shape.order.describe("Display order (auto-assigned if omitted)"),
11
+ },
12
+ }, async (params) => {
13
+ return executeSocketEmit(context, "create task group", () => context.socketClient.emitTaskGroupCreate({
14
+ projectId: context.projectId,
15
+ workflowId: context.workflowId,
16
+ name: params.name,
17
+ description: params.description,
18
+ order: params.order,
19
+ }));
20
+ });
21
+ }
22
+ //# sourceMappingURL=forge-create-task-group.js.map
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { McpSessionContext } from "../types.js";
3
+ export declare function registerForgeCreateTask(server: McpServer, context: McpSessionContext): void;
4
+ //# sourceMappingURL=forge-create-task.d.ts.map
@@ -0,0 +1,28 @@
1
+ import { ForgeCreateTaskInputSchema } from "@getforgeai/protocol";
2
+ import { executeSocketEmit } from "../mcp-response.js";
3
+ const shape = ForgeCreateTaskInputSchema.shape;
4
+ export function registerForgeCreateTask(server, context) {
5
+ server.registerTool("forge_create_task", {
6
+ description: "Create a task in the current project. Optionally assign it to a task group by name. Returns the created task's id and title.",
7
+ inputSchema: {
8
+ title: shape.title.describe("Task title"),
9
+ description: shape.description.describe("Task description"),
10
+ priority: shape.priority.describe("Task priority"),
11
+ storyPoints: shape.storyPoints.describe("Story points estimate"),
12
+ groupName: shape.groupName.describe("Task group name to assign this task to"),
13
+ order: shape.order.describe("Display order (auto-assigned if omitted)"),
14
+ },
15
+ }, async (params) => {
16
+ return executeSocketEmit(context, "create task", () => context.socketClient.emitTaskCreate({
17
+ projectId: context.projectId,
18
+ workflowId: context.workflowId,
19
+ title: params.title,
20
+ description: params.description,
21
+ priority: params.priority,
22
+ storyPoints: params.storyPoints,
23
+ groupName: params.groupName,
24
+ order: params.order,
25
+ }));
26
+ });
27
+ }
28
+ //# sourceMappingURL=forge-create-task.js.map
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { McpSessionContext } from "../types.js";
3
+ export declare function registerForgeDeleteTaskGroup(server: McpServer, context: McpSessionContext): void;
4
+ //# sourceMappingURL=forge-delete-task-group.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { ForgeDeleteTaskGroupInputSchema } from "@getforgeai/protocol";
2
+ import { executeSocketEmit } from "../mcp-response.js";
3
+ const shape = ForgeDeleteTaskGroupInputSchema.shape;
4
+ export function registerForgeDeleteTaskGroup(server, context) {
5
+ server.registerTool("forge_delete_task_group", {
6
+ description: "Delete a task group by name (most recently created match). Tasks in the group are orphaned (moved to ungrouped), not deleted.",
7
+ inputSchema: {
8
+ name: shape.name.describe("Group name to find and delete"),
9
+ },
10
+ }, async (params) => {
11
+ return executeSocketEmit(context, "delete task group", () => context.socketClient.emitMcpTaskGroupDelete({
12
+ projectId: context.projectId,
13
+ name: params.name,
14
+ }));
15
+ });
16
+ }
17
+ //# sourceMappingURL=forge-delete-task-group.js.map
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { McpSessionContext } from "../types.js";
3
+ export declare function registerForgeDeleteTask(server: McpServer, context: McpSessionContext): void;
4
+ //# sourceMappingURL=forge-delete-task.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { ForgeDeleteTaskInputSchema } from "@getforgeai/protocol";
2
+ import { executeSocketEmit } from "../mcp-response.js";
3
+ const shape = ForgeDeleteTaskInputSchema.shape;
4
+ export function registerForgeDeleteTask(server, context) {
5
+ server.registerTool("forge_delete_task", {
6
+ description: "Delete a task by name (most recently created match). This permanently removes the task and its dependencies.",
7
+ inputSchema: {
8
+ name: shape.name.describe("Task title to find and delete"),
9
+ },
10
+ }, async (params) => {
11
+ return executeSocketEmit(context, "delete task", () => context.socketClient.emitMcpTaskDelete({
12
+ projectId: context.projectId,
13
+ name: params.name,
14
+ }));
15
+ });
16
+ }
17
+ //# sourceMappingURL=forge-delete-task.js.map
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { McpSessionContext } from "../types.js";
3
+ export declare function registerForgeGetTask(server: McpServer, context: McpSessionContext): void;
4
+ //# sourceMappingURL=forge-get-task.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ import { mcpSuccess, handleRestError } from "../mcp-response.js";
3
+ export function registerForgeGetTask(server, context) {
4
+ server.registerTool("forge_get_task", {
5
+ description: "Fetch a task from ForgeAI Cloud with full context (description, acceptance criteria, checkpoints, handoff context)",
6
+ inputSchema: { taskId: z.string().min(1).describe("Task ID to fetch") },
7
+ }, async ({ taskId }) => {
8
+ try {
9
+ const task = await context.restClient.getTask(taskId);
10
+ return mcpSuccess(task);
11
+ }
12
+ catch (error) {
13
+ return handleRestError(error, taskId);
14
+ }
15
+ });
16
+ }
17
+ //# sourceMappingURL=forge-get-task.js.map
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { McpSessionContext } from "../types.js";
3
+ export declare function registerForgeMergeBack(server: McpServer, _context: McpSessionContext): void;
4
+ //# sourceMappingURL=forge-merge-back.d.ts.map
@@ -0,0 +1,179 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { z } from "zod";
4
+ import { mcpSuccess, mcpError } from "../mcp-response.js";
5
+ import { MCP_ERROR_CODE } from "../types.js";
6
+ const execFileAsync = promisify(execFile);
7
+ /** Maximum output bytes to include in conflict details. */
8
+ const MAX_CONFLICT_OUTPUT = 10 * 1024; // 10 KB
9
+ function truncate(text, maxBytes) {
10
+ if (text.length <= maxBytes)
11
+ return text;
12
+ return text.slice(0, maxBytes) + "\n\n--- truncated ---";
13
+ }
14
+ /**
15
+ * Run a git command in the given repo path and return stdout.
16
+ * Throws on non-zero exit.
17
+ */
18
+ async function git(repoPath, args) {
19
+ const { stdout } = await execFileAsync("git", args, { cwd: repoPath });
20
+ return stdout.trim();
21
+ }
22
+ /**
23
+ * Get the list of conflicted files from `git diff --name-only --diff-filter=U`.
24
+ */
25
+ async function getConflictedFiles(repoPath) {
26
+ try {
27
+ const output = await git(repoPath, [
28
+ "diff",
29
+ "--name-only",
30
+ "--diff-filter=U",
31
+ ]);
32
+ return output
33
+ .split("\n")
34
+ .map((f) => f.trim())
35
+ .filter(Boolean);
36
+ }
37
+ catch {
38
+ return [];
39
+ }
40
+ }
41
+ /**
42
+ * Get conflict markers content for a specific file (limited output).
43
+ */
44
+ async function getConflictContent(repoPath, filePath) {
45
+ try {
46
+ const { stdout } = await execFileAsync("git", ["diff", filePath], {
47
+ cwd: repoPath,
48
+ });
49
+ return truncate(stdout, MAX_CONFLICT_OUTPUT);
50
+ }
51
+ catch {
52
+ return "(could not read conflict diff)";
53
+ }
54
+ }
55
+ export function registerForgeMergeBack(server, _context) {
56
+ server.registerTool("forge_merge_back", {
57
+ description: "Merge a child branch into a parent branch. Fetches latest remote state, checks out the parent branch, and performs `git merge`. Returns conflict details if the merge fails so the agent can resolve or call forge_ask_human.",
58
+ inputSchema: {
59
+ childBranch: z
60
+ .string()
61
+ .min(1)
62
+ .describe("The child branch to merge (source)"),
63
+ parentBranch: z
64
+ .string()
65
+ .min(1)
66
+ .describe("The parent branch to merge into (target)"),
67
+ repoPath: z
68
+ .string()
69
+ .min(1)
70
+ .describe("Absolute path to the git repository"),
71
+ },
72
+ }, async ({ childBranch, parentBranch, repoPath }) => {
73
+ try {
74
+ // 1. Fetch latest state for both branches
75
+ try {
76
+ await git(repoPath, ["fetch", "origin", parentBranch]);
77
+ }
78
+ catch {
79
+ // Remote may not be available — use local state
80
+ }
81
+ try {
82
+ await git(repoPath, ["fetch", "origin", childBranch]);
83
+ }
84
+ catch {
85
+ // Remote may not be available — use local state
86
+ }
87
+ // 2. Checkout parent branch
88
+ try {
89
+ await git(repoPath, ["checkout", parentBranch]);
90
+ }
91
+ catch (error) {
92
+ const message = error instanceof Error ? error.message : String(error);
93
+ return mcpError({
94
+ error: MCP_ERROR_CODE.VALIDATION_ERROR,
95
+ message: `Failed to checkout parent branch "${parentBranch}": ${message}`,
96
+ hint: "Ensure the parent branch exists and the working tree is clean.",
97
+ });
98
+ }
99
+ // 3. Pull latest parent (fast-forward if possible)
100
+ try {
101
+ await git(repoPath, ["pull", "--ff-only", "origin", parentBranch]);
102
+ }
103
+ catch {
104
+ // May fail if no remote tracking or no new commits — not fatal
105
+ }
106
+ // 4. Attempt merge
107
+ try {
108
+ const mergeOutput = await git(repoPath, [
109
+ "merge",
110
+ childBranch,
111
+ "--no-edit",
112
+ ]);
113
+ // Merge succeeded — push to remote
114
+ let pushed = false;
115
+ try {
116
+ await git(repoPath, ["push", "origin", parentBranch]);
117
+ pushed = true;
118
+ }
119
+ catch {
120
+ // Push failed — merge is local only, agent can push later
121
+ }
122
+ return mcpSuccess({
123
+ success: true,
124
+ childBranch,
125
+ parentBranch,
126
+ pushed,
127
+ message: `Successfully merged "${childBranch}" into "${parentBranch}".${pushed ? " Pushed to remote." : " Push to remote failed — push manually."}`,
128
+ mergeOutput,
129
+ });
130
+ }
131
+ catch (mergeError) {
132
+ // 5. Merge failed — check if it's a conflict
133
+ const conflictedFiles = await getConflictedFiles(repoPath);
134
+ if (conflictedFiles.length > 0) {
135
+ // Gather conflict details for the agent
136
+ const conflicts = await Promise.all(conflictedFiles.map(async (file) => ({
137
+ file,
138
+ diff: await getConflictContent(repoPath, file),
139
+ })));
140
+ return mcpError({
141
+ error: "MERGE_CONFLICT",
142
+ message: `Merge of "${childBranch}" into "${parentBranch}" resulted in ${conflictedFiles.length} conflicted file(s).`,
143
+ conflictedFiles,
144
+ conflicts,
145
+ hint: "Resolve the conflicts manually in the listed files, then run `git add <file>` and `git commit` to complete the merge. If you cannot resolve, call forge_ask_human for help.",
146
+ repoPath,
147
+ });
148
+ }
149
+ // Not a conflict — some other merge error
150
+ const message = mergeError instanceof Error
151
+ ? mergeError.message
152
+ : String(mergeError);
153
+ // Abort the failed merge to leave repo in clean state
154
+ try {
155
+ await git(repoPath, ["merge", "--abort"]);
156
+ }
157
+ catch {
158
+ // Abort may fail if no merge in progress
159
+ }
160
+ return mcpError({
161
+ error: MCP_ERROR_CODE.VALIDATION_ERROR,
162
+ message: `Merge failed: ${message}`,
163
+ childBranch,
164
+ parentBranch,
165
+ hint: "The merge was aborted. Check branch names and repository state.",
166
+ });
167
+ }
168
+ }
169
+ catch (error) {
170
+ const message = error instanceof Error ? error.message : String(error);
171
+ return mcpError({
172
+ error: MCP_ERROR_CODE.NETWORK_ERROR,
173
+ message: `Unexpected error during merge: ${message}`,
174
+ hint: "Check that the repository path is valid and accessible.",
175
+ });
176
+ }
177
+ });
178
+ }
179
+ //# sourceMappingURL=forge-merge-back.js.map
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { McpSessionContext } from "../types.js";
3
+ export declare function registerForgeReleaseMerge(server: McpServer, _context: McpSessionContext): void;
4
+ //# sourceMappingURL=forge-release-merge.d.ts.map
@@ -0,0 +1,159 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { z } from "zod";
4
+ import { mcpSuccess, mcpError } from "../mcp-response.js";
5
+ import { MCP_ERROR_CODE } from "../types.js";
6
+ const execFileAsync = promisify(execFile);
7
+ export function registerForgeReleaseMerge(server, _context) {
8
+ server.registerTool("forge_release_merge", {
9
+ description: "Create a release branch from a target branch and merge selected workflow branches into it sequentially. Returns the merge result for each branch, including conflict details if any.",
10
+ inputSchema: {
11
+ version: z.string().min(1).describe("Release version (e.g. 'v1.0.0')"),
12
+ targetBranch: z
13
+ .string()
14
+ .min(1)
15
+ .describe("Target branch to create release from (e.g. 'main')"),
16
+ workflowBranches: z
17
+ .array(z.string().min(1))
18
+ .min(1)
19
+ .describe("List of workflow branch names to merge into the release branch"),
20
+ repoPath: z
21
+ .string()
22
+ .min(1)
23
+ .describe("Absolute path to the git repository"),
24
+ },
25
+ }, async ({ version, targetBranch, workflowBranches, repoPath }) => {
26
+ const releaseBranch = `release/${version}`;
27
+ // 1. Fetch the latest target branch from remote
28
+ try {
29
+ await execFileAsync("git", ["fetch", "origin", targetBranch], {
30
+ cwd: repoPath,
31
+ });
32
+ }
33
+ catch {
34
+ // Remote may not be available — use local ref
35
+ }
36
+ // 2. Create the release branch from origin/targetBranch
37
+ try {
38
+ // Check if release branch already exists
39
+ try {
40
+ const { stdout } = await execFileAsync("git", ["rev-parse", "--verify", releaseBranch], { cwd: repoPath });
41
+ if (stdout.trim()) {
42
+ // Release branch already exists — check it out
43
+ await execFileAsync("git", ["checkout", releaseBranch], {
44
+ cwd: repoPath,
45
+ });
46
+ }
47
+ }
48
+ catch {
49
+ // Branch doesn't exist — create it
50
+ let baseRef = targetBranch;
51
+ try {
52
+ await execFileAsync("git", ["rev-parse", "--verify", `origin/${targetBranch}`], { cwd: repoPath });
53
+ baseRef = `origin/${targetBranch}`;
54
+ }
55
+ catch {
56
+ // origin ref not available, use local
57
+ }
58
+ await execFileAsync("git", ["checkout", "-b", releaseBranch, baseRef], { cwd: repoPath });
59
+ }
60
+ }
61
+ catch (error) {
62
+ const message = error instanceof Error ? error.message : String(error);
63
+ return mcpError({
64
+ error: MCP_ERROR_CODE.NETWORK_ERROR,
65
+ message: `Failed to create release branch "${releaseBranch}": ${message}`,
66
+ });
67
+ }
68
+ // 3. Merge each workflow branch sequentially
69
+ const results = [];
70
+ for (const branch of workflowBranches) {
71
+ // Fetch the workflow branch from remote
72
+ try {
73
+ await execFileAsync("git", ["fetch", "origin", branch], {
74
+ cwd: repoPath,
75
+ });
76
+ }
77
+ catch {
78
+ // Use local ref if remote fetch fails
79
+ }
80
+ // Determine the ref to merge (prefer origin/ if available)
81
+ let mergeRef = branch;
82
+ try {
83
+ await execFileAsync("git", ["rev-parse", "--verify", `origin/${branch}`], { cwd: repoPath });
84
+ mergeRef = `origin/${branch}`;
85
+ }
86
+ catch {
87
+ // Use local ref
88
+ }
89
+ try {
90
+ await execFileAsync("git", ["merge", mergeRef, "--no-edit"], {
91
+ cwd: repoPath,
92
+ });
93
+ results.push({ branch, status: "merged" });
94
+ }
95
+ catch (error) {
96
+ // Check if it's a merge conflict
97
+ try {
98
+ const { stdout: conflictOutput } = await execFileAsync("git", ["diff", "--name-only", "--diff-filter=U"], { cwd: repoPath });
99
+ const conflictFiles = conflictOutput
100
+ .split("\n")
101
+ .map((f) => f.trim())
102
+ .filter(Boolean);
103
+ if (conflictFiles.length > 0) {
104
+ results.push({
105
+ branch,
106
+ status: "conflict",
107
+ conflictFiles,
108
+ error: `Merge conflict in ${conflictFiles.length} file(s)`,
109
+ });
110
+ // Return immediately on conflict — agent needs to resolve before continuing
111
+ return mcpSuccess({
112
+ releaseBranch,
113
+ targetBranch,
114
+ version,
115
+ mergeResults: results,
116
+ hasConflicts: true,
117
+ conflictBranch: branch,
118
+ conflictFiles,
119
+ message: `Merge conflict detected when merging "${branch}". Resolve conflicts in the listed files, then run git add and git commit to continue.`,
120
+ });
121
+ }
122
+ }
123
+ catch {
124
+ // Could not determine conflict files
125
+ }
126
+ const message = error instanceof Error ? error.message : String(error);
127
+ results.push({
128
+ branch,
129
+ status: "failed",
130
+ error: message,
131
+ });
132
+ return mcpError({
133
+ error: MCP_ERROR_CODE.NETWORK_ERROR,
134
+ message: `Failed to merge branch "${branch}": ${message}`,
135
+ releaseBranch,
136
+ mergeResults: results,
137
+ });
138
+ }
139
+ }
140
+ // 4. Push the release branch to remote
141
+ try {
142
+ await execFileAsync("git", ["push", "-u", "origin", releaseBranch], {
143
+ cwd: repoPath,
144
+ });
145
+ }
146
+ catch {
147
+ // Non-fatal — branch exists locally, can be pushed later
148
+ }
149
+ return mcpSuccess({
150
+ releaseBranch,
151
+ targetBranch,
152
+ version,
153
+ mergeResults: results,
154
+ hasConflicts: false,
155
+ message: `Successfully created release branch "${releaseBranch}" and merged ${results.length} workflow branch(es).`,
156
+ });
157
+ });
158
+ }
159
+ //# sourceMappingURL=forge-release-merge.js.map
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { McpSessionContext } from "../types.js";
3
+ export declare function registerForgeReleaseNotes(server: McpServer, _context: McpSessionContext): void;
4
+ //# sourceMappingURL=forge-release-notes.d.ts.map
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ import { mcpSuccess } from "../mcp-response.js";
3
+ export function registerForgeReleaseNotes(server, _context) {
4
+ server.registerTool("forge_release_notes", {
5
+ description: "Generate markdown release notes from workflow and task data. Groups tasks by workflow for a structured changelog.",
6
+ inputSchema: {
7
+ version: z.string().min(1).describe("Release version (e.g. 'v1.0.0')"),
8
+ workflowNames: z
9
+ .array(z.string().min(1))
10
+ .min(1)
11
+ .describe("List of workflow names included in the release"),
12
+ taskTitles: z
13
+ .array(z.string().min(1))
14
+ .optional()
15
+ .describe("Optional flat list of task titles. If provided alongside workflowNames, tasks are listed under a general section."),
16
+ },
17
+ }, async ({ version, workflowNames, taskTitles }) => {
18
+ const lines = [];
19
+ lines.push(`# Release ${version}`);
20
+ lines.push("");
21
+ lines.push(`_Released on ${new Date().toISOString().split("T")[0]}_`);
22
+ lines.push("");
23
+ // Workflows section
24
+ lines.push("## Included Workflows");
25
+ lines.push("");
26
+ for (const name of workflowNames) {
27
+ lines.push(`### ${name}`);
28
+ lines.push("");
29
+ }
30
+ // Tasks section (flat list if provided)
31
+ if (taskTitles && taskTitles.length > 0) {
32
+ lines.push("## Changes");
33
+ lines.push("");
34
+ for (const title of taskTitles) {
35
+ lines.push(`- ${title}`);
36
+ }
37
+ lines.push("");
38
+ }
39
+ lines.push("---");
40
+ lines.push(`*Generated by ForgeAI*`);
41
+ const markdown = lines.join("\n");
42
+ return mcpSuccess({
43
+ version,
44
+ markdown,
45
+ workflowCount: workflowNames.length,
46
+ taskCount: taskTitles?.length ?? 0,
47
+ message: `Generated release notes for ${version} with ${workflowNames.length} workflow(s) and ${taskTitles?.length ?? 0} task(s).`,
48
+ });
49
+ });
50
+ }
51
+ //# sourceMappingURL=forge-release-notes.js.map
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { McpSessionContext } from "../types.js";
3
+ export declare function registerForgeReleaseTag(server: McpServer, _context: McpSessionContext): void;
4
+ //# sourceMappingURL=forge-release-tag.d.ts.map
@@ -0,0 +1,125 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { z } from "zod";
4
+ import { mcpSuccess, mcpError } from "../mcp-response.js";
5
+ import { MCP_ERROR_CODE } from "../types.js";
6
+ const execFileAsync = promisify(execFile);
7
+ export function registerForgeReleaseTag(server, _context) {
8
+ server.registerTool("forge_release_tag", {
9
+ description: "Merge a release branch into the target branch, create an annotated git tag, and push both to remote. Used during the RELEASING phase after human approval.",
10
+ inputSchema: {
11
+ version: z
12
+ .string()
13
+ .min(1)
14
+ .describe("Release version for the tag (e.g. 'v1.0.0')"),
15
+ targetBranch: z
16
+ .string()
17
+ .min(1)
18
+ .describe("Target branch to merge into (e.g. 'main')"),
19
+ releaseBranch: z
20
+ .string()
21
+ .min(1)
22
+ .describe("Release branch to merge from (e.g. 'release/v1.0.0')"),
23
+ repoPath: z
24
+ .string()
25
+ .min(1)
26
+ .describe("Absolute path to the git repository"),
27
+ },
28
+ }, async ({ version, targetBranch, releaseBranch, repoPath }) => {
29
+ // 1. Checkout target branch
30
+ try {
31
+ await execFileAsync("git", ["fetch", "origin", targetBranch], {
32
+ cwd: repoPath,
33
+ });
34
+ }
35
+ catch {
36
+ // Remote may not be available
37
+ }
38
+ try {
39
+ await execFileAsync("git", ["checkout", targetBranch], {
40
+ cwd: repoPath,
41
+ });
42
+ }
43
+ catch (error) {
44
+ const message = error instanceof Error ? error.message : String(error);
45
+ return mcpError({
46
+ error: MCP_ERROR_CODE.NETWORK_ERROR,
47
+ message: `Failed to checkout "${targetBranch}": ${message}`,
48
+ });
49
+ }
50
+ // 2. Pull latest target branch
51
+ try {
52
+ await execFileAsync("git", ["pull", "origin", targetBranch], {
53
+ cwd: repoPath,
54
+ });
55
+ }
56
+ catch {
57
+ // Non-fatal — continue with local state
58
+ }
59
+ // 3. Merge release branch into target
60
+ let mergeCommit;
61
+ try {
62
+ await execFileAsync("git", ["merge", releaseBranch, "--no-edit"], {
63
+ cwd: repoPath,
64
+ });
65
+ const { stdout } = await execFileAsync("git", ["rev-parse", "HEAD"], {
66
+ cwd: repoPath,
67
+ });
68
+ mergeCommit = stdout.trim();
69
+ }
70
+ catch (error) {
71
+ const message = error instanceof Error ? error.message : String(error);
72
+ return mcpError({
73
+ error: MCP_ERROR_CODE.NETWORK_ERROR,
74
+ message: `Failed to merge "${releaseBranch}" into "${targetBranch}": ${message}`,
75
+ });
76
+ }
77
+ // 4. Create annotated tag
78
+ const tagName = version;
79
+ try {
80
+ await execFileAsync("git", ["tag", "-a", tagName, "-m", `Release ${version}`], { cwd: repoPath });
81
+ }
82
+ catch (error) {
83
+ const message = error instanceof Error ? error.message : String(error);
84
+ return mcpError({
85
+ error: MCP_ERROR_CODE.NETWORK_ERROR,
86
+ message: `Failed to create tag "${tagName}": ${message}`,
87
+ });
88
+ }
89
+ // 5. Push target branch to remote
90
+ try {
91
+ await execFileAsync("git", ["push", "origin", targetBranch], {
92
+ cwd: repoPath,
93
+ });
94
+ }
95
+ catch (error) {
96
+ const message = error instanceof Error ? error.message : String(error);
97
+ return mcpError({
98
+ error: MCP_ERROR_CODE.NETWORK_ERROR,
99
+ message: `Failed to push "${targetBranch}" to remote: ${message}`,
100
+ });
101
+ }
102
+ // 6. Push tag to remote
103
+ try {
104
+ await execFileAsync("git", ["push", "origin", tagName], {
105
+ cwd: repoPath,
106
+ });
107
+ }
108
+ catch (error) {
109
+ const message = error instanceof Error ? error.message : String(error);
110
+ return mcpError({
111
+ error: MCP_ERROR_CODE.NETWORK_ERROR,
112
+ message: `Failed to push tag "${tagName}" to remote: ${message}`,
113
+ });
114
+ }
115
+ return mcpSuccess({
116
+ tagName,
117
+ mergeCommit,
118
+ targetBranch,
119
+ releaseBranch,
120
+ version,
121
+ message: `Successfully merged "${releaseBranch}" into "${targetBranch}", created tag "${tagName}", and pushed to remote.`,
122
+ });
123
+ });
124
+ }
125
+ //# sourceMappingURL=forge-release-tag.js.map