@h-rig/cli 0.0.6-alpha.90 → 0.0.6-alpha.91

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 (118) hide show
  1. package/README.md +18 -19
  2. package/dist/bin/build-rig-binaries.js +22 -10
  3. package/dist/bin/rig.d.ts +71 -1
  4. package/dist/bin/rig.js +15078 -11169
  5. package/dist/config/rig-default-config.yml +5 -0
  6. package/dist/src/app/drone-ui.d.ts +11 -14
  7. package/dist/src/app/drone-ui.js +70 -86
  8. package/dist/src/commands/_async-ui.d.ts +1 -4
  9. package/dist/src/commands/_async-ui.js +9 -111
  10. package/dist/src/commands/_cli-format.d.ts +16 -9
  11. package/dist/src/commands/_cli-format.js +167 -295
  12. package/dist/src/commands/_connection-state.d.ts +11 -1
  13. package/dist/src/commands/_connection-state.js +50 -5
  14. package/dist/src/commands/_doctor-checks.d.ts +0 -6
  15. package/dist/src/commands/_doctor-checks.js +79 -382
  16. package/dist/src/commands/_help-catalog.d.ts +1 -1
  17. package/dist/src/commands/_help-catalog.js +217 -157
  18. package/dist/src/commands/_inprocess-services.d.ts +33 -0
  19. package/dist/src/commands/_inprocess-services.js +102 -0
  20. package/dist/src/commands/_json-output.js +4 -0
  21. package/dist/src/commands/_lazy-reconcile.d.ts +34 -0
  22. package/dist/src/commands/_lazy-reconcile.js +102 -0
  23. package/dist/src/commands/_paths.js +1 -1
  24. package/dist/src/commands/_pi-frontend.d.ts +18 -10
  25. package/dist/src/commands/_pi-frontend.js +37 -715
  26. package/dist/src/commands/_pi-install.js +18 -36
  27. package/dist/src/commands/_policy.d.ts +1 -1
  28. package/dist/src/commands/_policy.js +56 -15
  29. package/dist/src/commands/_run-bridge.d.ts +114 -0
  30. package/dist/src/commands/_run-bridge.js +387 -0
  31. package/dist/src/commands/_run-diagnostics.d.ts +9 -0
  32. package/dist/src/commands/_run-diagnostics.js +51 -0
  33. package/dist/src/commands/_run-driver-helpers.d.ts +8 -81
  34. package/dist/src/commands/_run-driver-helpers.js +79 -283
  35. package/dist/src/commands/_run-projection.d.ts +50 -0
  36. package/dist/src/commands/_run-projection.js +349 -0
  37. package/dist/src/commands/_run-subcommands.d.ts +3 -0
  38. package/dist/src/commands/_run-subcommands.js +31 -0
  39. package/dist/src/commands/_spinner.js +1 -1
  40. package/dist/src/commands/agent.d.ts +1 -1
  41. package/dist/src/commands/agent.js +8559 -239
  42. package/dist/src/commands/dist.d.ts +1 -1
  43. package/dist/src/commands/dist.js +27 -19
  44. package/dist/src/commands/doctor.d.ts +1 -1
  45. package/dist/src/commands/doctor.js +93 -475
  46. package/dist/src/commands/github.d.ts +1 -1
  47. package/dist/src/commands/github.js +113 -387
  48. package/dist/src/commands/inbox.d.ts +22 -24
  49. package/dist/src/commands/inbox.js +420 -691
  50. package/dist/src/commands/init.d.ts +6 -16
  51. package/dist/src/commands/init.js +334 -971
  52. package/dist/src/commands/inspect.d.ts +19 -2
  53. package/dist/src/commands/inspect.js +644 -610
  54. package/dist/src/commands/pi.d.ts +1 -1
  55. package/dist/src/commands/plugin.d.ts +1 -1
  56. package/dist/src/commands/plugin.js +486 -7
  57. package/dist/src/commands/profile-and-review.d.ts +1 -1
  58. package/dist/src/commands/profile-and-review.js +94 -56
  59. package/dist/src/commands/queue.js +1 -21
  60. package/dist/src/commands/remote.d.ts +1 -1
  61. package/dist/src/commands/remote.js +837 -14
  62. package/dist/src/commands/repo-git-harness.d.ts +1 -1
  63. package/dist/src/commands/repo-git-harness.js +57 -14
  64. package/dist/src/commands/run.d.ts +20 -2
  65. package/dist/src/commands/run.js +17579 -1759
  66. package/dist/src/commands/server.d.ts +2 -6
  67. package/dist/src/commands/server.js +141 -723
  68. package/dist/src/commands/setup.d.ts +1 -1
  69. package/dist/src/commands/setup.js +102 -484
  70. package/dist/src/commands/stats.d.ts +13 -10
  71. package/dist/src/commands/stats.js +689 -761
  72. package/dist/src/commands/task-run-driver.d.ts +50 -88
  73. package/dist/src/commands/task-run-driver.js +116 -2717
  74. package/dist/src/commands/task.d.ts +34 -13
  75. package/dist/src/commands/task.js +668 -2523
  76. package/dist/src/commands/test.d.ts +1 -1
  77. package/dist/src/commands/triage.d.ts +11 -0
  78. package/dist/src/commands/triage.js +227 -0
  79. package/dist/src/commands/workspace.d.ts +1 -1
  80. package/dist/src/commands.d.ts +0 -16
  81. package/dist/src/commands.js +16657 -12250
  82. package/dist/src/index.js +16528 -12497
  83. package/dist/src/launcher.js +4 -0
  84. package/dist/src/operator-cli.d.ts +2 -0
  85. package/dist/src/operator-cli.js +17837 -0
  86. package/dist/src/operator-entry.d.ts +1 -0
  87. package/dist/src/operator-entry.js +3 -0
  88. package/package.json +18 -12
  89. package/dist/src/app/board.d.ts +0 -23
  90. package/dist/src/app/board.js +0 -1786
  91. package/dist/src/app/theme.d.ts +0 -47
  92. package/dist/src/app/theme.js +0 -150
  93. package/dist/src/commands/_authority-runs.d.ts +0 -22
  94. package/dist/src/commands/_authority-runs.js +0 -110
  95. package/dist/src/commands/_operator-surface.d.ts +0 -34
  96. package/dist/src/commands/_operator-surface.js +0 -220
  97. package/dist/src/commands/_operator-view.d.ts +0 -30
  98. package/dist/src/commands/_operator-view.js +0 -1070
  99. package/dist/src/commands/_preflight.d.ts +0 -22
  100. package/dist/src/commands/_preflight.js +0 -540
  101. package/dist/src/commands/_run-replay.d.ts +0 -24
  102. package/dist/src/commands/_run-replay.js +0 -142
  103. package/dist/src/commands/_server-client.d.ts +0 -186
  104. package/dist/src/commands/_server-client.js +0 -681
  105. package/dist/src/commands/_snapshot-upload.d.ts +0 -39
  106. package/dist/src/commands/_snapshot-upload.js +0 -455
  107. package/dist/src/commands/_task-picker.d.ts +0 -9
  108. package/dist/src/commands/_task-picker.js +0 -201
  109. package/dist/src/commands/browser.d.ts +0 -65
  110. package/dist/src/commands/browser.js +0 -1173
  111. package/dist/src/commands/connect.d.ts +0 -7
  112. package/dist/src/commands/connect.js +0 -419
  113. package/dist/src/commands/inspector.d.ts +0 -3
  114. package/dist/src/commands/inspector.js +0 -263
  115. package/dist/src/commands/task-report-bug.d.ts +0 -19
  116. package/dist/src/commands/task-report-bug.js +0 -1281
  117. package/dist/src/report-bug.d.ts +0 -44
  118. package/dist/src/report-bug.js +0 -260
@@ -1,1281 +0,0 @@
1
- // @bun
2
- // packages/cli/src/commands/task-report-bug.ts
3
- import { existsSync as existsSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
4
- import { resolve as resolve3 } from "path";
5
-
6
- // packages/cli/src/app/drone-ui.ts
7
- import { ProcessTerminal, TUI, Text, Input, SelectList, matchesKey } from "@earendil-works/pi-tui";
8
-
9
- // packages/cli/src/app/theme.ts
10
- var RIG_PALETTE = {
11
- ink: "#f2f3f6",
12
- ink2: "#aeb0ba",
13
- ink3: "#6c6e79",
14
- ink4: "#44464f",
15
- accent: "#ccff4d",
16
- accentDim: "#a9d63f",
17
- cyan: "#56d8ff",
18
- red: "#ff5d5d",
19
- yellow: "#ffd24d"
20
- };
21
- function hexToRgb(hex) {
22
- const value = hex.replace("#", "");
23
- return [
24
- Number.parseInt(value.slice(0, 2), 16),
25
- Number.parseInt(value.slice(2, 4), 16),
26
- Number.parseInt(value.slice(4, 6), 16)
27
- ];
28
- }
29
- function fg(hex) {
30
- const [r, g, b] = hexToRgb(hex);
31
- return (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
32
- }
33
- var ink = fg(RIG_PALETTE.ink);
34
- var ink2 = fg(RIG_PALETTE.ink2);
35
- var ink3 = fg(RIG_PALETTE.ink3);
36
- var ink4 = fg(RIG_PALETTE.ink4);
37
- var accent = fg(RIG_PALETTE.accent);
38
- var accentDim = fg(RIG_PALETTE.accentDim);
39
- var cyan = fg(RIG_PALETTE.cyan);
40
- var red = fg(RIG_PALETTE.red);
41
- var yellow = fg(RIG_PALETTE.yellow);
42
- function bold(text) {
43
- return `\x1B[1m${text}\x1B[22m`;
44
- }
45
- var DRONE_ART = [
46
- " .-=-. .-=-. ",
47
- " ( !!! ) ( !!! ) ",
48
- " '-=-'._ _.'-=-' ",
49
- " '._ _.' ",
50
- " '=$$$$$$$=.' ",
51
- " =$$$$$$$$$$$= ",
52
- " $$$@@@@@@@@@@$$$ ",
53
- " $$$@@ @@$$$ ",
54
- " $$@ ? @$$$ ",
55
- " $$$@ '-' @$$$ ",
56
- " $$$@@ @@$$$ ",
57
- " $$$@@@@@@@@@@$$$ ",
58
- " =$$$$$$$$$$$= ",
59
- " '=$$$$$$$=.' ",
60
- " _.' '._ ",
61
- " .-=-.' '.-=-. ",
62
- " ( !!! ) ( !!! ) ",
63
- " '-=-' '-=-' "
64
- ];
65
- var EYE_FRAMES = ["@", "o", "."];
66
- var DRONE_WIDTH = DRONE_ART[0].length;
67
- var DRONE_HEIGHT = DRONE_ART.length;
68
- var MICRO_BLADES = ["---", "\\\\\\", "|||", "///"];
69
- function microDroneFrame(tick) {
70
- const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
71
- const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
72
- return `(${blade})${eye}(${blade})`;
73
- }
74
- var MICRO_DRONE_FRAMES = Array.from({ length: 12 }, (_, index) => microDroneFrame(index));
75
-
76
- // packages/cli/src/app/drone-ui.ts
77
- var isTty = () => Boolean(process.stdout.isTTY);
78
- function hairline(width = Math.min(process.stdout.columns ?? 80, 100)) {
79
- return ink4("\u2500".repeat(Math.max(10, width)));
80
- }
81
- function droneIntro(title, subtitle) {
82
- console.log("");
83
- console.log(` ${accent("\u258D")}${bold(ink(title))}${subtitle ? ink3(` \u2014 ${subtitle}`) : ""}`);
84
- console.log(hairline());
85
- }
86
- function droneOutro(text) {
87
- console.log(hairline());
88
- console.log(` ${accent("\u25C6")} ${ink2(text)}`);
89
- console.log("");
90
- }
91
- function droneNote(message, title) {
92
- if (title)
93
- console.log(` ${accentDim("\u25C7")} ${bold(ink2(title))}`);
94
- for (const line of message.split(`
95
- `)) {
96
- console.log(` ${ink4("\u2502")} ${line}`);
97
- }
98
- }
99
- function droneStep(text) {
100
- console.log(` ${accent("\u203A")} ${ink(text)}`);
101
- }
102
- function droneInfo(text) {
103
- console.log(` ${cyan("\xB7")} ${ink2(text)}`);
104
- }
105
- function droneCancel(text) {
106
- console.log(` ${red("\u2716")} ${ink3(text)}`);
107
- }
108
- var SELECT_THEME = {
109
- selectedPrefix: (text) => accent(text),
110
- selectedText: (text) => bold(ink(text)),
111
- description: (text) => ink3(text),
112
- scrollInfo: (text) => ink4(text),
113
- noMatch: (text) => ink3(text)
114
- };
115
- async function runMiniTui(build) {
116
- const terminal = new ProcessTerminal;
117
- const tui = new TUI(terminal);
118
- let settled = false;
119
- return await new Promise((resolve) => {
120
- const finish = (result) => {
121
- if (settled)
122
- return;
123
- settled = true;
124
- tui.stop();
125
- resolve(result);
126
- };
127
- build(tui, finish);
128
- tui.start();
129
- });
130
- }
131
- async function droneSelect(input) {
132
- if (!isTty() || input.options.length === 0) {
133
- return input.initialValue ?? input.options[0]?.value ?? null;
134
- }
135
- return runMiniTui((tui, finish) => {
136
- tui.addChild(new Text(` ${accent("\u258D")}${bold(ink(input.message))}`));
137
- const items = input.options.map((option) => ({
138
- value: option.value,
139
- label: option.label,
140
- ...option.hint ? { description: option.hint } : {}
141
- }));
142
- const list = new SelectList(items, Math.min(items.length, 12), SELECT_THEME);
143
- const initialIndex = input.initialValue ? items.findIndex((item) => item.value === input.initialValue) : -1;
144
- if (initialIndex > 0)
145
- list.setSelectedIndex(initialIndex);
146
- list.onSelect = (item) => finish(item.value);
147
- list.onCancel = () => finish(null);
148
- tui.addChild(list);
149
- tui.addChild(new Text(ink4(" \u2191\u2193 navigate \xB7 enter select \xB7 esc cancel")));
150
- tui.setFocus(list);
151
- tui.addInputListener((data) => {
152
- if (matchesKey(data, "ctrl+c") || matchesKey(data, "escape")) {
153
- finish(null);
154
- return { consume: true };
155
- }
156
- return;
157
- });
158
- });
159
- }
160
- async function droneText(input) {
161
- if (!isTty())
162
- return input.initialValue ?? null;
163
- return runMiniTui((tui, finish) => {
164
- tui.addChild(new Text(` ${accent("\u258D")}${bold(ink(input.message))}${input.placeholder ? ink4(` (${input.placeholder})`) : ""}`));
165
- const field = new Input;
166
- if (input.initialValue)
167
- field.setValue(input.initialValue);
168
- field.onSubmit = (value) => finish(value);
169
- field.onEscape = () => finish(null);
170
- tui.addChild(field);
171
- tui.addChild(new Text(ink4(" enter submit \xB7 esc cancel")));
172
- tui.setFocus(field);
173
- tui.addInputListener((data) => {
174
- if (matchesKey(data, "ctrl+c")) {
175
- finish(null);
176
- return { consume: true };
177
- }
178
- return;
179
- });
180
- });
181
- }
182
- async function droneConfirm(input) {
183
- const answer = await droneSelect({
184
- message: input.message,
185
- options: [
186
- { value: "yes", label: "Yes" },
187
- { value: "no", label: "No" }
188
- ],
189
- initialValue: input.initialValue === false ? "no" : "yes"
190
- });
191
- return answer === null ? null : answer === "yes";
192
- }
193
-
194
- // packages/cli/src/commands/task-report-bug.ts
195
- import pc from "picocolors";
196
-
197
- // packages/cli/src/runner.ts
198
- import { EventBus } from "@rig/runtime/control-plane/runtime/events";
199
- import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
200
- import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
201
- import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
202
-
203
- class CliError extends RuntimeCliError {
204
- hint;
205
- constructor(message, exitCode = 1, options = {}) {
206
- super(message, exitCode);
207
- if (options.hint?.trim()) {
208
- this.hint = options.hint.trim();
209
- }
210
- }
211
- }
212
- function formatCommand(parts) {
213
- return parts.map((part) => /[^a-zA-Z0-9_./:-]/.test(part) ? JSON.stringify(part) : part).join(" ");
214
- }
215
- function takeFlag(args, flag) {
216
- const rest = [];
217
- let value = false;
218
- for (const arg of args) {
219
- if (arg === flag) {
220
- value = true;
221
- continue;
222
- }
223
- rest.push(arg);
224
- }
225
- return { value, rest };
226
- }
227
- function takeOption(args, option) {
228
- const rest = [];
229
- let value;
230
- for (let index = 0;index < args.length; index += 1) {
231
- const current = args[index];
232
- if (current === option) {
233
- const next = args[index + 1];
234
- if (!next || next.startsWith("-")) {
235
- throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
236
- }
237
- value = next;
238
- index += 1;
239
- continue;
240
- }
241
- if (current !== undefined) {
242
- rest.push(current);
243
- }
244
- }
245
- return { value, rest };
246
- }
247
- function requireNoExtraArgs(args, usage) {
248
- if (args.length > 0) {
249
- throw new CliError(`Unexpected arguments: ${args.join(" ")}
250
- Usage: ${usage}`);
251
- }
252
- }
253
-
254
- // packages/cli/src/commands/task-report-bug.ts
255
- import {
256
- appendJsonlRecord,
257
- readJsonFile,
258
- readJsonlFile,
259
- writeJsonFile
260
- } from "@rig/runtime/control-plane/authority-files";
261
- import { runCapture, unique } from "@rig/runtime/control-plane/native/utils";
262
-
263
- // packages/cli/src/commands/_paths.ts
264
- import { resolve } from "path";
265
- import { resolveMonorepoRoot } from "@rig/runtime/control-plane/native/utils";
266
- function resolveControlPlaneMonorepoRoot(projectRoot) {
267
- return resolveMonorepoRoot(projectRoot);
268
- }
269
- function resolveControlPlaneTaskConfigPath(projectRoot) {
270
- return resolve(resolveControlPlaneMonorepoRoot(projectRoot), ".rig", "task-config.json");
271
- }
272
-
273
- // packages/cli/src/report-bug.ts
274
- import { copyFileSync, existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
275
- import { basename, extname, join, resolve as resolve2 } from "path";
276
- function slugifyBugTitle(value) {
277
- const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80).replace(/-+$/g, "");
278
- return slug || "bug-report";
279
- }
280
- function defaultBrowserBugProfile(title) {
281
- return `hp-next-${slugifyBugTitle(title)}`;
282
- }
283
- function defaultBrowserBugOptions(environment) {
284
- if (environment === "shared-dev") {
285
- return {
286
- preset: "hp-next-shared",
287
- attachUrl: "http://127.0.0.1:9341",
288
- stateDir: ".tmp/rig-browser/hp-next-shared"
289
- };
290
- }
291
- return {
292
- preset: "hp-next-local",
293
- attachUrl: "http://127.0.0.1:9333",
294
- stateDir: ".tmp/rig-browser/hp-next"
295
- };
296
- }
297
- function createBugReportFiles(input) {
298
- const slug = slugifyBugTitle(input.slug || input.title);
299
- const outputDir = resolve2(input.projectRoot, input.outputRoot);
300
- const reportDir = resolve2(outputDir, slug);
301
- const assetDir = join(reportDir, "assets");
302
- const screenshotDir = join(reportDir, "screenshots");
303
- const taskPath = join(reportDir, "task.md");
304
- const browserRequired = input.browserRequired ?? true;
305
- const browserPath = browserRequired ? join(reportDir, "browser.json") : null;
306
- const manifestPath = join(reportDir, "manifest.json");
307
- if (existsSync(reportDir)) {
308
- if (!input.overwrite) {
309
- throw new Error(`Bug report directory already exists: ${reportDir}`);
310
- }
311
- rmSync(reportDir, { recursive: true, force: true });
312
- }
313
- mkdirSync(assetDir, { recursive: true });
314
- mkdirSync(screenshotDir, { recursive: true });
315
- const copiedScreenshots = copyEvidenceFiles(input.projectRoot, screenshotDir, input.screenshots ?? [], "screenshot");
316
- const copiedAssets = copyEvidenceFiles(input.projectRoot, assetDir, input.assets ?? [], "asset");
317
- const manifestAssets = [
318
- ...copiedScreenshots.map((name) => ({
319
- path: `screenshots/${name}`,
320
- type: mediaTypeForFileName(name)
321
- })),
322
- ...copiedAssets.map((name) => ({
323
- path: `assets/${name}`,
324
- type: mediaTypeForFileName(name)
325
- }))
326
- ];
327
- const browser = browserRequired ? buildBrowserBlock(input) : null;
328
- const manifest = {
329
- schema_version: 1,
330
- slug,
331
- issue_id: input.issueId ?? null,
332
- title: input.title,
333
- environment: input.environment,
334
- url: input.url,
335
- viewport: input.viewport,
336
- task_file: "task.md",
337
- browser_file: browserRequired ? "browser.json" : null,
338
- screenshots: copiedScreenshots.map((name) => `screenshots/${name}`),
339
- assets: manifestAssets,
340
- created_at: new Date().toISOString(),
341
- platform: process.platform,
342
- arch: process.arch
343
- };
344
- writeFileSync(join(assetDir, "README.md"), buildAssetReadme(input.title), "utf8");
345
- writeFileSync(join(screenshotDir, "README.md"), buildScreenshotReadme(input.title), "utf8");
346
- if (browserPath && browser) {
347
- writeFileSync(browserPath, `${JSON.stringify(browser, null, 2)}
348
- `, "utf8");
349
- }
350
- writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}
351
- `, "utf8");
352
- writeFileSync(taskPath, buildBugReportMarkdown(input, browser, copiedScreenshots, copiedAssets), "utf8");
353
- return {
354
- slug,
355
- reportDir,
356
- taskPath,
357
- browserPath,
358
- manifestPath,
359
- assetDir,
360
- screenshotDir,
361
- copiedAssets,
362
- copiedScreenshots
363
- };
364
- }
365
- function buildBrowserBlock(input) {
366
- return {
367
- browser: {
368
- required: true,
369
- preset: input.preset,
370
- profile: input.profile,
371
- attach_url: input.attachUrl,
372
- state_dir: input.stateDir,
373
- dev_command: "bun run app:dev:browser:hp-next",
374
- launch_command: "bun run app:start:browser:hp-next",
375
- check_command: "bun run app:check:browser:hp-next",
376
- e2e_command: "bun run app:e2e:browser:hp-next",
377
- mode: input.mode
378
- }
379
- };
380
- }
381
- function buildBugReportMarkdown(input, browser, screenshots, assets) {
382
- const browserRequired = input.browserRequired ?? true;
383
- const assetEntries = [
384
- ...screenshots.map((name) => ({ name, path: `screenshots/${name}` })),
385
- ...assets.map((name) => ({ name, path: `assets/${name}` }))
386
- ];
387
- const assetLines = assetEntries.length > 0 ? assetEntries.map(({ name, path }) => formatAssetMarkdown(name, path)) : ["- No assets attached yet. Drag screenshots or videos into `assets/` and link them here."];
388
- const evidenceLines = input.evidence.length > 0 ? input.evidence.map((item) => `- ${item}`) : ["- Console errors: not captured yet.", "- Failed network requests: not captured yet."];
389
- return [
390
- `# ${input.title}`,
391
- "",
392
- ...input.issueId ? ["## Task", "", `Task ID: \`${input.issueId}\``, ""] : [],
393
- "## Summary",
394
- "",
395
- input.summary || "TODO: describe the user-visible bug.",
396
- "",
397
- ...browser ? [
398
- "## Browser Block",
399
- "",
400
- "```json",
401
- JSON.stringify(browser, null, 2),
402
- "```",
403
- ""
404
- ] : [],
405
- "## Environment",
406
- "",
407
- `- URL: \`${input.url}\``,
408
- `- Environment: \`${input.environment}\``,
409
- ...browserRequired ? [
410
- `- Preset: \`${input.preset}\``,
411
- `- Profile: \`${input.profile}\``,
412
- `- Viewport: \`${input.viewport}\``
413
- ] : ["- Browser: not required for this report."],
414
- "",
415
- "## Reproduction",
416
- "",
417
- ...numberedLines(input.steps.length > 0 ? input.steps : ["TODO: add reproduction step."]),
418
- "",
419
- "## Expected",
420
- "",
421
- input.expected || "TODO: expected behavior.",
422
- "",
423
- "## Actual",
424
- "",
425
- input.actual || "TODO: actual behavior.",
426
- "",
427
- "## Assets",
428
- "",
429
- ...assetLines,
430
- "",
431
- "## Evidence",
432
- "",
433
- ...evidenceLines,
434
- "",
435
- "## Agent Handoff",
436
- "",
437
- ...input.issueId ? [
438
- `- Canonical task assets live under \`artifacts/${input.issueId}/bug-report/\`.`,
439
- `- Start with \`artifacts/${input.issueId}/bug-report/task.md\` and the files in \`artifacts/${input.issueId}/bug-report/assets/\`.`,
440
- browserRequired ? `- Run \`rig task info --task ${input.issueId}\` to confirm browser wiring before debugging.` : `- Run \`rig task info --task ${input.issueId}\` to confirm scope and artifact links before debugging.`
441
- ] : [
442
- "- Draft-only report: convert this into a Rig task before assigning it to an agent run."
443
- ],
444
- "",
445
- "## Validation",
446
- "",
447
- "```bash",
448
- ...input.issueId ? [`rig task validate --task ${input.issueId}`] : [],
449
- ...browserRequired ? [
450
- "bun run app:check:browser:hp-next",
451
- "bun run app:e2e:browser:hp-next"
452
- ] : [],
453
- "```",
454
- ""
455
- ].join(`
456
- `);
457
- }
458
- function numberedLines(items) {
459
- return items.map((item, index) => `${index + 1}. ${item}`);
460
- }
461
- function buildAssetReadme(title) {
462
- return [
463
- `# Evidence Assets For ${title}`,
464
- "",
465
- "Drag and drop screenshots or videos for this bug report into this directory.",
466
- "Use stable, descriptive file names such as `login-loading.png`, `chunk-404-network.png`, or `otp-flow.webm`.",
467
- "Reference images from `../task.md` with Markdown image links and videos with normal file links.",
468
- ""
469
- ].join(`
470
- `);
471
- }
472
- function buildScreenshotReadme(title) {
473
- return [
474
- `# Screenshots For ${title}`,
475
- "",
476
- "Drop legacy screenshot files for this bug report into this directory.",
477
- "Use `assets/` for new screenshot, video, and mixed evidence attachments.",
478
- "Reference screenshots from `../task.md` with Markdown image links.",
479
- ""
480
- ].join(`
481
- `);
482
- }
483
- function copyEvidenceFiles(projectRoot, targetDir, paths, label) {
484
- const copied = [];
485
- const used = new Set;
486
- for (const rawPath of paths.map((path) => path.trim()).filter(Boolean)) {
487
- const source = resolve2(projectRoot, rawPath);
488
- if (!existsSync(source)) {
489
- throw new Error(`${label} does not exist: ${source}`);
490
- }
491
- const targetName = uniqueEvidenceName(rawPath, used, label);
492
- copyFileSync(source, join(targetDir, targetName));
493
- copied.push(targetName);
494
- }
495
- return copied;
496
- }
497
- function uniqueEvidenceName(path, used, fallback) {
498
- const original = sanitizeFileName(basename(path)) || fallback;
499
- const extension = extname(original);
500
- const stem = extension ? original.slice(0, -extension.length) : original;
501
- let candidate = original;
502
- let index = 2;
503
- while (used.has(candidate)) {
504
- candidate = `${stem}-${index}${extension}`;
505
- index += 1;
506
- }
507
- used.add(candidate);
508
- return candidate;
509
- }
510
- function sanitizeFileName(value) {
511
- return value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
512
- }
513
- function mediaTypeForFileName(fileName) {
514
- const extension = extname(fileName).toLowerCase();
515
- if ([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"].includes(extension)) {
516
- return "image";
517
- }
518
- if ([".mp4", ".mov", ".m4v", ".webm", ".avi", ".mkv"].includes(extension)) {
519
- return "video";
520
- }
521
- return "file";
522
- }
523
- function formatAssetMarkdown(name, path) {
524
- return mediaTypeForFileName(name) === "image" ? `- ![${name}](${path})` : `- [${name}](${path})`;
525
- }
526
-
527
- // packages/cli/src/commands/task-report-bug.ts
528
- var BROWSER_BUG_REPORT_VALIDATION = "integration:browser-bug-report-task";
529
- function shouldColorizeCliOutput() {
530
- return Boolean(process.stdout.isTTY) && process.env.NO_COLOR === undefined && process.env.TERM !== "dumb";
531
- }
532
- async function executeTaskReportBug(context, args) {
533
- let pending = args;
534
- const noPromptFlag = takeFlag(pending, "--no-prompt");
535
- pending = noPromptFlag.rest;
536
- const noBeadsFlag = takeFlag(pending, "--no-beads");
537
- pending = noBeadsFlag.rest;
538
- const browserFlag = takeFlag(pending, "--browser");
539
- pending = browserFlag.rest;
540
- const noBrowserFlag = takeFlag(pending, "--no-browser");
541
- pending = noBrowserFlag.rest;
542
- if (browserFlag.value && noBrowserFlag.value) {
543
- throw new CliError("Pass only one of --browser or --no-browser.", 1, { hint: "Re-run `rig task report-bug` with just one of the flags." });
544
- }
545
- const overwriteFlag = takeFlag(pending, "--overwrite");
546
- pending = overwriteFlag.rest;
547
- const titleResult = takeOption(pending, "--title");
548
- pending = titleResult.rest;
549
- const urlResult = takeOption(pending, "--url");
550
- pending = urlResult.rest;
551
- const environmentResult = takeOption(pending, "--environment");
552
- pending = environmentResult.rest;
553
- const presetResult = takeOption(pending, "--preset");
554
- pending = presetResult.rest;
555
- const profileResult = takeOption(pending, "--profile");
556
- pending = profileResult.rest;
557
- const attachUrlResult = takeOption(pending, "--attach-url");
558
- pending = attachUrlResult.rest;
559
- const stateDirResult = takeOption(pending, "--state-dir");
560
- pending = stateDirResult.rest;
561
- const modeResult = takeOption(pending, "--mode");
562
- pending = modeResult.rest;
563
- const viewportResult = takeOption(pending, "--viewport");
564
- pending = viewportResult.rest;
565
- const summaryResult = takeOption(pending, "--summary");
566
- pending = summaryResult.rest;
567
- const stepsResult = takeOption(pending, "--steps");
568
- pending = stepsResult.rest;
569
- const expectedResult = takeOption(pending, "--expected");
570
- pending = expectedResult.rest;
571
- const actualResult = takeOption(pending, "--actual");
572
- pending = actualResult.rest;
573
- const evidenceResult = takeOption(pending, "--evidence");
574
- pending = evidenceResult.rest;
575
- const priorityResult = takeOption(pending, "--priority");
576
- pending = priorityResult.rest;
577
- const labelsResult = takeOption(pending, "--labels");
578
- pending = labelsResult.rest;
579
- const parentResult = takeOption(pending, "--parent");
580
- pending = parentResult.rest;
581
- const assigneeResult = takeOption(pending, "--assignee");
582
- pending = assigneeResult.rest;
583
- const ownerResult = takeOption(pending, "--owner");
584
- pending = ownerResult.rest;
585
- const screenshotResult = takeRepeatedOption(pending, "--screenshot");
586
- pending = screenshotResult.rest;
587
- const assetResult = takeRepeatedOption(pending, "--asset");
588
- pending = assetResult.rest;
589
- const videoResult = takeRepeatedOption(pending, "--video");
590
- pending = videoResult.rest;
591
- const outputRootResult = takeOption(pending, "--output-dir");
592
- pending = outputRootResult.rest;
593
- const slugResult = takeOption(pending, "--slug");
594
- pending = slugResult.rest;
595
- requireNoExtraArgs(pending, "rig report-bug [--no-prompt] [--no-beads] [--browser|--no-browser] --title <text> --url <url> [--asset <dragged-file>]");
596
- let draft = {
597
- outputRoot: outputRootResult.value || "",
598
- title: titleResult.value,
599
- url: urlResult.value,
600
- browserRequired: !noBrowserFlag.value,
601
- environment: environmentResult.value || "local",
602
- preset: presetResult.value,
603
- profile: profileResult.value,
604
- attachUrl: attachUrlResult.value,
605
- stateDir: stateDirResult.value,
606
- mode: modeResult.value || "persistent",
607
- viewport: viewportResult.value || "1440x900",
608
- summary: summaryResult.value,
609
- steps: splitPromptList(stepsResult.value),
610
- expected: expectedResult.value,
611
- actual: actualResult.value,
612
- evidence: splitPromptList(evidenceResult.value),
613
- priority: priorityResult.value || "P2",
614
- labels: splitLabelList(labelsResult.value),
615
- parent: parentResult.value,
616
- assignee: assigneeResult.value,
617
- owner: ownerResult.value,
618
- screenshots: screenshotResult.values.flatMap(splitCliAssetOption),
619
- assets: [...assetResult.values, ...videoResult.values].flatMap(splitCliAssetOption),
620
- slug: slugResult.value,
621
- overwrite: overwriteFlag.value,
622
- createBeadsTask: !noBeadsFlag.value
623
- };
624
- const shouldPrompt = !noPromptFlag.value;
625
- if (shouldPrompt) {
626
- if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
627
- throw new CliError("Interactive bug reporting requires a TTY in text mode. Pass --no-prompt with flags for automation.");
628
- }
629
- draft = await promptForBugReportDetails(draft);
630
- }
631
- const title = requireReportBugValue(draft.title, "--title");
632
- const url = requireReportBugValue(draft.url, "--url");
633
- const defaults = defaultBrowserBugOptions(draft.environment);
634
- const profile = draft.profile || defaultBrowserBugProfile(title);
635
- const baseInput = {
636
- projectRoot: context.projectRoot,
637
- outputRoot: draft.outputRoot || "docs/bug-reports",
638
- title,
639
- url,
640
- browserRequired: draft.browserRequired,
641
- environment: draft.environment,
642
- preset: draft.preset || defaults.preset,
643
- profile,
644
- attachUrl: draft.attachUrl || defaults.attachUrl,
645
- stateDir: draft.stateDir || defaults.stateDir,
646
- mode: draft.mode,
647
- viewport: draft.viewport,
648
- summary: draft.summary || "TODO: describe the user-visible bug.",
649
- steps: draft.steps,
650
- expected: draft.expected || "TODO: expected behavior.",
651
- actual: draft.actual || "TODO: actual behavior.",
652
- evidence: draft.evidence,
653
- screenshots: resolveBugReportDroppedFiles(context.projectRoot, draft.screenshots),
654
- assets: resolveBugReportDroppedFiles(context.projectRoot, draft.assets),
655
- slug: draft.slug,
656
- overwrite: draft.overwrite
657
- };
658
- validateBugReportInputFiles(baseInput);
659
- if (context.dryRun) {
660
- const slug = draft.createBeadsTask ? "<new-task-id>/bug-report" : slugifyBugTitle(baseInput.slug || baseInput.title);
661
- const reportDir = draft.createBeadsTask ? resolve3(resolveControlPlaneMonorepoRoot(context.projectRoot), "artifacts", slug) : resolve3(context.projectRoot, baseInput.outputRoot, slug);
662
- if (context.outputMode === "text") {
663
- console.log(draft.createBeadsTask ? `Would create beads task and assets: ${reportDir}` : `Would create bug report: ${reportDir}`);
664
- }
665
- return {
666
- ok: true,
667
- group: "task",
668
- command: "report-bug",
669
- details: { dryRun: true, slug, reportDir }
670
- };
671
- }
672
- const monorepoRoot = draft.createBeadsTask ? resolveControlPlaneMonorepoRoot(context.projectRoot) : null;
673
- const issueCreation = monorepoRoot ? createBugReportBeadsTask(monorepoRoot, baseInput, draft) : null;
674
- const issueId = issueCreation?.issueId ?? null;
675
- const input = {
676
- ...baseInput,
677
- projectRoot: monorepoRoot ?? context.projectRoot,
678
- outputRoot: issueId ? `artifacts/${issueId}` : baseInput.outputRoot,
679
- slug: issueId ? "bug-report" : baseInput.slug,
680
- issueId: issueId ?? undefined
681
- };
682
- const result = createBugReportFiles(input);
683
- const reportRelativePath = issueId ? `artifacts/${issueId}/bug-report/task.md` : null;
684
- const taskConfigPath = issueId ? upsertBugReportTaskConfig(context.projectRoot, issueId, input, reportRelativePath) : null;
685
- if (issueCreation && monorepoRoot && reportRelativePath) {
686
- updateBugReportBeadsTask(monorepoRoot, issueCreation, readFileSync(result.taskPath, "utf8"), reportRelativePath, input);
687
- }
688
- if (context.outputMode === "text") {
689
- console.log(issueId ? `Bug task created: ${issueId}` : "Draft bug report created");
690
- console.log(`Bug report assets: ${result.reportDir}`);
691
- console.log(`Task: ${result.taskPath}`);
692
- if (result.browserPath) {
693
- console.log(`Browser block: ${result.browserPath}`);
694
- } else {
695
- console.log("Browser block: not requested");
696
- }
697
- console.log(`Evidence assets: ${result.assetDir}`);
698
- if (taskConfigPath) {
699
- console.log(`Task config: ${taskConfigPath}`);
700
- console.log(`Run: rig task info --task ${issueId}`);
701
- }
702
- }
703
- return {
704
- ok: true,
705
- group: "task",
706
- command: "report-bug",
707
- details: {
708
- slug: result.slug,
709
- issueId,
710
- reportDir: result.reportDir,
711
- taskPath: result.taskPath,
712
- browserPath: result.browserPath,
713
- manifestPath: result.manifestPath,
714
- assetDir: result.assetDir,
715
- assets: result.copiedAssets,
716
- screenshotDir: result.screenshotDir,
717
- screenshots: result.copiedScreenshots,
718
- taskConfigPath,
719
- taskRecordBackend: issueCreation?.backend ?? null
720
- }
721
- };
722
- }
723
- function validateBugReportInputFiles(input) {
724
- const files = [
725
- ...(input.screenshots ?? []).map((path) => ({ path, label: "Screenshot" })),
726
- ...(input.assets ?? []).map((path) => ({ path, label: "Asset" }))
727
- ];
728
- for (const { path, label } of files.map((item) => ({ ...item, path: item.path.trim() })).filter((item) => item.path)) {
729
- const source = resolve3(input.projectRoot, path);
730
- if (!existsSync2(source)) {
731
- throw new CliError(`${label} does not exist: ${source}`, 1, { hint: "Check the path and re-run `rig task report-bug` with an existing file." });
732
- }
733
- }
734
- }
735
- function resolveBugReportDroppedFiles(projectRoot, paths) {
736
- return paths.map((path) => {
737
- const trimmed = path.trim();
738
- return trimmed ? resolve3(projectRoot, trimmed) : trimmed;
739
- });
740
- }
741
- function createBugReportBeadsTask(monorepoRoot, input, draft) {
742
- const labels = buildBugReportLabels(input, draft);
743
- const priority = normalizeBugReportPriority(draft.priority);
744
- const command = [
745
- "br",
746
- "create",
747
- "--title",
748
- input.title,
749
- "--type",
750
- "task",
751
- "--priority",
752
- priority,
753
- "--description",
754
- buildInitialBugReportDescription(input),
755
- "--labels",
756
- labels.join(","),
757
- "--json"
758
- ];
759
- if (draft.parent?.trim()) {
760
- command.push("--parent", draft.parent.trim());
761
- }
762
- if (draft.assignee?.trim()) {
763
- command.push("--assignee", draft.assignee.trim());
764
- }
765
- if (draft.owner?.trim()) {
766
- command.push("--owner", draft.owner.trim());
767
- }
768
- const issue = tryRunBrJson(monorepoRoot, command);
769
- if (!issue.ok) {
770
- if (!isRecoverableBeadsJsonlFailure(issue.error)) {
771
- throw new CliError(issue.error);
772
- }
773
- return {
774
- issueId: generateBugReportIssueId(monorepoRoot, input.title),
775
- backend: "jsonl",
776
- labels,
777
- priority
778
- };
779
- }
780
- const issueId = issue.value.id?.trim();
781
- if (!issueId) {
782
- throw new CliError("br create did not return an issue id.", 1, { hint: "Re-run `rig task report-bug`; if it persists, file the bug via your task source directly." });
783
- }
784
- return {
785
- issueId,
786
- backend: "br",
787
- labels,
788
- priority
789
- };
790
- }
791
- function updateBugReportBeadsTask(monorepoRoot, issueCreation, taskMarkdown, externalRef, input) {
792
- if (issueCreation.backend === "jsonl") {
793
- appendBugReportJsonlTask(monorepoRoot, issueCreation, taskMarkdown, externalRef, input);
794
- return;
795
- }
796
- runBrJson(monorepoRoot, [
797
- "br",
798
- "update",
799
- issueCreation.issueId,
800
- "--description",
801
- taskMarkdown,
802
- "--acceptance-criteria",
803
- buildBugReportAcceptanceCriteria(input),
804
- "--external-ref",
805
- externalRef,
806
- "--json"
807
- ]);
808
- }
809
- function appendBugReportJsonlTask(monorepoRoot, issueCreation, taskMarkdown, externalRef, input) {
810
- const now = new Date().toISOString();
811
- appendJsonlRecord(resolve3(monorepoRoot, ".beads", "issues.jsonl"), {
812
- id: issueCreation.issueId,
813
- title: input.title,
814
- description: taskMarkdown,
815
- acceptance_criteria: buildBugReportAcceptanceCriteria(input),
816
- status: "open",
817
- priority: priorityToNumber(issueCreation.priority),
818
- issue_type: "task",
819
- created_at: now,
820
- created_by: process.env.USER || process.env.USERNAME || "rig",
821
- updated_at: now,
822
- external_ref: externalRef,
823
- source_repo: ".",
824
- compaction_level: 0,
825
- original_size: 0,
826
- labels: issueCreation.labels
827
- });
828
- }
829
- function upsertBugReportTaskConfig(projectRoot, issueId, input, reportRelativePath) {
830
- const taskConfigPath = resolveControlPlaneTaskConfigPath(projectRoot);
831
- const browserRequired = input.browserRequired ?? true;
832
- const entry = {
833
- description: [
834
- "Browser bug report generated by `rig report-bug`.",
835
- "",
836
- `Canonical report: \`${reportRelativePath}\``
837
- ].join(`
838
- `),
839
- acceptance_criteria: buildBugReportAcceptanceCriteria(input),
840
- scope: [
841
- `artifacts/${issueId}/bug-report/**`,
842
- "hp-next/**",
843
- "microservices/hp-next-frontend/**",
844
- "humoongate/humanity/hp-next/**"
845
- ],
846
- role: "verifier",
847
- criticality: "normal"
848
- };
849
- if (browserRequired) {
850
- entry.browser = {
851
- required: true,
852
- preset: input.preset,
853
- profile: input.profile,
854
- attach_url: input.attachUrl,
855
- state_dir: input.stateDir,
856
- dev_command: "bun run app:dev:browser:hp-next",
857
- launch_command: "bun run app:start:browser:hp-next",
858
- check_command: "bun run app:check:browser:hp-next",
859
- e2e_command: "bun run app:e2e:browser:hp-next",
860
- mode: input.mode
861
- };
862
- entry.validation = [BROWSER_BUG_REPORT_VALIDATION];
863
- }
864
- appendTaskConfigEntryPreservingText(taskConfigPath, issueId, entry);
865
- return taskConfigPath;
866
- }
867
- function appendTaskConfigEntryPreservingText(taskConfigPath, issueId, entry) {
868
- const raw = existsSync2(taskConfigPath) ? readFileSync(taskConfigPath, "utf8") : `{}
869
- `;
870
- if (raw.includes(`"${issueId}"`)) {
871
- const parsed = readJsonFile(taskConfigPath, {});
872
- parsed[issueId] = entry;
873
- writeJsonFile(taskConfigPath, parsed);
874
- return;
875
- }
876
- const trimmed = raw.trim();
877
- const serializedEntry = `${JSON.stringify(issueId)}: ${JSON.stringify(entry, null, 2)}`.split(`
878
- `).map((line) => ` ${line}`).join(`
879
- `);
880
- if (!trimmed || trimmed === "{}") {
881
- writeFileSync2(taskConfigPath, `{
882
- ${serializedEntry}
883
- }
884
- `, "utf8");
885
- return;
886
- }
887
- const insertAt = raw.lastIndexOf("}");
888
- if (insertAt < 0) {
889
- throw new CliError(`Invalid task config JSON object: ${taskConfigPath}`, 1, { hint: "Fix or reset that file (it must be a JSON object), then re-run `rig task report-bug`." });
890
- }
891
- const before = raw.slice(0, insertAt).replace(/\s*$/, "");
892
- const after = raw.slice(insertAt + 1).trim();
893
- if (after) {
894
- throw new CliError(`Invalid trailing content in task config: ${taskConfigPath}`, 1, { hint: "Remove the trailing content so the file is a single JSON object, then retry." });
895
- }
896
- const comma = before.trim() === "{" ? "" : ",";
897
- writeFileSync2(taskConfigPath, `${before}${comma}
898
- ${serializedEntry}
899
- }
900
- `, "utf8");
901
- }
902
- function buildBugReportLabels(input, draft) {
903
- return unique([
904
- "bug",
905
- "bug-report",
906
- ...input.browserRequired === false ? [] : ["browser-required"],
907
- "hp-next",
908
- `env:${input.environment}`,
909
- "role:verifier",
910
- ...draft.labels
911
- ].map(sanitizeBugReportLabel).filter(Boolean));
912
- }
913
- function buildInitialBugReportDescription(input) {
914
- return [
915
- input.summary || "Browser bug report generated by `rig report-bug`.",
916
- "",
917
- "The detailed task assets are written after task creation under `artifacts/<task-id>/bug-report/`."
918
- ].join(`
919
- `);
920
- }
921
- function buildBugReportAcceptanceCriteria(input) {
922
- if (input.browserRequired === false) {
923
- return [
924
- "1. Reproduce the bug using the provided steps and task assets.",
925
- "2. Fix the underlying product or server issue without removing the reported user flow.",
926
- "3. Preserve all provided task assets and evidence in the task artifact directory.",
927
- `4. Verify the target URL or endpoint behaves correctly: ${input.url}`,
928
- "5. Run the generated task validation when available, or document the manual verification performed."
929
- ].join(`
930
- `);
931
- }
932
- return [
933
- "1. Reproduce the bug with Rig Browser using the browser block in the task assets.",
934
- "2. Fix the underlying product or server issue without removing the reported user flow.",
935
- "3. Preserve all provided screenshots and evidence in the task artifact directory.",
936
- `4. Verify the target URL renders correctly: ${input.url}`,
937
- "5. Run the generated task validation plus `bun run app:check:browser:hp-next` and `bun run app:e2e:browser:hp-next`, or document why either browser command cannot run."
938
- ].join(`
939
- `);
940
- }
941
- function normalizeBugReportPriority(priority) {
942
- const normalized = priority.trim().toUpperCase();
943
- return /^P?[0-4]$/.test(normalized) ? normalized : "P2";
944
- }
945
- function priorityToNumber(priority) {
946
- const normalized = normalizeBugReportPriority(priority).replace(/^P/, "");
947
- return Number.parseInt(normalized, 10);
948
- }
949
- function sanitizeBugReportLabel(label) {
950
- return label.trim().toLowerCase().replace(/\s+/g, "-");
951
- }
952
- function generateBugReportIssueId(monorepoRoot, title) {
953
- const existingIds = new Set(readJsonlFile(resolve3(monorepoRoot, ".beads", "issues.jsonl")).filter((entry) => !!entry && typeof entry === "object").map((entry) => entry.id).filter((id) => typeof id === "string" && id.trim().length > 0));
954
- const base = slugifyBugTitle(title).replace(/-/g, "").slice(0, 8) || "bug";
955
- for (let attempt = 0;attempt < 10; attempt += 1) {
956
- const suffix = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
957
- const id = `bd-${base}-${suffix}`.slice(0, 48);
958
- if (!existingIds.has(id)) {
959
- return id;
960
- }
961
- }
962
- throw new CliError("Could not generate a unique bug task id.", 1, { hint: "Re-run `rig task report-bug`; pass an explicit title to vary the generated id." });
963
- }
964
- function isRecoverableBeadsJsonlFailure(message) {
965
- return /Invalid JSON|missing field|JSONL is newer than DB|stale database/i.test(message);
966
- }
967
- function runBrJson(cwd, command) {
968
- const result = tryRunBrJson(cwd, command);
969
- if (!result.ok) {
970
- throw new CliError(result.error);
971
- }
972
- return result.value;
973
- }
974
- function tryRunBrJson(cwd, command) {
975
- const result = runCapture(command, cwd);
976
- if (result.exitCode !== 0) {
977
- const detail = [result.stderr.trim(), result.stdout.trim()].filter(Boolean).join(`
978
- `);
979
- return {
980
- ok: false,
981
- error: `beads command failed: ${formatCommand(command)}${detail ? `
982
- ${detail}` : ""}`
983
- };
984
- }
985
- try {
986
- return { ok: true, value: JSON.parse(result.stdout) };
987
- } catch (error) {
988
- return {
989
- ok: false,
990
- error: `beads command did not return JSON: ${formatCommand(command)}
991
- ${result.stdout.trim() || String(error)}`
992
- };
993
- }
994
- }
995
- async function promptForBugReportDetails(initial) {
996
- droneIntro("rig report-bug");
997
- droneNote([
998
- "Creates an agent-ready issue with copied evidence files.",
999
- "When prompted for assets, drag screenshots/videos into the terminal and press Enter."
1000
- ].join(`
1001
- `), "Bug report wizard");
1002
- droneStep("1/5 Incident");
1003
- droneNote(bugPromptExampleText("incident"), "Input examples");
1004
- const title = await promptBugText("Bug title", initial.title, {
1005
- required: true,
1006
- placeholder: "Login page never leaves loading"
1007
- });
1008
- const url = await promptBugText("URL to reproduce", initial.url, {
1009
- required: true,
1010
- placeholder: "https://dev.rig.hptestingsite.com/login?next=%2F"
1011
- });
1012
- const environmentChoice = await promptBugSelect("Environment", [
1013
- { value: "local", label: "local", hint: "local hp-next / service fabric" },
1014
- { value: "shared-dev", label: "shared-dev", hint: "shared deployed dev surface" },
1015
- { value: "staging", label: "staging" },
1016
- { value: "production", label: "production" },
1017
- { value: "custom", label: "custom", hint: "enter a custom environment name" }
1018
- ], normalizeBugEnvironmentChoice(initial.environment));
1019
- const environment = environmentChoice === "custom" ? await promptBugText("Custom environment", ["local", "shared-dev", "staging", "production", "custom"].includes(initial.environment) ? undefined : initial.environment, { required: true, placeholder: "qa-dev" }) : environmentChoice;
1020
- const defaults = defaultBrowserBugOptions(environment);
1021
- const summary = await promptBugText("Short summary", initial.summary, {
1022
- placeholder: "Email field never appears after navigating from credentials."
1023
- });
1024
- droneStep("2/5 Reproduction");
1025
- droneNote(bugPromptExampleText("reproduction"), "Input examples");
1026
- const steps = splitPromptList(await promptBugText("Repro steps (semicolon-separated)", initial.steps.join("; "), {
1027
- placeholder: "Open /login; Enter qa@example.com; Press Continue"
1028
- }));
1029
- const expected = await promptBugText("Expected behavior", initial.expected, {
1030
- placeholder: "OTP form is visible and accepts a code."
1031
- });
1032
- const actual = await promptBugText("Actual behavior", initial.actual, {
1033
- placeholder: "Loading shell stays forever."
1034
- });
1035
- droneStep("3/5 Evidence");
1036
- droneNote(bugPromptExampleText("evidence"), "Input examples");
1037
- const evidence = splitPromptList(await promptBugText("Evidence notes (console/network/API; semicolon-separated)", initial.evidence.join("; "), {
1038
- placeholder: "Console: ChunkLoadError for credentials route; Network: /assets/app.js 404"
1039
- }));
1040
- const assets = splitDroppedAssetList(await promptBugText("Drag screenshots/videos here (Enter to skip)", initial.assets.map(formatDroppedAssetDefault).join(" "), { placeholder: "/Users/me/Desktop/login-loading.png /Users/me/Desktop/otp-flow.webm" }));
1041
- droneStep("4/5 Routing");
1042
- droneNote(bugPromptExampleText("routing"), "Input examples");
1043
- const priority = await promptBugSelect("Priority", [
1044
- { value: "P0", label: "P0", hint: "urgent / blocking" },
1045
- { value: "P1", label: "P1", hint: "high" },
1046
- { value: "P2", label: "P2", hint: "normal default" },
1047
- { value: "P3", label: "P3", hint: "low" },
1048
- { value: "P4", label: "P4", hint: "backlog" }
1049
- ], normalizeBugReportPriority(initial.priority || "P2"));
1050
- const labels = splitLabelList(await promptBugText("Extra labels, comma- or semicolon-separated", initial.labels.join(", "), {
1051
- placeholder: "auth, otp, hp-next"
1052
- }));
1053
- const parent = await promptBugText("Parent task or epic id (optional)", initial.parent, {
1054
- placeholder: "bd-auth-epic-123"
1055
- });
1056
- droneStep("5/5 Browser");
1057
- droneNote(bugPromptExampleText("browser"), "Input examples");
1058
- const browserRequired = await promptBugConfirm("Does this task need Rig Browser wiring?", initial.browserRequired);
1059
- let viewport = initial.viewport || "1440x900";
1060
- let profile = initial.profile;
1061
- if (browserRequired) {
1062
- droneNote([
1063
- "A profile is the browser state bucket: cookies, localStorage, auth session, and cache.",
1064
- "Use a bug-specific profile for clean login/OTP runs; use a shared profile only when saved auth matters."
1065
- ].join(`
1066
- `), "Rig Browser profile");
1067
- viewport = await promptBugText("Viewport", viewport, { placeholder: "1440x900" });
1068
- profile = await promptBugText("Rig Browser profile", initial.profile || (title ? defaultBrowserBugProfile(title) : undefined), { placeholder: "hp-next-login-loading-clean" });
1069
- } else {
1070
- droneInfo("Browser wiring skipped. The task will keep assets and steps, but no browser block or browser validation.");
1071
- }
1072
- const outputRoot = initial.createBeadsTask ? initial.outputRoot : await promptBugText("Output directory", initial.outputRoot || "docs/bug-reports");
1073
- const overwrite = await promptBugConfirm("Overwrite if report exists?", initial.overwrite);
1074
- droneOutro("Bug report input captured.");
1075
- return {
1076
- ...initial,
1077
- outputRoot,
1078
- title,
1079
- url,
1080
- browserRequired,
1081
- environment,
1082
- preset: initial.preset || defaults.preset,
1083
- profile,
1084
- attachUrl: initial.attachUrl || defaults.attachUrl,
1085
- stateDir: initial.stateDir || defaults.stateDir,
1086
- viewport,
1087
- summary,
1088
- steps,
1089
- expected,
1090
- actual,
1091
- evidence,
1092
- screenshots: initial.screenshots,
1093
- assets,
1094
- priority,
1095
- labels,
1096
- parent: parent || undefined,
1097
- overwrite
1098
- };
1099
- }
1100
- async function promptBugText(message, defaultValue, options = {}) {
1101
- for (;; ) {
1102
- const result = await droneText({
1103
- message,
1104
- ...options.placeholder ? { placeholder: options.placeholder } : {},
1105
- ...defaultValue?.trim() ? { initialValue: defaultValue } : {}
1106
- });
1107
- if (result === null) {
1108
- droneCancel("Bug report cancelled.");
1109
- throw new CliError("Bug report cancelled by user.");
1110
- }
1111
- const invalid = options.required ? validateRequiredBugPromptValue(result, message) : undefined;
1112
- if (!invalid)
1113
- return result.trim();
1114
- droneInfo(invalid);
1115
- }
1116
- }
1117
- function validateRequiredBugPromptValue(value, label) {
1118
- return value?.trim() ? undefined : `${label} is required.`;
1119
- }
1120
- function bugPromptExampleText(section, options = {}) {
1121
- const c = pc.createColors(options.color ?? shouldColorizeCliOutput());
1122
- const label = (value) => c.bold(c.cyan(value));
1123
- const example = (value) => c.green(value);
1124
- const note = (value) => c.dim(value);
1125
- const rows = {
1126
- incident: [
1127
- ["Bug title", "Login page never leaves loading", "short user-visible failure"],
1128
- ["URL", "https://dev.rig.hptestingsite.com/login?next=%2F", "exact route or endpoint"],
1129
- ["Summary", "Email field never appears after navigating from credentials", "one sentence"]
1130
- ],
1131
- reproduction: [
1132
- ["Steps", "Open /login; Enter qa@example.com; Press Continue", "semicolon-separated"],
1133
- ["Expected", "OTP form is visible and accepts a code"],
1134
- ["Actual", "Loading shell stays forever"]
1135
- ],
1136
- evidence: [
1137
- ["Evidence", "Console: ChunkLoadError for credentials route", "console/network/API notes"],
1138
- ["Assets", "/Users/me/Desktop/login-loading.png /Users/me/Desktop/otp-flow.webm", "drag files into terminal"]
1139
- ],
1140
- routing: [
1141
- ["Priority", "P1 when login is blocked for all testers"],
1142
- ["Labels", "auth, otp, hp-next", "comma- or semicolon-separated"],
1143
- ["Parent", "bd-auth-epic-123", "optional"]
1144
- ],
1145
- browser: [
1146
- ["Browser needed", "Yes for console/network/auth/visual bugs"],
1147
- ["Browser not needed", "No for API-only/server/config bugs"],
1148
- ["Profile", "hp-next-login-loading-clean", "clean, bug-specific auth state"],
1149
- ["Viewport", "1440x900", "match the failing screen size"]
1150
- ]
1151
- };
1152
- return rows[section].map(([name, value, detail]) => {
1153
- const suffix = detail ? ` ${note(`(${detail})`)}` : "";
1154
- return `${label(name)}: ${example(value)}${suffix}`;
1155
- }).join(`
1156
- `);
1157
- }
1158
- async function promptBugConfirm(message, initialValue) {
1159
- const result = await droneConfirm({ message, initialValue });
1160
- if (result === null) {
1161
- droneCancel("Bug report cancelled.");
1162
- throw new CliError("Bug report cancelled by user.");
1163
- }
1164
- return result;
1165
- }
1166
- async function promptBugSelect(message, options, initialValue) {
1167
- const result = await droneSelect({ message, options, initialValue });
1168
- if (result === null) {
1169
- droneCancel("Bug report cancelled.");
1170
- throw new CliError("Bug report cancelled by user.");
1171
- }
1172
- return result;
1173
- }
1174
- function normalizeBugEnvironmentChoice(environment) {
1175
- return environment === "local" || environment === "shared-dev" || environment === "staging" || environment === "production" ? environment : "custom";
1176
- }
1177
- function requireReportBugValue(value, option) {
1178
- const normalized = value?.trim();
1179
- if (!normalized) {
1180
- throw new CliError(`Missing ${option}. Run interactively or pass ${option} <value>.`);
1181
- }
1182
- return normalized;
1183
- }
1184
- function splitPromptList(value) {
1185
- if (!value) {
1186
- return [];
1187
- }
1188
- return value.split(/[;\n]/).map((item) => item.trim()).filter(Boolean);
1189
- }
1190
- function splitCliAssetOption(value) {
1191
- return splitDroppedAssetList(value, { splitWhitespace: false });
1192
- }
1193
- function formatDroppedAssetDefault(value) {
1194
- return value.replace(/([\\\s;"'])/g, "\\$1");
1195
- }
1196
- function splitDroppedAssetList(value, options = {}) {
1197
- if (!value) {
1198
- return [];
1199
- }
1200
- const splitWhitespace = options.splitWhitespace ?? true;
1201
- const values = [];
1202
- let current = "";
1203
- let quote = null;
1204
- let escaped = false;
1205
- const push = () => {
1206
- const normalized = current.trim();
1207
- if (normalized) {
1208
- values.push(normalized);
1209
- }
1210
- current = "";
1211
- };
1212
- for (const char of value) {
1213
- if (escaped) {
1214
- current += char;
1215
- escaped = false;
1216
- continue;
1217
- }
1218
- if (char === "\\") {
1219
- escaped = true;
1220
- continue;
1221
- }
1222
- if (quote) {
1223
- if (char === quote) {
1224
- quote = null;
1225
- } else {
1226
- current += char;
1227
- }
1228
- continue;
1229
- }
1230
- if (char === "'" || char === '"') {
1231
- quote = char;
1232
- continue;
1233
- }
1234
- if (char === ";" || char === `
1235
- ` || splitWhitespace && /\s/.test(char)) {
1236
- push();
1237
- continue;
1238
- }
1239
- current += char;
1240
- }
1241
- if (escaped) {
1242
- current += "\\";
1243
- }
1244
- push();
1245
- return values;
1246
- }
1247
- function splitLabelList(value) {
1248
- if (!value) {
1249
- return [];
1250
- }
1251
- return value.split(/[;,\n]/).map(sanitizeBugReportLabel).filter(Boolean);
1252
- }
1253
- function takeRepeatedOption(args, option) {
1254
- const rest = [];
1255
- const values = [];
1256
- for (let index = 0;index < args.length; index += 1) {
1257
- const current = args[index];
1258
- if (current === option) {
1259
- const next = args[index + 1];
1260
- if (!next || next.startsWith("-")) {
1261
- throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
1262
- }
1263
- values.push(next);
1264
- index += 1;
1265
- continue;
1266
- }
1267
- if (current !== undefined) {
1268
- rest.push(current);
1269
- }
1270
- }
1271
- return { values, rest };
1272
- }
1273
- export {
1274
- validateRequiredBugPromptValue,
1275
- splitDroppedAssetList,
1276
- promptBugText,
1277
- promptBugConfirm,
1278
- formatDroppedAssetDefault,
1279
- executeTaskReportBug,
1280
- bugPromptExampleText
1281
- };