@codexstar/bug-hunter 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +151 -0
  2. package/LICENSE +21 -0
  3. package/README.md +665 -0
  4. package/SKILL.md +624 -0
  5. package/bin/bug-hunter +222 -0
  6. package/evals/evals.json +362 -0
  7. package/modes/_dispatch.md +121 -0
  8. package/modes/extended.md +94 -0
  9. package/modes/fix-loop.md +115 -0
  10. package/modes/fix-pipeline.md +384 -0
  11. package/modes/large-codebase.md +212 -0
  12. package/modes/local-sequential.md +143 -0
  13. package/modes/loop.md +125 -0
  14. package/modes/parallel.md +113 -0
  15. package/modes/scaled.md +76 -0
  16. package/modes/single-file.md +38 -0
  17. package/modes/small.md +86 -0
  18. package/package.json +56 -0
  19. package/prompts/doc-lookup.md +44 -0
  20. package/prompts/examples/hunter-examples.md +131 -0
  21. package/prompts/examples/skeptic-examples.md +87 -0
  22. package/prompts/fixer.md +103 -0
  23. package/prompts/hunter.md +146 -0
  24. package/prompts/recon.md +159 -0
  25. package/prompts/referee.md +122 -0
  26. package/prompts/skeptic.md +143 -0
  27. package/prompts/threat-model.md +122 -0
  28. package/scripts/bug-hunter-state.cjs +537 -0
  29. package/scripts/code-index.cjs +541 -0
  30. package/scripts/context7-api.cjs +133 -0
  31. package/scripts/delta-mode.cjs +219 -0
  32. package/scripts/dep-scan.cjs +343 -0
  33. package/scripts/doc-lookup.cjs +316 -0
  34. package/scripts/fix-lock.cjs +167 -0
  35. package/scripts/init-test-fixture.sh +19 -0
  36. package/scripts/payload-guard.cjs +197 -0
  37. package/scripts/run-bug-hunter.cjs +892 -0
  38. package/scripts/tests/bug-hunter-state.test.cjs +87 -0
  39. package/scripts/tests/code-index.test.cjs +57 -0
  40. package/scripts/tests/delta-mode.test.cjs +47 -0
  41. package/scripts/tests/fix-lock.test.cjs +36 -0
  42. package/scripts/tests/fixtures/flaky-worker.cjs +63 -0
  43. package/scripts/tests/fixtures/low-confidence-worker.cjs +73 -0
  44. package/scripts/tests/fixtures/success-worker.cjs +42 -0
  45. package/scripts/tests/payload-guard.test.cjs +41 -0
  46. package/scripts/tests/run-bug-hunter.test.cjs +403 -0
  47. package/scripts/tests/test-utils.cjs +59 -0
  48. package/scripts/tests/worktree-harvest.test.cjs +297 -0
  49. package/scripts/triage.cjs +528 -0
  50. package/scripts/worktree-harvest.cjs +516 -0
  51. package/templates/subagent-wrapper.md +109 -0
@@ -0,0 +1,516 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * worktree-harvest.cjs — Worktree lifecycle manager for bug-hunter fix pipeline.
5
+ *
6
+ * Manages isolated git worktrees for Fixer subagents:
7
+ * prepare → create worktree on the fix branch
8
+ * harvest → validate Fixer commits, detect uncommitted work
9
+ * checkout-fix → return main working tree to the fix branch
10
+ * cleanup → remove a single worktree
11
+ * cleanup-all → remove all worktrees under a directory
12
+ * status → report worktree health
13
+ *
14
+ * Design (inspired by Droid Mission Control):
15
+ * Workers are process-isolated but commit to the SAME branch.
16
+ * The worktree checks out the fix branch directly — no cherry-picking.
17
+ * The orchestrator manages the lifecycle: prepare → dispatch → harvest → cleanup.
18
+ */
19
+
20
+ const { execFileSync } = require('child_process');
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Helpers
26
+ // ---------------------------------------------------------------------------
27
+
28
+ function usage() {
29
+ console.error('Usage:');
30
+ console.error(' worktree-harvest.cjs prepare <fixBranch> <worktreeDir>');
31
+ console.error(' worktree-harvest.cjs harvest <worktreeDir>');
32
+ console.error(' worktree-harvest.cjs checkout-fix <fixBranch>');
33
+ console.error(' worktree-harvest.cjs cleanup <worktreeDir>');
34
+ console.error(' worktree-harvest.cjs cleanup-all <parentDir>');
35
+ console.error(' worktree-harvest.cjs status <worktreeDir>');
36
+ }
37
+
38
+ function out(obj) {
39
+ console.log(JSON.stringify(obj, null, 2));
40
+ }
41
+
42
+ /** Run git with execFileSync — no shell, no injection risk. */
43
+ function git(args, cwd) {
44
+ const opts = { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] };
45
+ if (cwd) opts.cwd = cwd;
46
+ return execFileSync('git', args, opts).trim();
47
+ }
48
+
49
+ /** Same as git() but returns { ok, output } instead of throwing. */
50
+ function gitSafe(args, cwd) {
51
+ try {
52
+ return { ok: true, output: git(args, cwd) };
53
+ } catch (err) {
54
+ const stderr = err.stderr ? err.stderr.toString().trim() : '';
55
+ return { ok: false, output: stderr || (err.message || '').trim() };
56
+ }
57
+ }
58
+
59
+ function ensureDir(dir) {
60
+ fs.mkdirSync(dir, { recursive: true });
61
+ }
62
+
63
+ const MANIFEST_NAME = '.worktree-manifest.json';
64
+ const HARVEST_NAME = '.harvest-result.json';
65
+ const STALE_AGE_MS = 60 * 60 * 1000; // 1 hour
66
+
67
+ function manifestPath(worktreeDir) {
68
+ return path.join(worktreeDir, MANIFEST_NAME);
69
+ }
70
+
71
+ function harvestPath(worktreeDir) {
72
+ return path.join(worktreeDir, HARVEST_NAME);
73
+ }
74
+
75
+ function readJsonFile(filePath) {
76
+ if (!fs.existsSync(filePath)) return null;
77
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
78
+ }
79
+
80
+ function writeJsonFile(filePath, data) {
81
+ ensureDir(path.dirname(filePath));
82
+ fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
83
+ }
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // prepare — create worktree on the fix branch
87
+ // ---------------------------------------------------------------------------
88
+
89
+ function prepare(fixBranch, worktreeDir) {
90
+ const absDir = path.resolve(worktreeDir);
91
+
92
+ // 1. Verify fix branch exists
93
+ const branchCheck = gitSafe(['rev-parse', '--verify', fixBranch]);
94
+ if (!branchCheck.ok) {
95
+ out({ ok: false, error: 'fix-branch-not-found', detail: branchCheck.output });
96
+ process.exit(1);
97
+ }
98
+
99
+ // 2. If worktreeDir already exists, clean up stale worktree
100
+ if (fs.existsSync(absDir)) {
101
+ gitSafe(['worktree', 'remove', absDir, '--force']);
102
+ if (fs.existsSync(absDir)) {
103
+ fs.rmSync(absDir, { recursive: true, force: true });
104
+ }
105
+ gitSafe(['worktree', 'prune']);
106
+ }
107
+
108
+ // 3. Detach main working tree if it's on the fix branch
109
+ // (git won't allow two worktrees on the same branch)
110
+ const currentBranch = gitSafe(['rev-parse', '--abbrev-ref', 'HEAD']);
111
+ let detached = false;
112
+ if (currentBranch.ok && currentBranch.output === fixBranch) {
113
+ git(['checkout', '--detach']);
114
+ detached = true;
115
+ }
116
+
117
+ // 4. Create worktree on the fix branch
118
+ ensureDir(path.dirname(absDir));
119
+ const addResult = gitSafe(['worktree', 'add', absDir, fixBranch]);
120
+ if (!addResult.ok) {
121
+ // Restore branch if we detached
122
+ if (detached) gitSafe(['checkout', fixBranch]);
123
+ out({ ok: false, error: 'worktree-add-failed', detail: addResult.output });
124
+ process.exit(1);
125
+ }
126
+
127
+ // 5. Record pre-harvest HEAD
128
+ const preHarvestHead = git(['rev-parse', 'HEAD'], absDir);
129
+
130
+ // 6. Write manifest
131
+ const manifest = {
132
+ fixBranch,
133
+ preHarvestHead,
134
+ worktreeDir: absDir,
135
+ detachedMainTree: detached,
136
+ createdAtMs: Date.now(),
137
+ createdAt: new Date().toISOString()
138
+ };
139
+ writeJsonFile(manifestPath(absDir), manifest);
140
+
141
+ out({
142
+ ok: true,
143
+ worktreeDir: absDir,
144
+ fixBranch,
145
+ preHarvestHead,
146
+ detachedMainTree: detached,
147
+ createdAt: manifest.createdAt
148
+ });
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // harvest — validate Fixer commits and detect uncommitted work
153
+ // ---------------------------------------------------------------------------
154
+
155
+ /** Meta files written into the worktree — excluded from dirty detection. */
156
+ const META_FILES = [MANIFEST_NAME, HARVEST_NAME];
157
+
158
+ /**
159
+ * Core harvest logic. Returns the result object or throws on error.
160
+ * Does NOT call process.exit — safe for internal callers like cleanup-all.
161
+ */
162
+ function harvestCore(worktreeDir) {
163
+ const absDir = path.resolve(worktreeDir);
164
+
165
+ // 1. Read manifest
166
+ const manifest = readJsonFile(manifestPath(absDir));
167
+ if (!manifest) {
168
+ throw new Error(JSON.stringify({
169
+ ok: false, error: 'no-manifest', detail: `${manifestPath(absDir)} not found`
170
+ }));
171
+ }
172
+
173
+ const { preHarvestHead, fixBranch } = manifest;
174
+
175
+ // 2. Verify worktree is still on the fix branch
176
+ const wtBranch = gitSafe(['rev-parse', '--abbrev-ref', 'HEAD'], absDir);
177
+ if (wtBranch.ok && wtBranch.output !== fixBranch) {
178
+ const result = {
179
+ ok: true,
180
+ error: 'branch-switched',
181
+ detail: `Fixer switched from ${fixBranch} to ${wtBranch.output}`,
182
+ commits: [],
183
+ branchSwitched: true
184
+ };
185
+ writeJsonFile(harvestPath(absDir), result);
186
+ return result;
187
+ }
188
+
189
+ // 3. Find new commits since preHarvestHead
190
+ const logResult = gitSafe(
191
+ ['log', '--oneline', '--reverse', `${preHarvestHead}..HEAD`],
192
+ absDir
193
+ );
194
+ const logOutput = logResult.ok ? logResult.output : '';
195
+ const commitLines = logOutput ? logOutput.split('\n').filter(Boolean) : [];
196
+
197
+ // 4. Parse commits
198
+ const commits = commitLines.map(line => {
199
+ const spaceIdx = line.indexOf(' ');
200
+ const hash = line.slice(0, spaceIdx);
201
+ const message = line.slice(spaceIdx + 1);
202
+ const bugMatch = message.match(/BUG-(\d+)/);
203
+ return {
204
+ hash,
205
+ message,
206
+ bugId: bugMatch ? `BUG-${bugMatch[1]}` : null
207
+ };
208
+ });
209
+
210
+ // 5. Check for uncommitted changes (exclude our own meta files)
211
+ const statusOutput = gitSafe(['status', '--porcelain'], absDir);
212
+ const statusLines = statusOutput.ok
213
+ ? statusOutput.output.split('\n').filter(Boolean)
214
+ : [];
215
+ const relevantLines = statusLines.filter(line => {
216
+ const fileName = line.slice(3); // strip status prefix (e.g. "?? ")
217
+ return !META_FILES.some(mf => fileName === mf || fileName.endsWith(`/${mf}`));
218
+ });
219
+ const dirty = relevantLines.length > 0;
220
+
221
+ let uncommittedStashed = false;
222
+ let stashRef = null;
223
+
224
+ if (dirty) {
225
+ // Stash uncommitted work so it's not lost
226
+ const stashMsg = `bug-hunter-fixer-uncommitted-${Date.now()}`;
227
+ gitSafe(['add', '-A'], absDir);
228
+ const stashResult = gitSafe(['stash', 'push', '-m', stashMsg], absDir);
229
+ if (stashResult.ok) {
230
+ uncommittedStashed = true;
231
+ const stashList = gitSafe(['stash', 'list', '--max-count=1'], absDir);
232
+ stashRef = stashList.ok ? stashList.output.split(':')[0] : null;
233
+ }
234
+ }
235
+
236
+ const postHarvestHead = gitSafe(['rev-parse', 'HEAD'], absDir);
237
+
238
+ const result = {
239
+ ok: true,
240
+ commits,
241
+ harvestedCount: commits.length,
242
+ noChanges: commits.length === 0 && !dirty,
243
+ uncommittedStashed,
244
+ stashRef,
245
+ preHarvestHead,
246
+ postHarvestHead: postHarvestHead.ok ? postHarvestHead.output : null
247
+ };
248
+
249
+ writeJsonFile(harvestPath(absDir), result);
250
+ return result;
251
+ }
252
+
253
+ /** CLI-facing harvest — prints result and exits on error. */
254
+ function harvest(worktreeDir) {
255
+ try {
256
+ const result = harvestCore(worktreeDir);
257
+ out(result);
258
+ } catch (err) {
259
+ // Error from harvestCore is a JSON string
260
+ try {
261
+ const parsed = JSON.parse(err.message);
262
+ out(parsed);
263
+ } catch (_) {
264
+ out({ ok: false, error: 'harvest-failed', detail: err.message });
265
+ }
266
+ process.exit(1);
267
+ }
268
+ }
269
+
270
+ // ---------------------------------------------------------------------------
271
+ // checkout-fix — return main working tree to the fix branch
272
+ // ---------------------------------------------------------------------------
273
+
274
+ function checkoutFix(fixBranch) {
275
+ // Verify no non-main worktrees have this branch checked out
276
+ const worktreeList = git(['worktree', 'list', '--porcelain']);
277
+ const entries = worktreeList.split('\n\n').filter(Boolean);
278
+ const mainWorktree = git(['rev-parse', '--show-toplevel']);
279
+
280
+ for (const entry of entries) {
281
+ const lines = entry.split('\n');
282
+ const worktreeLine = lines.find(l => l.startsWith('worktree '));
283
+ const branchLine = lines.find(l => l.startsWith('branch '));
284
+ if (!branchLine || !worktreeLine) continue;
285
+
286
+ const branch = branchLine.replace('branch refs/heads/', '');
287
+ const wtPath = worktreeLine.replace('worktree ', '');
288
+
289
+ if (branch === fixBranch && wtPath !== mainWorktree) {
290
+ out({
291
+ ok: false,
292
+ error: 'worktree-still-active',
293
+ detail: `Branch ${fixBranch} is checked out in worktree: ${wtPath}`
294
+ });
295
+ process.exit(1);
296
+ }
297
+ }
298
+
299
+ const result = gitSafe(['checkout', fixBranch]);
300
+ if (!result.ok) {
301
+ out({ ok: false, error: 'checkout-failed', detail: result.output });
302
+ process.exit(1);
303
+ }
304
+
305
+ const head = git(['rev-parse', 'HEAD']);
306
+ out({ ok: true, branch: fixBranch, head });
307
+ }
308
+
309
+ // ---------------------------------------------------------------------------
310
+ // cleanup — remove a single worktree
311
+ // ---------------------------------------------------------------------------
312
+
313
+ function cleanup(worktreeDir) {
314
+ const absDir = path.resolve(worktreeDir);
315
+
316
+ if (!fs.existsSync(absDir)) {
317
+ gitSafe(['worktree', 'prune']);
318
+ out({ ok: true, removed: false, reason: 'not-found' });
319
+ return;
320
+ }
321
+
322
+ // If harvest hasn't run yet, run it defensively
323
+ if (!readJsonFile(harvestPath(absDir))) {
324
+ try { harvestCore(absDir); } catch (_) { /* best-effort */ }
325
+ }
326
+
327
+ const manifest = readJsonFile(manifestPath(absDir));
328
+
329
+ // Remove worktree
330
+ const removeResult = gitSafe(['worktree', 'remove', absDir, '--force']);
331
+ if (!removeResult.ok && fs.existsSync(absDir)) {
332
+ fs.rmSync(absDir, { recursive: true, force: true });
333
+ }
334
+
335
+ gitSafe(['worktree', 'prune']);
336
+
337
+ out({
338
+ ok: true,
339
+ removed: true,
340
+ detachedMainTree: manifest ? manifest.detachedMainTree : false
341
+ });
342
+ }
343
+
344
+ // ---------------------------------------------------------------------------
345
+ // cleanup-all — remove all worktrees under a parent directory
346
+ // ---------------------------------------------------------------------------
347
+
348
+ function cleanupAll(parentDir) {
349
+ const absParent = path.resolve(parentDir);
350
+
351
+ if (!fs.existsSync(absParent)) {
352
+ out({ ok: true, cleaned: 0, entries: [] });
353
+ return;
354
+ }
355
+
356
+ let entries;
357
+ try {
358
+ entries = fs.readdirSync(absParent, { withFileTypes: true })
359
+ .filter(d => d.isDirectory())
360
+ .map(d => d.name);
361
+ } catch (_) {
362
+ out({ ok: true, cleaned: 0, entries: [] });
363
+ return;
364
+ }
365
+
366
+ const results = [];
367
+ for (const name of entries) {
368
+ const wtDir = path.join(absParent, name);
369
+ try {
370
+ // Defensive harvest before cleanup
371
+ if (!readJsonFile(harvestPath(wtDir))) {
372
+ try { harvestCore(wtDir); } catch (_) { /* best-effort */ }
373
+ }
374
+ gitSafe(['worktree', 'remove', wtDir, '--force']);
375
+ if (fs.existsSync(wtDir)) {
376
+ fs.rmSync(wtDir, { recursive: true, force: true });
377
+ }
378
+ results.push({ name, removed: true });
379
+ } catch (err) {
380
+ results.push({ name, removed: false, error: err.message });
381
+ }
382
+ }
383
+
384
+ gitSafe(['worktree', 'prune']);
385
+
386
+ // Remove parent if empty
387
+ try {
388
+ const remaining = fs.readdirSync(absParent);
389
+ if (remaining.length === 0) fs.rmdirSync(absParent);
390
+ } catch (_) { /* ignore */ }
391
+
392
+ out({ ok: true, cleaned: results.filter(r => r.removed).length, entries: results });
393
+ }
394
+
395
+ // ---------------------------------------------------------------------------
396
+ // status — report worktree health
397
+ // ---------------------------------------------------------------------------
398
+
399
+ function statusCmd(worktreeDir) {
400
+ const absDir = path.resolve(worktreeDir);
401
+
402
+ if (!fs.existsSync(absDir)) {
403
+ out({ ok: true, exists: false });
404
+ return;
405
+ }
406
+
407
+ const manifest = readJsonFile(manifestPath(absDir));
408
+ const harvestResult = readJsonFile(harvestPath(absDir));
409
+ const age = manifest ? Date.now() - manifest.createdAtMs : null;
410
+ const isStale = age !== null && age > STALE_AGE_MS;
411
+
412
+ const statusOutput = gitSafe(['status', '--porcelain'], absDir);
413
+ const hasUncommitted = statusOutput.ok && statusOutput.output.length > 0;
414
+
415
+ let commitCount = 0;
416
+ if (manifest) {
417
+ const logResult = gitSafe(
418
+ ['log', '--oneline', `${manifest.preHarvestHead}..HEAD`],
419
+ absDir
420
+ );
421
+ if (logResult.ok && logResult.output) {
422
+ commitCount = logResult.output.split('\n').filter(Boolean).length;
423
+ }
424
+ }
425
+
426
+ const branch = gitSafe(['rev-parse', '--abbrev-ref', 'HEAD'], absDir);
427
+
428
+ out({
429
+ ok: true,
430
+ exists: true,
431
+ branch: branch.ok ? branch.output : null,
432
+ fixBranch: manifest ? manifest.fixBranch : null,
433
+ ageMs: age,
434
+ isStale,
435
+ hasUncommitted,
436
+ commitCount,
437
+ harvested: harvestResult !== null,
438
+ createdAt: manifest ? manifest.createdAt : null
439
+ });
440
+ }
441
+
442
+ // ---------------------------------------------------------------------------
443
+ // Main
444
+ // ---------------------------------------------------------------------------
445
+
446
+ function main() {
447
+ const args = process.argv.slice(2);
448
+ const command = args[0];
449
+
450
+ if (!command) {
451
+ usage();
452
+ process.exit(1);
453
+ }
454
+
455
+ switch (command) {
456
+ case 'prepare':
457
+ if (!args[1] || !args[2]) {
458
+ console.error('prepare requires <fixBranch> <worktreeDir>');
459
+ process.exit(1);
460
+ }
461
+ prepare(args[1], args[2]);
462
+ break;
463
+
464
+ case 'harvest':
465
+ if (!args[1]) {
466
+ console.error('harvest requires <worktreeDir>');
467
+ process.exit(1);
468
+ }
469
+ harvest(args[1]);
470
+ break;
471
+
472
+ case 'checkout-fix':
473
+ if (!args[1]) {
474
+ console.error('checkout-fix requires <fixBranch>');
475
+ process.exit(1);
476
+ }
477
+ checkoutFix(args[1]);
478
+ break;
479
+
480
+ case 'cleanup':
481
+ if (!args[1]) {
482
+ console.error('cleanup requires <worktreeDir>');
483
+ process.exit(1);
484
+ }
485
+ cleanup(args[1]);
486
+ break;
487
+
488
+ case 'cleanup-all':
489
+ if (!args[1]) {
490
+ console.error('cleanup-all requires <parentDir>');
491
+ process.exit(1);
492
+ }
493
+ cleanupAll(args[1]);
494
+ break;
495
+
496
+ case 'status':
497
+ if (!args[1]) {
498
+ console.error('status requires <worktreeDir>');
499
+ process.exit(1);
500
+ }
501
+ statusCmd(args[1]);
502
+ break;
503
+
504
+ default:
505
+ usage();
506
+ process.exit(1);
507
+ }
508
+ }
509
+
510
+ try {
511
+ main();
512
+ } catch (error) {
513
+ const message = error instanceof Error ? error.message : String(error);
514
+ console.error(message);
515
+ process.exit(1);
516
+ }
@@ -0,0 +1,109 @@
1
+ # Subagent Task Wrapper Template
2
+
3
+ Use this template when dispatching any bug-hunter subagent via the `subagent` or `teams` tool. Fill in the `{VARIABLES}` before dispatch.
4
+
5
+ The orchestrator (main agent) MUST:
6
+ 1. Read the relevant prompt file with the Read tool
7
+ 2. Read this template with the Read tool
8
+ 3. Fill all `{VARIABLES}` with actual values
9
+ 4. Dispatch using the selected `AGENT_BACKEND`
10
+
11
+ ---
12
+
13
+ ## Context
14
+ You are a specialized analysis agent invoked by a bug-hunting pipeline.
15
+ You operate in your own context window. Your work feeds into a multi-phase
16
+ adversarial review process.
17
+
18
+ ## Your Role: {ROLE_NAME}
19
+
20
+ {ROLE_DESCRIPTION}
21
+
22
+ ## Your System Prompt
23
+
24
+ ---BEGIN SYSTEM PROMPT---
25
+ {PROMPT_CONTENT}
26
+ ---END SYSTEM PROMPT---
27
+
28
+ ## Non-negotiable Rules
29
+
30
+ - **Stay within scope.** Only analyze the files assigned to you below.
31
+ - **Do NOT fix code.** Do NOT add tests. Report findings only.
32
+ - **Do NOT report style issues**, unused imports, missing types, or refactoring ideas.
33
+ - **Do NOT expand scope.** If you find something interesting outside your assigned files, note it in UNTRACED CROSS-REFS but do not investigate.
34
+ - **Be honest about coverage.** If you run out of context reading files, STOP and report partial coverage in FILES SKIPPED. Do not inflate FILES SCANNED.
35
+ - **Use the output format EXACTLY** as specified in your system prompt.
36
+ - **Write output to the specified file.** The orchestrator reads this file for the next phase.
37
+ - **Stop when done.** Do not continue to other phases or offer next-step suggestions.
38
+ - **NEVER run destructive commands** like `rm -rf`.
39
+
40
+ ## Worktree Isolation Rules (Fixer role only)
41
+
42
+ {WORKTREE_RULES}
43
+
44
+ If worktree rules are provided above (non-empty), these apply:
45
+ - You are working in an **isolated git worktree**. Your edits cannot affect the user's main working tree.
46
+ - You **MUST** `git add` and `git commit` each fix before you finish. Uncommitted changes will be lost and marked as `FIX_FAILED`.
47
+ - Commit message format: `fix(bug-hunter): BUG-N — [short description]`
48
+ - Do **NOT** call `EnterWorktree` or `ExitWorktree` tools.
49
+ - Do **NOT** run `git checkout`, `git switch`, or `git branch`.
50
+ - If you encounter a git error, report it in your output and stop. Do not attempt recovery.
51
+
52
+ ## Your Assignment
53
+
54
+ ---BEGIN ASSIGNMENT---
55
+
56
+ **Scan target:** {TARGET_DESCRIPTION}
57
+
58
+ **SKILL_DIR:** {SKILL_DIR}
59
+ (Use this path for all helper script invocations like `node "$SKILL_DIR/scripts/doc-lookup.cjs"` or the fallback `node "$SKILL_DIR/scripts/context7-api.cjs"`)
60
+
61
+ **Files to scan (in risk-map order):**
62
+ {FILE_LIST}
63
+
64
+ **Risk map:**
65
+ {RISK_MAP}
66
+
67
+ **Tech stack:**
68
+ {TECH_STACK}
69
+
70
+ **Phase-specific context:**
71
+ {PHASE_SPECIFIC_CONTEXT}
72
+
73
+ ---END ASSIGNMENT---
74
+
75
+ ## Output Requirements
76
+
77
+ **Write your complete output to:** `{OUTPUT_FILE_PATH}`
78
+
79
+ Follow the output format specified in your system prompt EXACTLY.
80
+ The orchestrator will read this file to pass your results to the next pipeline phase.
81
+
82
+ If the file path directory does not exist, create it first:
83
+ ```bash
84
+ mkdir -p "$(dirname '{OUTPUT_FILE_PATH}')"
85
+ ```
86
+
87
+ ## Completion
88
+
89
+ When you have finished your analysis:
90
+ 1. Write your report to `{OUTPUT_FILE_PATH}`
91
+ 2. Output a brief summary to stdout (one paragraph)
92
+ 3. Stop. Do not continue to other phases.
93
+
94
+ ---
95
+
96
+ ## Variable Reference (for the orchestrator)
97
+
98
+ | Variable | Description | Example |
99
+ |----------|-------------|---------|
100
+ | `{ROLE_NAME}` | Agent role identifier | `hunter`, `skeptic`, `referee`, `recon`, `fixer` |
101
+ | `{ROLE_DESCRIPTION}` | One-line role description | "Bug Hunter — find behavioral bugs in source code" |
102
+ | `{PROMPT_CONTENT}` | Full contents of the prompt .md file | Contents of `prompts/hunter.md` |
103
+ | `{TARGET_DESCRIPTION}` | What is being scanned | "FindCoffee monorepo, packages/auth + packages/order" |
104
+ | `{SKILL_DIR}` | Absolute path to the bug-hunter skill directory | `/Users/codex/.agents/skills/bug-hunter` |
105
+ | `{FILE_LIST}` | Newline-separated file paths in scan order | CRITICAL files first, then HIGH, then MEDIUM |
106
+ | `{RISK_MAP}` | Recon output risk classification | From `.bug-hunter/recon.md` |
107
+ | `{TECH_STACK}` | Framework, auth, DB, key dependencies | "Express + JWT + Prisma + Redis" |
108
+ | `{PHASE_SPECIFIC_CONTEXT}` | Extra context for this phase | For Skeptic: the Hunter findings. For Referee: findings + Skeptic challenges. |
109
+ | `{OUTPUT_FILE_PATH}` | Where to write the output | `.bug-hunter/findings.md` |