@visulima/vis 1.0.0-alpha.11 → 1.0.0-alpha.13

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 (116) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/LICENSE.md +559 -186
  3. package/README.md +18 -0
  4. package/dist/bin.js +1 -9
  5. package/dist/config/index.d.ts +477 -556
  6. package/dist/config/index.js +1 -2
  7. package/dist/generate/index.js +1 -3
  8. package/dist/packem_chunks/applyDefaults.js +2 -336
  9. package/dist/packem_chunks/bin.js +234 -9552
  10. package/dist/packem_chunks/doctor-probe.js +2 -112
  11. package/dist/packem_chunks/fix.js +11 -234
  12. package/dist/packem_chunks/handler.js +1 -99
  13. package/dist/packem_chunks/handler10.js +2 -53
  14. package/dist/packem_chunks/handler11.js +1 -32
  15. package/dist/packem_chunks/handler12.js +5 -100
  16. package/dist/packem_chunks/handler13.js +1 -25
  17. package/dist/packem_chunks/handler14.js +18 -916
  18. package/dist/packem_chunks/handler15.js +15 -201
  19. package/dist/packem_chunks/handler16.js +1 -124
  20. package/dist/packem_chunks/handler17.js +1 -13
  21. package/dist/packem_chunks/handler18.js +1 -106
  22. package/dist/packem_chunks/handler19.js +1 -19
  23. package/dist/packem_chunks/handler2.js +2 -75
  24. package/dist/packem_chunks/handler20.js +5 -29
  25. package/dist/packem_chunks/handler21.js +1 -222
  26. package/dist/packem_chunks/handler22.js +1 -237
  27. package/dist/packem_chunks/handler23.js +5 -101
  28. package/dist/packem_chunks/handler24.js +1 -110
  29. package/dist/packem_chunks/handler25.js +3 -402
  30. package/dist/packem_chunks/handler26.js +1 -13
  31. package/dist/packem_chunks/handler27.js +1 -63
  32. package/dist/packem_chunks/handler28.js +7 -34
  33. package/dist/packem_chunks/handler29.js +21 -456
  34. package/dist/packem_chunks/handler3.js +4 -95
  35. package/dist/packem_chunks/handler30.js +3 -170
  36. package/dist/packem_chunks/handler31.js +1 -530
  37. package/dist/packem_chunks/handler32.js +2 -214
  38. package/dist/packem_chunks/handler33.js +25 -119
  39. package/dist/packem_chunks/handler34.js +2 -630
  40. package/dist/packem_chunks/handler35.js +3 -283
  41. package/dist/packem_chunks/handler36.js +22 -542
  42. package/dist/packem_chunks/handler37.js +410 -744
  43. package/dist/packem_chunks/handler38.js +22 -989
  44. package/dist/packem_chunks/handler39.js +22 -574
  45. package/dist/packem_chunks/handler4.js +2 -90
  46. package/dist/packem_chunks/handler40.js +22 -1685
  47. package/dist/packem_chunks/handler41.js +6 -1088
  48. package/dist/packem_chunks/handler42.js +5 -797
  49. package/dist/packem_chunks/handler43.js +10 -2658
  50. package/dist/packem_chunks/handler44.js +51 -3784
  51. package/dist/packem_chunks/handler45.js +25 -2574
  52. package/dist/packem_chunks/handler46.js +3 -3769
  53. package/dist/packem_chunks/handler47.js +21 -1485
  54. package/dist/packem_chunks/handler48.js +42 -0
  55. package/dist/packem_chunks/handler5.js +8 -174
  56. package/dist/packem_chunks/handler6.js +1 -95
  57. package/dist/packem_chunks/handler7.js +1 -115
  58. package/dist/packem_chunks/handler8.js +1 -12
  59. package/dist/packem_chunks/handler9.js +1 -29
  60. package/dist/packem_chunks/heal-accept.js +10 -522
  61. package/dist/packem_chunks/heal.js +14 -673
  62. package/dist/packem_chunks/index.js +7 -873
  63. package/dist/packem_chunks/loader.js +1 -23
  64. package/dist/packem_chunks/tar.js +3 -0
  65. package/dist/packem_shared/ai-analysis-hm8d2W7z.js +67 -0
  66. package/dist/packem_shared/ai-cache-DoiF80AR.js +1 -0
  67. package/dist/packem_shared/ai-fix-nn4zOE95.js +43 -0
  68. package/dist/packem_shared/cache-directory-CwHlJhgx.js +1 -0
  69. package/dist/packem_shared/dependency-scan-COr5n63B.js +2 -0
  70. package/dist/packem_shared/docker-D6OGr5_S.js +2 -0
  71. package/dist/packem_shared/failure-log-iUVLf6ts.js +2 -0
  72. package/dist/packem_shared/flakiness-D9wf0t56.js +1 -0
  73. package/dist/packem_shared/giget-CcEy_Elm.js +2 -0
  74. package/dist/packem_shared/index-DH-5hsrC.js +1 -0
  75. package/dist/packem_shared/otel-DxDUPJJH.js +6 -0
  76. package/dist/packem_shared/otelPlugin-CQq6poq8.js +1 -0
  77. package/dist/packem_shared/registry-CkubDdiY.js +2 -0
  78. package/dist/packem_shared/run-summary-utils-BfBvjzhY.js +1 -0
  79. package/dist/packem_shared/runtime-check-BXZ43CBW.js +1 -0
  80. package/dist/packem_shared/selectors-BylODRiM.js +3 -0
  81. package/dist/packem_shared/symbols-CQmER5MT.js +1 -0
  82. package/dist/packem_shared/toolchain-BgBOUHII.js +5 -0
  83. package/dist/packem_shared/typosquats-CcZl99B1.js +1 -0
  84. package/dist/packem_shared/use-measured-height-DjYgUOKk.js +1 -0
  85. package/dist/packem_shared/utils-DrNg0XTR.js +1 -0
  86. package/dist/packem_shared/verify-Baj5mFJ7.js +1 -0
  87. package/dist/packem_shared/vis-update-app-D1jl0UZZ.js +1 -0
  88. package/dist/packem_shared/xxh3-DrAUNq4n.js +1 -0
  89. package/index.js +556 -727
  90. package/package.json +19 -29
  91. package/schemas/project.schema.json +739 -297
  92. package/schemas/vis-config.schema.json +3365 -384
  93. package/templates/buildkite-ci/template.yml +20 -20
  94. package/dist/packem_shared/VisUpdateApp-D-Yz_wvg.js +0 -1316
  95. package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +0 -5
  96. package/dist/packem_shared/ai-analysis-CHeB1joD.js +0 -367
  97. package/dist/packem_shared/ai-cache-Be_jexe4.js +0 -142
  98. package/dist/packem_shared/ai-fix-B9iQVcD2.js +0 -379
  99. package/dist/packem_shared/cache-directory-2qvs4goY.js +0 -98
  100. package/dist/packem_shared/catalog-BJTtyi-O.js +0 -1371
  101. package/dist/packem_shared/dependency-scan-A0KSklpG.js +0 -188
  102. package/dist/packem_shared/docker-2iZzc280.js +0 -181
  103. package/dist/packem_shared/failure-log-Cz3Z4SKL.js +0 -100
  104. package/dist/packem_shared/flakiness-goTxXuCX.js +0 -180
  105. package/dist/packem_shared/otel-DCvqCTz_.js +0 -158
  106. package/dist/packem_shared/otelPlugin-DFaLDvJf.js +0 -3
  107. package/dist/packem_shared/registry-CbqXI0rc.js +0 -272
  108. package/dist/packem_shared/run-summary-utils-PVMl4aIh.js +0 -130
  109. package/dist/packem_shared/runtime-check-Cobi3p6l.js +0 -127
  110. package/dist/packem_shared/selectors-SM69TfqC.js +0 -194
  111. package/dist/packem_shared/symbols-Ta7g2nU-.js +0 -14
  112. package/dist/packem_shared/toolchain-BdZd9eBi.js +0 -975
  113. package/dist/packem_shared/typosquats-C-bCh3PX.js +0 -1210
  114. package/dist/packem_shared/use-measured-height-CNP0vT4M.js +0 -20
  115. package/dist/packem_shared/utils-CthVdBPS.js +0 -40
  116. package/dist/packem_shared/xxh3-Ck8mXNg1.js +0 -239
@@ -1,673 +1,14 @@
1
- import { createRequire as __cjs_createRequire } from "node:module";
2
-
3
- const __cjs_require = __cjs_createRequire(import.meta.url);
4
-
5
- const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
6
-
7
- const __cjs_getBuiltinModule = (module) => {
8
- // Check if we're in Node.js and version supports getBuiltinModule
9
- if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
10
- const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
11
- // Node.js 20.16.0+ and 22.3.0+
12
- if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
13
- return __cjs_getProcess.getBuiltinModule(module);
14
- }
15
- }
16
- // Fallback to createRequire
17
- return __cjs_require(module);
18
- };
19
-
20
- const {
21
- spawn
22
- } = __cjs_getBuiltinModule("node:child_process");
23
- import { yellow, bold, dim, red, green } from '@visulima/colorize';
24
- import { relative } from '@visulima/path';
25
- import { readLastRunSummary } from '@visulima/task-runner';
26
- import { a as aggregateFailureContext, r as runFixAnalysis, b as applyFixProposal, c as resolvePatchPath } from '../packem_shared/ai-fix-B9iQVcD2.js';
27
- const {
28
- readFile
29
- } = __cjs_getBuiltinModule("node:fs/promises");
30
- import { p as pail } from './bin.js';
31
- import { r as readRunSummaryById } from '../packem_shared/run-summary-utils-PVMl4aIh.js';
32
-
33
- const parsePrNumberFromGithubRef = (ref) => {
34
- if (!ref) {
35
- return void 0;
36
- }
37
- const match = /^refs\/pull\/(\d+)\//.exec(ref);
38
- return match ? Number.parseInt(match[1], 10) : void 0;
39
- };
40
- const readPrNumberFromGithubEvent = async (eventPath) => {
41
- if (!eventPath) {
42
- return { prNumber: void 0, sha: void 0 };
43
- }
44
- try {
45
- const raw = await readFile(eventPath, "utf8");
46
- const payload = JSON.parse(raw);
47
- const prNumber = payload.pull_request?.number ?? payload.issue?.number ?? payload.number;
48
- const sha = payload.pull_request?.head?.sha;
49
- return { prNumber, sha };
50
- } catch {
51
- return { prNumber: void 0, sha: void 0 };
52
- }
53
- };
54
- const detectGithubActions = async (env) => {
55
- const refPrNumber = parsePrNumberFromGithubRef(env.GITHUB_REF);
56
- const { prNumber: payloadPrNumber, sha: payloadSha } = refPrNumber === void 0 ? await readPrNumberFromGithubEvent(env.GITHUB_EVENT_PATH) : { prNumber: refPrNumber, sha: void 0 };
57
- return {
58
- apiBaseUrl: void 0,
59
- buildId: void 0,
60
- buildNumber: void 0,
61
- prNumber: refPrNumber ?? payloadPrNumber,
62
- provider: "github-actions",
63
- repo: env.GITHUB_REPOSITORY,
64
- // Prefer the PR head SHA from the event payload over GITHUB_SHA — for
65
- // pull_request events GITHUB_SHA is the synthetic merge commit, which
66
- // is not what reviewers see in the PR diff.
67
- sha: payloadSha ?? env.GITHUB_SHA,
68
- token: env.GITHUB_TOKEN
69
- };
70
- };
71
- const detectGitlabCi = (env) => {
72
- const mrIid = env.CI_MERGE_REQUEST_IID;
73
- const prNumber = mrIid !== void 0 && mrIid !== "" ? Number.parseInt(mrIid, 10) : void 0;
74
- const apiBaseUrl = env.CI_API_V4_URL;
75
- const token = env.GITLAB_TOKEN ?? env.CI_TOKEN;
76
- return {
77
- apiBaseUrl,
78
- buildId: void 0,
79
- buildNumber: void 0,
80
- prNumber: Number.isFinite(prNumber) ? prNumber : void 0,
81
- provider: "gitlab-ci",
82
- // GitLab accepts URL-encoded namespace/project or numeric ID.
83
- // CI_PROJECT_ID is more reliable when the project has been
84
- // renamed; CI_PROJECT_PATH is more readable. Prefer the ID.
85
- repo: env.CI_PROJECT_ID ?? env.CI_PROJECT_PATH,
86
- sha: env.CI_COMMIT_SHA,
87
- token
88
- };
89
- };
90
- const detectBuildkite = (env) => {
91
- const rawPr = env.BUILDKITE_PULL_REQUEST;
92
- const prNumber = rawPr !== void 0 && rawPr !== "" && rawPr !== "false" ? Number.parseInt(rawPr, 10) : void 0;
93
- const rawBuildNumber = env.BUILDKITE_BUILD_NUMBER;
94
- const buildNumber = rawBuildNumber !== void 0 && rawBuildNumber !== "" ? Number.parseInt(rawBuildNumber, 10) : void 0;
95
- const org = env.BUILDKITE_ORGANIZATION_SLUG;
96
- const pipeline = env.BUILDKITE_PIPELINE_SLUG;
97
- const repo = org !== void 0 && org !== "" && pipeline !== void 0 && pipeline !== "" ? `${org}/${pipeline}` : void 0;
98
- const apiBaseUrl = (env.BUILDKITE_API_BASE_URL ?? "https://api.buildkite.com").replace(/\/+$/, "");
99
- return {
100
- apiBaseUrl,
101
- buildId: env.BUILDKITE_BUILD_ID,
102
- buildNumber: Number.isFinite(buildNumber) ? buildNumber : void 0,
103
- prNumber: Number.isFinite(prNumber) ? prNumber : void 0,
104
- provider: "buildkite",
105
- repo,
106
- sha: env.BUILDKITE_COMMIT,
107
- // Only the REST fallback needs an explicit token; the
108
- // `buildkite-agent annotate` CLI uses the auto-injected
109
- // BUILDKITE_AGENT_ACCESS_TOKEN, which we don't surface here
110
- // because callers don't speak that protocol directly.
111
- token: env.BUILDKITE_API_TOKEN
112
- };
113
- };
114
- const detectCiContext = async (env = process.env) => {
115
- if (env.GITHUB_ACTIONS === "true") {
116
- return await detectGithubActions(env);
117
- }
118
- if (env.GITLAB_CI === "true") {
119
- return detectGitlabCi(env);
120
- }
121
- if (env.BUILDKITE === "true") {
122
- return detectBuildkite(env);
123
- }
124
- return {
125
- apiBaseUrl: void 0,
126
- buildId: void 0,
127
- buildNumber: void 0,
128
- prNumber: void 0,
129
- provider: "unknown",
130
- repo: void 0,
131
- sha: void 0,
132
- token: void 0
133
- };
134
- };
135
-
136
- const runGhComment = (ghBin, prNumber, body, repo) => new Promise((resolve) => {
137
- const args = ["pr", "comment", String(prNumber), "--body-file", "-"];
138
- if (repo) {
139
- args.push("--repo", repo);
140
- }
141
- const child = spawn(ghBin, args, { stdio: ["pipe", "ignore", "pipe"] });
142
- let stderr = "";
143
- child.stderr?.setEncoding("utf8");
144
- child.stderr?.on("data", (chunk) => {
145
- stderr += chunk;
146
- });
147
- child.once("error", () => {
148
- resolve({ exitCode: 127, stderr });
149
- });
150
- child.once("close", (code) => {
151
- resolve({ exitCode: code ?? -1, stderr });
152
- });
153
- child.stdin?.on("error", () => {
154
- });
155
- child.stdin?.end(body);
156
- });
157
- const postViaGithubRest = async (fetchImpl, repo, prNumber, body, token) => {
158
- const url = `https://api.github.com/repos/${repo}/issues/${String(prNumber)}/comments`;
159
- try {
160
- const response = await fetchImpl(url, {
161
- body: JSON.stringify({ body }),
162
- headers: {
163
- Accept: "application/vnd.github+json",
164
- Authorization: `Bearer ${token}`,
165
- "Content-Type": "application/json",
166
- "X-GitHub-Api-Version": "2022-11-28"
167
- },
168
- method: "POST"
169
- });
170
- if (!response.ok) {
171
- const text = await response.text().catch(() => "");
172
- return { error: `GitHub REST returned ${String(response.status)}: ${text.slice(0, 500)}`, ok: false };
173
- }
174
- return { ok: true };
175
- } catch (error) {
176
- return { error: error instanceof Error ? error.message : String(error), ok: false };
177
- }
178
- };
179
- const postViaGitlabRest = async (fetchImpl, apiBaseUrl, repo, mrIid, body, token) => {
180
- const projectId = encodeURIComponent(repo);
181
- const url = `${apiBaseUrl.replace(/\/+$/, "")}/projects/${projectId}/merge_requests/${String(mrIid)}/notes`;
182
- try {
183
- const response = await fetchImpl(url, {
184
- body: JSON.stringify({ body }),
185
- headers: {
186
- "Content-Type": "application/json",
187
- // GitLab's `PRIVATE-TOKEN` header is its native auth shape;
188
- // `Authorization: Bearer` also works for OAuth tokens but
189
- // we route to PRIVATE-TOKEN to support both PAT and project
190
- // tokens uniformly.
191
- "PRIVATE-TOKEN": token
192
- },
193
- method: "POST"
194
- });
195
- if (!response.ok) {
196
- const text = await response.text().catch(() => "");
197
- return { error: `GitLab REST returned ${String(response.status)}: ${text.slice(0, 500)}`, ok: false };
198
- }
199
- return { ok: true };
200
- } catch (error) {
201
- return { error: error instanceof Error ? error.message : String(error), ok: false };
202
- }
203
- };
204
- const runBuildkiteAnnotate = (binary, body, style, contextName) => new Promise((resolve) => {
205
- const args = ["annotate", "--style", style, "--context", contextName];
206
- const child = spawn(binary, args, { stdio: ["pipe", "ignore", "pipe"] });
207
- let stderr = "";
208
- child.stderr?.setEncoding("utf8");
209
- child.stderr?.on("data", (chunk) => {
210
- stderr += chunk;
211
- });
212
- child.once("error", () => {
213
- resolve({ exitCode: 127, stderr });
214
- });
215
- child.once("close", (code) => {
216
- resolve({ exitCode: code ?? -1, stderr });
217
- });
218
- child.stdin?.on("error", () => {
219
- });
220
- child.stdin?.end(body);
221
- });
222
- const postViaBuildkiteRest = async (fetchImpl, apiBaseUrl, repo, buildNumber, body, style, contextName, token) => {
223
- const [organization, pipeline] = repo.split("/", 2);
224
- if (!organization || !pipeline) {
225
- return { error: `Buildkite repo identifier \`${repo}\` is not in {org}/{pipeline} form.`, ok: false };
226
- }
227
- const url = `${apiBaseUrl}/v2/organizations/${encodeURIComponent(organization)}/pipelines/${encodeURIComponent(pipeline)}/builds/${String(buildNumber)}/annotations`;
228
- try {
229
- const response = await fetchImpl(url, {
230
- body: JSON.stringify({ body, context: contextName, style }),
231
- headers: {
232
- Authorization: `Bearer ${token}`,
233
- "Content-Type": "application/json"
234
- },
235
- method: "POST"
236
- });
237
- if (!response.ok) {
238
- const text = await response.text().catch(() => "");
239
- return { error: `Buildkite REST returned ${String(response.status)}: ${text.slice(0, 500)}`, ok: false };
240
- }
241
- return { ok: true };
242
- } catch (error) {
243
- return { error: error instanceof Error ? error.message : String(error), ok: false };
244
- }
245
- };
246
- const postBuildkiteAnnotation = async (body, context, buildkiteAgentBin, fetchImpl) => {
247
- const contextName = context.buildId ? `vis-ai-heal-${context.buildId}` : "vis-ai-heal";
248
- const style = "info";
249
- const cliResult = await runBuildkiteAnnotate(buildkiteAgentBin, body, style, contextName);
250
- if (cliResult.exitCode === 0) {
251
- return { method: "buildkite-cli", posted: true };
252
- }
253
- if (!context.apiBaseUrl || !context.repo || context.buildNumber === void 0 || !context.token) {
254
- const missing = [];
255
- if (!context.repo) missing.push("BUILDKITE_ORGANIZATION_SLUG / BUILDKITE_PIPELINE_SLUG");
256
- if (context.buildNumber === void 0) missing.push("BUILDKITE_BUILD_NUMBER");
257
- if (!context.token) missing.push("BUILDKITE_API_TOKEN (with `write_build_annotations` scope)");
258
- return {
259
- error: `buildkite-agent annotate exited ${String(cliResult.exitCode)} (${cliResult.stderr.trim().slice(0, 200)}); cannot fall back to REST without ${missing.join(", ")}`,
260
- method: "buildkite-cli",
261
- posted: false
262
- };
263
- }
264
- const restResult = await postViaBuildkiteRest(fetchImpl, context.apiBaseUrl, context.repo, context.buildNumber, body, style, contextName, context.token);
265
- if (restResult.ok) {
266
- return { method: "rest", posted: true };
267
- }
268
- return {
269
- error: `buildkite-agent annotate exited ${String(cliResult.exitCode)}; REST fallback also failed: ${restResult.error ?? "unknown"}`,
270
- method: "rest",
271
- posted: false
272
- };
273
- };
274
- const postGithubComment = async (body, context, ghBin, fetchImpl) => {
275
- if (context.prNumber === void 0) {
276
- return { method: "skipped", posted: false };
277
- }
278
- const ghResult = await runGhComment(ghBin, context.prNumber, body, context.repo);
279
- if (ghResult.exitCode === 0) {
280
- return { method: "gh-cli", posted: true };
281
- }
282
- if (!context.repo || !context.token) {
283
- return {
284
- error: `gh exited ${String(ghResult.exitCode)} (${ghResult.stderr.trim().slice(0, 200)}); cannot fall back to REST without GITHUB_REPOSITORY + GITHUB_TOKEN`,
285
- method: "gh-cli",
286
- posted: false
287
- };
288
- }
289
- const restResult = await postViaGithubRest(fetchImpl, context.repo, context.prNumber, body, context.token);
290
- if (restResult.ok) {
291
- return { method: "rest", posted: true };
292
- }
293
- return {
294
- error: `gh exited ${String(ghResult.exitCode)}; REST fallback also failed: ${restResult.error ?? "unknown"}`,
295
- method: "rest",
296
- posted: false
297
- };
298
- };
299
- const postGitlabComment = async (body, context, fetchImpl) => {
300
- if (context.prNumber === void 0) {
301
- return { method: "skipped", posted: false };
302
- }
303
- if (!context.apiBaseUrl || !context.repo) {
304
- return {
305
- error: "GitLab CI context is missing CI_API_V4_URL or CI_PROJECT_ID; cannot post note.",
306
- method: "rest",
307
- posted: false
308
- };
309
- }
310
- if (!context.token) {
311
- return {
312
- error: "GitLab CI context has no token. CI_JOB_TOKEN cannot post MR notes — set GITLAB_TOKEN to a personal/project access token with `api` scope.",
313
- method: "rest",
314
- posted: false
315
- };
316
- }
317
- const restResult = await postViaGitlabRest(fetchImpl, context.apiBaseUrl, context.repo, context.prNumber, body, context.token);
318
- if (restResult.ok) {
319
- return { method: "rest", posted: true };
320
- }
321
- return { error: restResult.error, method: "rest", posted: false };
322
- };
323
- const postPrComment = async (options) => {
324
- const { body, buildkiteAgentBin = "buildkite-agent", context, fetchImpl = globalThis.fetch, ghBin = "gh" } = options;
325
- if (context.provider === "github-actions") {
326
- return await postGithubComment(body, context, ghBin, fetchImpl);
327
- }
328
- if (context.provider === "gitlab-ci") {
329
- return await postGitlabComment(body, context, fetchImpl);
330
- }
331
- if (context.provider === "buildkite") {
332
- return await postBuildkiteAnnotation(body, context, buildkiteAgentBin, fetchImpl);
333
- }
334
- return { method: "skipped", posted: false };
335
- };
336
-
337
- const summarizeApply = (results) => {
338
- let applied = 0;
339
- let failed = 0;
340
- for (const result of results) {
341
- if (result.status === "applied") {
342
- applied += 1;
343
- } else {
344
- failed += 1;
345
- }
346
- }
347
- return { applied, failed };
348
- };
349
- const formatDisplayPath = (workspaceRoot, cwd, file) => {
350
- const absolute = resolvePatchPath(workspaceRoot, cwd, file);
351
- const rel = relative(workspaceRoot, absolute);
352
- return rel === "" || rel.startsWith("..") ? absolute : rel;
353
- };
354
- const validateFixByRerun = (workspaceRoot, project, target, timeoutMs) => new Promise((resolve) => {
355
- const visBin = process.argv[1];
356
- if (!visBin) {
357
- resolve({ exitCode: -1, stderr: "Cannot locate vis bin (process.argv[1] missing).", stdout: "" });
358
- return;
359
- }
360
- const args = [visBin, "run", target, "--projects", project, "--no-cache", "--summarize", "--fail-fast"];
361
- const child = spawn(process.execPath, args, {
362
- cwd: workspaceRoot,
363
- env: { ...process.env, NO_COLOR: "1" },
364
- stdio: ["ignore", "pipe", "pipe"]
365
- });
366
- let stdout = "";
367
- let stderr = "";
368
- const timer = setTimeout(() => {
369
- child.kill("SIGTERM");
370
- setTimeout(() => child.kill("SIGKILL"), 2e3).unref();
371
- }, timeoutMs);
372
- child.stdout?.setEncoding("utf8");
373
- child.stdout?.on("data", (chunk) => {
374
- stdout += chunk;
375
- });
376
- child.stderr?.setEncoding("utf8");
377
- child.stderr?.on("data", (chunk) => {
378
- stderr += chunk;
379
- });
380
- child.once("error", (error) => {
381
- clearTimeout(timer);
382
- resolve({ exitCode: -1, stderr: error.message, stdout });
383
- });
384
- child.once("close", (code) => {
385
- clearTimeout(timer);
386
- resolve({ exitCode: code ?? -1, stderr, stdout });
387
- });
388
- });
389
- const MAX_COMMENT_BYTES = 6e4;
390
- const pickFence = (content) => {
391
- let longest = 0;
392
- const matches = content.match(/`{3,}/g);
393
- if (matches) {
394
- for (const m of matches) {
395
- longest = Math.max(longest, m.length);
396
- }
397
- }
398
- return "`".repeat(Math.max(3, longest + 1));
399
- };
400
- const renderProposalDiff = (proposal, workspaceRoot, cwd) => {
401
- if (proposal.patches.length === 0) {
402
- return "_No patches proposed._";
403
- }
404
- const blocks = [];
405
- for (const [index, patch] of proposal.patches.entries()) {
406
- const displayPath = formatDisplayPath(workspaceRoot, cwd, patch.file);
407
- const lines = [];
408
- lines.push(`**[${String(index + 1)}] \`${displayPath}\`**`);
409
- if (patch.reason) {
410
- lines.push(`_${patch.reason}_`);
411
- }
412
- const fence = pickFence(`${patch.oldString}
413
- ${patch.newString}`);
414
- lines.push(`${fence}diff`);
415
- for (const line of patch.oldString.split("\n")) {
416
- lines.push(`- ${line}`);
417
- }
418
- for (const line of patch.newString.split("\n")) {
419
- lines.push(`+ ${line}`);
420
- }
421
- lines.push(fence);
422
- blocks.push(lines.join("\n"));
423
- }
424
- return blocks.join("\n\n");
425
- };
426
- const renderCommentBody = (proposal, failureContext, workspaceRoot, sha) => {
427
- const header = [];
428
- header.push("## vis ai heal — proposed fix");
429
- header.push("");
430
- header.push(`Failing task: \`${failureContext.taskId}\` (provider: \`${proposal.provider}\`, confidence: \`${proposal.confidence}\`)`);
431
- if (sha) {
432
- header.push(`Run anchored at \`${sha.slice(0, 7)}\`.`);
433
- }
434
- header.push("");
435
- header.push("### Root cause");
436
- header.push(proposal.explanation || "_(no explanation)_");
437
- header.push("");
438
- const diffSection = [];
439
- diffSection.push("### Proposed patch");
440
- diffSection.push(renderProposalDiff(proposal, workspaceRoot, failureContext.cwd));
441
- diffSection.push("");
442
- const footer = [];
443
- footer.push("### Validation");
444
- footer.push(`Re-ran \`${failureContext.taskId}\` after applying the patch on the CI runner — task **passed**.`);
445
- footer.push("");
446
- footer.push("### Apply locally");
447
- footer.push("```sh");
448
- footer.push(`vis ai fix ${failureContext.taskId} --apply`);
449
- footer.push("```");
450
- footer.push("");
451
- footer.push("---");
452
- footer.push("");
453
- footer.push("_Auto-generated by `vis ai heal`. Auto-commit is on the roadmap; for now the patch lives in this comment and is yours to accept or reject._");
454
- const full = [...header, ...diffSection, ...footer].join("\n");
455
- if (Buffer.byteLength(full, "utf8") <= MAX_COMMENT_BYTES) {
456
- return full;
457
- }
458
- const truncatedDiff = [
459
- "### Proposed patch",
460
- `_Patch set is too large for a comment (${String(proposal.patches.length)} files). Run \`vis ai fix ${failureContext.taskId} --apply\` locally to inspect and apply it._`,
461
- ""
462
- ];
463
- return [...header, ...truncatedDiff, ...footer].join("\n");
464
- };
465
- const renderProposalForLog = (proposal, workspaceRoot, cwd) => {
466
- const lines = [];
467
- lines.push(bold(`Proposal (${proposal.provider}, confidence: ${proposal.confidence})`));
468
- lines.push(proposal.explanation || dim("<no explanation>"));
469
- for (const [index, patch] of proposal.patches.entries()) {
470
- lines.push("");
471
- lines.push(`[${String(index + 1)}] ${formatDisplayPath(workspaceRoot, cwd, patch.file)}`);
472
- if (patch.reason) {
473
- lines.push(dim(` reason: ${patch.reason}`));
474
- }
475
- for (const line of patch.oldString.split("\n")) {
476
- lines.push(red(` - ${line}`));
477
- }
478
- for (const line of patch.newString.split("\n")) {
479
- lines.push(green(` + ${line}`));
480
- }
481
- }
482
- return lines.join("\n");
483
- };
484
- const findFirstFailedTask = async (workspaceRoot, runId) => {
485
- const summary = runId === void 0 ? await readLastRunSummary(workspaceRoot) : await readRunSummaryById(workspaceRoot, runId);
486
- if (!summary) {
487
- return void 0;
488
- }
489
- for (const task of summary.tasks) {
490
- const failed = task.exitCode !== void 0 && task.exitCode !== 0;
491
- if (failed) {
492
- return {
493
- project: task.target?.project,
494
- runId: summary.id,
495
- target: task.target?.target,
496
- taskId: task.taskId
497
- };
498
- }
499
- }
500
- return void 0;
501
- };
502
- const DEFAULT_VALIDATION_TIMEOUT_MS = 30 * 60 * 1e3;
503
- const findHealCandidate = async (workspaceRoot, runId) => {
504
- const failed = await findFirstFailedTask(workspaceRoot, runId);
505
- if (!failed) {
506
- return { outcome: "no-failed-task" };
507
- }
508
- if (!failed.project || !failed.target) {
509
- return { failedTask: failed, outcome: "missing-metadata" };
510
- }
511
- const failureContext = await aggregateFailureContext(workspaceRoot, failed.taskId, { runId: failed.runId });
512
- if (!failureContext) {
513
- return { failedTask: { project: failed.project, runId: failed.runId, target: failed.target, taskId: failed.taskId }, outcome: "no-failure-context" };
514
- }
515
- return {
516
- failedTask: { project: failed.project, runId: failed.runId, target: failed.target, taskId: failed.taskId },
517
- failureContext,
518
- outcome: "ready"
519
- };
520
- };
521
- const proposeAndApply = async (toolbox, candidate) => {
522
- const { logger, options, visConfig, workspaceRoot: wsRoot } = toolbox;
523
- const workspaceRoot = wsRoot ?? process.cwd();
524
- const dryRun = options.dryRun === true;
525
- const aiConfig = visConfig?.ai;
526
- const proposal = await runFixAnalysis(candidate.failureContext, logger, {
527
- cache: options.noCache !== true,
528
- config: aiConfig
529
- });
530
- if (!proposal) {
531
- return { outcome: "no-proposal" };
532
- }
533
- if (proposal.cannotFix) {
534
- return { detail: proposal.cannotFix, outcome: "cannot-fix", proposal };
535
- }
536
- if (proposal.patches.length === 0) {
537
- return { outcome: "empty-patches", proposal };
538
- }
539
- if (dryRun) {
540
- return { outcome: "dry-run", proposal };
541
- }
542
- const applyResults = await applyFixProposal(workspaceRoot, candidate.failureContext.cwd, proposal);
543
- const applySummary = summarizeApply(applyResults);
544
- if (applySummary.applied === 0) {
545
- return { applyResults, outcome: "no-patches-applied", proposal };
546
- }
547
- return { applyResults, outcome: "applied", proposal };
548
- };
549
- const validateAppliedFix = async (toolbox, candidate, deps = {}) => {
550
- const workspaceRoot = toolbox.workspaceRoot ?? process.cwd();
551
- const validationTimeoutMs = toolbox.options.validationTimeout === void 0 ? DEFAULT_VALIDATION_TIMEOUT_MS : toolbox.options.validationTimeout * 1e3;
552
- const validate = deps.validate ?? ((project, target) => validateFixByRerun(workspaceRoot, project, target, validationTimeoutMs));
553
- return await validate(candidate.failedTask.project, candidate.failedTask.target);
554
- };
555
- const postHealComment = async (workspaceRoot, proposal, failureContext, deps = {}) => {
556
- const detectCi = deps.detectCi ?? detectCiContext;
557
- const ciContext = await detectCi();
558
- const surface = ciContext.provider === "gitlab-ci" ? "MR" : ciContext.provider === "buildkite" ? "annotation" : "PR";
559
- if (ciContext.provider === "unknown") {
560
- return { ciContext, outcome: "no-ci", surface };
561
- }
562
- const commentBody = renderCommentBody(proposal, failureContext, workspaceRoot, ciContext.sha);
563
- const postCommentImpl = deps.postComment ?? (async (body, context) => postPrComment({ body, context }));
564
- const postResult = await postCommentImpl(commentBody, ciContext);
565
- if (postResult.posted) {
566
- return { ciContext, method: postResult.method, outcome: "posted", surface };
567
- }
568
- if (postResult.method === "skipped") {
569
- return { ciContext, outcome: "no-pr", surface };
570
- }
571
- return { ciContext, error: postResult.error, method: postResult.method, outcome: "post-failed", surface };
572
- };
573
- const heal = async (toolbox, deps = {}) => {
574
- const { logger, workspaceRoot: wsRoot } = toolbox;
575
- const workspaceRoot = wsRoot ?? process.cwd();
576
- const candidateResult = await findHealCandidate(workspaceRoot, toolbox.options.run);
577
- if (candidateResult.outcome === "no-failed-task") {
578
- pail.info("No failed tasks found in the latest run summary. Nothing to heal.");
579
- return;
580
- }
581
- if (candidateResult.outcome === "missing-metadata") {
582
- pail.error(`Failed task ${candidateResult.failedTask.taskId} is missing project/target metadata in the run summary; cannot validate a fix.`);
583
- process.exitCode = 1;
584
- return;
585
- }
586
- if (candidateResult.outcome === "no-failure-context") {
587
- pail.error(`No failure log or run summary found for ${candidateResult.failedTask.taskId}.`);
588
- process.exitCode = 1;
589
- return;
590
- }
591
- const candidate = { failedTask: candidateResult.failedTask, failureContext: candidateResult.failureContext };
592
- pail.info(`Healing ${candidate.failedTask.taskId} (run ${candidate.failedTask.runId ?? "unknown"})`);
593
- if (!candidate.failureContext.terminalOutputCaptured) {
594
- pail.warn(`No captured terminal output for ${candidate.failedTask.taskId}; the AI proposal will be weaker without it.`);
595
- }
596
- const proposeResult = await proposeAndApply(toolbox, candidate);
597
- if (proposeResult.outcome === "no-proposal") {
598
- pail.error("AI fix proposal failed or no provider available.");
599
- process.exitCode = 1;
600
- return;
601
- }
602
- if (proposeResult.outcome === "cannot-fix") {
603
- pail.warn(`AI declined to fix: ${proposeResult.detail ?? "(no reason)"}`);
604
- return;
605
- }
606
- if (proposeResult.outcome === "empty-patches") {
607
- pail.warn("AI returned an empty patch set.");
608
- return;
609
- }
610
- const proposal = proposeResult.proposal;
611
- logger.info(renderProposalForLog(proposal, workspaceRoot, candidate.failureContext.cwd));
612
- if (proposeResult.outcome === "dry-run") {
613
- pail.info("Dry run: skipping apply, validation, and PR comment.");
614
- return;
615
- }
616
- const applyResults = proposeResult.applyResults ?? [];
617
- const applySummary = summarizeApply(applyResults);
618
- pail.info(`Applied ${String(applySummary.applied)}/${String(applyResults.length)} patches.`);
619
- if (proposeResult.outcome === "no-patches-applied") {
620
- pail.error("No patches could be applied (all failed validation).");
621
- process.exitCode = 1;
622
- return;
623
- }
624
- pail.info(`Re-running ${candidate.failedTask.taskId} to validate the fix...`);
625
- const validation = await validateAppliedFix(toolbox, candidate, { validate: deps.validate });
626
- if (validation.exitCode !== 0) {
627
- pail.error(`Validation failed (exit ${String(validation.exitCode)}). Patch is not posted.`);
628
- if (validation.stderr.trim().length > 0) {
629
- logger.info(yellow("--- validation stderr (tail) ---"));
630
- logger.info(validation.stderr.split("\n").slice(-20).join("\n"));
631
- }
632
- process.exitCode = 1;
633
- return;
634
- }
635
- pail.success(`Validation passed.`);
636
- const postResult = await postHealComment(workspaceRoot, proposal, candidate.failureContext, {
637
- detectCi: deps.detectCi,
638
- postComment: deps.postComment
639
- });
640
- if (postResult.outcome === "no-ci") {
641
- pail.notice("Not running in a recognised CI provider; skipping PR comment. Patch was applied + validated locally.");
642
- return;
643
- }
644
- if (postResult.outcome === "posted") {
645
- const identifier = postResult.surface === "annotation" ? `build #${String(postResult.ciContext.buildNumber ?? "?")}` : `${postResult.surface} #${String(postResult.ciContext.prNumber)}`;
646
- pail.success(`Posted fix proposal to ${identifier} via ${postResult.method ?? "unknown"}.`);
647
- return;
648
- }
649
- if (postResult.outcome === "no-pr") {
650
- pail.notice(`No ${postResult.surface} number detected (push-event run); skipping comment.`);
651
- return;
652
- }
653
- const noun = postResult.surface === "annotation" ? "annotation" : `${postResult.surface} comment`;
654
- pail.error(`Failed to post ${noun}: ${postResult.error ?? "unknown error"}`);
655
- };
656
- const aiHeal = async (toolbox) => {
657
- await heal(toolbox);
658
- };
659
-
660
- const heal$1 = /*#__PURE__*/Object.defineProperty({
661
- __proto__: null,
662
- aiHeal,
663
- findHealCandidate,
664
- pickFenceForTesting: pickFence,
665
- postHealComment,
666
- proposeAndApply,
667
- renderCommentBodyForTesting: renderCommentBody,
668
- renderProposalDiffForTesting: renderProposalDiff,
669
- runHealForTesting: heal,
670
- validateAppliedFix
671
- }, Symbol.toStringTag, { value: 'Module' });
672
-
673
- export { postPrComment as a, detectCiContext as d, findHealCandidate as f, heal$1 as h, proposeAndApply as p, validateAppliedFix as v };
1
+ var U=Object.defineProperty;var I=(t,e)=>U(t,"name",{value:e,configurable:!0});import{createRequire as A}from"node:module";import{bold as O,dim as v,red as G,green as j,yellow as F}from"@visulima/colorize";import{relative as D}from"@visulima/path";import{readLastRunSummary as K}from"@visulima/task-runner";import{r as H,a as M,b as V,c as J}from"../packem_shared/ai-fix-nn4zOE95.js";import{p as u}from"./bin.js";import{r as z}from"../packem_shared/run-summary-utils-BfBvjzhY.js";const L=A(import.meta.url),b=typeof globalThis<"u"&&typeof globalThis.process<"u"?globalThis.process:process,T=I(t=>{if(typeof b<"u"&&b.versions&&b.versions.node){const[e,o]=b.versions.node.split(".").map(Number);if(e>22||e===22&&o>=3||e===20&&o>=16)return b.getBuiltinModule(t)}return L(t)},"__cjs_getBuiltinModule"),{spawn:k}=T("node:child_process"),{readFile:q}=T("node:fs/promises");var Q=Object.defineProperty,g=I((t,e)=>Q(t,"name",{value:e,configurable:!0}),"i");const W=g(t=>{if(!t)return;const e=/^refs\/pull\/(\d+)\//.exec(t);return e?Number.parseInt(e[1],10):void 0},"parsePrNumberFromGithubRef"),Y=g(async t=>{if(!t)return{prNumber:void 0,sha:void 0};try{const e=await q(t,"utf8"),o=JSON.parse(e),r=o.pull_request?.number??o.issue?.number??o.number,i=o.pull_request?.head?.sha;return{prNumber:r,sha:i}}catch{return{prNumber:void 0,sha:void 0}}},"readPrNumberFromGithubEvent"),Z=g(async t=>{const e=W(t.GITHUB_REF),{prNumber:o,sha:r}=e===void 0?await Y(t.GITHUB_EVENT_PATH):{prNumber:e,sha:void 0};return{apiBaseUrl:void 0,buildId:void 0,buildNumber:void 0,prNumber:e??o,provider:"github-actions",repo:t.GITHUB_REPOSITORY,sha:r??t.GITHUB_SHA,token:t.GITHUB_TOKEN}},"detectGithubActions"),X=g(t=>{const e=t.CI_MERGE_REQUEST_IID,o=e!==void 0&&e!==""?Number.parseInt(e,10):void 0,r=t.CI_API_V4_URL,i=t.GITLAB_TOKEN??t.CI_TOKEN;return{apiBaseUrl:r,buildId:void 0,buildNumber:void 0,prNumber:Number.isFinite(o)?o:void 0,provider:"gitlab-ci",repo:t.CI_PROJECT_ID??t.CI_PROJECT_PATH,sha:t.CI_COMMIT_SHA,token:i}},"detectGitlabCi"),ee=g(t=>{const e=t.BUILDKITE_PULL_REQUEST,o=e!==void 0&&e!==""&&e!=="false"?Number.parseInt(e,10):void 0,r=t.BUILDKITE_BUILD_NUMBER,i=r!==void 0&&r!==""?Number.parseInt(r,10):void 0,n=t.BUILDKITE_ORGANIZATION_SLUG,a=t.BUILDKITE_PIPELINE_SLUG,s=n!==void 0&&n!==""&&a!==void 0&&a!==""?`${n}/${a}`:void 0;return{apiBaseUrl:(t.BUILDKITE_API_BASE_URL??"https://api.buildkite.com").replace(/\/+$/,""),buildId:t.BUILDKITE_BUILD_ID,buildNumber:Number.isFinite(i)?i:void 0,prNumber:Number.isFinite(o)?o:void 0,provider:"buildkite",repo:s,sha:t.BUILDKITE_COMMIT,token:t.BUILDKITE_API_TOKEN}},"detectBuildkite"),te=g(async(t=process.env)=>t.GITHUB_ACTIONS==="true"?await Z(t):t.GITLAB_CI==="true"?X(t):t.BUILDKITE==="true"?ee(t):{apiBaseUrl:void 0,buildId:void 0,buildNumber:void 0,prNumber:void 0,provider:"unknown",repo:void 0,sha:void 0,token:void 0},"detectCiContext");var oe=Object.defineProperty,h=I((t,e)=>oe(t,"name",{value:e,configurable:!0}),"p");const re=h((t,e,o,r)=>new Promise(i=>{const n=["pr","comment",String(e),"--body-file","-"];r&&n.push("--repo",r);const a=k(t,n,{stdio:["pipe","ignore","pipe"]});let s="";a.stderr?.setEncoding("utf8"),a.stderr?.on("data",d=>{s+=d}),a.once("error",()=>{i({exitCode:127,stderr:s})}),a.once("close",d=>{i({exitCode:d??-1,stderr:s})}),a.stdin?.on("error",()=>{}),a.stdin?.end(o)}),"runGhComment"),ie=h(async(t,e,o,r,i)=>{const n=`https://api.github.com/repos/${e}/issues/${String(o)}/comments`;try{const a=await t(n,{body:JSON.stringify({body:r}),headers:{Accept:"application/vnd.github+json",Authorization:`Bearer ${i}`,"Content-Type":"application/json","X-GitHub-Api-Version":"2022-11-28"},method:"POST"});if(!a.ok){const s=await a.text().catch(()=>"");return{error:`GitHub REST returned ${String(a.status)}: ${s.slice(0,500)}`,ok:!1}}return{ok:!0}}catch(a){return{error:a instanceof Error?a.message:String(a),ok:!1}}},"postViaGithubRest"),ne=h(async(t,e,o,r,i,n)=>{const a=encodeURIComponent(o),s=`${e.replace(/\/+$/,"")}/projects/${a}/merge_requests/${String(r)}/notes`;try{const d=await t(s,{body:JSON.stringify({body:i}),headers:{"Content-Type":"application/json","PRIVATE-TOKEN":n},method:"POST"});if(!d.ok){const p=await d.text().catch(()=>"");return{error:`GitLab REST returned ${String(d.status)}: ${p.slice(0,500)}`,ok:!1}}return{ok:!0}}catch(d){return{error:d instanceof Error?d.message:String(d),ok:!1}}},"postViaGitlabRest"),ae=h((t,e,o,r)=>new Promise(i=>{const n=k(t,["annotate","--style",o,"--context",r],{stdio:["pipe","ignore","pipe"]});let a="";n.stderr?.setEncoding("utf8"),n.stderr?.on("data",s=>{a+=s}),n.once("error",()=>{i({exitCode:127,stderr:a})}),n.once("close",s=>{i({exitCode:s??-1,stderr:a})}),n.stdin?.on("error",()=>{}),n.stdin?.end(e)}),"runBuildkiteAnnotate"),se=h(async(t,e,o,r,i,n,a,s)=>{const[d,p]=o.split("/",2);if(!d||!p)return{error:`Buildkite repo identifier \`${o}\` is not in {org}/{pipeline} form.`,ok:!1};const m=`${e}/v2/organizations/${encodeURIComponent(d)}/pipelines/${encodeURIComponent(p)}/builds/${String(r)}/annotations`;try{const c=await t(m,{body:JSON.stringify({body:i,context:a,style:n}),headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},method:"POST"});if(!c.ok){const l=await c.text().catch(()=>"");return{error:`Buildkite REST returned ${String(c.status)}: ${l.slice(0,500)}`,ok:!1}}return{ok:!0}}catch(c){return{error:c instanceof Error?c.message:String(c),ok:!1}}},"postViaBuildkiteRest"),de=h(async(t,e,o,r)=>{const i=e.buildId?`vis-ai-heal-${e.buildId}`:"vis-ai-heal",n="info",a=await ae(o,t,n,i);if(a.exitCode===0)return{method:"buildkite-cli",posted:!0};if(!e.apiBaseUrl||!e.repo||e.buildNumber===void 0||!e.token){const d=[];return e.repo||d.push("BUILDKITE_ORGANIZATION_SLUG / BUILDKITE_PIPELINE_SLUG"),e.buildNumber===void 0&&d.push("BUILDKITE_BUILD_NUMBER"),e.token||d.push("BUILDKITE_API_TOKEN (with `write_build_annotations` scope)"),{error:`buildkite-agent annotate exited ${String(a.exitCode)} (${a.stderr.trim().slice(0,200)}); cannot fall back to REST without ${d.join(", ")}`,method:"buildkite-cli",posted:!1}}const s=await se(r,e.apiBaseUrl,e.repo,e.buildNumber,t,n,i,e.token);return s.ok?{method:"rest",posted:!0}:{error:`buildkite-agent annotate exited ${String(a.exitCode)}; REST fallback also failed: ${s.error??"unknown"}`,method:"rest",posted:!1}},"postBuildkiteAnnotation"),pe=h(async(t,e,o,r)=>{if(e.prNumber===void 0)return{method:"skipped",posted:!1};const i=await re(o,e.prNumber,t,e.repo);if(i.exitCode===0)return{method:"gh-cli",posted:!0};if(!e.repo||!e.token)return{error:`gh exited ${String(i.exitCode)} (${i.stderr.trim().slice(0,200)}); cannot fall back to REST without GITHUB_REPOSITORY + GITHUB_TOKEN`,method:"gh-cli",posted:!1};const n=await ie(r,e.repo,e.prNumber,t,e.token);return n.ok?{method:"rest",posted:!0}:{error:`gh exited ${String(i.exitCode)}; REST fallback also failed: ${n.error??"unknown"}`,method:"rest",posted:!1}},"postGithubComment"),ce=h(async(t,e,o)=>{if(e.prNumber===void 0)return{method:"skipped",posted:!1};if(!e.apiBaseUrl||!e.repo)return{error:"GitLab CI context is missing CI_API_V4_URL or CI_PROJECT_ID; cannot post note.",method:"rest",posted:!1};if(!e.token)return{error:"GitLab CI context has no token. CI_JOB_TOKEN cannot post MR notes — set GITLAB_TOKEN to a personal/project access token with `api` scope.",method:"rest",posted:!1};const r=await ne(o,e.apiBaseUrl,e.repo,e.prNumber,t,e.token);return r.ok?{method:"rest",posted:!0}:{error:r.error,method:"rest",posted:!1}},"postGitlabComment"),ue=h(async t=>{const{body:e,buildkiteAgentBin:o="buildkite-agent",context:r,fetchImpl:i=globalThis.fetch,ghBin:n="gh"}=t;return r.provider==="github-actions"?await pe(e,r,n,i):r.provider==="gitlab-ci"?await ce(e,r,i):r.provider==="buildkite"?await de(e,r,o,i):{method:"skipped",posted:!1}},"postPrComment");var le=Object.defineProperty,f=I((t,e)=>le(t,"name",{value:e,configurable:!0}),"l");const C=f(t=>{let e=0,o=0;for(const r of t)r.status==="applied"?e+=1:o+=1;return{applied:e,failed:o}},"summarizeApply"),_=f((t,e,o)=>{const r=H(t,e,o),i=D(t,r);return i===""||i.startsWith("..")?r:i},"formatDisplayPath"),fe=f((t,e,o,r)=>new Promise(i=>{const n=process.argv[1];if(!n){i({exitCode:-1,stderr:"Cannot locate vis bin (process.argv[1] missing).",stdout:""});return}const a=[n,"run",o,"--projects",e,"--no-cache","--summarize","--fail-fast"],s=k(process.execPath,a,{cwd:t,env:{...process.env,NO_COLOR:"1"},stdio:["ignore","pipe","pipe"]});let d="",p="";const m=setTimeout(()=>{s.kill("SIGTERM"),setTimeout(()=>s.kill("SIGKILL"),2e3).unref()},r);s.stdout?.setEncoding("utf8"),s.stdout?.on("data",c=>{d+=c}),s.stderr?.setEncoding("utf8"),s.stderr?.on("data",c=>{p+=c}),s.once("error",c=>{clearTimeout(m),i({exitCode:-1,stderr:c.message,stdout:d})}),s.once("close",c=>{clearTimeout(m),i({exitCode:c??-1,stderr:p,stdout:d})})}),"validateFixByRerun"),me=6e4,y=f(t=>{let e=0;const o=t.match(/`{3,}/g);if(o)for(const r of o)e=Math.max(e,r.length);return"`".repeat(Math.max(3,e+1))},"pickFence"),$=f((t,e,o)=>{if(t.patches.length===0)return"_No patches proposed._";const r=[];for(const[i,n]of t.patches.entries()){const a=_(e,o,n.file),s=[`**[${String(i+1)}] \`${a}\`**`];n.reason&&s.push(`_${n.reason}_`);const d=y(`${n.oldString}
2
+ ${n.newString}`);s.push(`${d}diff`);for(const p of n.oldString.split(`
3
+ `))s.push(`- ${p}`);for(const p of n.newString.split(`
4
+ `))s.push(`+ ${p}`);s.push(d),r.push(s.join(`
5
+ `))}return r.join(`
6
+
7
+ `)},"renderProposalDiff"),x=f((t,e,o,r)=>{const i=["## vis ai heal — proposed fix","",`Failing task: \`${e.taskId}\` (provider: \`${t.provider}\`, confidence: \`${t.confidence}\`)`];r&&i.push(`Run anchored at \`${r.slice(0,7)}\`.`),i.push(""),i.push("### Root cause"),i.push(t.explanation||"_(no explanation)_"),i.push("");const n=["### Proposed patch",$(t,o,e.cwd),""],a=["### Validation",`Re-ran \`${e.taskId}\` after applying the patch on the CI runner — task **passed**.`,"","### Apply locally","```sh",`vis ai fix ${e.taskId} --apply`,"```","","---","","_Auto-generated by `vis ai heal`. Auto-commit is on the roadmap; for now the patch lives in this comment and is yours to accept or reject._"],s=[...i,...n,...a].join(`
8
+ `);if(Buffer.byteLength(s,"utf8")<=me)return s;const d=["### Proposed patch",`_Patch set is too large for a comment (${String(t.patches.length)} files). Run \`vis ai fix ${e.taskId} --apply\` locally to inspect and apply it._`,""];return[...i,...d,...a].join(`
9
+ `)},"renderCommentBody"),he=f((t,e,o)=>{const r=[O(`Proposal (${t.provider}, confidence: ${t.confidence})`),t.explanation||v("<no explanation>")];for(const[i,n]of t.patches.entries()){r.push(""),r.push(`[${String(i+1)}] ${_(e,o,n.file)}`),n.reason&&r.push(v(` reason: ${n.reason}`));for(const a of n.oldString.split(`
10
+ `))r.push(G(` - ${a}`));for(const a of n.newString.split(`
11
+ `))r.push(j(` + ${a}`))}return r.join(`
12
+ `)},"renderProposalForLog"),ge=f(async(t,e)=>{const o=e===void 0?await K(t):await z(t,e);if(o){for(const r of o.tasks)if(r.exitCode!==void 0&&r.exitCode!==0)return{project:r.target?.project,runId:o.id,target:r.target?.target,taskId:r.taskId}}},"findFirstFailedTask"),Ie=1800*1e3,w=f(async(t,e)=>{const o=await ge(t,e);if(!o)return{outcome:"no-failed-task"};if(!o.project||!o.target)return{failedTask:o,outcome:"missing-metadata"};const r=await M(t,o.taskId,{runId:o.runId});return r?{failedTask:{project:o.project,runId:o.runId,target:o.target,taskId:o.taskId},failureContext:r,outcome:"ready"}:{failedTask:{project:o.project,runId:o.runId,target:o.target,taskId:o.taskId},outcome:"no-failure-context"}},"findHealCandidate"),N=f(async(t,e)=>{const{logger:o,options:r,visConfig:i,workspaceRoot:n}=t,a=n??process.cwd(),s=r.dryRun===!0,d=i?.ai,p=await V(e.failureContext,o,{cache:r.noCache!==!0,config:d});if(!p)return{outcome:"no-proposal"};if(p.cannotFix)return{detail:p.cannotFix,outcome:"cannot-fix",proposal:p};if(p.patches.length===0)return{outcome:"empty-patches",proposal:p};if(s)return{outcome:"dry-run",proposal:p};const m=await J(a,e.failureContext.cwd,p);return C(m).applied===0?{applyResults:m,outcome:"no-patches-applied",proposal:p}:{applyResults:m,outcome:"applied",proposal:p}},"proposeAndApply"),E=f(async(t,e,o={})=>{const r=t.workspaceRoot??process.cwd(),i=t.options.validationTimeout===void 0?Ie:t.options.validationTimeout*1e3;return await(o.validate??((n,a)=>fe(r,n,a,i)))(e.failedTask.project,e.failedTask.target)},"validateAppliedFix"),S=f(async(t,e,o,r={})=>{const i=await(r.detectCi??te)(),n=i.provider==="gitlab-ci"?"MR":i.provider==="buildkite"?"annotation":"PR";if(i.provider==="unknown")return{ciContext:i,outcome:"no-ci",surface:n};const a=x(e,o,t,i.sha),s=await(r.postComment??(async(d,p)=>ue({body:d,context:p})))(a,i);return s.posted?{ciContext:i,method:s.method,outcome:"posted",surface:n}:s.method==="skipped"?{ciContext:i,outcome:"no-pr",surface:n}:{ciContext:i,error:s.error,method:s.method,outcome:"post-failed",surface:n}},"postHealComment"),R=f(async(t,e={})=>{const{logger:o,workspaceRoot:r}=t,i=r??process.cwd(),n=await w(i,t.options.run);if(n.outcome==="no-failed-task"){u.info("No failed tasks found in the latest run summary. Nothing to heal.");return}if(n.outcome==="missing-metadata"){u.error(`Failed task ${n.failedTask.taskId} is missing project/target metadata in the run summary; cannot validate a fix.`),process.exitCode=1;return}if(n.outcome==="no-failure-context"){u.error(`No failure log or run summary found for ${n.failedTask.taskId}.`),process.exitCode=1;return}const a={failedTask:n.failedTask,failureContext:n.failureContext};u.info(`Healing ${a.failedTask.taskId} (run ${a.failedTask.runId??"unknown"})`),a.failureContext.terminalOutputCaptured||u.warn(`No captured terminal output for ${a.failedTask.taskId}; the AI proposal will be weaker without it.`);const s=await N(t,a);if(s.outcome==="no-proposal"){u.error("AI fix proposal failed or no provider available."),process.exitCode=1;return}if(s.outcome==="cannot-fix"){u.warn(`AI declined to fix: ${s.detail??"(no reason)"}`);return}if(s.outcome==="empty-patches"){u.warn("AI returned an empty patch set.");return}const d=s.proposal;if(o.info(he(d,i,a.failureContext.cwd)),s.outcome==="dry-run"){u.info("Dry run: skipping apply, validation, and PR comment.");return}const p=s.applyResults??[],m=C(p);if(u.info(`Applied ${String(m.applied)}/${String(p.length)} patches.`),s.outcome==="no-patches-applied"){u.error("No patches could be applied (all failed validation)."),process.exitCode=1;return}u.info(`Re-running ${a.failedTask.taskId} to validate the fix...`);const c=await E(t,a,{validate:e.validate});if(c.exitCode!==0){u.error(`Validation failed (exit ${String(c.exitCode)}). Patch is not posted.`),c.stderr.trim().length>0&&(o.info(F("--- validation stderr (tail) ---")),o.info(c.stderr.split(`
13
+ `).slice(-20).join(`
14
+ `))),process.exitCode=1;return}u.success("Validation passed.");const l=await S(i,d,a.failureContext,{detectCi:e.detectCi,postComment:e.postComment});if(l.outcome==="no-ci"){u.notice("Not running in a recognised CI provider; skipping PR comment. Patch was applied + validated locally.");return}if(l.outcome==="posted"){const P=l.surface==="annotation"?`build #${String(l.ciContext.buildNumber??"?")}`:`${l.surface} #${String(l.ciContext.prNumber)}`;u.success(`Posted fix proposal to ${P} via ${l.method??"unknown"}.`);return}if(l.outcome==="no-pr"){u.notice(`No ${l.surface} number detected (push-event run); skipping comment.`);return}const B=l.surface==="annotation"?"annotation":`${l.surface} comment`;u.error(`Failed to post ${B}: ${l.error??"unknown error"}`)},"heal"),be=f(async t=>{await R(t)},"aiHeal"),we=Object.defineProperty({__proto__:null,aiHeal:be,findHealCandidate:w,pickFenceForTesting:y,postHealComment:S,proposeAndApply:N,renderCommentBodyForTesting:x,renderProposalDiffForTesting:$,runHealForTesting:R,validateAppliedFix:E},Symbol.toStringTag,{value:"Module"});export{E as I,w as P,N as R,te as d,we as h,ue as p};