@ipul-labs/forge 1.16.8 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/README.md +39 -21
  2. package/lib/bin/cli/analyze.d.ts +9 -0
  3. package/lib/bin/cli/analyze.d.ts.map +1 -0
  4. package/lib/bin/cli/analyze.js +491 -0
  5. package/lib/bin/cli/analyze.js.map +1 -0
  6. package/lib/bin/cli/apply.d.ts +2 -0
  7. package/lib/bin/cli/apply.d.ts.map +1 -0
  8. package/lib/bin/cli/apply.js +262 -0
  9. package/lib/bin/cli/apply.js.map +1 -0
  10. package/lib/bin/cli/cifix.d.ts +2 -0
  11. package/lib/bin/cli/cifix.d.ts.map +1 -0
  12. package/lib/bin/cli/cifix.js +310 -0
  13. package/lib/bin/cli/cifix.js.map +1 -0
  14. package/lib/bin/cli/close.d.ts +2 -0
  15. package/lib/bin/cli/close.d.ts.map +1 -0
  16. package/lib/bin/cli/close.js +113 -0
  17. package/lib/bin/cli/close.js.map +1 -0
  18. package/lib/bin/cli/createIssue.d.ts +2 -0
  19. package/lib/bin/cli/createIssue.d.ts.map +1 -0
  20. package/lib/bin/cli/createIssue.js +108 -0
  21. package/lib/bin/cli/createIssue.js.map +1 -0
  22. package/lib/bin/cli/doctor.d.ts +2 -0
  23. package/lib/bin/cli/doctor.d.ts.map +1 -0
  24. package/lib/bin/cli/doctor.js +145 -0
  25. package/lib/bin/cli/doctor.js.map +1 -0
  26. package/lib/bin/cli/init.d.ts +4 -0
  27. package/lib/bin/cli/init.d.ts.map +1 -0
  28. package/lib/bin/cli/init.js +288 -0
  29. package/lib/bin/cli/init.js.map +1 -0
  30. package/lib/bin/cli/ink.d.ts +23 -0
  31. package/lib/bin/cli/ink.d.ts.map +1 -0
  32. package/{bin → lib/bin}/cli/ink.js +18 -19
  33. package/lib/bin/cli/ink.js.map +1 -0
  34. package/lib/bin/cli/issue.d.ts +2 -0
  35. package/lib/bin/cli/issue.d.ts.map +1 -0
  36. package/lib/bin/cli/issue.js +114 -0
  37. package/lib/bin/cli/issue.js.map +1 -0
  38. package/lib/bin/cli/issues.d.ts +2 -0
  39. package/lib/bin/cli/issues.d.ts.map +1 -0
  40. package/lib/bin/cli/issues.js +149 -0
  41. package/lib/bin/cli/issues.js.map +1 -0
  42. package/lib/bin/cli/merge.d.ts +2 -0
  43. package/lib/bin/cli/merge.d.ts.map +1 -0
  44. package/lib/bin/cli/merge.js +162 -0
  45. package/lib/bin/cli/merge.js.map +1 -0
  46. package/lib/bin/cli/refine.d.ts +2 -0
  47. package/lib/bin/cli/refine.d.ts.map +1 -0
  48. package/lib/bin/cli/refine.js +249 -0
  49. package/lib/bin/cli/refine.js.map +1 -0
  50. package/lib/bin/cli/repl.d.ts +2 -0
  51. package/lib/bin/cli/repl.d.ts.map +1 -0
  52. package/lib/bin/cli/repl.js +309 -0
  53. package/lib/bin/cli/repl.js.map +1 -0
  54. package/lib/bin/cli/repos.d.ts +2 -0
  55. package/lib/bin/cli/repos.d.ts.map +1 -0
  56. package/lib/bin/cli/repos.js +86 -0
  57. package/lib/bin/cli/repos.js.map +1 -0
  58. package/lib/bin/cli/resolution.d.ts +2 -0
  59. package/lib/bin/cli/resolution.d.ts.map +1 -0
  60. package/lib/bin/cli/resolution.js +139 -0
  61. package/lib/bin/cli/resolution.js.map +1 -0
  62. package/lib/bin/cli/serverClient.d.ts +26 -0
  63. package/lib/bin/cli/serverClient.d.ts.map +1 -0
  64. package/lib/bin/cli/serverClient.js +111 -0
  65. package/lib/bin/cli/serverClient.js.map +1 -0
  66. package/lib/bin/cli/session.d.ts +8 -0
  67. package/lib/bin/cli/session.d.ts.map +1 -0
  68. package/lib/bin/cli/session.js +212 -0
  69. package/lib/bin/cli/session.js.map +1 -0
  70. package/lib/bin/cli/sessionStore.d.ts +69 -0
  71. package/lib/bin/cli/sessionStore.d.ts.map +1 -0
  72. package/lib/bin/cli/sessionStore.js +196 -0
  73. package/lib/bin/cli/sessionStore.js.map +1 -0
  74. package/lib/bin/cli/ship.d.ts +33 -0
  75. package/lib/bin/cli/ship.d.ts.map +1 -0
  76. package/lib/bin/cli/ship.js +737 -0
  77. package/lib/bin/cli/ship.js.map +1 -0
  78. package/lib/bin/cli/types.d.ts +59 -0
  79. package/lib/bin/cli/types.d.ts.map +1 -0
  80. package/lib/bin/cli/types.js +2 -0
  81. package/lib/bin/cli/types.js.map +1 -0
  82. package/lib/bin/cli/update.d.ts +2 -0
  83. package/lib/bin/cli/update.d.ts.map +1 -0
  84. package/lib/bin/cli/update.js +87 -0
  85. package/lib/bin/cli/update.js.map +1 -0
  86. package/lib/bin/cli/utils.d.ts +43 -0
  87. package/lib/bin/cli/utils.d.ts.map +1 -0
  88. package/lib/bin/cli/utils.js +171 -0
  89. package/lib/bin/cli/utils.js.map +1 -0
  90. package/lib/bin/cli/validate.d.ts +2 -0
  91. package/lib/bin/cli/validate.d.ts.map +1 -0
  92. package/lib/bin/cli/validate.js +283 -0
  93. package/lib/bin/cli/validate.js.map +1 -0
  94. package/lib/bin/cli/watch.d.ts +5 -0
  95. package/lib/bin/cli/watch.d.ts.map +1 -0
  96. package/lib/bin/cli/watch.js +263 -0
  97. package/lib/bin/cli/watch.js.map +1 -0
  98. package/lib/bin/forge-agent.d.ts +3 -0
  99. package/lib/bin/forge-agent.d.ts.map +1 -0
  100. package/{bin → lib/bin}/forge-agent.js +104 -105
  101. package/lib/bin/forge-agent.js.map +1 -0
  102. package/lib/server/forge/core/ForgeAgent.d.ts +260 -0
  103. package/lib/server/forge/core/ForgeAgent.d.ts.map +1 -0
  104. package/lib/server/forge/core/ForgeAgent.js +995 -0
  105. package/lib/server/forge/core/ForgeAgent.js.map +1 -0
  106. package/lib/server/forge/core/agentClient.d.ts +39 -0
  107. package/lib/server/forge/core/agentClient.d.ts.map +1 -0
  108. package/{server → lib/server}/forge/core/agentClient.js +7 -12
  109. package/lib/server/forge/core/agentClient.js.map +1 -0
  110. package/lib/server/forge/core/branchName.d.ts +20 -0
  111. package/lib/server/forge/core/branchName.d.ts.map +1 -0
  112. package/lib/server/forge/core/branchName.js +39 -0
  113. package/lib/server/forge/core/branchName.js.map +1 -0
  114. package/lib/server/forge/core/claudeClient.d.ts +60 -0
  115. package/lib/server/forge/core/claudeClient.d.ts.map +1 -0
  116. package/lib/server/forge/core/claudeClient.js +347 -0
  117. package/lib/server/forge/core/claudeClient.js.map +1 -0
  118. package/lib/server/forge/core/configStore.d.ts +76 -0
  119. package/lib/server/forge/core/configStore.d.ts.map +1 -0
  120. package/lib/server/forge/core/configStore.js +213 -0
  121. package/lib/server/forge/core/configStore.js.map +1 -0
  122. package/lib/server/forge/core/gitlabClient.d.ts +144 -0
  123. package/lib/server/forge/core/gitlabClient.d.ts.map +1 -0
  124. package/{server → lib/server}/forge/core/gitlabClient.js +98 -180
  125. package/lib/server/forge/core/gitlabClient.js.map +1 -0
  126. package/lib/server/forge/core/jobs.d.ts +67 -0
  127. package/lib/server/forge/core/jobs.d.ts.map +1 -0
  128. package/lib/server/forge/core/jobs.js +185 -0
  129. package/lib/server/forge/core/jobs.js.map +1 -0
  130. package/lib/server/forge/core/k8s.d.ts +18 -0
  131. package/lib/server/forge/core/k8s.d.ts.map +1 -0
  132. package/lib/server/forge/core/k8s.js +49 -0
  133. package/lib/server/forge/core/k8s.js.map +1 -0
  134. package/lib/server/forge/core/localRepo.d.ts +36 -0
  135. package/lib/server/forge/core/localRepo.d.ts.map +1 -0
  136. package/lib/server/forge/core/localRepo.js +165 -0
  137. package/lib/server/forge/core/localRepo.js.map +1 -0
  138. package/lib/server/forge/core/openclawClient.d.ts +61 -0
  139. package/lib/server/forge/core/openclawClient.d.ts.map +1 -0
  140. package/lib/server/forge/core/openclawClient.js +111 -0
  141. package/lib/server/forge/core/openclawClient.js.map +1 -0
  142. package/lib/server/forge/core/prompts.d.ts +96 -0
  143. package/lib/server/forge/core/prompts.d.ts.map +1 -0
  144. package/lib/server/forge/core/prompts.js +450 -0
  145. package/lib/server/forge/core/prompts.js.map +1 -0
  146. package/lib/server/forge/core/providers/azuredevops.d.ts +134 -0
  147. package/lib/server/forge/core/providers/azuredevops.d.ts.map +1 -0
  148. package/lib/server/forge/core/providers/azuredevops.js +360 -0
  149. package/lib/server/forge/core/providers/azuredevops.js.map +1 -0
  150. package/lib/server/forge/core/providers/base.d.ts +114 -0
  151. package/lib/server/forge/core/providers/base.d.ts.map +1 -0
  152. package/lib/server/forge/core/providers/base.js +98 -0
  153. package/lib/server/forge/core/providers/base.js.map +1 -0
  154. package/lib/server/forge/core/providers/github.d.ts +133 -0
  155. package/lib/server/forge/core/providers/github.d.ts.map +1 -0
  156. package/lib/server/forge/core/providers/github.js +307 -0
  157. package/lib/server/forge/core/providers/github.js.map +1 -0
  158. package/lib/server/forge/core/providers/gitlab.d.ts +144 -0
  159. package/lib/server/forge/core/providers/gitlab.d.ts.map +1 -0
  160. package/lib/server/forge/core/providers/gitlab.js +407 -0
  161. package/lib/server/forge/core/providers/gitlab.js.map +1 -0
  162. package/lib/server/forge/core/providers/index.d.ts +21 -0
  163. package/lib/server/forge/core/providers/index.d.ts.map +1 -0
  164. package/lib/server/forge/core/providers/index.js +43 -0
  165. package/lib/server/forge/core/providers/index.js.map +1 -0
  166. package/lib/server/forge/core/providers/types.d.ts +80 -0
  167. package/lib/server/forge/core/providers/types.d.ts.map +1 -0
  168. package/lib/server/forge/core/providers/types.js +2 -0
  169. package/lib/server/forge/core/providers/types.js.map +1 -0
  170. package/lib/server/forge/core/quotaClient.d.ts +48 -0
  171. package/lib/server/forge/core/quotaClient.d.ts.map +1 -0
  172. package/lib/server/forge/core/quotaClient.js +88 -0
  173. package/lib/server/forge/core/quotaClient.js.map +1 -0
  174. package/lib/server/forge/core/types.d.ts +160 -0
  175. package/lib/server/forge/core/types.d.ts.map +1 -0
  176. package/lib/server/forge/core/types.js +2 -0
  177. package/lib/server/forge/core/types.js.map +1 -0
  178. package/lib/server/forge/core/validator/attribute.d.ts +31 -0
  179. package/lib/server/forge/core/validator/attribute.d.ts.map +1 -0
  180. package/lib/server/forge/core/validator/attribute.js +82 -0
  181. package/lib/server/forge/core/validator/attribute.js.map +1 -0
  182. package/lib/server/forge/core/validator/clone.d.ts +14 -0
  183. package/lib/server/forge/core/validator/clone.d.ts.map +1 -0
  184. package/lib/server/forge/core/validator/clone.js +99 -0
  185. package/lib/server/forge/core/validator/clone.js.map +1 -0
  186. package/lib/server/forge/core/validator/cmd.d.ts +23 -0
  187. package/lib/server/forge/core/validator/cmd.d.ts.map +1 -0
  188. package/lib/server/forge/core/validator/cmd.js +87 -0
  189. package/lib/server/forge/core/validator/cmd.js.map +1 -0
  190. package/lib/server/forge/core/validator/fs.d.ts +5 -0
  191. package/lib/server/forge/core/validator/fs.d.ts.map +1 -0
  192. package/{server → lib/server}/forge/core/validator/fs.js +8 -3
  193. package/lib/server/forge/core/validator/fs.js.map +1 -0
  194. package/lib/server/forge/core/validator/overlay.d.ts +13 -0
  195. package/lib/server/forge/core/validator/overlay.d.ts.map +1 -0
  196. package/lib/server/forge/core/validator/overlay.js +53 -0
  197. package/lib/server/forge/core/validator/overlay.js.map +1 -0
  198. package/lib/server/forge/core/validator/projectType.d.ts +20 -0
  199. package/lib/server/forge/core/validator/projectType.d.ts.map +1 -0
  200. package/lib/server/forge/core/validator/projectType.js +94 -0
  201. package/lib/server/forge/core/validator/projectType.js.map +1 -0
  202. package/lib/server/forge/core/validator/runners/go.d.ts +21 -0
  203. package/lib/server/forge/core/validator/runners/go.d.ts.map +1 -0
  204. package/lib/server/forge/core/validator/runners/go.js +99 -0
  205. package/lib/server/forge/core/validator/runners/go.js.map +1 -0
  206. package/lib/server/forge/core/validator/runners/node.d.ts +12 -0
  207. package/lib/server/forge/core/validator/runners/node.d.ts.map +1 -0
  208. package/lib/server/forge/core/validator/runners/node.js +75 -0
  209. package/lib/server/forge/core/validator/runners/node.js.map +1 -0
  210. package/lib/server/forge/core/validator/tdd.d.ts +30 -0
  211. package/lib/server/forge/core/validator/tdd.d.ts.map +1 -0
  212. package/lib/server/forge/core/validator/tdd.js +96 -0
  213. package/lib/server/forge/core/validator/tdd.js.map +1 -0
  214. package/lib/server/forge/core/validator.d.ts +58 -0
  215. package/lib/server/forge/core/validator.d.ts.map +1 -0
  216. package/lib/server/forge/core/validator.js +150 -0
  217. package/lib/server/forge/core/validator.js.map +1 -0
  218. package/lib/server/forge/routes.d.ts +22 -0
  219. package/lib/server/forge/routes.d.ts.map +1 -0
  220. package/lib/server/forge/routes.js +984 -0
  221. package/lib/server/forge/routes.js.map +1 -0
  222. package/lib/server.d.ts +2 -0
  223. package/lib/server.d.ts.map +1 -0
  224. package/lib/server.js +52 -0
  225. package/lib/server.js.map +1 -0
  226. package/lib/shared/events.d.ts +12 -0
  227. package/lib/shared/events.d.ts.map +1 -0
  228. package/lib/shared/events.js +12 -0
  229. package/lib/shared/events.js.map +1 -0
  230. package/package.json +15 -13
  231. package/scripts/fix-shebang.cjs +10 -0
  232. package/bin/cli/analyze.js +0 -516
  233. package/bin/cli/apply.js +0 -264
  234. package/bin/cli/cifix.js +0 -300
  235. package/bin/cli/close.js +0 -125
  236. package/bin/cli/createIssue.js +0 -112
  237. package/bin/cli/doctor.js +0 -161
  238. package/bin/cli/init.js +0 -306
  239. package/bin/cli/issue.js +0 -116
  240. package/bin/cli/issues.js +0 -155
  241. package/bin/cli/merge.js +0 -175
  242. package/bin/cli/refine.js +0 -253
  243. package/bin/cli/repl.js +0 -301
  244. package/bin/cli/repos.js +0 -91
  245. package/bin/cli/resolution.js +0 -126
  246. package/bin/cli/session.js +0 -230
  247. package/bin/cli/sessionStore.js +0 -211
  248. package/bin/cli/ship.js +0 -762
  249. package/bin/cli/update.js +0 -95
  250. package/bin/cli/utils.js +0 -145
  251. package/bin/cli/validate.js +0 -280
  252. package/bin/cli/watch.js +0 -274
  253. package/server/forge/core/ForgeAgent.js +0 -1179
  254. package/server/forge/core/ForgeAgent.test.js +0 -179
  255. package/server/forge/core/branchName.js +0 -41
  256. package/server/forge/core/claudeClient.js +0 -378
  257. package/server/forge/core/configStore.js +0 -229
  258. package/server/forge/core/configStore.test.js +0 -89
  259. package/server/forge/core/jobs.js +0 -180
  260. package/server/forge/core/k8s.js +0 -54
  261. package/server/forge/core/localRepo.js +0 -163
  262. package/server/forge/core/openclawClient.js +0 -125
  263. package/server/forge/core/prompts.js +0 -483
  264. package/server/forge/core/prompts.test.js +0 -219
  265. package/server/forge/core/providers/azuredevops.js +0 -409
  266. package/server/forge/core/providers/base.js +0 -208
  267. package/server/forge/core/providers/github.js +0 -348
  268. package/server/forge/core/providers/gitlab.js +0 -529
  269. package/server/forge/core/providers/index.js +0 -47
  270. package/server/forge/core/quotaClient.js +0 -101
  271. package/server/forge/core/validator/attribute.js +0 -84
  272. package/server/forge/core/validator/clone.js +0 -116
  273. package/server/forge/core/validator/cmd.js +0 -98
  274. package/server/forge/core/validator/overlay.js +0 -52
  275. package/server/forge/core/validator/projectType.js +0 -87
  276. package/server/forge/core/validator/runners/go.js +0 -107
  277. package/server/forge/core/validator/runners/node.js +0 -84
  278. package/server/forge/core/validator/tdd.js +0 -105
  279. package/server/forge/core/validator.js +0 -167
  280. package/server/forge/routes.js +0 -1027
@@ -0,0 +1,737 @@
1
+ /**
2
+ * `forge ship <iid>` — the headline command.
3
+ *
4
+ * Walks an issue from "open ticket" all the way to "deployed in production"
5
+ * with a yes/no Confirm gate at every state-changing step. Each phase calls
6
+ * the same underlying ForgeAgent / provider / validator code that the
7
+ * standalone subcommands use; this orchestrator just sequences them and
8
+ * keeps the user oriented.
9
+ *
10
+ * The flow:
11
+ *
12
+ * start
13
+ * → analyzing (or load cached if session.phase=ANALYZED+)
14
+ * → review (render proposal)
15
+ * → accept-gate (yes / no=abort / refine=loop with feedback)
16
+ * → refining (back to review)
17
+ * → validating (auto unless --skip-validate)
18
+ * → apply-gate (yes / no=abort)
19
+ * → applying
20
+ * → watching-ci
21
+ * → ci-failed | ci-fix-gate | ci-fixing → watching-ci
22
+ * → merge-gate (yes / no=abort; --auto arms auto-merge instead)
23
+ * → merging
24
+ * → watching-deploy
25
+ * → close-gate (yes / no=skip)
26
+ * → closing
27
+ * → done | aborted | error
28
+ *
29
+ * `--yes` (or FORGE_YES=1) bypasses every gate. Resumable: re-running
30
+ * `ship <iid>` reads session.phase and starts from the next gate.
31
+ */
32
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
33
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
34
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
35
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
36
+ });
37
+ }
38
+ return path;
39
+ };
40
+ import path from "node:path";
41
+ import { useEffect, useState } from "react";
42
+ import Spinner from "ink-spinner";
43
+ import TextInput from "ink-text-input";
44
+ import { h, Box, Text, useApp, runApp, Confirm } from "./ink.js";
45
+ import { canonicalRepoId, projectRoot, resolveRepoSet } from "./utils.js";
46
+ import { PHASES, appendEvent, readAnalysis, readState, setHead, updateState, writeAnalysis, } from "./sessionStore.js";
47
+ const POLL_MS = 5000;
48
+ const TERMINAL_PIPELINE = new Set([
49
+ "success",
50
+ "failed",
51
+ "canceled",
52
+ "skipped",
53
+ "manual",
54
+ ]);
55
+ function StepLine({ status, label, detail }) {
56
+ let glyph;
57
+ let color;
58
+ if (status === "running") {
59
+ glyph = h `<${Spinner} type="dots"/>`;
60
+ color = "cyan";
61
+ }
62
+ else if (status === "ok") {
63
+ glyph = "✓";
64
+ color = "green";
65
+ }
66
+ else if (status === "fail") {
67
+ glyph = "✘";
68
+ color = "red";
69
+ }
70
+ else if (status === "skip") {
71
+ glyph = "○";
72
+ color = "gray";
73
+ }
74
+ else {
75
+ glyph = "·";
76
+ color = undefined;
77
+ }
78
+ return h `<${Box}>
79
+ <${Text} color=${color}>${typeof glyph === "string" ? glyph : glyph}</>
80
+ <${Text}> ${label}</>
81
+ ${detail ? h `<${Text} dimColor> ${detail}</>` : null}
82
+ </>`;
83
+ }
84
+ function FeedbackInput({ onSubmit }) {
85
+ const [v, setV] = useState("");
86
+ return h `<${Box} flexDirection="column">
87
+ <${Box}>
88
+ <${Text} color="cyan">? </>
89
+ <${Text}>What feedback should the model address?</>
90
+ </>
91
+ <${Box} marginLeft=${2}>
92
+ <${TextInput}
93
+ value=${v}
94
+ onChange=${setV}
95
+ onSubmit=${(text) => {
96
+ const t = text.trim();
97
+ if (!t)
98
+ return;
99
+ onSubmit(t);
100
+ }} />
101
+ </>
102
+ </>`;
103
+ }
104
+ function ProposalSummary({ analysis }) {
105
+ const changes = analysis?.changes ?? [];
106
+ return h `<${Box} flexDirection="column">
107
+ <${Text} dimColor>${"────────── proposal ──────────"}</>
108
+ <${Box}><${Text} color="yellow">Issue: </><${Text}>#${analysis.issue?.iid}: ${analysis.issue?.title ?? ""}</></>
109
+ ${analysis.mrTitle
110
+ ? h `<${Box}><${Text} color="yellow">MR: </><${Text}>${analysis.mrTitle}</></>`
111
+ : null}
112
+ <${Box}><${Text} color="yellow">Changes:</><${Text}>${` ${changes.length} file(s) across ${new Set(changes.map((c) => c.repo)).size} repo(s)`}</></>
113
+ ${changes.map((c, i) => h `<${Text} key=${i} dimColor>${` ${c.action.padEnd(7)} ${c.repo}:${c.file}`}</>`)}
114
+ <${Text} dimColor>${" (full diff: forge show " + (analysis.issue?.iid ?? "") + ")"}</>
115
+ </>`;
116
+ }
117
+ function ShipView({ iid, primary, secondaries, bypassYes, autoMerge, skipValidate, onDone }) {
118
+ const { exit } = useApp();
119
+ const [phase, setPhase] = useState("init"); // see header for phase list
120
+ const [steps, setSteps] = useState([]); // visible step trace
121
+ const [analysis, setAnalysis] = useState(null);
122
+ const [sessionState, setSessionState] = useState(null);
123
+ const [pipeline, setPipeline] = useState(null);
124
+ const [jobs, setJobs] = useState([]);
125
+ const [error, setError] = useState(null);
126
+ const [feedback, setFeedback] = useState(null);
127
+ const repo = primary;
128
+ const pushStep = (label, detail) => setSteps((s) => [...s, { label, detail, status: "running" }]);
129
+ const finishStep = (label, status, detail) => setSteps((s) => s.map((row, i) => i === s.length - 1 && row.label === label
130
+ ? { ...row, status, detail: detail ?? row.detail }
131
+ : row));
132
+ useEffect(() => {
133
+ if (phase === "done" || phase === "aborted" || phase === "error") {
134
+ onDone({ phase, error });
135
+ exit();
136
+ }
137
+ }, [phase]);
138
+ // === init: load or start fresh ===
139
+ useEffect(() => {
140
+ if (phase !== "init")
141
+ return;
142
+ (async () => {
143
+ const state = (await readState(repo, iid).catch(() => null)) ?? null;
144
+ setSessionState(state);
145
+ const cached = await readAnalysis(repo, iid).catch(() => null);
146
+ if (cached && state?.phase) {
147
+ setAnalysis(cached);
148
+ // Resume at the right place. We always re-render the proposal so
149
+ // the user can see what's about to happen.
150
+ if (state.phase === PHASES.ANALYZED ||
151
+ state.phase === PHASES.REFINED) {
152
+ setPhase("review");
153
+ return;
154
+ }
155
+ if (state.phase === PHASES.VALIDATED) {
156
+ setPhase("review"); // still want accept gate before apply
157
+ return;
158
+ }
159
+ if (state.phase === PHASES.APPLIED) {
160
+ setPipeline(null);
161
+ setPhase("watching-ci");
162
+ return;
163
+ }
164
+ if (state.phase === PHASES.MERGED) {
165
+ setPhase("watching-deploy");
166
+ return;
167
+ }
168
+ if (state.phase === PHASES.RESOLVED) {
169
+ setPhase("close-gate");
170
+ return;
171
+ }
172
+ if (state.phase === PHASES.CLOSED) {
173
+ pushStep("Session already closed", undefined);
174
+ setPhase("done");
175
+ return;
176
+ }
177
+ }
178
+ setPhase("analyzing");
179
+ })();
180
+ }, [phase]);
181
+ // === analyzing ===
182
+ useEffect(() => {
183
+ if (phase !== "analyzing")
184
+ return;
185
+ let cancelled = false;
186
+ pushStep("Analyzing issue", `#${iid}`);
187
+ (async () => {
188
+ try {
189
+ const { ForgeAgent } = await import(__rewriteRelativeImportExtension(path.join(projectRoot, "server/forge/core/ForgeAgent.ts")));
190
+ const agent = new ForgeAgent({
191
+ project: primary,
192
+ secondaryProjects: secondaries,
193
+ });
194
+ let final = null;
195
+ const events = [];
196
+ const onEvent = (evt) => {
197
+ events.push(evt);
198
+ if (evt.type === "result")
199
+ final = evt.data;
200
+ };
201
+ await agent.analyzeIssue(iid, onEvent, undefined, undefined, undefined, "fresh");
202
+ if (cancelled)
203
+ return;
204
+ if (!final)
205
+ throw new Error("analyze produced no result");
206
+ await writeAnalysis(repo, iid, final);
207
+ await updateState(repo, iid, {
208
+ primary,
209
+ secondaries,
210
+ phase: PHASES.ANALYZED,
211
+ lastError: null,
212
+ });
213
+ await setHead(repo, iid);
214
+ for (const e of events) {
215
+ await appendEvent(repo, iid, e).catch(() => { });
216
+ }
217
+ setAnalysis(final);
218
+ finishStep("Analyzing issue", "ok", `${final.changes?.length ?? 0} change(s) proposed`);
219
+ setPhase("review");
220
+ }
221
+ catch (err) {
222
+ if (cancelled)
223
+ return;
224
+ finishStep("Analyzing issue", "fail", err.message ?? String(err));
225
+ setError(err);
226
+ setPhase("error");
227
+ }
228
+ })();
229
+ return () => {
230
+ cancelled = true;
231
+ };
232
+ }, [phase]);
233
+ // === refining ===
234
+ useEffect(() => {
235
+ if (phase !== "refining" || !feedback)
236
+ return;
237
+ let cancelled = false;
238
+ pushStep("Refining with feedback", feedback);
239
+ (async () => {
240
+ try {
241
+ const { ForgeAgent } = await import(__rewriteRelativeImportExtension(path.join(projectRoot, "server/forge/core/ForgeAgent.ts")));
242
+ const agent = new ForgeAgent({
243
+ project: primary,
244
+ secondaryProjects: secondaries,
245
+ });
246
+ let final = null;
247
+ const onEvent = (evt) => {
248
+ if (evt.type === "result")
249
+ final = evt.data;
250
+ };
251
+ await agent.refineIssue(iid, analysis.changes, feedback, onEvent, undefined);
252
+ if (cancelled)
253
+ return;
254
+ if (!final)
255
+ throw new Error("refine produced no result");
256
+ await writeAnalysis(repo, iid, final);
257
+ await updateState(repo, iid, {
258
+ phase: PHASES.REFINED,
259
+ lastFeedback: feedback,
260
+ lastError: null,
261
+ });
262
+ setAnalysis(final);
263
+ setFeedback(null);
264
+ finishStep("Refining with feedback", "ok", `${final.changes?.length ?? 0} change(s)`);
265
+ setPhase("review");
266
+ }
267
+ catch (err) {
268
+ if (cancelled)
269
+ return;
270
+ finishStep("Refining with feedback", "fail", err.message ?? String(err));
271
+ setError(err);
272
+ setPhase("error");
273
+ }
274
+ })();
275
+ return () => {
276
+ cancelled = true;
277
+ };
278
+ }, [phase, feedback]);
279
+ // === validating ===
280
+ useEffect(() => {
281
+ if (phase !== "validating")
282
+ return;
283
+ let cancelled = false;
284
+ pushStep("Validating proposal", "lint / typecheck / test");
285
+ (async () => {
286
+ try {
287
+ const { validate } = await import(__rewriteRelativeImportExtension(path.join(projectRoot, "server/forge/core/validator.ts")));
288
+ const res = await validate(analysis, () => { });
289
+ if (cancelled)
290
+ return;
291
+ if (!res.allPassed) {
292
+ finishStep("Validating proposal", "fail", "see: forge validate");
293
+ setError(new Error("validation failed"));
294
+ setPhase("error");
295
+ return;
296
+ }
297
+ await updateState(repo, iid, {
298
+ phase: PHASES.VALIDATED,
299
+ validation: { passed: true, checkedAt: new Date().toISOString() },
300
+ });
301
+ finishStep("Validating proposal", "ok", "all checks passed");
302
+ setPhase("apply-gate");
303
+ }
304
+ catch (err) {
305
+ if (cancelled)
306
+ return;
307
+ finishStep("Validating proposal", "fail", err.message ?? String(err));
308
+ setError(err);
309
+ setPhase("error");
310
+ }
311
+ })();
312
+ return () => {
313
+ cancelled = true;
314
+ };
315
+ }, [phase]);
316
+ // === applying ===
317
+ useEffect(() => {
318
+ if (phase !== "applying")
319
+ return;
320
+ let cancelled = false;
321
+ pushStep("Opening MR", "branch + commit + create MR");
322
+ (async () => {
323
+ try {
324
+ const { ForgeAgent } = await import(__rewriteRelativeImportExtension(path.join(projectRoot, "server/forge/core/ForgeAgent.ts")));
325
+ const agent = new ForgeAgent({
326
+ project: primary,
327
+ secondaryProjects: secondaries,
328
+ });
329
+ const result = await agent.applyFix(analysis);
330
+ if (cancelled)
331
+ return;
332
+ const primaryApp = result.applications?.find((a) => canonicalRepoId(a.repo) === canonicalRepoId(primary)) ?? result.applications?.[0];
333
+ await updateState(repo, iid, {
334
+ phase: PHASES.APPLIED,
335
+ branch: primaryApp?.branchName ?? null,
336
+ mrIid: primaryApp?.mr?.iid ?? null,
337
+ mrUrl: primaryApp?.mr?.web_url ?? null,
338
+ applications: result.applications ?? [],
339
+ lastError: null,
340
+ });
341
+ finishStep("Opening MR", "ok", primaryApp?.mr?.web_url
342
+ ? `${primaryApp.mr.web_url}`
343
+ : primaryApp?.branchName
344
+ ? `branch ${primaryApp.branchName}`
345
+ : "applied");
346
+ setSessionState((s) => ({
347
+ ...(s ?? { repo: primary, iid, phase: PHASES.APPLIED }),
348
+ branch: primaryApp?.branchName,
349
+ mrIid: primaryApp?.mr?.iid,
350
+ }));
351
+ setPhase("watching-ci");
352
+ }
353
+ catch (err) {
354
+ if (cancelled)
355
+ return;
356
+ finishStep("Opening MR", "fail", err.message ?? String(err));
357
+ setError(err);
358
+ setPhase("error");
359
+ }
360
+ })();
361
+ return () => {
362
+ cancelled = true;
363
+ };
364
+ }, [phase]);
365
+ // === watching-ci ===
366
+ useEffect(() => {
367
+ if (phase !== "watching-ci")
368
+ return;
369
+ let cancelled = false;
370
+ pushStep("Watching CI pipeline", "polling every 5s");
371
+ let timer;
372
+ (async () => {
373
+ try {
374
+ const state = await readState(repo, iid);
375
+ if (!state?.mrIid)
376
+ throw new Error("session has no mrIid; cannot watch");
377
+ const { createProvider } = await import(__rewriteRelativeImportExtension(path.join(projectRoot, "server/forge/core/providers/index.ts")));
378
+ const provider = createProvider(primary);
379
+ const poll = async () => {
380
+ if (cancelled)
381
+ return;
382
+ try {
383
+ const pipelines = await provider.getMRPipelines(state.mrIid);
384
+ if (pipelines?.length) {
385
+ const latest = pipelines[0];
386
+ const [pipe, list] = await Promise.all([
387
+ provider.getPipeline(latest.id),
388
+ provider.getPipelineJobs(latest.id),
389
+ ]);
390
+ if (cancelled)
391
+ return;
392
+ setPipeline(pipe);
393
+ setJobs(list);
394
+ if (TERMINAL_PIPELINE.has(pipe.status)) {
395
+ await updateState(repo, iid, {
396
+ lastPipelineStatus: pipe.status,
397
+ lastPipelineUrl: pipe.web_url,
398
+ });
399
+ if (pipe.status === "success") {
400
+ finishStep("Watching CI pipeline", "ok", `${pipe.status}`);
401
+ setPhase("merge-gate");
402
+ }
403
+ else {
404
+ finishStep("Watching CI pipeline", "fail", `${pipe.status}`);
405
+ setPhase("ci-fix-gate");
406
+ }
407
+ return;
408
+ }
409
+ }
410
+ }
411
+ catch (err) {
412
+ // transient — keep polling
413
+ setError(err);
414
+ }
415
+ timer = setTimeout(poll, POLL_MS);
416
+ };
417
+ await poll();
418
+ }
419
+ catch (err) {
420
+ if (cancelled)
421
+ return;
422
+ finishStep("Watching CI pipeline", "fail", err.message ?? String(err));
423
+ setError(err);
424
+ setPhase("error");
425
+ }
426
+ })();
427
+ return () => {
428
+ cancelled = true;
429
+ if (timer)
430
+ clearTimeout(timer);
431
+ };
432
+ }, [phase]);
433
+ // === ci-fixing ===
434
+ useEffect(() => {
435
+ if (phase !== "ci-fixing")
436
+ return;
437
+ let cancelled = false;
438
+ pushStep("CI fix", "re-analyzing with failure logs");
439
+ (async () => {
440
+ try {
441
+ const state = await readState(repo, iid);
442
+ const { createProvider } = await import(__rewriteRelativeImportExtension(path.join(projectRoot, "server/forge/core/providers/index.ts")));
443
+ const provider = createProvider(primary);
444
+ const pipelines = await provider.getMRPipelines(state.mrIid);
445
+ const latest = pipelines?.[0];
446
+ if (!latest)
447
+ throw new Error("no pipeline to inspect");
448
+ const allJobs = await provider.getPipelineJobs(latest.id);
449
+ const failed = allJobs.filter((j) => j.status === "failed");
450
+ if (!failed.length)
451
+ throw new Error("no failed jobs to fix");
452
+ const traces = await Promise.all(failed.map(async (j) => {
453
+ const trace = await provider.getJobTrace(j.id).catch(() => "");
454
+ const tail = trace.split("\n").slice(-150).join("\n");
455
+ return `### ${j.stage}/${j.name}\n\n\`\`\`\n${tail}\n\`\`\``;
456
+ }));
457
+ const ciContext = traces.join("\n\n");
458
+ const { ForgeAgent } = await import(__rewriteRelativeImportExtension(path.join(projectRoot, "server/forge/core/ForgeAgent.ts")));
459
+ const agent = new ForgeAgent({
460
+ project: primary,
461
+ secondaryProjects: secondaries,
462
+ });
463
+ let final = null;
464
+ const onEvent = (evt) => {
465
+ if (evt.type === "result")
466
+ final = evt.data;
467
+ };
468
+ await agent.ciFixIssue(iid, ciContext, onEvent);
469
+ if (cancelled)
470
+ return;
471
+ if (!final)
472
+ throw new Error("ci-fix produced no result");
473
+ await agent.commitToBranch(state.branch, final, primary);
474
+ await writeAnalysis(repo, iid, final);
475
+ await updateState(repo, iid, { phase: PHASES.APPLIED, lastError: null });
476
+ finishStep("CI fix", "ok", `pushed to ${state.branch}`);
477
+ setPipeline(null);
478
+ setJobs([]);
479
+ setPhase("watching-ci");
480
+ }
481
+ catch (err) {
482
+ if (cancelled)
483
+ return;
484
+ finishStep("CI fix", "fail", err.message ?? String(err));
485
+ setError(err);
486
+ setPhase("error");
487
+ }
488
+ })();
489
+ return () => {
490
+ cancelled = true;
491
+ };
492
+ }, [phase]);
493
+ // === merging ===
494
+ useEffect(() => {
495
+ if (phase !== "merging")
496
+ return;
497
+ let cancelled = false;
498
+ pushStep(autoMerge ? "Arming auto-merge" : "Merging MR", undefined);
499
+ (async () => {
500
+ try {
501
+ const state = await readState(repo, iid);
502
+ const { createProvider } = await import(__rewriteRelativeImportExtension(path.join(projectRoot, "server/forge/core/providers/index.ts")));
503
+ const provider = createProvider(primary);
504
+ if (autoMerge) {
505
+ if (typeof provider.setAutoMergeMR !== "function")
506
+ throw new Error("provider does not support auto-merge");
507
+ await provider.setAutoMergeMR(state.mrIid);
508
+ }
509
+ else {
510
+ await provider.mergeMR(state.mrIid);
511
+ }
512
+ if (cancelled)
513
+ return;
514
+ await updateState(repo, iid, {
515
+ phase: PHASES.MERGED,
516
+ mergedAt: new Date().toISOString(),
517
+ autoMergeArmed: !!autoMerge,
518
+ });
519
+ finishStep(autoMerge ? "Arming auto-merge" : "Merging MR", "ok", autoMerge ? "armed" : "merged");
520
+ if (autoMerge) {
521
+ // auto-merge means we don't know exactly when it lands; stop here
522
+ setPhase("done");
523
+ }
524
+ else {
525
+ setPhase("watching-deploy");
526
+ }
527
+ }
528
+ catch (err) {
529
+ if (cancelled)
530
+ return;
531
+ finishStep(autoMerge ? "Arming auto-merge" : "Merging MR", "fail", err.message ?? String(err));
532
+ setError(err);
533
+ setPhase("error");
534
+ }
535
+ })();
536
+ return () => {
537
+ cancelled = true;
538
+ };
539
+ }, [phase]);
540
+ // === watching-deploy: poll the post-merge pipeline (target branch) ===
541
+ useEffect(() => {
542
+ if (phase !== "watching-deploy")
543
+ return;
544
+ let cancelled = false;
545
+ pushStep("Watching deploy pipeline", "post-merge");
546
+ let timer;
547
+ (async () => {
548
+ try {
549
+ const { createProvider } = await import(__rewriteRelativeImportExtension(path.join(projectRoot, "server/forge/core/providers/index.ts")));
550
+ const provider = createProvider(primary);
551
+ const target = process.env.GITLAB_BASE_BRANCH ??
552
+ process.env.GITHUB_BASE_BRANCH ??
553
+ "main";
554
+ const poll = async () => {
555
+ if (cancelled)
556
+ return;
557
+ try {
558
+ const pipelines = await provider.getPipelinesByRef(target);
559
+ if (pipelines?.length) {
560
+ const latest = pipelines[0];
561
+ const pipe = await provider.getPipeline(latest.id);
562
+ if (cancelled)
563
+ return;
564
+ setPipeline(pipe);
565
+ if (TERMINAL_PIPELINE.has(pipe.status)) {
566
+ await updateState(repo, iid, {
567
+ deployStatus: pipe.status,
568
+ deployedAt: new Date().toISOString(),
569
+ });
570
+ finishStep("Watching deploy pipeline", pipe.status === "success" ? "ok" : "fail", pipe.status);
571
+ setPhase(pipe.status === "success" ? "close-gate" : "error");
572
+ if (pipe.status !== "success") {
573
+ setError(new Error(`deploy pipeline ${pipe.status}`));
574
+ }
575
+ return;
576
+ }
577
+ }
578
+ }
579
+ catch (err) {
580
+ setError(err);
581
+ }
582
+ timer = setTimeout(poll, POLL_MS);
583
+ };
584
+ await poll();
585
+ }
586
+ catch (err) {
587
+ if (cancelled)
588
+ return;
589
+ finishStep("Watching deploy pipeline", "fail", err.message ?? String(err));
590
+ setError(err);
591
+ setPhase("error");
592
+ }
593
+ })();
594
+ return () => {
595
+ cancelled = true;
596
+ if (timer)
597
+ clearTimeout(timer);
598
+ };
599
+ }, [phase]);
600
+ // === closing ===
601
+ useEffect(() => {
602
+ if (phase !== "closing")
603
+ return;
604
+ let cancelled = false;
605
+ pushStep("Closing issue", `#${iid}`);
606
+ (async () => {
607
+ try {
608
+ const { createProvider } = await import(__rewriteRelativeImportExtension(path.join(projectRoot, "server/forge/core/providers/index.ts")));
609
+ const provider = createProvider(primary);
610
+ await provider.closeIssue(iid);
611
+ if (cancelled)
612
+ return;
613
+ await updateState(repo, iid, {
614
+ phase: PHASES.CLOSED,
615
+ closedAt: new Date().toISOString(),
616
+ });
617
+ finishStep("Closing issue", "ok", undefined);
618
+ setPhase("done");
619
+ }
620
+ catch (err) {
621
+ if (cancelled)
622
+ return;
623
+ finishStep("Closing issue", "fail", err.message ?? String(err));
624
+ setError(err);
625
+ setPhase("error");
626
+ }
627
+ })();
628
+ return () => {
629
+ cancelled = true;
630
+ };
631
+ }, [phase]);
632
+ // ─── Render ─────────────────────────────────────────────────────────────
633
+ const stepsRender = h `<${Box} flexDirection="column">
634
+ ${steps.map((s, i) => h `<${StepLine} key=${i} ...${s} />`)}
635
+ </>`;
636
+ const pipelineRender = pipeline
637
+ ? h `<${Box} marginTop=${1}>
638
+ <${Text} dimColor>Pipeline </>
639
+ <${Text} dimColor>#${pipeline.id} </>
640
+ <${Text} color=${pipeline.status === "success" ? "green" : pipeline.status === "failed" ? "red" : "cyan"}>${pipeline.status}</>
641
+ ${pipeline.web_url ? h `<${Text} dimColor> ${pipeline.web_url}</>` : null}
642
+ </>`
643
+ : null;
644
+ let gate = null;
645
+ if (phase === "review") {
646
+ gate = h `<${Box} flexDirection="column" marginTop=${1}>
647
+ ${analysis ? h `<${ProposalSummary} analysis=${analysis} />` : null}
648
+ <${Box} marginTop=${1}><${Text} dimColor>${"Use 'no' to abort, 'yes' to continue (run `forge refine` separately to iterate)."}</></>
649
+ <${Confirm}
650
+ message="Accept this fix and continue toward production?"
651
+ bypassYes=${bypassYes}
652
+ onSelect=${(yes) => setPhase(yes ? (skipValidate ? "apply-gate" : "validating") : "aborted")} />
653
+ </>`;
654
+ }
655
+ else if (phase === "apply-gate") {
656
+ gate = h `<${Confirm}
657
+ message="Apply changes (open MR)?"
658
+ bypassYes=${bypassYes}
659
+ onSelect=${(yes) => setPhase(yes ? "applying" : "aborted")} />`;
660
+ }
661
+ else if (phase === "ci-fix-gate") {
662
+ gate = h `<${Confirm}
663
+ message="CI failed. Re-analyse with failure logs and push a fix?"
664
+ bypassYes=${bypassYes}
665
+ onSelect=${(yes) => setPhase(yes ? "ci-fixing" : "aborted")} />`;
666
+ }
667
+ else if (phase === "merge-gate") {
668
+ gate = h `<${Confirm}
669
+ message=${autoMerge ? "Arm auto-merge?" : "Merge MR to main?"}
670
+ bypassYes=${bypassYes}
671
+ onSelect=${(yes) => setPhase(yes ? "merging" : "aborted")} />`;
672
+ }
673
+ else if (phase === "close-gate") {
674
+ gate = h `<${Confirm}
675
+ message=${`Close issue #${iid}?`}
676
+ bypassYes=${bypassYes}
677
+ onSelect=${(yes) => setPhase(yes ? "closing" : "done")} />`;
678
+ }
679
+ let footer = null;
680
+ if (phase === "done") {
681
+ footer = h `<${Box} marginTop=${1}>
682
+ <${Text} color="green">${`✓ Ship complete for #${iid}.`}</>
683
+ </>`;
684
+ }
685
+ else if (phase === "aborted") {
686
+ footer = h `<${Box} marginTop=${1}>
687
+ <${Text} dimColor>${`Aborted. Resume with: forge ship ${iid}`}</>
688
+ </>`;
689
+ }
690
+ else if (phase === "error") {
691
+ footer = h `<${Box} marginTop=${1}>
692
+ <${Text} color="red">${`✘ ${error?.message ?? String(error)}`}</>
693
+ <${Text} dimColor>${` (resume with: forge ship ${iid})`}</>
694
+ </>`;
695
+ }
696
+ return h `<${Box} flexDirection="column">
697
+ <${Box}>
698
+ <${Text} bold>forge ship </>
699
+ <${Text} color="yellow">#${iid}</>
700
+ <${Text} dimColor>${` (${primary}${secondaries.length ? ` + ${secondaries.length} secondaries` : ""})`}</>
701
+ </>
702
+ ${stepsRender}
703
+ ${pipelineRender}
704
+ ${gate}
705
+ ${footer}
706
+ </>`;
707
+ }
708
+ export async function cmdShip(argv) {
709
+ const iid = Number(argv[1]);
710
+ if (!Number.isInteger(iid) || iid <= 0) {
711
+ console.error("Usage: forge ship <iid> [options]");
712
+ process.exit(1);
713
+ }
714
+ const { primary, secondaries } = await resolveRepoSet(argv);
715
+ if (!primary) {
716
+ console.error("No repo. Pass --repo <group/repo>, set FORGE_GITLAB_PROJECT, or run `forge init`.");
717
+ process.exit(1);
718
+ }
719
+ const bypassYes = argv.includes("--yes") || process.env.FORGE_YES === "1";
720
+ const autoMerge = argv.includes("--auto");
721
+ const skipValidate = argv.includes("--skip-validate");
722
+ let outcome = { phase: "pending", error: null };
723
+ await runApp(h `<${ShipView}
724
+ iid=${iid}
725
+ primary=${primary}
726
+ secondaries=${secondaries}
727
+ bypassYes=${bypassYes}
728
+ autoMerge=${autoMerge}
729
+ skipValidate=${skipValidate}
730
+ onDone=${(o) => (outcome = o)} />`);
731
+ if (outcome.phase === "done")
732
+ return;
733
+ if (outcome.phase === "aborted")
734
+ process.exit(0);
735
+ process.exit(2);
736
+ }
737
+ //# sourceMappingURL=ship.js.map