@ipul-labs/forge 1.16.8 → 1.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +181 -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 +11 -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,230 +0,0 @@
1
- import path from "node:path";
2
- import React from "react";
3
- import { h, Box, Text, runApp } from "./ink.js";
4
- import { getFlag, projectRoot } from "./utils.js";
5
- import {
6
- cleanAll,
7
- cleanSession,
8
- getHead,
9
- listSessions,
10
- readAnalysis,
11
- readState,
12
- resolveSessionTarget,
13
- sessionDir,
14
- } from "./sessionStore.js";
15
-
16
- function relTime(iso) {
17
- if (!iso) return "";
18
- const ms = Date.now() - new Date(iso).getTime();
19
- if (Number.isNaN(ms) || ms < 0) return "";
20
- const s = Math.floor(ms / 1000);
21
- if (s < 60) return `${s}s ago`;
22
- const m = Math.floor(s / 60);
23
- if (m < 60) return `${m}m ago`;
24
- const h = Math.floor(m / 60);
25
- if (h < 24) return `${h}h ago`;
26
- return `${Math.floor(h / 24)}d ago`;
27
- }
28
-
29
- function SessionsTable({ sessions, head }) {
30
- if (sessions.length === 0) {
31
- return h`<${Box} flexDirection="column">
32
- <${Text} dimColor>No sessions yet.</>
33
- <${Text}>${"Run: forge analyze <iid>"}</>
34
- </>`;
35
- }
36
- const rows = sessions.map((s) => {
37
- const isHead = head && head.repo === s.repo && head.iid === s.iid;
38
- return {
39
- marker: isHead ? "*" : " ",
40
- ref: `${s.repo}#${s.iid}`,
41
- phase: s.phase ?? "?",
42
- when: relTime(s.updatedAt),
43
- };
44
- });
45
- const wRef = Math.max(3, ...rows.map((r) => r.ref.length));
46
- const wPhase = Math.max(5, ...rows.map((r) => r.phase.length));
47
- return h`<${Box} flexDirection="column" marginTop=${1}>
48
- ${rows.map((r, i) =>
49
- h`<${Box} key=${i}>
50
- <${Text} color=${r.marker === "*" ? "green" : undefined}> ${r.marker}</>
51
- <${Box} width=${wRef + 2}><${Text}> ${r.ref}</></>
52
- <${Box} width=${wPhase + 2}><${Text} color="yellow">${r.phase}</></>
53
- <${Text} dimColor>${r.when}</>
54
- </>`,
55
- )}
56
- <${Box} marginTop=${1}>
57
- <${Text} dimColor>${"* HEAD — commands without <iid> resume here"}</>
58
- </>
59
- </>`;
60
- }
61
-
62
- function SessionDetail({ state, analysisSummary }) {
63
- return h`<${Box} flexDirection="column">
64
- <${Box}>
65
- <${Text} color="yellow">Session </>
66
- <${Text}>${state.repo}#${state.iid}</>
67
- </>
68
- <${Box}>
69
- <${Text} color="yellow">Phase </>
70
- <${Text}>${state.phase ?? "?"}</>
71
- </>
72
- ${state.primary
73
- ? h`<${Box}>
74
- <${Text} color="yellow">Primary </>
75
- <${Text}>${state.primary}</>
76
- </>`
77
- : null}
78
- ${state.secondaries?.length
79
- ? h`<${Box}>
80
- <${Text} color="yellow">Secondary</>
81
- <${Text}> ${state.secondaries.join(", ")}</>
82
- </>`
83
- : null}
84
- ${state.branch
85
- ? h`<${Box}>
86
- <${Text} color="yellow">Branch </>
87
- <${Text}>${state.branch}</>
88
- </>`
89
- : null}
90
- ${state.mrIid
91
- ? h`<${Box}>
92
- <${Text} color="yellow">MR </>
93
- <${Text}>!${state.mrIid}</>
94
- </>`
95
- : null}
96
- ${state.lastError
97
- ? h`<${Box}>
98
- <${Text} color="red">Error </>
99
- <${Text}>${state.lastError}</>
100
- </>`
101
- : null}
102
- <${Box} marginTop=${1}>
103
- <${Text} dimColor>${`Created ${relTime(state.createdAt)} · Updated ${relTime(state.updatedAt)}`}</>
104
- </>
105
- ${analysisSummary
106
- ? h`<${Box} flexDirection="column" marginTop=${1}>
107
- <${Text} dimColor>${"Cached analysis:"}</>
108
- <${Text}>${` ${analysisSummary.changes} change(s) · ${analysisSummary.repos} repo(s) · ${analysisSummary.mrTitle ?? "(no proposed title)"}`}</>
109
- </>`
110
- : h`<${Box} marginTop=${1}>
111
- <${Text} dimColor>${"No cached analysis on disk."}</>
112
- </>`}
113
- </>`;
114
- }
115
-
116
- async function listCommand(argv) {
117
- const json = argv.includes("--json");
118
- const sessions = await listSessions();
119
- if (json) {
120
- process.stdout.write(`${JSON.stringify(sessions, null, 2)}\n`);
121
- return;
122
- }
123
- const head = await getHead();
124
- await runApp(h`<${SessionsTable} sessions=${sessions} head=${head} />`);
125
- }
126
-
127
- async function showCommand(argv) {
128
- const json = argv.includes("--json");
129
- const explicitIid = Number(argv[2]);
130
- const explicitRepo = getFlag(argv, "--repo");
131
- const target = await resolveSessionTarget({
132
- explicitIid: Number.isInteger(explicitIid) && explicitIid > 0 ? explicitIid : null,
133
- explicitRepo,
134
- });
135
- if (!target?.repo || !target?.iid) {
136
- console.error(
137
- "No session target. Pass an <iid>, set --repo, or run analyze first.",
138
- );
139
- process.exit(1);
140
- }
141
- const state = await readState(target.repo, target.iid);
142
- if (!state) {
143
- console.error(`No session at ${sessionDir(target.repo, target.iid)}`);
144
- process.exit(1);
145
- }
146
- const analysis = await readAnalysis(target.repo, target.iid);
147
- if (json) {
148
- process.stdout.write(`${JSON.stringify({ state, analysis }, null, 2)}\n`);
149
- return;
150
- }
151
- const summary = analysis
152
- ? {
153
- changes: analysis.changes?.length ?? 0,
154
- repos: analysis.repos?.length ?? 0,
155
- mrTitle: analysis.mrTitle ?? null,
156
- }
157
- : null;
158
- await runApp(h`<${SessionDetail} state=${state} analysisSummary=${summary} />`);
159
- }
160
-
161
- async function cleanCommand(argv) {
162
- if (argv.includes("--all")) {
163
- await cleanAll();
164
- process.stderr.write(`✓ Removed all sessions\n`);
165
- return;
166
- }
167
- const explicitIid = Number(argv[2]);
168
- const explicitRepo = getFlag(argv, "--repo");
169
- const target = await resolveSessionTarget({
170
- explicitIid: Number.isInteger(explicitIid) && explicitIid > 0 ? explicitIid : null,
171
- explicitRepo,
172
- });
173
- if (!target?.repo || !target?.iid) {
174
- console.error(
175
- "No session target. Pass an <iid>, set --repo, or use --all to wipe everything.",
176
- );
177
- process.exit(1);
178
- }
179
- const removed = await cleanSession(target.repo, target.iid);
180
- if (removed) process.stderr.write(`✓ Removed ${target.repo}#${target.iid}\n`);
181
- else process.stderr.write(`Nothing to remove at ${target.repo}#${target.iid}\n`);
182
- }
183
-
184
- export async function cmdSession(argv) {
185
- const sub = argv[1] ?? "list";
186
- if (sub === "list" || sub === "ls") return listCommand(argv);
187
- if (sub === "show") return showCommand(argv);
188
- if (sub === "clean" || sub === "rm") return cleanCommand(argv);
189
- console.error(`Unknown subcommand: ${sub}`);
190
- console.error("Subcommands: list, show, clean");
191
- process.exit(1);
192
- }
193
-
194
- /**
195
- * Top-level `forge show [<iid>]` — re-renders the cached analysis from
196
- * the session through the same Ink view as live analyze. Resolves <iid> via
197
- * positional arg or HEAD pointer.
198
- */
199
- export async function cmdShow(argv) {
200
- const explicitIid = Number(argv[1]);
201
- const explicitRepo = getFlag(argv, "--repo");
202
- const json = argv.includes("--json");
203
- const target = await resolveSessionTarget({
204
- explicitIid: Number.isInteger(explicitIid) && explicitIid > 0 ? explicitIid : null,
205
- explicitRepo,
206
- });
207
- if (!target?.repo || !target?.iid) {
208
- console.error(
209
- "No session target. Pass an <iid>, or run analyze first to seed HEAD.",
210
- );
211
- process.exit(1);
212
- }
213
- const analysis = await readAnalysis(target.repo, target.iid);
214
- if (!analysis) {
215
- console.error(
216
- `No cached analysis at ${sessionDir(target.repo, target.iid)}.\n` +
217
- `Run: forge analyze ${target.iid}`,
218
- );
219
- process.exit(1);
220
- }
221
- if (json) {
222
- process.stdout.write(`${JSON.stringify(analysis, null, 2)}\n`);
223
- return;
224
- }
225
- // Reuse analyze.js's ResultBlock by importing it lazily — keeps the
226
- // dependency cycle simple (session.js depends on the analyze view, but
227
- // analyze.js doesn't depend on session.js's UI).
228
- const { renderCachedResult } = await import("./analyze.js");
229
- await renderCachedResult(analysis);
230
- }
@@ -1,211 +0,0 @@
1
- /**
2
- * Session store for the ship workflow.
3
- *
4
- * ~/.forge/sessions/<canonical-repo>/<iid>/
5
- * analysis.json — last cached ForgeAgent.analyzeIssue result
6
- * state.json — { phase, primary, secondaries, branch?, mrIid?, ... }
7
- * events.ndjson — append-only log of every event we received
8
- *
9
- * ~/.forge/sessions/HEAD
10
- * — single line: "<canonical-repo>/<iid>" of the most recently touched
11
- * session. Used so subsequent commands without an explicit <iid> can
12
- * resume the current unit of work, in the spirit of git's HEAD.
13
- *
14
- * Override the root with FORGE_SESSIONS_PATH (test isolation).
15
- */
16
-
17
- import { existsSync } from "node:fs";
18
- import fs from "node:fs/promises";
19
- import os from "node:os";
20
- import path from "node:path";
21
- import { canonicalRepoId } from "./utils.js";
22
-
23
- export const PHASES = Object.freeze({
24
- ANALYZED: "analyzed",
25
- REFINED: "refined",
26
- VALIDATED: "validated",
27
- APPLIED: "applied",
28
- WATCHING: "watching",
29
- MERGED: "merged",
30
- RESOLVED: "resolved",
31
- CLOSED: "closed",
32
- });
33
-
34
- function rootDir() {
35
- return (
36
- process.env.FORGE_SESSIONS_PATH ??
37
- path.join(os.homedir(), ".forge", "sessions")
38
- );
39
- }
40
-
41
- /**
42
- * Resolve the on-disk directory for a session. Repo is canonicalised so
43
- * "iplayulisten/web-app" and "gitlab:iplayulisten/web-app" map to the
44
- * same folder.
45
- */
46
- export function sessionDir(repo, iid) {
47
- return path.join(rootDir(), canonicalRepoId(String(repo)), String(iid));
48
- }
49
-
50
- async function ensureDir(dir) {
51
- await fs.mkdir(dir, { recursive: true, mode: 0o700 });
52
- }
53
-
54
- export async function writeAnalysis(repo, iid, analysis) {
55
- const dir = sessionDir(repo, iid);
56
- await ensureDir(dir);
57
- const file = path.join(dir, "analysis.json");
58
- await fs.writeFile(file, JSON.stringify(analysis, null, 2), { encoding: "utf8", mode: 0o600 });
59
- }
60
-
61
- export async function readAnalysis(repo, iid) {
62
- const file = path.join(sessionDir(repo, iid), "analysis.json");
63
- if (!existsSync(file)) return null;
64
- return JSON.parse(await fs.readFile(file, "utf8"));
65
- }
66
-
67
- export async function readState(repo, iid) {
68
- const file = path.join(sessionDir(repo, iid), "state.json");
69
- if (!existsSync(file)) return null;
70
- return JSON.parse(await fs.readFile(file, "utf8"));
71
- }
72
-
73
- export async function updateState(repo, iid, partial) {
74
- const dir = sessionDir(repo, iid);
75
- await ensureDir(dir);
76
- const file = path.join(dir, "state.json");
77
- const now = new Date().toISOString();
78
- const prev = (await readState(repo, iid)) ?? {
79
- repo: canonicalRepoId(String(repo)),
80
- iid: Number(iid),
81
- createdAt: now,
82
- };
83
- const next = { ...prev, ...partial, updatedAt: now };
84
- await fs.writeFile(file, JSON.stringify(next, null, 2), { encoding: "utf8", mode: 0o600 });
85
- return next;
86
- }
87
-
88
- export async function appendEvent(repo, iid, evt) {
89
- const dir = sessionDir(repo, iid);
90
- await ensureDir(dir);
91
- await fs.appendFile(
92
- path.join(dir, "events.ndjson"),
93
- `${JSON.stringify({ ts: new Date().toISOString(), ...evt })}\n`,
94
- "utf8",
95
- );
96
- }
97
-
98
- export async function setHead(repo, iid) {
99
- const root = rootDir();
100
- await fs.mkdir(root, { recursive: true });
101
- await fs.writeFile(
102
- path.join(root, "HEAD"),
103
- `${canonicalRepoId(String(repo))}/${iid}\n`,
104
- "utf8",
105
- );
106
- }
107
-
108
- /**
109
- * Return { repo, iid } of the HEAD session, or null.
110
- */
111
- export async function getHead() {
112
- const file = path.join(rootDir(), "HEAD");
113
- if (!existsSync(file)) return null;
114
- const raw = (await fs.readFile(file, "utf8")).trim();
115
- const lastSlash = raw.lastIndexOf("/");
116
- if (lastSlash <= 0) return null;
117
- const repo = raw.slice(0, lastSlash);
118
- const iid = Number(raw.slice(lastSlash + 1));
119
- if (!Number.isInteger(iid) || iid <= 0) return null;
120
- return { repo, iid };
121
- }
122
-
123
- /**
124
- * Walk the on-disk tree and return every session it finds. Sorted by
125
- * updatedAt descending (most recent first).
126
- */
127
- export async function listSessions() {
128
- const root = rootDir();
129
- if (!existsSync(root)) return [];
130
-
131
- const out = [];
132
- const repoDirs = await safeReadDir(root);
133
- for (const repoEntry of repoDirs) {
134
- if (!repoEntry.isDirectory()) continue;
135
- // Repos may be nested one level (e.g. "iplayulisten/web-app").
136
- const repoLevel1 = path.join(root, repoEntry.name);
137
- const repoSubdirs = await safeReadDir(repoLevel1);
138
- for (const sub of repoSubdirs) {
139
- if (!sub.isDirectory()) continue;
140
- const candidatePath = path.join(repoLevel1, sub.name);
141
-
142
- // Two layouts:
143
- // a) repoLevel1/<iid>/state.json (single-segment repo)
144
- // b) repoLevel1/<sub>/<iid>/state.json (two-segment repo)
145
- if (existsSync(path.join(candidatePath, "state.json"))) {
146
- const state = JSON.parse(
147
- await fs.readFile(path.join(candidatePath, "state.json"), "utf8"),
148
- );
149
- out.push(state);
150
- continue;
151
- }
152
- const iidEntries = await safeReadDir(candidatePath);
153
- for (const iidEntry of iidEntries) {
154
- if (!iidEntry.isDirectory()) continue;
155
- const stateFile = path.join(candidatePath, iidEntry.name, "state.json");
156
- if (!existsSync(stateFile)) continue;
157
- const state = JSON.parse(await fs.readFile(stateFile, "utf8"));
158
- out.push(state);
159
- }
160
- }
161
- }
162
-
163
- out.sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""));
164
- return out;
165
- }
166
-
167
- async function safeReadDir(dir) {
168
- try {
169
- return await fs.readdir(dir, { withFileTypes: true });
170
- } catch {
171
- return [];
172
- }
173
- }
174
-
175
- export async function cleanSession(repo, iid) {
176
- const dir = sessionDir(repo, iid);
177
- if (!existsSync(dir)) return false;
178
- await fs.rm(dir, { recursive: true, force: true });
179
- const head = await getHead();
180
- if (head && canonicalRepoId(head.repo) === canonicalRepoId(String(repo)) && head.iid === Number(iid)) {
181
- await fs.rm(path.join(rootDir(), "HEAD"), { force: true });
182
- }
183
- return true;
184
- }
185
-
186
- export async function cleanAll() {
187
- const root = rootDir();
188
- if (!existsSync(root)) return;
189
- await fs.rm(root, { recursive: true, force: true });
190
- }
191
-
192
- /**
193
- * Resolve a session target from CLI argv. Honours an explicit positional iid
194
- * or HEAD as fallback. Returns { repo, iid } or null.
195
- *
196
- * forge show → HEAD
197
- * forge show 42 → primary repo + iid 42
198
- * forge show 42 --repo X → repo X + iid 42
199
- */
200
- export async function resolveSessionTarget({ explicitIid, explicitRepo }) {
201
- if (Number.isInteger(explicitIid) && explicitIid > 0) {
202
- // repo defaults to HEAD's repo if available, else explicitRepo, else
203
- // the configStore primary. Keeps `show 42` working when there's a
204
- // prior HEAD that was on the same repo.
205
- if (explicitRepo) return { repo: explicitRepo, iid: explicitIid };
206
- const head = await getHead();
207
- if (head) return { repo: head.repo, iid: explicitIid };
208
- return { repo: null, iid: explicitIid };
209
- }
210
- return await getHead();
211
- }