@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,219 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
-
3
- import {
4
- classifyIssue,
5
- buildIdentifyFilesPrompt,
6
- buildProposeFixPrompt,
7
- buildRefineOrAskPrompt,
8
- } from "./prompts.js";
9
-
10
- // ─── classifyIssue ───────────────────────────────────────────────────────────
11
-
12
- describe("classifyIssue", () => {
13
- it("detects feat prefix in title", () => {
14
- expect(classifyIssue({ title: "feat(mixes): Live Mixes section" })).toBe("feature");
15
- expect(classifyIssue({ title: "feat: add new page" })).toBe("feature");
16
- });
17
-
18
- it("detects fix prefix in title", () => {
19
- expect(classifyIssue({ title: "fix: broken auth flow" })).toBe("fix");
20
- expect(classifyIssue({ title: "fix(auth): token refresh race" })).toBe("fix");
21
- });
22
-
23
- it("detects chore prefix", () => {
24
- expect(classifyIssue({ title: "chore: bump dependencies" })).toBe("chore");
25
- });
26
-
27
- it("detects refactor prefix", () => {
28
- expect(classifyIssue({ title: "refactor: extract auth service" })).toBe("refactor");
29
- });
30
-
31
- it("falls back to labels when no title prefix", () => {
32
- expect(classifyIssue({ title: "Add mixes page", labels: ["feature"] })).toBe("feature");
33
- expect(classifyIssue({ title: "Add mixes page", labels: ["enhancement"] })).toBe("feature");
34
- expect(classifyIssue({ title: "Auth broken", labels: ["bug"] })).toBe("fix");
35
- });
36
-
37
- it("handles label objects with name field", () => {
38
- expect(classifyIssue({ title: "Add page", labels: [{ name: "Feature" }] })).toBe("feature");
39
- });
40
-
41
- it("falls back to description heuristics", () => {
42
- expect(classifyIssue({
43
- title: "Live Mixes section",
44
- description: "## Approach\nReuse existing infrastructure...",
45
- })).toBe("feature");
46
-
47
- expect(classifyIssue({
48
- title: "Multi-track mashups",
49
- description: "## Frontend changes\n- Generalize StemsEngine...",
50
- })).toBe("feature");
51
- });
52
-
53
- it("defaults to fix when nothing matches", () => {
54
- expect(classifyIssue({ title: "Something is wrong" })).toBe("fix");
55
- expect(classifyIssue({ title: "Update thing", labels: [] })).toBe("fix");
56
- });
57
-
58
- it("handles missing/null fields gracefully", () => {
59
- expect(classifyIssue({})).toBe("fix");
60
- expect(classifyIssue({ title: null, labels: null })).toBe("fix");
61
- });
62
- });
63
-
64
- // ─── buildIdentifyFilesPrompt ────────────────────────────────────────────────
65
-
66
- describe("buildIdentifyFilesPrompt", () => {
67
- const repoTrees = [
68
- { repo: "group/web-app", files: ["src/App.tsx", "src/types.ts"] },
69
- ];
70
-
71
- it("uses feature selection rules for feat issues", () => {
72
- const issue = { iid: 25, title: "feat(mixes): Live Mixes section", labels: [] };
73
- const prompt = buildIdentifyFilesPrompt(issue, repoTrees);
74
-
75
- expect(prompt).toContain("feature implementation");
76
- expect(prompt).toContain("PATTERNS");
77
- expect(prompt).toContain("max 15");
78
- expect(prompt).not.toContain("failing assertion");
79
- });
80
-
81
- it("uses bug-fix selection rules for fix issues", () => {
82
- const issue = { iid: 10, title: "fix: broken auth", labels: [] };
83
- const prompt = buildIdentifyFilesPrompt(issue, repoTrees);
84
-
85
- expect(prompt).toContain("bug fix");
86
- expect(prompt).toContain("failing assertion");
87
- expect(prompt).toContain("max 10");
88
- expect(prompt).not.toContain("PATTERNS");
89
- });
90
-
91
- it("says implementing for features, fixing for bugs", () => {
92
- const feat = buildIdentifyFilesPrompt(
93
- { iid: 1, title: "feat: new page", labels: [] }, repoTrees,
94
- );
95
- const fix = buildIdentifyFilesPrompt(
96
- { iid: 2, title: "fix: crash", labels: [] }, repoTrees,
97
- );
98
-
99
- expect(feat).toContain("implementing");
100
- expect(fix).toContain("fixing");
101
- });
102
-
103
- it("includes multi-repo guidance when multiple repos present", () => {
104
- const multi = [
105
- { repo: "group/web-app", files: ["src/App.tsx"] },
106
- { repo: "group/api", files: ["internal/handler.go"] },
107
- ];
108
- const prompt = buildIdentifyFilesPrompt(
109
- { iid: 1, title: "fix: thing", labels: [] }, multi,
110
- );
111
- expect(prompt).toContain("Multi-repo guidance");
112
- expect(prompt).toContain("group/web-app, group/api");
113
- });
114
- });
115
-
116
- // ─── buildProposeFixPrompt ───────────────────────────────────────────────────
117
-
118
- describe("buildProposeFixPrompt", () => {
119
- const fileContents = [
120
- { repo: "group/web-app", path: "src/App.tsx", content: "export default function App() {}" },
121
- ];
122
-
123
- it("uses feat: prefix for feature issues", () => {
124
- const issue = { iid: 25, title: "feat: add mixes", labels: [] };
125
- const prompt = buildProposeFixPrompt(issue, fileContents);
126
-
127
- expect(prompt).toContain('"feat:');
128
- expect(prompt).toContain("Implement this feature");
129
- expect(prompt).not.toContain("Root-cause discipline");
130
- });
131
-
132
- it("uses fix: prefix for bug issues", () => {
133
- const issue = { iid: 10, title: "fix: broken auth", labels: [] };
134
- const prompt = buildProposeFixPrompt(issue, fileContents);
135
-
136
- expect(prompt).toContain('"fix:');
137
- expect(prompt).toContain("Propose a targeted fix");
138
- expect(prompt).toContain("Root-cause discipline");
139
- });
140
-
141
- it("includes feature discipline for feature issues", () => {
142
- const issue = { iid: 25, title: "feat: mixes", labels: [] };
143
- const prompt = buildProposeFixPrompt(issue, fileContents);
144
-
145
- expect(prompt).toContain("Feature implementation rules");
146
- expect(prompt).toContain("Follow the issue specification");
147
- expect(prompt).toContain("Do NOT invent changes");
148
- });
149
-
150
- it("injects promptRules when provided", () => {
151
- const issue = { iid: 1, title: "fix: thing", labels: [] };
152
- const prompt = buildProposeFixPrompt(issue, fileContents, {
153
- promptRules: "Never restructure the NavBar",
154
- });
155
-
156
- expect(prompt).toContain("Custom rules");
157
- expect(prompt).toContain("Never restructure the NavBar");
158
- });
159
-
160
- it("omits promptRules block when empty", () => {
161
- const issue = { iid: 1, title: "fix: thing", labels: [] };
162
- const prompt = buildProposeFixPrompt(issue, fileContents, { promptRules: "" });
163
-
164
- expect(prompt).not.toContain("Custom rules");
165
- });
166
-
167
- it("includes CI context when provided", () => {
168
- const issue = { iid: 1, title: "fix: thing", labels: [] };
169
- const prompt = buildProposeFixPrompt(issue, fileContents, {
170
- ciContext: "Error: Cannot find module './missing'",
171
- });
172
-
173
- expect(prompt).toContain("CI pipeline failure logs");
174
- expect(prompt).toContain("Cannot find module");
175
- });
176
- });
177
-
178
- // ─── buildRefineOrAskPrompt ─────────────────────────────────────────────────
179
-
180
- describe("buildRefineOrAskPrompt", () => {
181
- const issue = { iid: 1, title: "fix: thing", labels: [] };
182
- const fileContents = [
183
- { repo: "r", path: "src/a.ts", content: "const x = 1;" },
184
- ];
185
-
186
- it("injects promptRules", () => {
187
- const prompt = buildRefineOrAskPrompt(issue, fileContents, {
188
- feedback: "fix the types",
189
- promptRules: "Always use strict TypeScript",
190
- });
191
-
192
- expect(prompt).toContain("Custom rules");
193
- expect(prompt).toContain("Always use strict TypeScript");
194
- });
195
-
196
- it("omits promptRules when empty", () => {
197
- const prompt = buildRefineOrAskPrompt(issue, fileContents, {
198
- feedback: "fix it",
199
- promptRules: "",
200
- });
201
- expect(prompt).not.toContain("Custom rules");
202
- });
203
-
204
- it("includes repo access block for tools mode", () => {
205
- const prompt = buildRefineOrAskPrompt(issue, fileContents, {
206
- feedback: "check types",
207
- repoAccessMode: "tools",
208
- });
209
- expect(prompt).toContain("current working directory is a checkout");
210
- });
211
-
212
- it("includes repo access block for tree mode", () => {
213
- const prompt = buildRefineOrAskPrompt(issue, fileContents, {
214
- feedback: "check types",
215
- repoAccessMode: "tree",
216
- });
217
- expect(prompt).toContain("full file tree above");
218
- });
219
- });
@@ -1,409 +0,0 @@
1
- /**
2
- * server/forge/core/providers/azuredevops.js
3
- *
4
- * Azure DevOps REST API implementation of ScmProvider.
5
- * Maps Work Items → issues, Pull Requests → MRs, Pipelines → CI.
6
- *
7
- * Required env vars:
8
- * AZURE_DEVOPS_TOKEN — Personal Access Token (Base64 or raw PAT)
9
- * AZURE_DEVOPS_ORG — organization name, e.g. "ipul-labs"
10
- * AZURE_DEVOPS_PROJECT — team project name, e.g. "web-app"
11
- * AZURE_DEVOPS_REPO — git repo name (often same as project)
12
- *
13
- * Optional:
14
- * AZURE_DEVOPS_BASE_BRANCH — default target branch (default: "main")
15
- */
16
-
17
- import { getConnection } from "../configStore.js";
18
- import { ScmProvider } from "./base.js";
19
-
20
- export class AzureDevOpsProvider extends ScmProvider {
21
- /**
22
- * @param {string} [repo] "org/project/repo" — overrides env vars
23
- */
24
- constructor(repo) {
25
- super();
26
- if (repo && repo.includes("/")) {
27
- const parts = repo.split("/");
28
- this._org = parts[0];
29
- this._project = parts[1];
30
- this._repo = parts[2] ?? parts[1];
31
- } else {
32
- this._org = process.env.AZURE_DEVOPS_ORG ?? "";
33
- this._project = process.env.AZURE_DEVOPS_PROJECT ?? "";
34
- this._repo = process.env.AZURE_DEVOPS_REPO ?? this._project;
35
- }
36
- this._defaultBranch = process.env.AZURE_DEVOPS_BASE_BRANCH ?? "main";
37
- this._vsBase = `https://dev.azure.com/${this._org}`;
38
- this._vssmBase = `https://vsrm.dev.azure.com/${this._org}`;
39
- }
40
-
41
- get name() { return "azuredevops"; }
42
- get supportsRebase() { return false; }
43
-
44
- // ─── Internal helpers ──────────────────────────────────────────────────────
45
-
46
- _headers() {
47
- const token = getConnection("azuredevops").token;
48
- // ADO accepts Basic auth with a PAT as the password (username can be anything)
49
- const encoded = Buffer.from(`:${token}`).toString("base64");
50
- return {
51
- Authorization: `Basic ${encoded}`,
52
- "Content-Type": "application/json",
53
- };
54
- }
55
-
56
- _gitBase() {
57
- return `${this._vsBase}/${encodeURIComponent(this._project)}/_apis/git/repositories/${encodeURIComponent(this._repo)}`;
58
- }
59
-
60
- _witBase() {
61
- return `${this._vsBase}/${encodeURIComponent(this._project)}/_apis/wit`;
62
- }
63
-
64
- _pipelineBase() {
65
- return `${this._vsBase}/${encodeURIComponent(this._project)}/_apis/pipelines`;
66
- }
67
-
68
- _buildBase() {
69
- return `${this._vsBase}/${encodeURIComponent(this._project)}/_apis/build`;
70
- }
71
-
72
- async _req(url, options = {}) {
73
- const sep = url.includes("?") ? "&" : "?";
74
- const versioned = url.includes("api-version") ? url : `${url}${sep}api-version=7.1`;
75
- const res = await fetch(versioned, {
76
- ...options,
77
- headers: { ...this._headers(), ...options.headers },
78
- });
79
- if (res.status === 204) return null;
80
- if (!res.ok) {
81
- const text = await res.text().catch(() => "");
82
- throw new Error(`AzureDevOps ${res.status} on ${url}: ${text}`);
83
- }
84
- return res.json();
85
- }
86
-
87
- // ─── Normalizers ──────────────────────────────────────────────────────────
88
-
89
- _issue(raw) {
90
- const fields = raw.fields ?? {};
91
- return {
92
- iid: raw.id,
93
- title: fields["System.Title"] ?? "",
94
- description: fields["System.Description"] ?? null,
95
- labels: (fields["System.Tags"] ?? "").split(";").map((t) => t.trim()).filter(Boolean),
96
- author: { name: fields["System.CreatedBy"]?.displayName ?? "" },
97
- created_at: fields["System.CreatedDate"] ?? "",
98
- web_url: raw._links?.html?.href ?? "",
99
- };
100
- }
101
-
102
- _mr(raw) {
103
- const status = raw.status ?? "";
104
- return {
105
- iid: raw.pullRequestId,
106
- title: raw.title,
107
- web_url: `${this._vsBase}/${encodeURIComponent(this._project)}/_git/${encodeURIComponent(this._repo)}/pullrequest/${raw.pullRequestId}`,
108
- source_branch: (raw.sourceRefName ?? "").replace("refs/heads/", ""),
109
- target_branch: (raw.targetRefName ?? "").replace("refs/heads/", ""),
110
- state: status === "completed" ? "merged" : status === "abandoned" ? "closed" : "opened",
111
- has_conflicts: raw.mergeStatus === "conflicts",
112
- rebase_in_progress: false,
113
- merge_status: raw.mergeStatus ?? "",
114
- detailed_merge_status: null,
115
- draft: raw.isDraft ?? false,
116
- head_pipeline: null,
117
- author: { name: raw.createdBy?.displayName ?? "" },
118
- created_at: raw.creationDate ?? "",
119
- merged_at: status === "completed" ? (raw.closedDate ?? null) : null,
120
- };
121
- }
122
-
123
- _pipeline(raw) {
124
- return {
125
- id: raw.id,
126
- status: this._buildStatus(raw),
127
- web_url: raw._links?.web?.href ?? "",
128
- created_at: raw.queueTime ?? raw.startTime ?? "",
129
- };
130
- }
131
-
132
- _buildStatus(raw) {
133
- const result = raw.result;
134
- if (result === "succeeded") return "success";
135
- if (result === "failed" || result === "partiallySucceeded") return "failed";
136
- if (result === "canceled") return "cancelled";
137
- const status = raw.status;
138
- if (status === "inProgress") return "running";
139
- if (status === "notStarted") return "pending";
140
- return status ?? "unknown";
141
- }
142
-
143
- _job(raw) {
144
- return {
145
- id: raw.id,
146
- name: raw.name,
147
- stage: raw.stageName ?? "",
148
- status: this._taskStatus(raw),
149
- web_url: raw._links?.web?.href ?? "",
150
- duration: raw.finishTime && raw.startTime
151
- ? Math.round((new Date(raw.finishTime) - new Date(raw.startTime)) / 1000)
152
- : null,
153
- };
154
- }
155
-
156
- _taskStatus(raw) {
157
- const result = raw.result;
158
- if (result === "succeeded") return "success";
159
- if (result === "failed") return "failed";
160
- if (result === "skipped") return "skipped";
161
- const state = raw.state;
162
- if (state === "inProgress") return "running";
163
- if (state === "pending") return "pending";
164
- return state ?? "unknown";
165
- }
166
-
167
- _diff(raw) {
168
- const changeType = raw.changeType ?? "";
169
- return {
170
- old_path: raw.item?.path ?? "",
171
- new_path: raw.item?.path ?? "",
172
- diff: "", // ADO diff content requires a separate API call
173
- new_file: changeType === "add",
174
- renamed_file: changeType === "rename",
175
- deleted_file: changeType === "delete",
176
- };
177
- }
178
-
179
- // ─── Issues (Work Items) ──────────────────────────────────────────────────
180
-
181
- async listOpenIssues() {
182
- // WIQL query for open issues in the project
183
- const wiql = {
184
- query: `SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = '${this._project}' AND [System.State] <> 'Done' AND [System.State] <> 'Closed' AND [System.WorkItemType] IN ('Bug','Issue','Task','User Story') ORDER BY [System.ChangedDate] DESC`,
185
- };
186
- const result = await this._req(
187
- `${this._witBase()}/wiql?$top=25`,
188
- { method: "POST", body: JSON.stringify(wiql) },
189
- );
190
- const ids = (result?.workItems ?? []).map((w) => w.id);
191
- if (ids.length === 0) return [];
192
-
193
- const batch = await this._req(
194
- `${this._witBase()}/workitems?ids=${ids.join(",")}&$expand=fields`,
195
- );
196
- return (batch?.value ?? []).map((wi) => this._issue(wi));
197
- }
198
-
199
- async getIssue(iid) {
200
- const raw = await this._req(`${this._witBase()}/workitems/${iid}?$expand=fields`);
201
- return this._issue(raw);
202
- }
203
-
204
- async createIssue(title, description = "") {
205
- const patch = [
206
- { op: "add", path: "/fields/System.Title", value: title },
207
- { op: "add", path: "/fields/System.Description", value: description },
208
- ];
209
- const raw = await this._req(
210
- `${this._witBase()}/workitems/$Issue`,
211
- {
212
- method: "POST",
213
- headers: { "Content-Type": "application/json-patch+json" },
214
- body: JSON.stringify(patch),
215
- },
216
- );
217
- return this._issue(raw);
218
- }
219
-
220
- async updateIssue(iid, { title, description }) {
221
- const patch = [];
222
- if (title !== undefined) patch.push({ op: "replace", path: "/fields/System.Title", value: title });
223
- if (description !== undefined) patch.push({ op: "replace", path: "/fields/System.Description", value: description });
224
- const raw = await this._req(
225
- `${this._witBase()}/workitems/${iid}`,
226
- {
227
- method: "PATCH",
228
- headers: { "Content-Type": "application/json-patch+json" },
229
- body: JSON.stringify(patch),
230
- },
231
- );
232
- return this._issue(raw);
233
- }
234
-
235
- async closeIssue(iid) {
236
- const patch = [{ op: "replace", path: "/fields/System.State", value: "Closed" }];
237
- const raw = await this._req(
238
- `${this._witBase()}/workitems/${iid}`,
239
- {
240
- method: "PATCH",
241
- headers: { "Content-Type": "application/json-patch+json" },
242
- body: JSON.stringify(patch),
243
- },
244
- );
245
- return this._issue(raw);
246
- }
247
-
248
- // ─── Repository ───────────────────────────────────────────────────────────
249
-
250
- async getFileTree(srcPath = "", ref = this._defaultBranch) {
251
- const scopeParam = srcPath ? `&$scopePath=/${srcPath}` : "";
252
- const data = await this._req(
253
- `${this._gitBase()}/trees/${ref}?recursive=true${scopeParam}`,
254
- );
255
- return (data?.treeEntries ?? [])
256
- .filter((item) => item.gitObjectType === "blob")
257
- .filter((item) => /\.(ts|tsx|js|jsx|json|css|yaml|yml|md|go|py|rs|java|rb|sh|toml)$/.test(item.relativePath))
258
- .map((item) => item.relativePath);
259
- }
260
-
261
- async getFileContent(filePath, ref = this._defaultBranch) {
262
- const encoded = encodeURIComponent(filePath);
263
- const url = `${this._gitBase()}/items?path=${encoded}&versionDescriptor.version=${ref}&versionDescriptor.versionType=branch&$format=text&api-version=7.1`;
264
- const res = await fetch(url, { headers: this._headers() });
265
- if (!res.ok) {
266
- const text = await res.text().catch(() => "");
267
- throw new Error(`AzureDevOps ${res.status} on items/${filePath}: ${text}`);
268
- }
269
- return res.text();
270
- }
271
-
272
- // ─── Mutations ────────────────────────────────────────────────────────────
273
-
274
- async createBranch(branchName, ref = this._defaultBranch) {
275
- // Get current SHA for ref
276
- const refs = await this._req(`${this._gitBase()}/refs?filter=heads/${ref}`);
277
- const sha = refs?.value?.[0]?.objectId;
278
- if (!sha) throw new Error(`AzureDevOps: could not resolve ref ${ref}`);
279
-
280
- return this._req(`${this._gitBase()}/refs`, {
281
- method: "POST",
282
- body: JSON.stringify([
283
- {
284
- name: `refs/heads/${branchName}`,
285
- newObjectId: sha,
286
- oldObjectId: "0000000000000000000000000000000000000000",
287
- },
288
- ]),
289
- });
290
- }
291
-
292
- async commitFiles(branch, message, actions) {
293
- const changes = actions.map((action) => ({
294
- changeType: action.action === "create" ? "add" : action.action === "delete" ? "delete" : "edit",
295
- item: { path: `/${action.file_path}` },
296
- newContent: action.action !== "delete"
297
- ? { content: action.content, contentType: "rawtext" }
298
- : undefined,
299
- }));
300
-
301
- // Get current commit SHA
302
- const refs = await this._req(`${this._gitBase()}/refs?filter=heads/${branch}`);
303
- const oldSha = refs?.value?.[0]?.objectId ?? "0000000000000000000000000000000000000000";
304
-
305
- return this._req(`${this._gitBase()}/pushes`, {
306
- method: "POST",
307
- body: JSON.stringify({
308
- refUpdates: [{ name: `refs/heads/${branch}`, oldObjectId: oldSha }],
309
- commits: [{ comment: message, changes }],
310
- }),
311
- });
312
- }
313
-
314
- async createMR({ sourceBranch, targetBranch = this._defaultBranch, title, description = "" }) {
315
- const raw = await this._req(`${this._gitBase()}/pullrequests`, {
316
- method: "POST",
317
- body: JSON.stringify({
318
- sourceRefName: `refs/heads/${sourceBranch}`,
319
- targetRefName: `refs/heads/${targetBranch}`,
320
- title,
321
- description,
322
- }),
323
- });
324
- return this._mr(raw);
325
- }
326
-
327
- // ─── PR queries ───────────────────────────────────────────────────────────
328
-
329
- async listAllForgeMRs(state = "open") {
330
- const adoStatus =
331
- state === "merged" ? "completed"
332
- : state === "closed" ? "abandoned"
333
- : "active";
334
- const data = await this._req(
335
- `${this._gitBase()}/pullrequests?searchCriteria.status=${adoStatus}&$top=50`,
336
- );
337
- return (data?.value ?? [])
338
- .filter((pr) => pr.sourceRefName?.includes("refs/heads/forge/"))
339
- .map((pr) => this._mr(pr));
340
- }
341
-
342
- async listForgeMRsForIssue(issueIid) {
343
- const data = await this._req(
344
- `${this._gitBase()}/pullrequests?searchCriteria.status=active&$top=50`,
345
- );
346
- return (data?.value ?? [])
347
- .filter((pr) => pr.sourceRefName?.includes(`refs/heads/forge/issue-${issueIid}-`))
348
- .map((pr) => this._mr(pr));
349
- }
350
-
351
- async listMergedForgeMRsForIssue(issueIid) {
352
- const data = await this._req(
353
- `${this._gitBase()}/pullrequests?searchCriteria.status=completed&$top=50`,
354
- );
355
- return (data?.value ?? [])
356
- .filter((pr) => pr.sourceRefName?.includes(`refs/heads/forge/issue-${issueIid}-`))
357
- .map((pr) => this._mr(pr));
358
- }
359
-
360
- async getMR(mrIid) {
361
- const raw = await this._req(`${this._gitBase()}/pullrequests/${mrIid}`);
362
- return this._mr(raw);
363
- }
364
-
365
- async getMRDiffs(mrIid) {
366
- const data = await this._req(
367
- `${this._gitBase()}/pullrequests/${mrIid}/iterations/1/changes`,
368
- );
369
- return (data?.changeEntries ?? []).map((c) => this._diff(c));
370
- }
371
-
372
- // ─── CI / Pipelines ───────────────────────────────────────────────────────
373
-
374
- async getMRPipelines(mrIid) {
375
- // Get PR statuses which link to builds
376
- const data = await this._req(
377
- `${this._gitBase()}/pullrequests/${mrIid}/statuses`,
378
- );
379
- const buildIds = (data?.value ?? [])
380
- .map((s) => s.targetUrl?.match(/buildId=(\d+)/)?.[1])
381
- .filter(Boolean)
382
- .map(Number);
383
-
384
- if (buildIds.length === 0) return [];
385
- return Promise.all(buildIds.map((id) => this.getPipeline(id)));
386
- }
387
-
388
- async getPipeline(pipelineId) {
389
- const raw = await this._req(`${this._buildBase()}/builds/${pipelineId}`);
390
- return this._pipeline(raw);
391
- }
392
-
393
- async getPipelineJobs(pipelineId) {
394
- const data = await this._req(`${this._buildBase()}/builds/${pipelineId}/timeline`);
395
- return (data?.records ?? [])
396
- .filter((r) => r.type === "Task")
397
- .map((r) => this._job(r));
398
- }
399
-
400
- async getJobTrace(jobId) {
401
- const url = `${this._buildBase()}/builds/${jobId}/logs/1?api-version=7.1`;
402
- const res = await fetch(url, { headers: this._headers() });
403
- if (!res.ok) {
404
- const text = await res.text().catch(() => "");
405
- throw new Error(`AzureDevOps ${res.status} on builds/${jobId}/logs: ${text}`);
406
- }
407
- return res.text();
408
- }
409
- }