|
| 1 | +# Report Command Design |
| 2 | + |
| 3 | +Create GitHub issues with pre-filled system data from CLI and Web UI. |
| 4 | + |
| 5 | +## Target repositories |
| 6 | + |
| 7 | +| # | Component | GitHub repo | |
| 8 | +|---|-----------|-------------| |
| 9 | +| 1 | nullhub | nullclaw/nullhub | |
| 10 | +| 2 | nullclaw | nullclaw/nullclaw | |
| 11 | +| 3 | nullboiler | nullclaw/NullBoiler | |
| 12 | +| 4 | nulltickets | nullclaw/nulltickets | |
| 13 | +| 5 | nullwatch | nullclaw/nullwatch | |
| 14 | + |
| 15 | +Order is fixed (as listed above) in both CLI and Web UI selectors. |
| 16 | + |
| 17 | +The target list is hardcoded in `report.zig`, independent of `known_components` in `registry.zig`. nullwatch exists as a repo but is not yet in the component registry. |
| 18 | + |
| 19 | +## Report types and labels |
| 20 | + |
| 21 | +| Report type | CLI value | Label(s) | |
| 22 | +|---|---|---| |
| 23 | +| Bug: crash (process exits or hangs) | `bug:crash` | `bug`, `bug:crash` | |
| 24 | +| Bug: behavior (incorrect output/state) | `bug:behavior` | `bug`, `bug:behavior` | |
| 25 | +| Bug: regression (worked before, now fails) | `regression` | `bug`, `regression` | |
| 26 | +| Feature request | `feature` | `enhancement` | |
| 27 | + |
| 28 | +Labels `regression`, `bug:behavior`, `bug:crash` must be created in all 5 repos before launch (one-time script, not committed). |
| 29 | + |
| 30 | +## System data collected automatically |
| 31 | + |
| 32 | +| Field | Source | Notes | |
| 33 | +|---|---|---| |
| 34 | +| nullhub version | `version.zig` (CalVer, e.g. `2026.3.13`) | Always available (compile-time) | |
| 35 | +| Platform | `platform.zig` (e.g. `aarch64-macos`) | Always available (compile-time) | |
| 36 | +| OS version | `uname -s -r` at runtime | Child process call | |
| 37 | +| Installed components + versions | `state.json` via `state.zig` | Versions only, no runtime status — CLI runs without the server | |
| 38 | + |
| 39 | +Feature requests include only nullhub version and platform (intentionally lightweight — system details are less relevant for feature requests). |
| 40 | + |
| 41 | +## Issue body format |
| 42 | + |
| 43 | +### Bug report |
| 44 | + |
| 45 | +```markdown |
| 46 | +### Bug type |
| 47 | + |
| 48 | +<bug type text> |
| 49 | + |
| 50 | +### Description |
| 51 | + |
| 52 | +<user description> |
| 53 | + |
| 54 | +### System information |
| 55 | + |
| 56 | +| Field | Value | |
| 57 | +|---|---| |
| 58 | +| nullhub version | 2026.3.13 | |
| 59 | +| Platform | aarch64-macos | |
| 60 | +| OS version | Darwin 25.1.0 | |
| 61 | + |
| 62 | +### Installed components |
| 63 | + |
| 64 | +| Component | Version | |
| 65 | +|---|---| |
| 66 | +| nullclaw | 2026.3.14 | |
| 67 | +| nullboiler | 2026.3.10 | |
| 68 | +``` |
| 69 | + |
| 70 | +Title: `[Bug]: <description>` |
| 71 | + |
| 72 | +### Feature request |
| 73 | + |
| 74 | +```markdown |
| 75 | +### Summary |
| 76 | + |
| 77 | +<user description> |
| 78 | + |
| 79 | +### System information |
| 80 | + |
| 81 | +| Field | Value | |
| 82 | +|---|---| |
| 83 | +| nullhub version | 2026.3.13 | |
| 84 | +| Platform | aarch64-macos | |
| 85 | +``` |
| 86 | + |
| 87 | +Title: `[Feature]: <description>` |
| 88 | + |
| 89 | +## CLI flow |
| 90 | + |
| 91 | +### Interactive mode |
| 92 | + |
| 93 | +``` |
| 94 | +$ nullhub report |
| 95 | +
|
| 96 | +Where is the problem? |
| 97 | + 1. nullhub |
| 98 | + 2. nullclaw |
| 99 | + 3. nullboiler |
| 100 | + 4. nulltickets |
| 101 | + 5. nullwatch |
| 102 | +> 1 |
| 103 | +
|
| 104 | +Report type? |
| 105 | + 1. Bug: crash (process exits or hangs) |
| 106 | + 2. Bug: behavior (incorrect output/state) |
| 107 | + 3. Bug: regression (worked before, now fails) |
| 108 | + 4. Feature request |
| 109 | +> 2 |
| 110 | +
|
| 111 | +Description: Dashboard shows stale status after restart |
| 112 | +
|
| 113 | +Preview: |
| 114 | +────────────────────────── |
| 115 | +Title: [Bug]: Dashboard shows stale status after restart |
| 116 | +
|
| 117 | +### Bug type |
| 118 | +... |
| 119 | +────────────────────────── |
| 120 | +Submit? [Y/n/e] |
| 121 | +``` |
| 122 | + |
| 123 | +- `Y` / Enter — submit |
| 124 | +- `n` — cancel |
| 125 | +- `e` — open `$EDITOR` to edit the full markdown before submitting. If `$EDITOR` is unset, falls back to `vi`. Writes markdown to a temp file, invokes editor, reads back on exit, deletes temp file. |
| 126 | + |
| 127 | +### Non-interactive mode |
| 128 | + |
| 129 | +``` |
| 130 | +$ nullhub report --repo nullhub --type bug:behavior --message "Dashboard shows stale status" |
| 131 | +``` |
| 132 | + |
| 133 | +Flags: |
| 134 | +- `--repo <name>` — one of: nullhub, nullclaw, nullboiler, nulltickets, nullwatch |
| 135 | +- `--type <type>` — one of: bug:crash, bug:behavior, regression, feature |
| 136 | +- `--message <text>` — one-line description |
| 137 | +- `--yes` — skip confirmation (for scripting) |
| 138 | +- `--dry-run` — show preview and exit without submitting |
| 139 | + |
| 140 | +Non-interactive mode still shows preview and asks for confirmation unless `--yes` is passed. |
| 141 | + |
| 142 | +### TTY detection |
| 143 | + |
| 144 | +If stdin is not a TTY (piped input), all three flags (`--repo`, `--type`, `--message`) are required. If any is missing, print error and exit. No interactive prompts in non-TTY mode. |
| 145 | + |
| 146 | +## Web UI flow |
| 147 | + |
| 148 | +Page at `/report`: |
| 149 | + |
| 150 | +1. Select: repository (dropdown, same order as CLI) |
| 151 | +2. Select: report type (dropdown) |
| 152 | +3. Input: description (single-line text input) |
| 153 | +4. Click "Next" → calls `POST /api/report/preview` → shows preview step with full markdown in an editable textarea |
| 154 | +5. Click "Submit" (calls `POST /api/report` with final markdown) or "Back" to edit inputs |
| 155 | + |
| 156 | +On success: show link to created issue. |
| 157 | +On fallback: show copyable markdown block + hint to install gh. |
| 158 | + |
| 159 | +System data collected on server side — user does not fill system fields manually. |
| 160 | + |
| 161 | +## Submission fallback chain |
| 162 | + |
| 163 | +Each step falls through to the next on failure (non-zero exit, network error, missing tool, etc.): |
| 164 | + |
| 165 | +``` |
| 166 | +1. `gh` in PATH + `gh auth status` succeeds? |
| 167 | + → gh issue create --repo <repo> --title <title> --body <body> --label <labels> |
| 168 | + → Success: return issue URL |
| 169 | +
|
| 170 | +2. Token from `gh auth token`? |
| 171 | + → curl POST https://api.github.com/repos/<repo>/issues |
| 172 | + -H "Authorization: Bearer <token>" |
| 173 | + → Success: return issue URL |
| 174 | +
|
| 175 | +3. $GITHUB_TOKEN env var set? |
| 176 | + → curl POST (same as above with env token) |
| 177 | + → Success: return issue URL |
| 178 | +
|
| 179 | +4. No auth available: |
| 180 | + → Output formatted markdown + hint: |
| 181 | + "Install and authenticate gh CLI to submit automatically: |
| 182 | + https://cli.github.com/" |
| 183 | +``` |
| 184 | + |
| 185 | +Web UI uses the same chain server-side. If all fail, API returns the fallback response and UI renders a copyable block. |
| 186 | + |
| 187 | +## Architecture |
| 188 | + |
| 189 | +### New files |
| 190 | + |
| 191 | +| File | Purpose | |
| 192 | +|---|---| |
| 193 | +| `src/report.zig` | Core module: repo/type enums, system data collection, issue body formatting, submission via fallback chain | |
| 194 | +| `src/report_cli.zig` | Interactive CLI flow: stdin prompts, flag parsing, preview, editor support | |
| 195 | +| `src/api/report.zig` | API endpoint handlers for `POST /api/report` and `POST /api/report/preview` | |
| 196 | +| `ui/src/routes/report/+page.svelte` | Report form page | |
| 197 | + |
| 198 | +### Modified files |
| 199 | + |
| 200 | +| File | Change | |
| 201 | +|---|---| |
| 202 | +| `src/cli.zig` | Add `ReportOptions` struct, `ReportRepo` enum, `ReportType` enum, `report: ReportOptions` to `Command` union, `parseReport()` sub-parser, update `printUsage()` | |
| 203 | +| `src/main.zig` | Add `.report` case to command dispatch switch | |
| 204 | +| `src/server.zig` | Add `POST /api/report` and `POST /api/report/preview` routes | |
| 205 | + |
| 206 | +### Enums and options |
| 207 | + |
| 208 | +```zig |
| 209 | +pub const ReportRepo = enum { |
| 210 | + nullhub, |
| 211 | + nullclaw, |
| 212 | + nullboiler, |
| 213 | + nulltickets, |
| 214 | + nullwatch, |
| 215 | +
|
| 216 | + pub fn fromStr(s: []const u8) ?ReportRepo { ... } |
| 217 | + pub fn toGithubRepo(self: ReportRepo) []const u8 { ... } |
| 218 | + pub fn displayName(self: ReportRepo) []const u8 { ... } |
| 219 | +}; |
| 220 | +
|
| 221 | +pub const ReportType = enum { |
| 222 | + bug_crash, |
| 223 | + bug_behavior, |
| 224 | + regression, |
| 225 | + feature, |
| 226 | +
|
| 227 | + pub fn fromStr(s: []const u8) ?ReportType { ... } |
| 228 | + pub fn toLabels(self: ReportType) []const []const u8 { ... } |
| 229 | + pub fn displayName(self: ReportType) []const u8 { ... } |
| 230 | +}; |
| 231 | +
|
| 232 | +pub const ReportOptions = struct { |
| 233 | + repo: ?ReportRepo = null, |
| 234 | + report_type: ?ReportType = null, |
| 235 | + message: ?[]const u8 = null, |
| 236 | + yes: bool = false, |
| 237 | + dry_run: bool = false, |
| 238 | +}; |
| 239 | +``` |
| 240 | + |
| 241 | +### API endpoints |
| 242 | + |
| 243 | +#### `POST /api/report/preview` |
| 244 | + |
| 245 | +Generates preview without submitting. |
| 246 | + |
| 247 | +Request: |
| 248 | +```json |
| 249 | +{ |
| 250 | + "repo": "nullhub", |
| 251 | + "type": "bug:behavior", |
| 252 | + "message": "Dashboard shows stale status" |
| 253 | +} |
| 254 | +``` |
| 255 | + |
| 256 | +Response: |
| 257 | +```json |
| 258 | +{ |
| 259 | + "title": "[Bug]: Dashboard shows stale status", |
| 260 | + "markdown": "### Bug type\n...", |
| 261 | + "labels": ["bug", "bug:behavior"], |
| 262 | + "repo": "nullclaw/nullhub" |
| 263 | +} |
| 264 | +``` |
| 265 | + |
| 266 | +#### `POST /api/report` |
| 267 | + |
| 268 | +Submits issue (with optional edited markdown from preview). |
| 269 | + |
| 270 | +Request: |
| 271 | +```json |
| 272 | +{ |
| 273 | + "repo": "nullhub", |
| 274 | + "type": "bug:behavior", |
| 275 | + "message": "Dashboard shows stale status", |
| 276 | + "markdown": "### Bug type\n..." |
| 277 | +} |
| 278 | +``` |
| 279 | + |
| 280 | +The `markdown` field is optional. If provided (edited by user in preview), it replaces the auto-generated body. If omitted, the server generates it. |
| 281 | + |
| 282 | +Success response: |
| 283 | +```json |
| 284 | +{ |
| 285 | + "status": "created", |
| 286 | + "url": "https://github.com/nullclaw/nullhub/issues/42" |
| 287 | +} |
| 288 | +``` |
| 289 | + |
| 290 | +Fallback response (no auth): |
| 291 | +```json |
| 292 | +{ |
| 293 | + "status": "no_auth", |
| 294 | + "title": "[Bug]: Dashboard shows stale status", |
| 295 | + "markdown": "### Bug type\n...", |
| 296 | + "labels": ["bug", "bug:behavior"], |
| 297 | + "repo": "nullclaw/nullhub", |
| 298 | + "hint": "Install and authenticate gh CLI to submit automatically: https://cli.github.com/" |
| 299 | +} |
| 300 | +``` |
| 301 | + |
| 302 | +Error response (invalid input): |
| 303 | +```json |
| 304 | +{ |
| 305 | + "status": "error", |
| 306 | + "error": "invalid repo: foo" |
| 307 | +} |
| 308 | +``` |
| 309 | + |
| 310 | +Returns HTTP 400 for invalid/missing fields (repo, type, message). |
| 311 | + |
| 312 | +### Auth on report endpoint |
| 313 | + |
| 314 | +`POST /api/report` and `POST /api/report/preview` follow the existing auth pattern in `server.zig` — if a bearer token is configured, these endpoints require it. Same as all other `/api/` routes. |
0 commit comments