@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
@@ -1,516 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import React, { useEffect, useState } from "react";
4
- import Spinner from "ink-spinner";
5
- import { createPatch } from "diff";
6
- import { h, Box, Text, useApp, runApp } from "./ink.js";
7
- import {
8
- projectRoot,
9
- getFlag,
10
- resolveRepoSet,
11
- prefixDivergence,
12
- } from "./utils.js";
13
- import {
14
- PHASES,
15
- appendEvent,
16
- setHead,
17
- updateState,
18
- writeAnalysis,
19
- } from "./sessionStore.js";
20
-
21
- const DIFF_MAX_LINES = 60;
22
-
23
- function StepRow({ step, message, status }) {
24
- let glyph;
25
- if (status === "running")
26
- glyph = h`<${Text} color="cyan"><${Spinner} type="dots"/></>`;
27
- else if (status === "done") glyph = h`<${Text} color="green">✓</>`;
28
- else glyph = h`<${Text} color="red">✘</>`;
29
- return h`<${Box}>
30
- ${glyph}
31
- <${Text} dimColor> [${step}]</>
32
- <${Text}> ${message}</>
33
- </>`;
34
- }
35
-
36
- function ItemRow({ item }) {
37
- if (item.kind === "step") return h`<${StepRow} ...${item} />`;
38
- if (item.kind === "log")
39
- return h`<${Box}><${Text} dimColor> ${item.text}</></>`;
40
- if (item.kind === "usage") {
41
- const cost = typeof item.cost === "number" ? `$${item.cost.toFixed(4)}` : "$?";
42
- return h`<${Box}>
43
- <${Text} dimColor> ${item.inputTokens}in / ${item.outputTokens}out tokens, ${cost}</>
44
- </>`;
45
- }
46
- if (item.kind === "error")
47
- return h`<${Box}><${Text} color="red">ERROR</><${Text}>: ${item.message}</></>`;
48
- return null;
49
- }
50
-
51
- function colorForDiffLine(line) {
52
- if (line.startsWith("+++") || line.startsWith("---")) return "white";
53
- if (line.startsWith("@@")) return "cyan";
54
- if (line.startsWith("+")) return "green";
55
- if (line.startsWith("-")) return "red";
56
- return undefined;
57
- }
58
-
59
- function DiffBlock({ original, proposed, file }) {
60
- const patch = createPatch(file, original ?? "", proposed ?? "", "current", "proposed");
61
- const lines = patch.split("\n").slice(2);
62
- const truncated = lines.length > DIFF_MAX_LINES;
63
- const shown = truncated ? lines.slice(0, DIFF_MAX_LINES) : lines;
64
- return h`<${Box} flexDirection="column" marginLeft=${4}>
65
- ${shown.map((line, i) => {
66
- const color = colorForDiffLine(line);
67
- return h`<${Text} key=${i} color=${color} dimColor=${color === undefined}>${line || " "}</>`;
68
- })}
69
- ${truncated
70
- ? h`<${Text} dimColor>${` … ${lines.length - DIFF_MAX_LINES} more diff lines (use --json to see full payload)`}</>`
71
- : null}
72
- </>`;
73
- }
74
-
75
- function ChangeBlock({ change, index }) {
76
- const isCreate = change.action === "create";
77
- const isDelete = change.action === "delete";
78
- const showDiff = !isDelete; // create: diff vs empty; delete: skip
79
- const header = ` ${index + 1}. ${change.action.toUpperCase()} ${change.repo}:${change.file}`;
80
- return h`<${Box} flexDirection="column" marginTop=${1}>
81
- <${Text} color="yellow">${header}</>
82
- ${change.summary
83
- ? h`<${Box} marginLeft=${5}><${Text} dimColor>${change.summary}</></>`
84
- : null}
85
- ${showDiff
86
- ? h`<${DiffBlock} file=${change.file} original=${isCreate ? "" : change.original ?? ""} proposed=${change.proposed ?? ""} />`
87
- : null}
88
- </>`;
89
- }
90
-
91
- function DivergenceWarning({ divergence }) {
92
- if (!divergence) return null;
93
- const text = `⚠ Issue prefix "${divergence.issue}:" but proposed MR prefix "${divergence.mr}:" — model may have misinterpreted the issue.`;
94
- return h`<${Box} marginTop=${1}>
95
- <${Text} color="yellow">${text}</>
96
- </>`;
97
- }
98
-
99
- function ResultBlock({ result }) {
100
- const changes = result.changes ?? [];
101
- const issueTitle = result.issue?.title ?? "";
102
- const divergence = prefixDivergence(issueTitle, result.mrTitle);
103
- return h`<${Box} flexDirection="column" marginTop=${1}>
104
- <${Text} dimColor>${"────────────── result ──────────────"}</>
105
- <${Box}>
106
- <${Text} color="yellow">Issue: </>
107
- <${Text}>#${result.issue?.iid}: ${issueTitle}</>
108
- </>
109
- ${result.mrTitle
110
- ? h`<${Box}>
111
- <${Text} color="yellow">MR: </>
112
- <${Text}>${result.mrTitle}</>
113
- </>`
114
- : null}
115
- ${result.repos?.length
116
- ? h`<${Box}>
117
- <${Text} color="yellow">Repos: </>
118
- <${Text}>${result.repos.join(", ")}</>
119
- </>`
120
- : null}
121
- <${DivergenceWarning} divergence=${divergence} />
122
- ${result.explanation
123
- ? h`<${Box} flexDirection="column" marginTop=${1}>
124
- <${Text} dimColor>Explanation</>
125
- <${Text}>${result.explanation}</>
126
- </>`
127
- : null}
128
- <${Box} marginTop=${1}>
129
- <${Text} dimColor>${`Changes (${changes.length}):`}</>
130
- </>
131
- ${changes.length === 0
132
- ? h`<${Box} marginLeft=${2}><${Text} dimColor>(none — model proposed no changes)</></>`
133
- : changes.map((c, i) => h`<${ChangeBlock} key=${i} change=${c} index=${i} />`)}
134
- </>`;
135
- }
136
-
137
- function AnalyzeView({
138
- iid,
139
- primary,
140
- secondaries,
141
- model,
142
- agentKind,
143
- userPrompt,
144
- eventSink,
145
- onDone,
146
- }) {
147
- const { exit } = useApp();
148
- const [items, setItems] = useState([]);
149
- const [result, setResult] = useState(null);
150
- const [finished, setFinished] = useState(null);
151
-
152
- useEffect(() => {
153
- if (!finished) return;
154
- onDone(finished);
155
- exit();
156
- }, [finished]);
157
-
158
- useEffect(() => {
159
- let cancelled = false;
160
- (async () => {
161
- try {
162
- const { ForgeAgent } = await import(
163
- path.join(projectRoot, "server/forge/core/ForgeAgent.js")
164
- );
165
- const agent = new ForgeAgent({
166
- project: primary,
167
- secondaryProjects: secondaries,
168
- agent: agentKind,
169
- });
170
- let final = null;
171
-
172
- const onEvent = (evt) => {
173
- if (cancelled) return;
174
- eventSink?.(evt);
175
- if (evt.type === "result") {
176
- final = evt.data;
177
- return;
178
- }
179
- setItems((prev) => {
180
- const closeRunning = (list) =>
181
- list.map((it) =>
182
- it.kind === "step" && it.status === "running"
183
- ? { ...it, status: "done" }
184
- : it,
185
- );
186
- switch (evt.type) {
187
- case "progress":
188
- return [
189
- ...closeRunning(prev),
190
- {
191
- kind: "step",
192
- step: evt.step,
193
- message: evt.message,
194
- status: "running",
195
- },
196
- ];
197
- case "log":
198
- return [...prev, { kind: "log", text: evt.message }];
199
- case "usage":
200
- return [
201
- ...prev,
202
- {
203
- kind: "usage",
204
- inputTokens: evt.inputTokens,
205
- outputTokens: evt.outputTokens,
206
- cost: evt.cost,
207
- },
208
- ];
209
- case "error":
210
- return [
211
- ...prev.map((it) =>
212
- it.kind === "step" && it.status === "running"
213
- ? { ...it, status: "failed" }
214
- : it,
215
- ),
216
- { kind: "error", message: evt.message },
217
- ];
218
- default:
219
- return prev;
220
- }
221
- });
222
- };
223
-
224
- await agent.analyzeIssue(iid, onEvent, model, undefined, userPrompt, "fresh");
225
- if (cancelled) return;
226
-
227
- setItems((prev) =>
228
- prev.map((it) =>
229
- it.kind === "step" && it.status === "running"
230
- ? { ...it, status: "done" }
231
- : it,
232
- ),
233
- );
234
- if (final) setResult(final);
235
- setFinished({ ok: true, result: final });
236
- } catch (err) {
237
- if (cancelled) return;
238
- setItems((prev) => [
239
- ...prev.map((it) =>
240
- it.kind === "step" && it.status === "running"
241
- ? { ...it, status: "failed" }
242
- : it,
243
- ),
244
- { kind: "error", message: err.message || String(err) },
245
- ]);
246
- setFinished({ ok: false, error: err });
247
- }
248
- })();
249
- return () => {
250
- cancelled = true;
251
- };
252
- }, []);
253
-
254
- return h`<${Box} flexDirection="column">
255
- ${items.map((it, i) => h`<${ItemRow} key=${i} item=${it} />`)}
256
- ${result ? h`<${ResultBlock} result=${result} />` : null}
257
- </>`;
258
- }
259
-
260
- /**
261
- * Render a cached analysis (read from session) through the same ResultBlock
262
- * the live analyze view uses. No streaming, no progress — just the final
263
- * payload. Used by `forge show`.
264
- */
265
- export async function renderCachedResult(result) {
266
- await runApp(
267
- h`<${Box} flexDirection="column">
268
- <${ResultBlock} result=${result} />
269
- </>`,
270
- );
271
- }
272
-
273
- /**
274
- * Persist analyze outcome to ~/.forge/sessions/<repo>/<iid>/. Writes
275
- * analysis.json (if a result is present), state.json with the new phase,
276
- * events.ndjson with the full event buffer, and bumps HEAD so the next
277
- * <iid>-less command resumes here. Errors during persistence are
278
- * surfaced on stderr but never abort the caller.
279
- */
280
- async function persistAnalyzeOutcome({ repo, iid, primary, secondaries, result, events, error }) {
281
- if (!repo || !iid) return;
282
- try {
283
- if (result) await writeAnalysis(repo, iid, result);
284
- await updateState(repo, iid, {
285
- repo,
286
- iid: Number(iid),
287
- primary,
288
- secondaries,
289
- phase: result ? PHASES.ANALYZED : "analyze-failed",
290
- lastError: error?.message ?? null,
291
- });
292
- await appendEvent(repo, iid, { type: "session:analyze-start" });
293
- for (const evt of events) await appendEvent(repo, iid, evt);
294
- await appendEvent(repo, iid, {
295
- type: "session:analyze-end",
296
- ok: !error,
297
- error: error?.message,
298
- });
299
- await setHead(repo, iid);
300
- } catch (err) {
301
- process.stderr.write(`forge: failed to persist session: ${err.message}\n`);
302
- }
303
- }
304
-
305
- /**
306
- * Render a saved NDJSON event stream through the same Ink view, without
307
- * re-running the agent. Useful for verifying renderer changes against a
308
- * known-good payload, and for users who want to re-inspect a prior result.
309
- */
310
- async function cmdAnalyzeReplay(replayPath) {
311
- const raw = await fs.readFile(replayPath, "utf8");
312
- const events = raw
313
- .split("\n")
314
- .filter(Boolean)
315
- .map((line) => {
316
- try {
317
- return JSON.parse(line);
318
- } catch {
319
- return null;
320
- }
321
- })
322
- .filter(Boolean);
323
-
324
- let outcome = { ok: true };
325
- await runApp(
326
- h`<${ReplayView} events=${events} onDone=${(o) => (outcome = o)} />`,
327
- );
328
- if (!outcome.ok) process.exit(2);
329
- }
330
-
331
- function ReplayView({ events, onDone }) {
332
- const { exit } = useApp();
333
- const [items, setItems] = useState([]);
334
- const [result, setResult] = useState(null);
335
- const [finished, setFinished] = useState(null);
336
-
337
- useEffect(() => {
338
- if (!finished) return;
339
- onDone(finished);
340
- exit();
341
- }, [finished]);
342
-
343
- useEffect(() => {
344
- let final = null;
345
- const next = [];
346
- const closeRunning = (list) =>
347
- list.map((it) =>
348
- it.kind === "step" && it.status === "running" ? { ...it, status: "done" } : it,
349
- );
350
- for (const evt of events) {
351
- if (evt.type === "result") {
352
- final = evt.data;
353
- continue;
354
- }
355
- switch (evt.type) {
356
- case "progress": {
357
- const closed = closeRunning(next);
358
- next.length = 0;
359
- next.push(...closed, {
360
- kind: "step",
361
- step: evt.step,
362
- message: evt.message,
363
- status: "running",
364
- });
365
- break;
366
- }
367
- case "log":
368
- next.push({ kind: "log", text: evt.message });
369
- break;
370
- case "usage":
371
- next.push({
372
- kind: "usage",
373
- inputTokens: evt.inputTokens,
374
- outputTokens: evt.outputTokens,
375
- cost: evt.cost,
376
- });
377
- break;
378
- case "error": {
379
- const closed = next.map((it) =>
380
- it.kind === "step" && it.status === "running"
381
- ? { ...it, status: "failed" }
382
- : it,
383
- );
384
- next.length = 0;
385
- next.push(...closed, { kind: "error", message: evt.message });
386
- break;
387
- }
388
- default:
389
- }
390
- }
391
- setItems(closeRunning(next));
392
- if (final) setResult(final);
393
- setFinished({ ok: true });
394
- }, []);
395
-
396
- return h`<${Box} flexDirection="column">
397
- ${items.map((it, i) => h`<${ItemRow} key=${i} item=${it} />`)}
398
- ${result ? h`<${ResultBlock} result=${result} />` : null}
399
- </>`;
400
- }
401
-
402
- export async function cmdAnalyze(argv) {
403
- const replayPath = getFlag(argv, "--replay");
404
- if (replayPath) return cmdAnalyzeReplay(replayPath);
405
-
406
- const iid = Number(argv[1]);
407
- if (!Number.isInteger(iid) || iid <= 0) {
408
- console.error("Usage: forge analyze <iid> [options]");
409
- process.exit(1);
410
- }
411
-
412
- const { primary, secondaries } = await resolveRepoSet(argv);
413
- if (!primary) {
414
- console.error(
415
- "No repo. Pass --repo <group/repo>, set FORGE_GITLAB_PROJECT, or run `forge init`.",
416
- );
417
- process.exit(1);
418
- }
419
-
420
- const model = getFlag(argv, "--model") || undefined;
421
- const agentKind = getFlag(argv, "--agent") || "claude";
422
- const userPrompt = getFlag(argv, "--prompt") || undefined;
423
- const json = argv.includes("--json");
424
- const quiet = argv.includes("--quiet");
425
-
426
- const { ForgeAgent } = await import(
427
- path.join(projectRoot, "server/forge/core/ForgeAgent.js")
428
- );
429
-
430
- if (json || quiet) {
431
- const agent = new ForgeAgent({
432
- project: primary,
433
- secondaryProjects: secondaries,
434
- agent: agentKind,
435
- });
436
- let finalResult = null;
437
- const events = [];
438
- const onEvent = (evt) => {
439
- if (evt.type === "result") finalResult = evt.data;
440
- events.push(evt);
441
- if (json) process.stdout.write(`${JSON.stringify(evt)}\n`);
442
- };
443
- try {
444
- await agent.analyzeIssue(iid, onEvent, model, undefined, userPrompt, "fresh");
445
- } catch (err) {
446
- if (json) {
447
- process.stdout.write(
448
- `${JSON.stringify({ type: "error", message: err.message })}\n`,
449
- );
450
- } else {
451
- process.stderr.write(`ERROR: ${err.message}\n`);
452
- }
453
- await persistAnalyzeOutcome({
454
- repo: primary,
455
- iid,
456
- primary,
457
- secondaries,
458
- result: null,
459
- events,
460
- error: err,
461
- });
462
- process.exit(2);
463
- }
464
- if (!finalResult) {
465
- if (!json) console.error("Analysis produced no result.");
466
- await persistAnalyzeOutcome({
467
- repo: primary,
468
- iid,
469
- primary,
470
- secondaries,
471
- result: null,
472
- events,
473
- error: new Error("no result"),
474
- });
475
- process.exit(2);
476
- }
477
- await persistAnalyzeOutcome({
478
- repo: primary,
479
- iid,
480
- primary,
481
- secondaries,
482
- result: finalResult,
483
- events,
484
- });
485
- if (quiet && !json) console.log(JSON.stringify(finalResult, null, 2));
486
- return;
487
- }
488
-
489
- let outcome = { ok: true };
490
- const events = [];
491
- await runApp(
492
- h`<${AnalyzeView}
493
- iid=${iid}
494
- primary=${primary}
495
- secondaries=${secondaries}
496
- model=${model}
497
- agentKind=${agentKind}
498
- userPrompt=${userPrompt}
499
- eventSink=${(e) => events.push(e)}
500
- onDone=${(o) => (outcome = o)} />`,
501
- );
502
- await persistAnalyzeOutcome({
503
- repo: primary,
504
- iid,
505
- primary,
506
- secondaries,
507
- result: outcome.result ?? null,
508
- events,
509
- error: outcome.error,
510
- });
511
- if (!outcome.ok) process.exit(2);
512
- if (!outcome.result) {
513
- console.error("Analysis produced no result.");
514
- process.exit(2);
515
- }
516
- }