|
description = """ |
|
Contribute a fix for a single gastownhall/gastown GitHub issue. |
|
|
|
This formula runs in three phases, each in a fresh session: |
|
|
|
**Phase 1 — Investigate** (read-only): load-issue → assess-feasibility → handoff |
|
**Phase 2 — Setup + Implement** (heavy file-reading): setup-fork-branch → implement → handoff |
|
**Phase 3 — Quality + Ship**: build-and-review → push-and-pr → notify-success → exit |
|
|
|
A `resume-check` step at the start routes to the right phase based on bead notes. |
|
The session that reads source files is NEVER the same session that runs /review --branch. |
|
|
|
## Branch naming |
|
issue-hunter/<number>-<short-slug> |
|
e.g. issue-hunter/3874-duplicate-commands-provisioned |
|
|
|
## PR target |
|
Base: gastownhall/gastown default branch |
|
Head: esciara/gastown:<branch> |
|
|
|
## Notification |
|
Reads TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID from env vars, falling back |
|
to macOS Keychain entries "telegram-bot-token" and "telegram-chat-id". |
|
If neither is available, notification is skipped (warning logged). |
|
|
|
## Variables |
|
| Variable | Description | |
|
|---------------|------------------------------------------| |
|
| github_issue | GitHub issue number to work on | |
|
| upstream_repo | Upstream repo (default: gastownhall/gastown) | |
|
| fork_repo | Fork repo (default: esciara/gastown) |""" |
|
formula = "mol-issue-hunter-work" |
|
version = 2 |
|
|
|
[[steps]] |
|
id = "resume-check" |
|
title = "Check resume state — route to the correct phase" |
|
description = """ |
|
Always run this step first. Read bead notes to determine which phase to execute. |
|
|
|
```bash |
|
bd show {{issue}} --format json 2>/dev/null | jq -r '.notes // ""' |
|
``` |
|
|
|
**Routing rules (read notes carefully):** |
|
- If notes contain `PHASE_2_COMPLETE` → **skip directly to `build-and-review` (Phase 3)** |
|
- If notes contain `PHASE_1_COMPLETE` → **skip directly to `setup-fork-branch` (Phase 2)** |
|
- Otherwise → **start from `load-issue` (Phase 1, fresh run)** |
|
|
|
Print your routing decision clearly so it's visible in the session log. |
|
|
|
**Exit criteria:** You know which phase to execute and have announced it.""" |
|
|
|
[[steps]] |
|
id = "load-issue" |
|
title = "Load issue and all comments [Phase 1]" |
|
needs = ["resume-check"] |
|
description = """ |
|
**SKIP this step if resuming from Phase 2 or Phase 3** (notes contain PHASE_1_COMPLETE or PHASE_2_COMPLETE). |
|
|
|
Read the full issue body and all comments. Build a complete picture before |
|
deciding anything. |
|
|
|
```bash |
|
# Full issue detail |
|
gh issue view {{github_issue}} \ |
|
--repo {{upstream_repo}} \ |
|
--json number,title,body,labels,author,comments,state \ |
|
> /tmp/ih-work-issue-{{github_issue}}.json |
|
|
|
# Human-readable view for reading |
|
gh issue view {{github_issue}} --repo {{upstream_repo}} --comments |
|
``` |
|
|
|
Read the output carefully: |
|
- What is the exact problem? |
|
- What is the expected behaviour? |
|
- Are there reproduction steps? |
|
- Are there open questions in the comments that haven't been answered? |
|
- Is there a suggested fix or design direction? |
|
|
|
**Exit criteria:** You have a clear understanding of the issue's requirements.""" |
|
|
|
[[steps]] |
|
id = "assess-feasibility" |
|
title = "Assess whether enough information exists to implement [Phase 1]" |
|
needs = ["load-issue"] |
|
description = """ |
|
**SKIP this step if resuming from Phase 2 or Phase 3** (notes contain PHASE_1_COMPLETE or PHASE_2_COMPLETE). |
|
|
|
Decide: can this issue be implemented now, or does it need human input? |
|
|
|
**Blockers that require human input:** |
|
- Missing reproduction steps for a bug |
|
- Design decision not yet made (multiple valid approaches, no consensus in comments) |
|
- Issue references external context not available (private system, customer data) |
|
- Conflicting requirements in issue vs. comments |
|
- Issue is marked as a discussion/RFC with no resolution |
|
|
|
**NOT a blocker (proceed):** |
|
- Missing tests (you write them) |
|
- Vague wording that you can reasonably interpret from context |
|
- No suggested implementation (you design it) |
|
- Issue is old but still valid |
|
|
|
**Decision:** |
|
- If blocked → proceed to `notify-blocked` step |
|
- If clear → proceed to `phase-1-handoff` step |
|
|
|
Document your decision: |
|
```bash |
|
# Note what you decided and why (survives session death) |
|
bd update {{issue}} --notes "Assessment: [CLEAR|BLOCKED] — <reason>" |
|
``` |
|
|
|
**Exit criteria:** Clear decision documented in bead notes.""" |
|
|
|
[[steps]] |
|
id = "notify-blocked" |
|
title = "Post clarification comment and notify via Telegram — then exit [Phase 1]" |
|
needs = ["assess-feasibility"] |
|
description = """ |
|
Only execute this step if the issue is BLOCKED on human input. |
|
If the issue is clear, go to `phase-1-handoff` instead. |
|
|
|
**1. Post a GitHub comment asking the specific question:** |
|
|
|
```bash |
|
QUESTION="<the specific question or missing info>" |
|
ISSUE_URL="https://github.com/{{upstream_repo}}/issues/{{github_issue}}" |
|
|
|
gh issue comment {{github_issue}} \ |
|
--repo {{upstream_repo}} \ |
|
--body "$(cat <<'BODY' |
|
Hi! I was looking at this issue to contribute a fix. |
|
|
|
Before I proceed, I need a bit more information: |
|
|
|
**Question:** ${QUESTION} |
|
|
|
Once this is clarified I can pick this up in the next automated run. |
|
BODY |
|
)" |
|
``` |
|
|
|
**2. Send Telegram notification:** |
|
|
|
```bash |
|
: "${TELEGRAM_BOT_TOKEN:=$(security find-generic-password -a "$USER" -s telegram-bot-token -w 2>/dev/null)}" |
|
: "${TELEGRAM_CHAT_ID:=$(security find-generic-password -a "$USER" -s telegram-chat-id -w 2>/dev/null)}" |
|
if [ -z "${TELEGRAM_BOT_TOKEN:-}" ] || [ -z "${TELEGRAM_CHAT_ID:-}" ]; then |
|
echo "WARNING: TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID not set — skipping notification" |
|
else |
|
MSG="🔍 Issue Hunter needs your input%0A%0AIssue: #{{github_issue}} — $(jq -r .title /tmp/ih-work-issue-{{github_issue}}.json)%0ARepo: {{upstream_repo}}%0A%0AQuestion posted as a comment. Please clarify so the next run can proceed.%0A%0Ahttps://github.com/{{upstream_repo}}/issues/{{github_issue}}" |
|
curl -s -X POST \ |
|
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ |
|
-d "chat_id=${TELEGRAM_CHAT_ID}&text=${MSG}&parse_mode=HTML" \ |
|
> /dev/null |
|
echo "Telegram notification sent." |
|
fi |
|
``` |
|
|
|
**3. Exit cleanly:** |
|
|
|
```bash |
|
rm -f /tmp/ih-work-issue-{{github_issue}}.json |
|
gt done --status DEFERRED --merge=local |
|
``` |
|
|
|
**Exit criteria:** Comment posted on GitHub, Telegram notification sent (if configured), polecat exits.""" |
|
|
|
[[steps]] |
|
id = "phase-1-handoff" |
|
title = "Save Phase 1 state and hand off to fresh session [Phase 1]" |
|
needs = ["assess-feasibility"] |
|
description = """ |
|
Only run this if the issue assessment was CLEAR (not blocked). |
|
**SKIP if resuming from Phase 2 or Phase 3.** |
|
|
|
Phase 1 is complete. Save the phase marker to bead notes and hand off to a fresh |
|
session so Phase 2 starts with a clean context budget for reading source files. |
|
|
|
**1. Save phase marker:** |
|
```bash |
|
# Read current notes (to preserve the assessment) |
|
CURRENT_NOTES=$(bd show {{issue}} --format json 2>/dev/null | jq -r '.notes // ""') |
|
bd update {{issue}} --notes "PHASE_1_COMPLETE |
|
${CURRENT_NOTES}" |
|
``` |
|
|
|
**2. Hand off to fresh session:** |
|
``` |
|
/handoff |
|
``` |
|
|
|
The new session will pick up this bead from the hook, run `resume-check`, see |
|
`PHASE_1_COMPLETE` in notes, and proceed directly to `setup-fork-branch`. |
|
|
|
**Exit criteria:** Phase marker saved to bead notes, /handoff called.""" |
|
|
|
[[steps]] |
|
id = "setup-fork-branch" |
|
title = "Sync fork with upstream and create feature branch [Phase 2]" |
|
needs = ["resume-check"] |
|
description = """ |
|
**SKIP if resuming from Phase 3** (notes contain PHASE_2_COMPLETE). |
|
|
|
Only execute this step if the issue assessment was CLEAR (confirmed by PHASE_1_COMPLETE in notes). |
|
|
|
**1. Add upstream remote if not already present:** |
|
```bash |
|
git remote -v | grep upstream || \ |
|
git remote add upstream https://github.com/{{upstream_repo}}.git |
|
``` |
|
|
|
**2. Fetch upstream and sync main:** |
|
```bash |
|
git fetch upstream |
|
git fetch origin |
|
git checkout main |
|
git merge upstream/main --ff-only |
|
git push origin main |
|
``` |
|
|
|
If `--ff-only` fails (diverged history), investigate before forcing. |
|
|
|
**3. Create feature branch:** |
|
```bash |
|
# Build a short slug from the issue title |
|
ISSUE_JSON=$(gh issue view {{github_issue}} \ |
|
--repo {{upstream_repo}} \ |
|
--json number,title,body,labels,author,comments,state) |
|
echo "${ISSUE_JSON}" > /tmp/ih-work-issue-{{github_issue}}.json |
|
|
|
SLUG=$(echo "${ISSUE_JSON}" | jq -r '.title' \ |
|
| tr '[:upper:]' '[:lower:]' \ |
|
| sed 's/[^a-z0-9]/-/g' \ |
|
| sed 's/--*/-/g' \ |
|
| cut -c1-40 \ |
|
| sed 's/-$//') |
|
|
|
BRANCH="issue-hunter/{{github_issue}}-${SLUG}" |
|
git checkout -b "${BRANCH}" upstream/main |
|
echo "Branch: ${BRANCH}" |
|
``` |
|
|
|
**4. Run project setup:** |
|
```bash |
|
go mod download 2>/dev/null || true |
|
``` |
|
|
|
**Exit criteria:** On a clean branch based on upstream/main, ready to implement.""" |
|
|
|
[[steps]] |
|
id = "implement" |
|
title = "Implement the fix [Phase 2]" |
|
needs = ["setup-fork-branch"] |
|
description = """ |
|
**SKIP if resuming from Phase 3** (notes contain PHASE_2_COMPLETE). |
|
|
|
Implement the fix for issue #{{github_issue}}. |
|
|
|
**Before writing code:** |
|
- Read the relevant source files thoroughly |
|
- Understand the existing patterns, naming conventions, test style |
|
- Check the CONTRIBUTING.md or docs/ for contribution guidelines: |
|
```bash |
|
cat CONTRIBUTING.md 2>/dev/null || cat docs/CONTRIBUTING.md 2>/dev/null || true |
|
``` |
|
- Check if there are existing tests to guide you: |
|
```bash |
|
go test ./... 2>&1 | tail -5 |
|
``` |
|
|
|
**Working principles:** |
|
- Follow the existing code style exactly (Go conventions, file layout) |
|
- Write tests for new behaviour — look at existing test files for the pattern |
|
- Keep changes scoped to the issue — no scope creep |
|
- Make atomic, logical commits: |
|
```bash |
|
git add <files> |
|
git commit -m "<type>: <description>" |
|
``` |
|
Types: feat, fix, refactor, test, docs, chore |
|
|
|
**Persist findings (session survival):** |
|
```bash |
|
bd update {{issue}} --notes "Progress: <what you've done, what's left>" |
|
``` |
|
|
|
**If stuck for >15 min:** |
|
The issue may be harder than assessed. Post a comment and notify: |
|
```bash |
|
gh issue comment {{github_issue}} --repo {{upstream_repo}} \ |
|
--body "Hit an unexpected complexity — <describe>. Will revisit." |
|
|
|
: "${TELEGRAM_BOT_TOKEN:=$(security find-generic-password -a "$USER" -s telegram-bot-token -w 2>/dev/null)}" |
|
: "${TELEGRAM_CHAT_ID:=$(security find-generic-password -a "$USER" -s telegram-chat-id -w 2>/dev/null)}" |
|
MSG="⚠️ Issue Hunter stuck on #{{github_issue}}%0A%0A<describe blocker>%0Ahttps://github.com/{{upstream_repo}}/issues/{{github_issue}}" |
|
[ -n "${TELEGRAM_BOT_TOKEN:-}" ] && curl -s -X POST \ |
|
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ |
|
-d "chat_id=${TELEGRAM_CHAT_ID}&text=${MSG}" > /dev/null |
|
|
|
gt done --status DEFERRED --merge=local |
|
``` |
|
|
|
**Exit criteria:** Implementation complete, all changes committed, `git status` clean.""" |
|
|
|
[[steps]] |
|
id = "phase-2-handoff" |
|
title = "Save Phase 2 state and hand off to fresh session [Phase 2]" |
|
needs = ["implement"] |
|
description = """ |
|
**SKIP if resuming from Phase 3** (notes contain PHASE_2_COMPLETE). |
|
|
|
Implementation is done. Save the phase marker and hand off to a fresh session. |
|
The fresh session will run /review --branch without the source-file reading |
|
context that would push us over the limit. |
|
|
|
**1. Capture branch name for Phase 3:** |
|
```bash |
|
BRANCH=$(git branch --show-current) |
|
echo "Branch to review: ${BRANCH}" |
|
``` |
|
|
|
**2. Save phase marker:** |
|
```bash |
|
CURRENT_NOTES=$(bd show {{issue}} --format json 2>/dev/null | jq -r '.notes // ""') |
|
bd update {{issue}} --notes "PHASE_2_COMPLETE |
|
branch=${BRANCH} |
|
${CURRENT_NOTES}" |
|
``` |
|
|
|
**3. Hand off to fresh session:** |
|
``` |
|
/handoff |
|
``` |
|
|
|
The new session will pick up this bead from the hook, run `resume-check`, see |
|
`PHASE_2_COMPLETE` in notes, and proceed directly to `build-and-review`. |
|
|
|
**Exit criteria:** Phase marker (with branch name) saved to bead notes, /handoff called.""" |
|
|
|
[[steps]] |
|
id = "build-and-review" |
|
title = "Build, test, lint, self-review [Phase 3]" |
|
needs = ["resume-check"] |
|
description = """ |
|
Phase 3 begins here. Read the branch name from bead notes: |
|
|
|
```bash |
|
BRANCH=$(bd show {{issue}} --format json 2>/dev/null | jq -r '.notes // ""' \ |
|
| grep '^branch=' | cut -d= -f2-) |
|
echo "Reviewing branch: ${BRANCH}" |
|
git checkout "${BRANCH}" 2>/dev/null || true |
|
``` |
|
|
|
**1. Build:** |
|
```bash |
|
go build ./... |
|
``` |
|
|
|
**2. Run tests relevant to changed packages:** |
|
```bash |
|
# Find changed packages |
|
PKGS=$(git diff upstream/main...HEAD --name-only \ |
|
| grep '\\.go$' \ |
|
| xargs -I{} dirname {} \ |
|
| sort -u \ |
|
| sed 's|^|./|' \ |
|
| tr '\n' ' ') |
|
go test ${PKGS} -timeout 2m |
|
``` |
|
|
|
**3. Lint:** |
|
```bash |
|
golangci-lint run ./... 2>/dev/null || go vet ./... |
|
``` |
|
|
|
**4. Self-review:** |
|
```bash |
|
/review --branch |
|
``` |
|
Fix any CRITICAL or MAJOR findings. Grade must be B or better. |
|
|
|
**5. Verify diff is clean (no unintended files):** |
|
```bash |
|
git diff --stat upstream/main...HEAD |
|
``` |
|
|
|
**Exit criteria:** Build passes, tests pass, self-review grade ≥ B.""" |
|
|
|
[[steps]] |
|
id = "push-and-pr" |
|
title = "Push branch to fork and open PR to upstream [Phase 3]" |
|
needs = ["build-and-review"] |
|
description = """ |
|
Push to your fork and open a PR targeting gastownhall/gastown. |
|
|
|
**1. Final rebase onto upstream/main:** |
|
```bash |
|
git fetch upstream |
|
git rebase upstream/main |
|
``` |
|
|
|
**2. Push to fork:** |
|
```bash |
|
BRANCH=$(git branch --show-current) |
|
git push origin "${BRANCH}" --force-with-lease |
|
``` |
|
|
|
**3. Re-fetch issue data for PR description (Phase 3 starts fresh):** |
|
```bash |
|
gh issue view {{github_issue}} --repo {{upstream_repo}} \ |
|
--json number,title,body,labels,author,comments,state \ |
|
> /tmp/ih-work-issue-{{github_issue}}.json |
|
gh issue view {{github_issue}} --repo {{upstream_repo}} |
|
``` |
|
|
|
**4. Build PR description:** |
|
|
|
The PR body must follow the upstream template (## Summary, ## Changes, ## Testing): |
|
```bash |
|
BRANCH=$(git branch --show-current) |
|
TITLE=$(jq -r '.title' /tmp/ih-work-issue-{{github_issue}}.json) |
|
|
|
gh pr create \ |
|
--repo {{upstream_repo}} \ |
|
--head "esciara:${BRANCH}" \ |
|
--base main \ |
|
--title "fix: ${TITLE}" \ |
|
--body "$(cat <<'PRBODY' |
|
## Summary |
|
|
|
<1-3 bullet points describing what the change does> |
|
|
|
## Changes |
|
|
|
<list of key files changed and why> |
|
|
|
## Testing |
|
|
|
- [ ] `go build ./...` passes |
|
- [ ] Relevant tests pass |
|
- [ ] `go vet ./...` passes |
|
|
|
Closes #{{github_issue}} |
|
PRBODY |
|
)" |
|
``` |
|
|
|
Substitute the template placeholders with actual content based on your implementation. |
|
|
|
**5. Capture the PR URL:** |
|
```bash |
|
gh pr view --repo {{upstream_repo}} "${BRANCH}" --json url -q .url |
|
``` |
|
|
|
**Exit criteria:** PR is open on gastownhall/gastown, URL captured.""" |
|
|
|
[[steps]] |
|
id = "notify-success" |
|
title = "Send Telegram success notification [Phase 3]" |
|
needs = ["push-and-pr"] |
|
description = """ |
|
Notify yourself that the PR is open. |
|
|
|
```bash |
|
BRANCH=$(git branch --show-current) |
|
PR_URL=$(gh pr view --repo {{upstream_repo}} "${BRANCH}" --json url -q .url 2>/dev/null || echo "unknown") |
|
TITLE=$(jq -r '.title' /tmp/ih-work-issue-{{github_issue}}.json 2>/dev/null || echo "unknown") |
|
|
|
: "${TELEGRAM_BOT_TOKEN:=$(security find-generic-password -a "$USER" -s telegram-bot-token -w 2>/dev/null)}" |
|
: "${TELEGRAM_CHAT_ID:=$(security find-generic-password -a "$USER" -s telegram-chat-id -w 2>/dev/null)}" |
|
if [ -z "${TELEGRAM_BOT_TOKEN:-}" ] || [ -z "${TELEGRAM_CHAT_ID:-}" ]; then |
|
echo "Skipping Telegram notification (credentials not found in env or Keychain)" |
|
echo "PR URL: ${PR_URL}" |
|
else |
|
MSG="✅ PR opened for issue #{{github_issue}}%0A%0A${TITLE}%0A%0A${PR_URL}" |
|
curl -s -X POST \ |
|
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ |
|
-d "chat_id=${TELEGRAM_CHAT_ID}&text=${MSG}" \ |
|
> /dev/null |
|
echo "Telegram notification sent: ${PR_URL}" |
|
fi |
|
``` |
|
|
|
**Exit criteria:** Notification sent (or skipped with warning). PR URL logged.""" |
|
|
|
[[steps]] |
|
id = "exit" |
|
title = "Clean up and exit [Phase 3]" |
|
needs = ["notify-success"] |
|
description = """ |
|
Clean up and exit. |
|
|
|
```bash |
|
rm -f /tmp/ih-work-issue-{{github_issue}}.json |
|
gt done --status COMPLETED --merge=local |
|
``` |
|
|
|
**Exit criteria:** Polecat exits cleanly. PR is live on upstream.""" |
|
|
|
[vars] |
|
[vars.github_issue] |
|
description = "GitHub issue number to work on" |
|
required = true |
|
|
|
[vars.upstream_repo] |
|
description = "Upstream GitHub repo" |
|
default = "gastownhall/gastown" |
|
|
|
[vars.fork_repo] |
|
description = "Your fork to push branches to" |
|
default = "esciara/gastown" |