@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
package/bin/cli/apply.js DELETED
@@ -1,264 +0,0 @@
1
- import path from "node:path";
2
- import React, { useEffect, useState } from "react";
3
- import Spinner from "ink-spinner";
4
- import { h, Box, Text, useApp, runApp, Confirm } from "./ink.js";
5
- import { canonicalRepoId, getFlag, projectRoot } from "./utils.js";
6
- import {
7
- PHASES,
8
- appendEvent,
9
- readAnalysis,
10
- readState,
11
- resolveSessionTarget,
12
- updateState,
13
- } from "./sessionStore.js";
14
-
15
- function changesByRepo(changes) {
16
- const map = new Map();
17
- for (const c of changes ?? []) {
18
- const key = c.repo ?? "(unknown)";
19
- if (!map.has(key)) map.set(key, []);
20
- map.get(key).push(c);
21
- }
22
- return map;
23
- }
24
-
25
- function Summary({ analysis }) {
26
- const grouped = changesByRepo(analysis.changes);
27
- const total = analysis.changes?.length ?? 0;
28
- return h`<${Box} flexDirection="column">
29
- <${Text} dimColor>${"────────── apply plan ──────────"}</>
30
- <${Box}>
31
- <${Text} color="yellow">Issue: </>
32
- <${Text}>#${analysis.issue?.iid}: ${analysis.issue?.title ?? ""}</>
33
- </>
34
- ${analysis.mrTitle
35
- ? h`<${Box}>
36
- <${Text} color="yellow">MR: </>
37
- <${Text}>${analysis.mrTitle}</>
38
- </>`
39
- : null}
40
- <${Box}>
41
- <${Text} color="yellow">Total: </>
42
- <${Text}>${`${total} change(s) across ${grouped.size} repo(s)`}</>
43
- </>
44
- <${Box} marginTop=${1}>
45
- <${Text} dimColor>${"Per repo:"}</>
46
- </>
47
- ${[...grouped.entries()].map(([repo, changes], i) =>
48
- h`<${Box} flexDirection="column" key=${i} marginLeft=${2}>
49
- <${Text} bold>${repo} (${changes.length})</>
50
- ${changes.map((c, j) =>
51
- h`<${Text} key=${j} dimColor>${` ${c.action.padEnd(7)} ${c.file}`}</>`,
52
- )}
53
- </>`,
54
- )}
55
- </>`;
56
- }
57
-
58
- function ApplyView({ analysis, primary, secondaries, bypassYes, onDone }) {
59
- const { exit } = useApp();
60
- const [phase, setPhase] = useState("review"); // review → confirm → applying → done | aborted | error
61
- const [applications, setApplications] = useState([]);
62
- const [error, setError] = useState(null);
63
- const [running, setRunning] = useState(null); // current repo being applied
64
-
65
- useEffect(() => {
66
- if (phase !== "applying") return;
67
- let cancelled = false;
68
- (async () => {
69
- try {
70
- const { ForgeAgent } = await import(
71
- path.join(projectRoot, "server/forge/core/ForgeAgent.js")
72
- );
73
- const agent = new ForgeAgent({
74
- project: primary,
75
- secondaryProjects: secondaries,
76
- });
77
- // applyFix doesn't currently emit per-step events on its own —
78
- // fall back to a coarse "applying repo X" indicator by inspecting
79
- // the analysis.changes grouping ahead of the call.
80
- const grouped = changesByRepo(analysis.changes);
81
- const ordered = [primary, ...secondaries].filter((r) => grouped.has(r));
82
- if (ordered.length === 0) {
83
- // Fallback to whatever the analysis says
84
- ordered.push(...grouped.keys());
85
- }
86
- setRunning(ordered[0] ?? null);
87
- const result = await agent.applyFix(analysis);
88
- if (cancelled) return;
89
- setApplications(result.applications ?? []);
90
- setRunning(null);
91
- setPhase("done");
92
- } catch (err) {
93
- if (cancelled) return;
94
- setError(err);
95
- setRunning(null);
96
- setPhase("error");
97
- }
98
- })();
99
- return () => {
100
- cancelled = true;
101
- };
102
- }, [phase]);
103
-
104
- useEffect(() => {
105
- if (phase === "done" || phase === "aborted" || phase === "error") {
106
- onDone({
107
- phase,
108
- applications,
109
- error,
110
- });
111
- exit();
112
- }
113
- }, [phase]);
114
-
115
- if (phase === "review" || phase === "confirm") {
116
- return h`<${Box} flexDirection="column">
117
- <${Summary} analysis=${analysis} />
118
- <${Confirm}
119
- message="Apply these changes (creates branch + opens MR per repo)?"
120
- bypassYes=${bypassYes}
121
- onSelect=${(yes) => setPhase(yes ? "applying" : "aborted")} />
122
- </>`;
123
- }
124
-
125
- if (phase === "applying") {
126
- return h`<${Box} flexDirection="column">
127
- <${Summary} analysis=${analysis} />
128
- <${Box} marginTop=${1}>
129
- <${Text} color="cyan"><${Spinner} type="dots"/></>
130
- <${Text}>${` Applying${running ? ` to ${running}` : "…"}`}</>
131
- </>
132
- </>`;
133
- }
134
-
135
- if (phase === "done") {
136
- return h`<${Box} flexDirection="column">
137
- <${Summary} analysis=${analysis} />
138
- <${Box} flexDirection="column" marginTop=${1}>
139
- <${Text} color="green">${`✓ Applied across ${applications.length} repo(s)`}</>
140
- ${applications.map((a, i) =>
141
- h`<${Box} key=${i} flexDirection="column" marginLeft=${2} marginTop=${1}>
142
- <${Text} bold>${a.repo}</>
143
- <${Text}>${` branch: ${a.branchName}`}</>
144
- ${a.mr?.web_url
145
- ? h`<${Text}>${` MR: ${a.mr.web_url}`}</>`
146
- : a.mr?.iid
147
- ? h`<${Text}>${` MR: !${a.mr.iid}`}</>`
148
- : h`<${Text} dimColor>${" MR: (not opened)"}</>`}
149
- ${a.pipelineUrl
150
- ? h`<${Text} dimColor>${` pipe: ${a.pipelineUrl}`}</>`
151
- : null}
152
- </>`,
153
- )}
154
- </>
155
- </>`;
156
- }
157
-
158
- if (phase === "aborted") {
159
- return h`<${Box} flexDirection="column">
160
- <${Summary} analysis=${analysis} />
161
- <${Box} marginTop=${1}>
162
- <${Text} dimColor>${"Aborted — no changes applied."}</>
163
- </>
164
- </>`;
165
- }
166
-
167
- return h`<${Box} flexDirection="column">
168
- <${Summary} analysis=${analysis} />
169
- <${Box} marginTop=${1}>
170
- <${Text} color="red">${`✘ ${error?.message ?? String(error)}`}</>
171
- </>
172
- </>`;
173
- }
174
-
175
- export async function cmdApply(argv) {
176
- const explicitIid = Number(argv[1]);
177
- const explicitRepo = getFlag(argv, "--repo");
178
- const target = await resolveSessionTarget({
179
- explicitIid: Number.isInteger(explicitIid) && explicitIid > 0 ? explicitIid : null,
180
- explicitRepo,
181
- });
182
- if (!target?.repo || !target?.iid) {
183
- console.error(
184
- "No session target. Pass an <iid>, set --repo, or run analyze first.",
185
- );
186
- process.exit(1);
187
- }
188
- const analysis = await readAnalysis(target.repo, target.iid);
189
- if (!analysis) {
190
- console.error(
191
- `No cached analysis at ${target.repo}#${target.iid}. Run: forge analyze ${target.iid}`,
192
- );
193
- process.exit(1);
194
- }
195
-
196
- const state = (await readState(target.repo, target.iid)) ?? {};
197
- const primary = state.primary ?? target.repo;
198
- const secondaries = state.secondaries ?? [];
199
-
200
- const bypassYes = argv.includes("--yes") || process.env.FORGE_YES === "1";
201
- const wantValidate = argv.includes("--validate");
202
-
203
- if (wantValidate) {
204
- const { cmdValidate } = await import("./validate.js");
205
- // Re-enter validate using the same session target. cmdValidate exits
206
- // with code 2 on red — we trust that gating.
207
- await cmdValidate(["validate", String(target.iid), "--repo", target.repo]);
208
- // If validate failed it would have exited; we reach here only on green.
209
- }
210
-
211
- let outcome = { phase: null };
212
- await runApp(
213
- h`<${ApplyView}
214
- analysis=${analysis}
215
- primary=${primary}
216
- secondaries=${secondaries}
217
- bypassYes=${bypassYes}
218
- onDone=${(o) => (outcome = o)} />`,
219
- );
220
-
221
- if (outcome.phase === "aborted") {
222
- await updateState(target.repo, target.iid, {
223
- phase: "apply-aborted",
224
- });
225
- await appendEvent(target.repo, target.iid, { type: "session:apply-aborted" });
226
- process.exit(0);
227
- }
228
- if (outcome.phase === "error") {
229
- await updateState(target.repo, target.iid, {
230
- phase: "apply-failed",
231
- lastError: outcome.error?.message ?? null,
232
- });
233
- await appendEvent(target.repo, target.iid, {
234
- type: "session:apply-failed",
235
- error: outcome.error?.message,
236
- });
237
- process.exit(2);
238
- }
239
-
240
- // Done — record per-repo branch + MR. ApplyFix returns one ApplicationResult
241
- // per repo; the primary's MR is the canonical session reference.
242
- const primaryApp =
243
- outcome.applications?.find(
244
- (a) => canonicalRepoId(a.repo) === canonicalRepoId(primary),
245
- ) ?? outcome.applications?.[0];
246
- await updateState(target.repo, target.iid, {
247
- phase: PHASES.APPLIED,
248
- branch: primaryApp?.branchName ?? null,
249
- mrIid: primaryApp?.mr?.iid ?? null,
250
- mrUrl: primaryApp?.mr?.web_url ?? null,
251
- pipelineUrl: primaryApp?.pipelineUrl ?? null,
252
- applications: outcome.applications ?? [],
253
- lastError: null,
254
- });
255
- await appendEvent(target.repo, target.iid, {
256
- type: "session:apply-done",
257
- applications: (outcome.applications ?? []).map((a) => ({
258
- repo: a.repo,
259
- branchName: a.branchName,
260
- mrIid: a.mr?.iid ?? null,
261
- mrUrl: a.mr?.web_url ?? null,
262
- })),
263
- });
264
- }
package/bin/cli/cifix.js DELETED
@@ -1,300 +0,0 @@
1
- import path from "node:path";
2
- import React, { useEffect, useState } from "react";
3
- import Spinner from "ink-spinner";
4
- import { h, Box, Text, useApp, runApp } from "./ink.js";
5
- import { getFlag, projectRoot } from "./utils.js";
6
- import {
7
- PHASES,
8
- appendEvent,
9
- readAnalysis,
10
- readState,
11
- resolveSessionTarget,
12
- updateState,
13
- writeAnalysis,
14
- } from "./sessionStore.js";
15
-
16
- const TRACE_TAIL_LINES = 150; // matches the existing CI refinement pattern
17
-
18
- async function fetchFailedJobTraces(provider, mrIid) {
19
- const pipelines = await provider.getMRPipelines(mrIid);
20
- if (!pipelines?.length) throw new Error(`No pipelines found for MR !${mrIid}`);
21
- const latest = pipelines[0];
22
- const jobs = await provider.getPipelineJobs(latest.id);
23
- const failed = jobs.filter((j) => j.status === "failed");
24
- if (!failed.length) {
25
- throw new Error(
26
- `Latest pipeline #${latest.id} has no failed jobs (status: ${latest.status}). Nothing to fix.`,
27
- );
28
- }
29
- const traces = await Promise.all(
30
- failed.map(async (j) => {
31
- const text = await provider.getJobTrace(j.id).catch(() => "(trace fetch failed)");
32
- const tail = text.split("\n").slice(-TRACE_TAIL_LINES).join("\n");
33
- return { name: j.name, stage: j.stage, tail };
34
- }),
35
- );
36
- return { pipeline: latest, traces };
37
- }
38
-
39
- function formatCiContext(traces) {
40
- return traces
41
- .map(
42
- (t) =>
43
- `### Failed job: ${t.name} (stage: ${t.stage})\n\n\`\`\`\n${t.tail}\n\`\`\``,
44
- )
45
- .join("\n\n");
46
- }
47
-
48
- function StepRow({ step, message, status }) {
49
- let glyph;
50
- if (status === "running")
51
- glyph = h`<${Text} color="cyan"><${Spinner} type="dots"/></>`;
52
- else if (status === "done") glyph = h`<${Text} color="green">✓</>`;
53
- else glyph = h`<${Text} color="red">✘</>`;
54
- return h`<${Box}>
55
- ${glyph}
56
- <${Text} dimColor> [${step}]</>
57
- <${Text}> ${message}</>
58
- </>`;
59
- }
60
-
61
- function CiFixView({
62
- repo,
63
- iid,
64
- primary,
65
- secondaries,
66
- branch,
67
- mrIid,
68
- model,
69
- eventSink,
70
- onDone,
71
- }) {
72
- const { exit } = useApp();
73
- const [phase, setPhase] = useState("fetching"); // fetching → analyzing → committing → done | error
74
- const [items, setItems] = useState([]);
75
- const [traces, setTraces] = useState([]);
76
- const [pipeline, setPipeline] = useState(null);
77
- const [result, setResult] = useState(null);
78
- const [error, setError] = useState(null);
79
-
80
- useEffect(() => {
81
- if (phase === "done" || phase === "error") {
82
- onDone({ phase, result, pipeline, error });
83
- exit();
84
- }
85
- }, [phase]);
86
-
87
- useEffect(() => {
88
- let cancelled = false;
89
- (async () => {
90
- try {
91
- const { createProvider } = await import(
92
- path.join(projectRoot, "server/forge/core/providers/index.js")
93
- );
94
- const provider = createProvider(repo);
95
- const { pipeline: pipe, traces: tr } = await fetchFailedJobTraces(
96
- provider,
97
- mrIid,
98
- );
99
- if (cancelled) return;
100
- setTraces(tr);
101
- setPipeline(pipe);
102
- setPhase("analyzing");
103
-
104
- const ciContext = formatCiContext(tr);
105
-
106
- const { ForgeAgent } = await import(
107
- path.join(projectRoot, "server/forge/core/ForgeAgent.js")
108
- );
109
- const agent = new ForgeAgent({
110
- project: primary,
111
- secondaryProjects: secondaries,
112
- });
113
- let final = null;
114
-
115
- const onEvent = (evt) => {
116
- if (cancelled) return;
117
- eventSink?.(evt);
118
- if (evt.type === "result") {
119
- final = evt.data;
120
- return;
121
- }
122
- setItems((prev) => {
123
- const closeRunning = (list) =>
124
- list.map((it) =>
125
- it.kind === "step" && it.status === "running"
126
- ? { ...it, status: "done" }
127
- : it,
128
- );
129
- switch (evt.type) {
130
- case "progress":
131
- return [
132
- ...closeRunning(prev),
133
- {
134
- kind: "step",
135
- step: evt.step,
136
- message: evt.message,
137
- status: "running",
138
- },
139
- ];
140
- case "log":
141
- return [...prev, { kind: "log", text: evt.message }];
142
- case "error":
143
- return [
144
- ...prev.map((it) =>
145
- it.kind === "step" && it.status === "running"
146
- ? { ...it, status: "failed" }
147
- : it,
148
- ),
149
- { kind: "error", message: evt.message },
150
- ];
151
- default:
152
- return prev;
153
- }
154
- });
155
- };
156
-
157
- await agent.ciFixIssue(iid, ciContext, onEvent, model);
158
- if (cancelled) return;
159
- setItems((prev) =>
160
- prev.map((it) =>
161
- it.kind === "step" && it.status === "running"
162
- ? { ...it, status: "done" }
163
- : it,
164
- ),
165
- );
166
- if (!final) {
167
- setError(new Error("CI fix produced no result."));
168
- setPhase("error");
169
- return;
170
- }
171
- setResult(final);
172
- setPhase("committing");
173
-
174
- await agent.commitToBranch(branch, final, primary);
175
- if (cancelled) return;
176
- setPhase("done");
177
- } catch (err) {
178
- if (cancelled) return;
179
- setError(err);
180
- setPhase("error");
181
- }
182
- })();
183
- return () => {
184
- cancelled = true;
185
- };
186
- }, []);
187
-
188
- return h`<${Box} flexDirection="column">
189
- <${Box}>
190
- <${Text} color="yellow">CI fix </>
191
- <${Text}>${repo} #${iid} → ${branch}</>
192
- </>
193
- ${phase === "fetching"
194
- ? h`<${Box} marginTop=${1}>
195
- <${Text} color="cyan"><${Spinner} type="dots"/></>
196
- <${Text}>${` Fetching failed job traces from MR !${mrIid}…`}</>
197
- </>`
198
- : null}
199
- ${traces.length
200
- ? h`<${Box} flexDirection="column" marginTop=${1}>
201
- <${Text} dimColor>${`Pipeline #${pipeline?.id} failed on:`}</>
202
- ${traces.map((t, i) =>
203
- h`<${Text} key=${i} dimColor>${` • ${t.stage}/${t.name} (${t.tail.split("\n").length} log lines)`}</>`,
204
- )}
205
- </>`
206
- : null}
207
- ${items.length
208
- ? h`<${Box} flexDirection="column" marginTop=${1}>
209
- ${items.map((it, i) => {
210
- if (it.kind === "step")
211
- return h`<${StepRow} key=${i} step=${it.step} message=${it.message} status=${it.status} />`;
212
- if (it.kind === "log")
213
- return h`<${Box} key=${i}><${Text} dimColor> ${it.text}</></>`;
214
- return h`<${Box} key=${i}>
215
- <${Text} color="red">ERROR</>
216
- <${Text}>: ${it.message}</>
217
- </>`;
218
- })}
219
- </>`
220
- : null}
221
- ${phase === "committing"
222
- ? h`<${Box} marginTop=${1}>
223
- <${Text} color="cyan"><${Spinner} type="dots"/></>
224
- <${Text}>${` Pushing fix commit to ${branch}…`}</>
225
- </>`
226
- : null}
227
- ${phase === "done"
228
- ? h`<${Box} marginTop=${1}>
229
- <${Text} color="green">${`✓ CI fix pushed to ${branch}. Re-run watch to follow the new pipeline.`}</>
230
- </>`
231
- : null}
232
- ${phase === "error"
233
- ? h`<${Box} marginTop=${1}>
234
- <${Text} color="red">${`✘ ${error?.message ?? String(error)}`}</>
235
- </>`
236
- : null}
237
- </>`;
238
- }
239
-
240
- export async function cmdCiFix(argv) {
241
- const explicitIid = Number(argv[1]);
242
- const explicitRepo = getFlag(argv, "--repo");
243
- const target = await resolveSessionTarget({
244
- explicitIid: Number.isInteger(explicitIid) && explicitIid > 0 ? explicitIid : null,
245
- explicitRepo,
246
- });
247
- if (!target?.repo || !target?.iid) {
248
- console.error(
249
- "No session target. Pass an <iid>, set --repo, or run analyze + apply first.",
250
- );
251
- process.exit(1);
252
- }
253
- const state = await readState(target.repo, target.iid);
254
- if (!state?.branch || !state?.mrIid) {
255
- console.error(
256
- `Session ${target.repo}#${target.iid} has no branch/MR yet. Run: forge apply ${target.iid}`,
257
- );
258
- process.exit(1);
259
- }
260
- const primary = state.primary ?? target.repo;
261
- const secondaries = state.secondaries ?? [];
262
- const model = getFlag(argv, "--model") || undefined;
263
-
264
- let outcome = { phase: null };
265
- const events = [];
266
- await runApp(
267
- h`<${CiFixView}
268
- repo=${primary}
269
- iid=${target.iid}
270
- primary=${primary}
271
- secondaries=${secondaries}
272
- branch=${state.branch}
273
- mrIid=${state.mrIid}
274
- model=${model}
275
- eventSink=${(e) => events.push(e)}
276
- onDone=${(o) => (outcome = o)} />`,
277
- );
278
-
279
- if (outcome.phase === "done" && outcome.result) {
280
- await writeAnalysis(target.repo, target.iid, outcome.result);
281
- await updateState(target.repo, target.iid, {
282
- phase: PHASES.APPLIED, // stays applied after a refresh commit
283
- lastError: null,
284
- });
285
- await appendEvent(target.repo, target.iid, {
286
- type: "session:cifix-done",
287
- pipelineId: outcome.pipeline?.id,
288
- });
289
- return;
290
- }
291
- await updateState(target.repo, target.iid, {
292
- phase: "cifix-failed",
293
- lastError: outcome.error?.message ?? null,
294
- }).catch(() => {});
295
- await appendEvent(target.repo, target.iid, {
296
- type: "session:cifix-failed",
297
- error: outcome.error?.message,
298
- }).catch(() => {});
299
- process.exit(2);
300
- }
package/bin/cli/close.js DELETED
@@ -1,125 +0,0 @@
1
- import path from "node:path";
2
- import React, { useEffect, useState } from "react";
3
- import Spinner from "ink-spinner";
4
- import { h, Box, Text, useApp, runApp, Confirm } from "./ink.js";
5
- import { getFlag, projectRoot } from "./utils.js";
6
- import {
7
- PHASES,
8
- appendEvent,
9
- readState,
10
- resolveSessionTarget,
11
- updateState,
12
- } from "./sessionStore.js";
13
-
14
- function CloseView({ repo, iid, bypassYes, onDone }) {
15
- const { exit } = useApp();
16
- const [phase, setPhase] = useState("review");
17
- const [error, setError] = useState(null);
18
-
19
- useEffect(() => {
20
- if (phase === "done" || phase === "aborted" || phase === "error") {
21
- onDone({ phase, error });
22
- exit();
23
- }
24
- }, [phase]);
25
-
26
- useEffect(() => {
27
- if (phase !== "closing") return;
28
- let cancelled = false;
29
- (async () => {
30
- try {
31
- const { createProvider } = await import(
32
- path.join(projectRoot, "server/forge/core/providers/index.js")
33
- );
34
- const provider = createProvider(repo);
35
- if (typeof provider.closeIssue !== "function") {
36
- throw new Error(
37
- `${provider.name ?? "provider"} does not support closeIssue.`,
38
- );
39
- }
40
- await provider.closeIssue(iid);
41
- if (cancelled) return;
42
- setPhase("done");
43
- } catch (err) {
44
- if (cancelled) return;
45
- setError(err);
46
- setPhase("error");
47
- }
48
- })();
49
- return () => {
50
- cancelled = true;
51
- };
52
- }, [phase]);
53
-
54
- if (phase === "review" || phase === "confirm") {
55
- return h`<${Box} flexDirection="column">
56
- <${Box}>
57
- <${Text} color="yellow">Issue </>
58
- <${Text}>${repo} #${iid}</>
59
- </>
60
- <${Confirm}
61
- message="Close this issue?"
62
- bypassYes=${bypassYes}
63
- onSelect=${(yes) => setPhase(yes ? "closing" : "aborted")} />
64
- </>`;
65
- }
66
-
67
- if (phase === "closing") {
68
- return h`<${Box}>
69
- <${Text} color="cyan"><${Spinner} type="dots"/></>
70
- <${Text}> Closing issue…</>
71
- </>`;
72
- }
73
-
74
- if (phase === "done") {
75
- return h`<${Text} color="green">${`✓ Closed #${iid}`}</>`;
76
- }
77
-
78
- if (phase === "aborted") {
79
- return h`<${Text} dimColor>Aborted — issue still open.</>`;
80
- }
81
-
82
- return h`<${Box}>
83
- <${Text} color="red">${`✘ ${error?.message ?? String(error)}`}</>
84
- </>`;
85
- }
86
-
87
- export async function cmdClose(argv) {
88
- const explicitIid = Number(argv[1]);
89
- const explicitRepo = getFlag(argv, "--repo");
90
- const bypassYes = argv.includes("--yes") || process.env.FORGE_YES === "1";
91
-
92
- const target = await resolveSessionTarget({
93
- explicitIid: Number.isInteger(explicitIid) && explicitIid > 0 ? explicitIid : null,
94
- explicitRepo,
95
- });
96
- if (!target?.repo || !target?.iid) {
97
- console.error(
98
- "No session target. Pass an <iid>, set --repo, or run analyze first.",
99
- );
100
- process.exit(1);
101
- }
102
- const state = await readState(target.repo, target.iid);
103
- const repo = state?.primary ?? target.repo;
104
-
105
- let outcome = { phase: null };
106
- await runApp(
107
- h`<${CloseView}
108
- repo=${repo}
109
- iid=${target.iid}
110
- bypassYes=${bypassYes}
111
- onDone=${(o) => (outcome = o)} />`,
112
- );
113
-
114
- if (outcome.phase === "done") {
115
- await updateState(target.repo, target.iid, {
116
- phase: PHASES.CLOSED,
117
- closedAt: new Date().toISOString(),
118
- }).catch(() => {});
119
- await appendEvent(target.repo, target.iid, { type: "session:closed" }).catch(
120
- () => {},
121
- );
122
- return;
123
- }
124
- if (outcome.phase === "error") process.exit(2);
125
- }