@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,105 +0,0 @@
1
- /**
2
- * TDD precheck: when a proposal adds *new* test files alongside impl changes,
3
- * run just those tests with the impl reverted. If they pass, the tests don't
4
- * actually exercise the new behavior — that's the gap we surface.
5
- *
6
- * Only fires when all three hold:
7
- * - at least one new test file (action === "create" on a test path)
8
- * - at least one non-test change (impl)
9
- * - the project has a `test` script
10
- *
11
- * The precheck reverts impl files via git (or `rm` for untracked creates),
12
- * runs vitest scoped to the new test paths, then re-overlays impl so the
13
- * downstream `test` step sees the full proposal.
14
- */
15
-
16
- import fs from "node:fs/promises";
17
- import path from "node:path";
18
-
19
- import { runCmd } from "./cmd.js";
20
-
21
- const TEST_FILE_RE_NODE = /\.(test|spec)\.[cm]?[jt]sx?$/;
22
-
23
- export function isNodeTestFile(filePath) {
24
- return TEST_FILE_RE_NODE.test(filePath);
25
- }
26
-
27
- export function partitionChangesNode(changes) {
28
- const impl = [];
29
- const newTests = [];
30
- for (const c of changes ?? []) {
31
- if (isNodeTestFile(c.file)) {
32
- if (c.action === "create") newTests.push(c);
33
- } else {
34
- impl.push(c);
35
- }
36
- }
37
- return { impl, newTests };
38
- }
39
-
40
- async function revertImplOverlay(dir, impl) {
41
- for (const c of impl) {
42
- const full = path.join(dir, c.file);
43
- if (c.action === "create") {
44
- await fs.rm(full, { force: true });
45
- } else {
46
- // modify or delete — restore the pre-overlay content from HEAD.
47
- await runCmd("git", ["checkout", "--", c.file], { cwd: dir });
48
- }
49
- }
50
- }
51
-
52
- async function reapplyImplOverlay(dir, impl) {
53
- for (const c of impl) {
54
- const full = path.join(dir, c.file);
55
- if (c.action === "delete") {
56
- await fs.rm(full, { force: true });
57
- } else {
58
- await fs.mkdir(path.dirname(full), { recursive: true });
59
- await fs.writeFile(full, c.proposed);
60
- }
61
- }
62
- }
63
-
64
- /**
65
- * @returns {Promise<null|{passed: boolean, exitCode: number}>} null when skipped.
66
- */
67
- export async function runTddPrecheckNode(dir, changes, emit) {
68
- const { impl, newTests } = partitionChangesNode(changes);
69
- if (newTests.length === 0 || impl.length === 0) return null;
70
-
71
- const testPaths = newTests.map((c) => c.file);
72
- emit({ type: "progress", step: "tdd_precheck", status: "running" });
73
- emit({
74
- type: "log",
75
- step: "tdd_precheck",
76
- message: `Verifying ${newTests.length} new test file(s) fail without impl: ${testPaths.join(", ")}`,
77
- });
78
-
79
- await revertImplOverlay(dir, impl);
80
- const start = Date.now();
81
- const res = await runCmd(
82
- "npx", ["vitest", "--run", "--reporter=default", ...testPaths],
83
- { cwd: dir },
84
- (l) => emit({ type: "log", step: "tdd_precheck", message: l }),
85
- );
86
- await reapplyImplOverlay(dir, impl);
87
-
88
- // TDD contract: without the impl, the new tests must fail (non-zero exit).
89
- // Exit 0 means either the tests passed without the impl — they don't
90
- // exercise the new behavior — or vitest found nothing to run.
91
- const precheckPassed = res.code !== 0;
92
- const tail = res.output.split("\n").slice(-60).join("\n");
93
- emit({
94
- type: "step_result",
95
- step: "tdd_precheck",
96
- passed: precheckPassed,
97
- exitCode: res.code,
98
- durationMs: Date.now() - start,
99
- tail,
100
- message: precheckPassed
101
- ? `${newTests.length} new test(s) failed without implementation (good TDD).`
102
- : `${newTests.length} new test(s) passed without the implementation change — they may not actually test the fix.`,
103
- });
104
- return { passed: precheckPassed, exitCode: res.code };
105
- }
@@ -1,167 +0,0 @@
1
- /**
2
- * server/forge/core/validator.js
3
- *
4
- * Pre-flight runner. Auto-attributes each proposed change to the repo whose
5
- * working tree actually contains that file, then runs the right toolchain
6
- * (Node npm scripts vs Go go-tool commands) on each repo with at least one
7
- * attributed change.
8
- *
9
- * Composition:
10
- *
11
- * validator/cmd.js — child-process + step-event helpers
12
- * validator/clone.js — cache + git clone management
13
- * validator/attribute.js — file-existence-based repo auto-detection
14
- * validator/overlay.js — overlay changes + readback after format
15
- * validator/projectType.js — per-repo type resolution (config + auto-detect)
16
- * validator/runners/node.js — npm ci + format/lint/typecheck/test
17
- * validator/runners/go.js — go mod + go fmt/vet/build/test
18
- *
19
- * Scope (v1):
20
- * - Validates only repos that actually own at least one change (1, both, or
21
- * none — never a phantom block from a misrouted attribution).
22
- * - GitLab HTTPS clone using GITLAB_TOKEN. GitHub / Azure DevOps raise a
23
- * clear "not supported yet" error.
24
- * - Step names per runner: format/lint/typecheck/test (Node) or
25
- * fmt/vet/build/test (Go). Missing scripts (Node) are silently skipped.
26
- */
27
-
28
- import { branchNameFor } from "./branchName.js";
29
- import * as configStore from "./configStore.js";
30
- import { attributeChanges } from "./validator/attribute.js";
31
- import { ensureClone } from "./validator/clone.js";
32
- import { overlayChanges } from "./validator/overlay.js";
33
- import { resolveProjectType } from "./validator/projectType.js";
34
- import * as goRunner from "./validator/runners/go.js";
35
- import * as nodeRunner from "./validator/runners/node.js";
36
-
37
- const RUNNERS = {
38
- go: { run: goRunner.runGoRepo, stepOrder: goRunner.STEP_ORDER, fixableSteps: goRunner.FIXABLE_STEPS },
39
- node: { run: nodeRunner.runNodeRepo, stepOrder: nodeRunner.STEP_ORDER, fixableSteps: nodeRunner.FIXABLE_STEPS },
40
- };
41
-
42
- async function validateRepo(repo, changes, branchName, emit) {
43
- // Stamp every downstream event with the current repo so the frontend can
44
- // route progress/log/step_result rows into the right per-repo block.
45
- const repoEmit = (e) => emit({ ...e, repo });
46
-
47
- const fileList = changes.map((c) => c.file).join(", ");
48
- repoEmit({
49
- type: "log",
50
- message: `Validating ${changes.length} change(s) for ${repo} on ${branchName} — ${fileList}`,
51
- });
52
-
53
- // Idempotent — the candidate-clone phase already populated the cache, so
54
- // this is essentially a fetch/reset that completes quickly and gives the
55
- // UI its `clone` step events.
56
- const dir = await ensureClone(repo, branchName, repoEmit);
57
-
58
- const projectType = await resolveProjectType(repo, dir, changes);
59
- const runner = RUNNERS[projectType] ?? RUNNERS.node;
60
-
61
- // Send the runner's step contract up front so the UI can render rows in
62
- // the runner's natural order (e.g. fmt/vet/build/test for Go) and gate
63
- // the "Fix with AI" button on the right step keys.
64
- repoEmit({
65
- type: "repo_started",
66
- projectType,
67
- stepOrder: runner.stepOrder,
68
- fixableSteps: runner.fixableSteps,
69
- });
70
-
71
- await overlayChanges(dir, changes, repoEmit);
72
-
73
- const results = await runner.run(dir, changes, repoEmit);
74
-
75
- const allPassed = Object.values(results).every((r) => r.passed);
76
- repoEmit({
77
- type: "repo_result",
78
- projectType,
79
- allPassed,
80
- checkedAt: new Date().toISOString(),
81
- });
82
- return { repo, branchName, projectType, results, allPassed };
83
- }
84
-
85
- function resolveCandidates(analysis) {
86
- const fromAnalysis = analysis.repos ?? [];
87
- if (fromAnalysis.length) return fromAnalysis;
88
- const fromChange = analysis.changes?.[0]?.repo;
89
- if (fromChange) return [fromChange];
90
- const primary = configStore.getPrimaryProject();
91
- return primary ? [primary] : [];
92
- }
93
-
94
- /**
95
- * @param {{ changes: Array<{repo?: string, file: string, action: string, proposed: string}>, repos?: string[], issue?: { iid: number } }} analysis
96
- * @param {(event: object) => void} onEvent
97
- */
98
- export async function validate(analysis, onEvent) {
99
- const emit = onEvent ?? (() => {});
100
-
101
- try {
102
- const candidates = resolveCandidates(analysis);
103
- if (!candidates.length) throw new Error("No repos to validate against.");
104
-
105
- const branchName = analysis.issue?.iid
106
- ? branchNameFor(analysis.issue.iid, analysis)
107
- : "forge/workspace";
108
-
109
- // Step 1: attribute each change by querying the SCM API for file
110
- // existence. No clones yet — we only clone repos we'll actually
111
- // validate. The model's `c.repo` is advisory; we trust the API.
112
- const { byRepo, unattributed } = await attributeChanges(analysis.changes, candidates, emit);
113
- if (unattributed.length) {
114
- for (const c of unattributed) {
115
- emit({
116
- type: "log",
117
- message: `Dropping ${c.file} (${c.action}): not found in ${candidates.join(" or ")}.`,
118
- });
119
- }
120
- }
121
- if (!byRepo.size) {
122
- throw new Error(
123
- `No proposed changes match files in ${candidates.join(" or ")}. ` +
124
- `The file paths in the proposal don't exist in those repos — likely a wrong path.`,
125
- );
126
- }
127
-
128
- // Step 3: order chosen repos by the analysis's repos[] (primary first).
129
- const ordered = candidates.filter((r) => byRepo.has(r));
130
-
131
- emit({
132
- type: "log",
133
- message: `Plan: ${ordered.length} repo(s): ${ordered.map((r) => `${r} (${byRepo.get(r).length} change${byRepo.get(r).length === 1 ? "" : "s"})`).join(", ")}`,
134
- });
135
-
136
- const perRepo = [];
137
- for (const repo of ordered) {
138
- perRepo.push(await validateRepo(repo, byRepo.get(repo), branchName, emit));
139
- }
140
-
141
- const allPassed = perRepo.length > 0 && perRepo.every((r) => r.allPassed);
142
- const repoLabel = perRepo.length === 1
143
- ? perRepo[0].repo
144
- : perRepo.map((r) => r.repo).join(", ");
145
- // Single-repo runs keep the original flat `results` shape; multi-repo
146
- // runs nest by repo so older single-repo consumers still see something
147
- // sensible at `results.<step>` for their own repo.
148
- const results = perRepo.length === 1
149
- ? perRepo[0].results
150
- : Object.fromEntries(perRepo.map((r) => [r.repo, r.results]));
151
-
152
- emit({
153
- type: "result",
154
- data: {
155
- repo: repoLabel,
156
- branchName,
157
- results,
158
- allPassed,
159
- checkedAt: new Date().toISOString(),
160
- },
161
- });
162
- return { repo: repoLabel, branchName, results, allPassed, perRepo };
163
- } catch (err) {
164
- emit({ type: "error", message: err?.message ?? "Validation failed" });
165
- throw err;
166
- }
167
- }