From 2d8dfea1cf6bf7199bf06f1735e4bf2eaff23d55 Mon Sep 17 00:00:00 2001 From: jepegit Date: Sat, 26 Sep 2026 18:19:24 +0200 Subject: [PATCH] Tell agents to pass multi-line GitHub text with --body-file. (#380) Bash heredocs fail in Windows PowerShell, so the skills that create issues and PR bodies now share one --body-file pattern. Co-authored-by: Cursor --- .cursor/skills/iflow-auto/SKILL.md | 17 ++++++++ .cursor/skills/iflow-build/SKILL.md | 17 ++++++++ .cursor/skills/iflow-cleanup/SKILL.md | 17 ++++++++ .cursor/skills/iflow-close/SKILL.md | 18 ++++++++ .cursor/skills/iflow-epic/SKILL.md | 17 ++++++++ .cursor/skills/iflow-fix/SKILL.md | 17 ++++++++ .cursor/skills/iflow-issue/SKILL.md | 17 ++++++++ .cursor/skills/iflow-pick/SKILL.md | 17 ++++++++ .cursor/skills/iflow-split/SKILL.md | 17 ++++++++ .../03-solved-issues/issue380_original.md | 41 +++++++++++++++++++ .issueflows/03-solved-issues/issue380_plan.md | 30 ++++++++++++++ .../03-solved-issues/issue380_status.md | 13 ++++++ .../04-designs-and-guides/test-registry.md | 1 + .issueflows/agent/skill-stamps.json | 18 ++++---- HISTORY.md | 1 + docs/how-to/for-agents.md | 18 ++++++++ docs/how-to/write-an-issue.md | 5 ++- docs/issue-workflow.md | 2 +- docs/llms.txt | 1 + .../templates/commands/iflow-build.md.j2 | 2 + .../templates/commands/iflow-cleanup.md.j2 | 2 + .../templates/commands/iflow-close.md.j2 | 2 + .../templates/commands/iflow-epic.md.j2 | 2 + .../templates/commands/iflow-fix.md.j2 | 2 + .../templates/commands/iflow-issue.md.j2 | 2 + .../templates/commands/iflow-pick.md.j2 | 2 + .../templates/commands/iflow-split.md.j2 | 2 + .../templates/docs/issue-workflow.md.j2 | 2 +- .../templates/skills/_gh_body_file.md.j2 | 15 +++++++ .../templates/skills/iflow_auto/SKILL.md.j2 | 2 + .../templates/skills/iflow_build/SKILL.md.j2 | 2 + .../skills/iflow_cleanup/SKILL.md.j2 | 2 + .../templates/skills/iflow_close/SKILL.md.j2 | 6 ++- .../templates/skills/iflow_epic/SKILL.md.j2 | 2 + .../templates/skills/iflow_fix/SKILL.md.j2 | 2 + .../templates/skills/iflow_issue/SKILL.md.j2 | 2 + .../templates/skills/iflow_pick/SKILL.md.j2 | 2 + .../templates/skills/iflow_split/SKILL.md.j2 | 2 + tests/test_templating.py | 14 +++++++ 39 files changed, 339 insertions(+), 14 deletions(-) create mode 100644 .issueflows/03-solved-issues/issue380_original.md create mode 100644 .issueflows/03-solved-issues/issue380_plan.md create mode 100644 .issueflows/03-solved-issues/issue380_status.md create mode 100644 src/issue_flow/templates/skills/_gh_body_file.md.j2 diff --git a/.cursor/skills/iflow-auto/SKILL.md b/.cursor/skills/iflow-auto/SKILL.md index a1ae258b..8d1bff80 100644 --- a/.cursor/skills/iflow-auto/SKILL.md +++ b/.cursor/skills/iflow-auto/SKILL.md @@ -138,6 +138,23 @@ When `.issueflows/04-designs-and-guides/multi-repo-workspaces.md` exists, read i ` with Spec, Goal, **`Model: deep`**, `Depends on`, and `Part of epic #.` — no new label in v1. Record numbers + notes in `auto_status.md` with `last_outcome: adversarial_findings`. + +**Multi-line GitHub text (PowerShell-safe).** Bash `<<'EOF'` heredocs fail in Windows PowerShell (`Missing file specification after redirection operator`). Write the text to a file and pass that file: + +- `gh issue create`, `gh issue edit`, and `gh pr create` take `--body-file `. +- `git commit` takes `-F `. + +```powershell +@' +line one + +line two +'@ | Set-Content -Encoding utf8 body.md +gh issue create --repo owner/repo --title "title" --body-file body.md +``` + +Bash accepts the same `--body-file` / `-F` flags. Use that pattern for every multi-line body. + e. No extra user prompts while acting under overnight / review confirm (except the budget ask in step 10). diff --git a/.cursor/skills/iflow-build/SKILL.md b/.cursor/skills/iflow-build/SKILL.md index 50d28a06..06453553 100644 --- a/.cursor/skills/iflow-build/SKILL.md +++ b/.cursor/skills/iflow-build/SKILL.md @@ -96,6 +96,23 @@ When `.issueflows/04-designs-and-guides/multi-repo-workspaces.md` exists, read i - Require an issue-style branch matching `^\d+-.+` (never the default branch) with a remote tracking ref. - Always pass `--repo `. **List before create:** `gh pr list --repo --head --state open --json number,url,title,isDraft`. If an open PR exists, note it and skip creating a second one. - Otherwise create a **draft**: `gh pr create --draft --repo …` with a WIP-friendly body and **`Refs #N`** (not `Closes #N` yet). + +**Multi-line GitHub text (PowerShell-safe).** Bash `<<'EOF'` heredocs fail in Windows PowerShell (`Missing file specification after redirection operator`). Write the text to a file and pass that file: + +- `gh issue create`, `gh issue edit`, and `gh pr create` take `--body-file `. +- `git commit` takes `-F `. + +```powershell +@' +line one + +line two +'@ | Set-Content -Encoding utf8 body.md +gh issue create --repo owner/repo --title "title" --body-file body.md +``` + +Bash accepts the same `--body-file` / `-F` flags. Use that pattern for every multi-line body. + - Record `PR: (#, draft)` in `issue_status.md`. - Do **not** write `HISTORY.md` here — `/iflow-close` owns the changelog bullet (even while a draft PR exists). diff --git a/.cursor/skills/iflow-cleanup/SKILL.md b/.cursor/skills/iflow-cleanup/SKILL.md index 64b42976..47607d49 100644 --- a/.cursor/skills/iflow-cleanup/SKILL.md +++ b/.cursor/skills/iflow-cleanup/SKILL.md @@ -139,6 +139,23 @@ Never: rebase default, `push --force` default, or push default to skip CI. - **Second consolidated confirm** (never folded into Phase A's yes): list every proposed action, then ask once: - Optional: for each **deletable** name, `git push origin --delete ` (or `gh api -X DELETE repos///git/refs/heads/`). Never `--force`. Never delete the default. On push failure (e.g. protection), report and continue. - Optional: create a findings issue with `gh issue create --repo ` after showing the draft title/body (deletable list + unique-work summaries). Suggested title: `chore: remote branch audit ()`. Create only on yes. + +**Multi-line GitHub text (PowerShell-safe).** Bash `<<'EOF'` heredocs fail in Windows PowerShell (`Missing file specification after redirection operator`). Write the text to a file and pass that file: + +- `gh issue create`, `gh issue edit`, and `gh pr create` take `--body-file `. +- `git commit` takes `-F `. + +```powershell +@' +line one + +line two +'@ | Set-Content -Encoding utf8 body.md +gh issue create --repo owner/repo --title "title" --body-file body.md +``` + +Bash accepts the same `--body-file` / `-F` flags. Use that pattern for every multi-line body. + - Phase B is **read-only until that second confirm**. Declining leaves remotes untouched. 10. **Report.** Summarize: default branch, PR/merge status, Phase A1 commands and `-d` deletions, Phase A2 `-D` deletions with their tip SHAs (or "declined" / "none offered"), branches left alone as unique work, folder sweep, epic stage-gate offer, self-update action (`upgraded` / `skipped` / `failed` / "not enabled"), and (when run) Phase B bucket counts, remote deletes, findings issue URL or "skipped". If this run used a workspace token, report each member. Else if `issue-flow agent resolve --json` reports `sibling_roots`, list them and remind the user that **each scaffolded repo needs its own `/iflow-cleanup`** (or `/iflow-cleanup workspace`) — do not loop automatically without the token. If other open PRs still show `DIRTY` / CONFLICTING (often `HISTORY.md`), **offer** `/iflow-pr-sync` — do not auto-run it. diff --git a/.cursor/skills/iflow-close/SKILL.md b/.cursor/skills/iflow-close/SKILL.md index 45f41d76..8147ac29 100644 --- a/.cursor/skills/iflow-close/SKILL.md +++ b/.cursor/skills/iflow-close/SKILL.md @@ -161,6 +161,24 @@ Marker: `@pytest.mark.essential`. Contract: 8. **Pull request** — Against the default branch; always pass `--repo `. - **List before create.** Run `gh pr list --repo --head --state open --json number,url,title,isDraft`. If an open PR already exists for this head (including a draft from `/iflow-build` early PR), **update** it (title/body as needed; prefer `Closes #n` when shipping) instead of opening a second one. Otherwise `gh pr create` — add `--draft` when the user passed the `draft` token. Body should explain the change, how to test, and link the GitHub issue (`Closes #n` / `Refs #n`). + +**Multi-line GitHub text (PowerShell-safe).** Bash `<<'EOF'` heredocs fail in Windows PowerShell (`Missing file specification after redirection operator`). Write the text to a file and pass that file: + +- `gh issue create`, `gh issue edit`, and `gh pr create` take `--body-file `. +- `git commit` takes `-F `. + +```powershell +@' +line one + +line two +'@ | Set-Content -Encoding utf8 body.md +gh issue create --repo owner/repo --title "title" --body-file body.md +``` + +Bash accepts the same `--body-file` / `-F` flags. Use that pattern for every multi-line body. + + - **Ready from draft (when not `draft`).** If the open PR is still a draft and the user did **not** pass `draft`, mark it ready for review (`gh pr ready --repo `) before the checks snapshot / yolo merge. - **Checks snapshot.** After the PR exists, run `gh pr checks --repo ` and report pass / fail / pending. "CI is green" means this command exits 0 (or JSON buckets are all `pass` / `skipping`). Without `yolo`, prefer this one-shot list; **offer** `issue-flow agent pr-ready --watch` when the user wants to wait until the PR is merge-ready (do **not** auto-run; honour the **15-minute** wall-clock cap). Full CI/`gh` cheatsheet (including `gh run list` / `gh run watch` fallback when PR checks are empty): `.cursor/skills/gh-ci/SKILL.md`. If `gh pr checks` returns empty or cannot resolve checks, fall back to `gh run list --repo ` then `gh run watch --repo ` under the same budget. diff --git a/.cursor/skills/iflow-epic/SKILL.md b/.cursor/skills/iflow-epic/SKILL.md index 574c8112..98155c72 100644 --- a/.cursor/skills/iflow-epic/SKILL.md +++ b/.cursor/skills/iflow-epic/SKILL.md @@ -165,6 +165,23 @@ Turn one stage of a **confirmed** plan into real GitHub issues. Requires `Status 2. **Dry-run listing.** Show what would be created: per spec — title, labels (`yolo` when the judgment says yes **and** the label exists per `gh label list`; otherwise note the gap), and the dependency lines after placeholder resolution. `stage issue ` placeholders pointing at already-published specs are rewritten to their real `#`; placeholders at still-unpublished specs stay verbatim with a note. 3. **Consolidated confirm** (destructive-ish — outward-facing writes; normal prose, never shortened). One prompt covering exactly: which issues get created, with which labels, and that the anchor issue's task list will be updated. Do not proceed without a clear yes. 4. **Create, in dependency order within the stage.** For each spec: `gh issue create --repo ` with the self-contained body (context, scope, acceptance criteria, **Goal:** and **Model:** lines when present in the plan, resolved `Depends on: #` lines, and a closing `Part of epic #.` line). Immediately record the new number in the plan file as `- Published: #` under that spec. + +**Multi-line GitHub text (PowerShell-safe).** Bash `<<'EOF'` heredocs fail in Windows PowerShell (`Missing file specification after redirection operator`). Write the text to a file and pass that file: + +- `gh issue create`, `gh issue edit`, and `gh pr create` take `--body-file `. +- `git commit` takes `-F `. + +```powershell +@' +line one + +line two +'@ | Set-Content -Encoding utf8 body.md +gh issue create --repo owner/repo --title "title" --body-file body.md +``` + +Bash accepts the same `--body-file` / `-F` flags. Use that pattern for every multi-line body. + 5. **Update the anchor issue's task list** (append/patch only — never rewrite the user's own body text): fetch the body, append a `## Stage — ` section (or extend it) with one `- [ ] #<M>` line per created issue, and write it back via `gh issue edit <N> --body-file`. 6. **Commit `Published:` lines off default.** The plan-file edits in step 4 must not sit as unpushed commits on home default. If you are on default (or would commit there), use a chore/issue branch (or a tiny dedicated PR). Never leave `Published: #<M>` unpushed on home default — that is what later makes `git pull --ff-only` diverge after a squash (issue #303). 7. **Report.** Created issues (numbers + titles + labels), skipped already-published specs, unresolved placeholders, and the reminder that the next stage publishes only after this one's issues close. diff --git a/.cursor/skills/iflow-fix/SKILL.md b/.cursor/skills/iflow-fix/SKILL.md index 35c4e066..4175ede8 100644 --- a/.cursor/skills/iflow-fix/SKILL.md +++ b/.cursor/skills/iflow-fix/SKILL.md @@ -69,6 +69,23 @@ When `.issueflows/04-designs-and-guides/multi-repo-workspaces.md` exists, read i 1. **Preflight.** Detect the default branch (`gh repo view --json defaultBranchRef -q .defaultBranchRef.name`; fall back to `git symbolic-ref --quiet --short refs/remotes/origin/HEAD`, else `main`). Run `git fetch --prune`. Report current branch + clean/dirty tree (`git status --porcelain`); if dirty with unrelated changes, ask to commit/stash first. 2. **Resolve the session name.** Baked `fix_auto_name = true`: pick the kebab slug yourself from an explicit invoke name, else invent a short descriptive slug from the user's intent/context (fallback `iterative-small-fixes`). Do **not** ask the user to approve or rename the title/slug — show it only inside the create confirm below. Configurable via `fix_auto_name` under `[issueflow]` in `.issueflows/config.toml` (re-run `issue-flow update` after changing). 3. **Create the GitHub issue (always, with confirmation).** Show the chosen title (e.g. `Iterative fixes: <name>`, or `Iterative small fixes`) and a body noting it is an interactive `/iflow-fix` session whose individual fixes are recorded in the status markdown and landed together via `/iflow-close`. Create it with `gh issue create` (add `--repo owner/repo` if ambiguous). Capture the returned number `N`. A fresh issue is created each time. Set the chat tab title to `Issue <N> <session name>`. + +**Multi-line GitHub text (PowerShell-safe).** Bash `<<'EOF'` heredocs fail in Windows PowerShell (`Missing file specification after redirection operator`). Write the text to a file and pass that file: + +- `gh issue create`, `gh issue edit`, and `gh pr create` take `--body-file <path>`. +- `git commit` takes `-F <path>`. + +```powershell +@' +line one + +line two +'@ | Set-Content -Encoding utf8 body.md +gh issue create --repo owner/repo --title "title" --body-file body.md +``` + +Bash accepts the same `--body-file` / `-F` flags. Use that pattern for every multi-line body. + 4. **Create the worktree (with confirmation).** Slug from the resolved name (kebab-case; default `iterative-small-fixes`); branch name `<N>-<slug>`. Require a clean tree. **Worktree-first start (default, issue #255 / #303 / #329).** After the dirty-tree gate and slug confirm — unless the user passed `inplace` / `no worktree`, or ops chose stay-on-current/default: diff --git a/.cursor/skills/iflow-issue/SKILL.md b/.cursor/skills/iflow-issue/SKILL.md index 7bb403b3..e21b2ee9 100644 --- a/.cursor/skills/iflow-issue/SKILL.md +++ b/.cursor/skills/iflow-issue/SKILL.md @@ -81,6 +81,23 @@ When `.issueflows/04-designs-and-guides/multi-repo-workspaces.md` exists, read i Refine with the user until they confirm the text. If the draft is clearly over-large for one PR, **offer** `/iflow-split` (flat parent/child) or `/iflow-epic` (staged) — do **not** auto-create sub-issues. 4. **Create (confirm first).** Show the final title and body (and, in epic-anchor mode, the planned `epic` label when present). On yes: `gh issue create --repo <owner/repo>` (add `--label epic` only when epic-anchor mode is on **and** `gh label list` shows `epic`). Capture number `N`. Set the chat tab title to `Issue <N> <short title>`. Optional labels/milestones other than the epic-anchor label: only if the user asked for them in this turn — do not invent them. +**Multi-line GitHub text (PowerShell-safe).** Bash `<<'EOF'` heredocs fail in Windows PowerShell (`Missing file specification after redirection operator`). Write the text to a file and pass that file: + +- `gh issue create`, `gh issue edit`, and `gh pr create` take `--body-file <path>`. +- `git commit` takes `-F <path>`. + +```powershell +@' +line one + +line two +'@ | Set-Content -Encoding utf8 body.md +gh issue create --repo owner/repo --title "title" --body-file body.md +``` + +Bash accepts the same `--body-file` / `-F` flags. Use that pattern for every multi-line body. + + ### Phase 2 — optional lifecycle setup 5. **Offer branch + init (default path).** Ask whether to start work now. On yes (require a clean tree; if dirty, stop and ask to commit/stash): diff --git a/.cursor/skills/iflow-pick/SKILL.md b/.cursor/skills/iflow-pick/SKILL.md index 4ab76a53..f79c23a1 100644 --- a/.cursor/skills/iflow-pick/SKILL.md +++ b/.cursor/skills/iflow-pick/SKILL.md @@ -75,6 +75,23 @@ When `.issueflows/04-designs-and-guides/multi-repo-workspaces.md` exists, read i - **Else GitHub** — `gh issue list --state open --json number,title,labels,milestone,updatedAt` (add `--repo owner/repo` if ambiguous). When `label:<L>` is active, add `--label <L>` (hard filter). Drop issues already captured under `01-current-issues/`, `02-partly-solved-issues/`, or `03-solved-issues/`. If the filtered set is empty, **stop** with “no open issues with label `<L>`.” 4. **Rank and present.** Rank by **epic membership** (an active epic's current-stage `next_candidates` first) + **milestone** (nearest/active, honour any hint) + **labels** (match recent work / soft hint when no `label:` filter) + **topical similarity** to recently solved issues (skim `.issueflows/03-solved-issues/` and recent branch names). Show a numbered shortlist (~3–7) with number, title, labels, milestone, and (for epic issues) the epic + stage, and **ask the user to confirm** the pick or override. Never pick silently — even when the filtered shortlist has a single entry. 5. **Create a `fix` issue (only when requested).** Use `gh issue create` (e.g. `chore: general fixes`), confirm title/body first, capture the new number. A fresh issue is created each time — never reuse an existing open general-fixes issue. + +**Multi-line GitHub text (PowerShell-safe).** Bash `<<'EOF'` heredocs fail in Windows PowerShell (`Missing file specification after redirection operator`). Write the text to a file and pass that file: + +- `gh issue create`, `gh issue edit`, and `gh pr create` take `--body-file <path>`. +- `git commit` takes `-F <path>`. + +```powershell +@' +line one + +line two +'@ | Set-Content -Encoding utf8 body.md +gh issue create --repo owner/repo --title "title" --body-file body.md +``` + +Bash accepts the same `--body-file` / `-F` flags. Use that pattern for every multi-line body. + 6. **Over-large issue (offer only).** If the chosen issue is too big for one PR, **mention** `/iflow-split` (flat parent/child) or `/iflow-epic` (staged) and ask. Default is proceed with the whole issue. Do **not** create children here. 7. **Label-driven ops flow.** If the chosen issue carries the **`ops`** label (case-insensitive), announce it and fold `/iflow-ops` into the pick confirmation (one prompt: optional branch vs stay on default + ops work + `close ops`). On yes, run Phase 2 (ask whether to create `<N>-<slug>` or stay on current/default — default branch is allowed for ops) then follow the `iflow-ops` skill **instead of** Phase 3 / yolo. If the issue also carries **`yolo`**, **ops wins** — announce the conflict. Configurable via `label_flows` / `ops_label` under `[issueflow]` in `.issueflows/config.toml` (re-run `issue-flow update` after changing). diff --git a/.cursor/skills/iflow-split/SKILL.md b/.cursor/skills/iflow-split/SKILL.md index 4ee4761d..cc2466cf 100644 --- a/.cursor/skills/iflow-split/SKILL.md +++ b/.cursor/skills/iflow-split/SKILL.md @@ -83,6 +83,23 @@ When `.issueflows/04-designs-and-guides/multi-repo-workspaces.md` exists, read i 5. **Consolidated confirm** (normal prose, never shortened). One prompt covering: parent `#N` stays **open** as the tracker; each listed child title will be created; each will be linked as a GitHub native sub-issue; a `- [ ] #<M>` task-list block will be appended on the parent under `## Sub-issues`. No yes → stop. 6. **Create + link (idempotent).** For each unpublished child: 1. `gh issue create --repo <owner/repo>` (labels/milestones only if the user asked this turn). Capture number `M`. + +**Multi-line GitHub text (PowerShell-safe).** Bash `<<'EOF'` heredocs fail in Windows PowerShell (`Missing file specification after redirection operator`). Write the text to a file and pass that file: + +- `gh issue create`, `gh issue edit`, and `gh pr create` take `--body-file <path>`. +- `git commit` takes `-F <path>`. + +```powershell +@' +line one + +line two +'@ | Set-Content -Encoding utf8 body.md +gh issue create --repo owner/repo --title "title" --body-file body.md +``` + +Bash accepts the same `--body-file` / `-F` flags. Use that pattern for every multi-line body. + 2. Link as a native sub-issue. Prefer the CLI fast path: `issue-flow agent sub-issue-add <N> <M> -C <project_root> [--repo owner/repo] --json` Fields: `linked`, `skipped` (already a child), `error`. On CLI missing or `error` set, fall back to the REST recipe below — then if that also fails (404 / permission / plan), **keep the created issue** and rely on the parent task list. diff --git a/.issueflows/03-solved-issues/issue380_original.md b/.issueflows/03-solved-issues/issue380_original.md new file mode 100644 index 00000000..91b69d5a --- /dev/null +++ b/.issueflows/03-solved-issues/issue380_original.md @@ -0,0 +1,41 @@ +# Issue #380: Windows PowerShell: bash heredocs break gh issue create / multi-line bodies in agent skills + +Source: https://github.com/jepegit/issue-flow/issues/380 + +## Original issue text + +### Problem / context +On Windows, Cursor agent sessions often run commands in **PowerShell**. Several agent workflows (including `/iflow-issue` creating a multi-line GitHub issue body) commonly use a **bash heredoc**: + +```bash +gh issue create --repo owner/repo --title "..." --body "$(cat <<'EOF' +... +EOF +)" +``` + +That pattern is also common in Cursor user rules for `git commit --trailer "Co-authored-by: Cursor <cursoragent@cursor.com>" -m "$(cat <<'EOF' ...)"`. PowerShell does **not** support `<<'EOF'`; it treats `<<` as redirection and fails with: + +``` +Missing file specification after redirection operator. +``` + +Observed while creating https://github.com/ife-bat/bess-wrangler/issues/1 via `/iflow-issue` (issue-flow 0.5.12, Windows 10, PowerShell). + +### Spec +Document (and preferably standardize in scaffolded skills) a **shell-portable** way to pass multi-line bodies to `gh` / git on Windows PowerShell, e.g.: + +1. Prefer `gh … --body-file <path>` (write body with the shell’s native here-string or a small Python/temp write). +2. Or detect shell and branch: bash heredoc vs PowerShell `@'…'@` / `-Body` / temp file. +3. Call out in agent-facing docs (`llms.txt` / how-to for agents / `/iflow-issue` skill) that bash heredocs are **not** safe on PowerShell. + +Same guidance should cover other multi-line `gh` writes skills already use (`gh issue edit … --body-file`, PR bodies, commit messages) so agents do not invent a second failing recipe. + +### Acceptance criteria +- [ ] Agent-facing docs or skills state that bash `<<'EOF'` heredocs fail under PowerShell and give a working alternative. +- [ ] `/iflow-issue` (and any other skill that creates multi-line `gh` bodies) recommends `--body-file` or an equivalent portable pattern — not bash-only heredoc as the sole example. +- [ ] A Windows/PowerShell agent can create an issue with a multi-line markdown body on the first attempt without a ParserError. + +### Out of scope +- Changing Cursor’s default shell. +- Implementing a full cross-shell command rewriter in the CLI. diff --git a/.issueflows/03-solved-issues/issue380_plan.md b/.issueflows/03-solved-issues/issue380_plan.md new file mode 100644 index 00000000..52b5e126 --- /dev/null +++ b/.issueflows/03-solved-issues/issue380_plan.md @@ -0,0 +1,30 @@ +# Plan — Issue #380: PowerShell-safe multi-line gh bodies + +## Goal + +Skills and agent docs that create multi-line GitHub text must say to use +`--body-file` (or `git commit -F`). Bash `<<'EOF'` heredocs fail in +Windows PowerShell. + +## Approach + +One shared snippet, `skills/_gh_body_file.md.j2`, included from every +skill and command that creates an issue or a PR body. Same note on the +agent how-to, the write-an-issue page, `llms.txt`, and the command +reference for `/iflow-issue`. No shell detector and no CLI rewriter. + +## Files to touch + +- `src/issue_flow/templates/skills/_gh_body_file.md.j2` (new) +- Issue, fix, pick, split, epic, cleanup, close, build, and auto + skill + command templates +- `docs/how-to/for-agents.md`, `docs/how-to/write-an-issue.md`, + `docs/llms.txt` +- `src/issue_flow/templates/docs/issue-workflow.md.j2` +- `tests/test_templating.py` +- Re-render with `issue-flow update` + +## Test strategy + +Template render asserts `/iflow-issue` skill and command contain +`--body-file` and `PowerShell`. Full pytest before close. diff --git a/.issueflows/03-solved-issues/issue380_status.md b/.issueflows/03-solved-issues/issue380_status.md new file mode 100644 index 00000000..c7eca32a --- /dev/null +++ b/.issueflows/03-solved-issues/issue380_status.md @@ -0,0 +1,13 @@ +# Status — Issue #380: Windows PowerShell heredocs + +- [x] Done + +## What's done + +- Shared `_gh_body_file.md.j2` included from issue, fix, pick, split, epic, cleanup, close, build, and auto. +- Agent how-to, write-an-issue, `llms.txt`, and the command reference say the same. +- New template test left unmarked. No version bump. + +## Remaining work + +- None. diff --git a/.issueflows/04-designs-and-guides/test-registry.md b/.issueflows/04-designs-and-guides/test-registry.md index 041d05a3..04872251 100644 --- a/.issueflows/04-designs-and-guides/test-registry.md +++ b/.issueflows/04-designs-and-guides/test-registry.md @@ -40,6 +40,7 @@ current issue**. `/iflow-doctor` may audit the whole suite against this table. | `tests/test_cleanup_yes_warning.py` (module) | no | no | `init._warn_cleanup_yes_a2` | #388 | Warning when `cleanup_yes_a2` is on; leave unmarked | | `tests/test_templating.py::test_cleanup_bakes_yes_knobs` | no | no | cleanup/close skill bake of `cleanup_yes_*` / `auto_cleanup` | #388 | Config default bake; leave unmarked | | `tests/test_doc_configuration.py::test_all_settings_table_lists_every_config_key_once` | yes | yes | `docs/configuration.md` vs `CONFIG_KEYS` | #388 | Already essential; digit keys (`cleanup_yes_a1`) must match | +| `tests/test_templating.py::test_issue_create_skills_use_body_file_not_bash_heredoc` | no | no | `_gh_body_file.md.j2` included from issue/fix/close | #380 | Docs/skill contract; leave unmarked | **Columns** diff --git a/.issueflows/agent/skill-stamps.json b/.issueflows/agent/skill-stamps.json index fe2bbf23..4a262995 100644 --- a/.issueflows/agent/skill-stamps.json +++ b/.issueflows/agent/skill-stamps.json @@ -6,29 +6,29 @@ ".cursor/skills/grill-me": "32796fde9c11d1b3fab845ca077e6a6f7888bceabe651ce1c66f69a2f713c48a", ".cursor/skills/iflow": "3490c89cd01cdc49b235f14283acee06672f2e25a53ae53a03c50226511e228a", ".cursor/skills/iflow-archive": "545f5e3eb21a701945b9b99c87f059141140c268c25455e3a7276fe0c5037039", - ".cursor/skills/iflow-auto": "03dd9abf2481288c0d104d86c94e338ae5ab6a94eb9543427b32dfded8259ce5", - ".cursor/skills/iflow-build": "c51698ed032ad6074ac2bb87259eb840b40b8d13bb02c1d148cff01f90a422b6", + ".cursor/skills/iflow-auto": "01fb4880a1cb59b8a384c9a037f3363370fcdffdd4c6409cb1859f59f61e11a9", + ".cursor/skills/iflow-build": "0dacc46d635dbe0c8743f043dc519d1d7961157b99665cf9fc2c9907ea70f939", ".cursor/skills/iflow-capture": "e3a5cc8bf9f4d7dd17b431b3bd2a45820576f6516dd66723e8b2afe0f8c537a7", - ".cursor/skills/iflow-cleanup": "1aa2e8382038ed25d981df52b01dccec6a27b56d52a5e915b5ce9832b0f04254", - ".cursor/skills/iflow-close": "e6dd9ac4a4737dc44ea4eec3dc221c2858f3d223ee3e0f55804157b36adb8a5f", + ".cursor/skills/iflow-cleanup": "bd619be144ee4925f690bd7848b3ac0031802ffcf9964983eb613801826324c6", + ".cursor/skills/iflow-close": "3edb7b5d2259dcc57faab1c4bf4aeb47bc109cb1c6a902ee665c09e45f6c1cf9", ".cursor/skills/iflow-comments": "270b0726c0223859da6aa694094d8a9609c0b34224b675c09a783d0f2ac23f83", ".cursor/skills/iflow-cycle": "18debb1c04ab511c06e3d5041daa21461d1e971538786b1b9482459fa96cfafb", ".cursor/skills/iflow-doctor": "24a8aad6a3b1ae88f8f18c05af86031dfcf30bd22db340657a6bc86a4cf7ca07", ".cursor/skills/iflow-drive": "d64283028d6840eb8367e1451691883b19e89452c328af308eca332adf661aed", - ".cursor/skills/iflow-epic": "18cd1bc25687d880f1c2b5a2131a34672a5b2ac7f5497ab78ed9621b46c0dd18", - ".cursor/skills/iflow-fix": "576a135f798913487739cbc105d07c8190d24bd99dea92073324eb95efc97d89", + ".cursor/skills/iflow-epic": "ee773f887fea98af0b745304a0e9e05389b115da4ee1f800319d4291eb07b66d", + ".cursor/skills/iflow-fix": "06b3ebf38d48a8d54ef086a4d6e7db86fdbd35e6403df8a4f88d7dfab651607b", ".cursor/skills/iflow-graphify": "556c5159bda6624e496413c359a075ae6621b19915ec9ced7e1d05b42b102203", ".cursor/skills/iflow-history-update": "2206288996486af4a12b636a0a89830bbdd147806bf318b47db76dbc4d063bc4", ".cursor/skills/iflow-init": "168f4f2263a7004a85afb36b3f9948df202f10d1c9504ca8c3afc5402db94b61", - ".cursor/skills/iflow-issue": "2bd73e8237e688c13e60fe40e171751de9df32db93d9b6ad32fc0d9ff8d3210f", + ".cursor/skills/iflow-issue": "356ff5ec813a3f656ed24ae8587b0b5fde0407b7c66e61aca66822795dfa60aa", ".cursor/skills/iflow-ops": "e90ce3764efd9703d0a9cd1c520c4ccc303714bfce917922d35e886f4cca01ee", ".cursor/skills/iflow-pause": "5f05d607bf76431728a6bc08a68246a4fccc8367a190b7c65afb371583298d85", - ".cursor/skills/iflow-pick": "5777240d76bf421a3d30e553bdcbb4fbd2ade3cec0da9c00d7c8d8400c8b9001", + ".cursor/skills/iflow-pick": "1923f765b11d2ad3d3a215e3e8ee8d1723f84f2e80e07a37561c8cce33b4c941", ".cursor/skills/iflow-plan": "5d8c49e168642a14b2a96d246e8bb4cc2439400bf1a8201f08b10958a676dfde", ".cursor/skills/iflow-pr-sync": "caf5ee53349481af7f30f78664e9edb87eaffe3a3c92c5dd7bb2bf49462c2e89", ".cursor/skills/iflow-review": "6529ed31ae354f12c287db18ffeda79f10c147127be76878fd07aa1db8ea723a", ".cursor/skills/iflow-setup": "342f5ed055587a7f56132ce05055c25ec98b1a9fd2cf9c54840a8af0b856e25e", - ".cursor/skills/iflow-split": "d6701a18d923f5ee03a3fcf72385ac168f0c3981dffd5bd6f3d52866b62f7238", + ".cursor/skills/iflow-split": "c5c531ab88ebbd7c029f3a848e86ce4f2246d11e7e1fbda028286823c6e29e6d", ".cursor/skills/iflow-status": "7b00cb519f3b81dcfaf1ec6ee7f6248554e9f81f38972bb4a63c30d9602a6094", ".cursor/skills/iflow-version-bump": "7110737d621468e97c86a7c2e0b6c26d33b5361ccfa06d8db15524815a9fc515", ".cursor/skills/iflow-workspace-git": "0782ab6569ca7235eb2eac9d53575a375176f9e7c840871ca858c0cf16254222", diff --git a/HISTORY.md b/HISTORY.md index 5013f2bc..c3903e59 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -10,6 +10,7 @@ than the GitHub release notes they link to. ## [Unreleased] - Epic #269 is complete: user-global config, per-repo lock, the project registry, and `update --all` shipped in stages #281–#298. (#269) +- Skills that create multi-line GitHub text now say to use `--body-file` or `git commit -F`. Bash `<<'EOF'` heredocs fail in Windows PowerShell. (#380) ## [0.5.15] - 2026-09-26 diff --git a/docs/how-to/for-agents.md b/docs/how-to/for-agents.md index ca98fe4c..f84abead 100644 --- a/docs/how-to/for-agents.md +++ b/docs/how-to/for-agents.md @@ -61,6 +61,24 @@ re-resolve that venv. Confirm before running upgrade/update unless the human already said to do it. +## Multi-line GitHub text on PowerShell + +Bash `<<'EOF'` heredocs fail in Windows PowerShell (`Missing file +specification after redirection operator`). Write the body to a file. + +```powershell +@' +line one + +line two +'@ | Set-Content -Encoding utf8 body.md +gh issue create --repo owner/repo --title "title" --body-file body.md +``` + +Use `--body-file` for `gh issue create`, `gh issue edit`, and `gh pr create`. +Use `git commit -F <path>` for a multi-line commit message. Bash accepts the +same flags. Do not offer a bash heredoc as the only example. + ## Initialize issue-flow globally User-global `iflow-init` is written on the first successful `issue-flow init` diff --git a/docs/how-to/write-an-issue.md b/docs/how-to/write-an-issue.md index 55b404a6..cb3543d9 100644 --- a/docs/how-to/write-an-issue.md +++ b/docs/how-to/write-an-issue.md @@ -17,8 +17,9 @@ acceptance criteria), and optionally start working on it straight away. 2. The agent drafts a title and a body with **Problem / context**, **Spec**, **Acceptance criteria**, and (optionally) **Out of scope**. Edit it together until it says what you mean. -3. Confirm the final text. The agent runs `gh issue create` and reports the - new number `N`. +3. Confirm the final text. The agent writes the body to a file and runs + `gh issue create --body-file`. A bash `<<'EOF'` heredoc fails in Windows + PowerShell. The agent reports the new number `N`. 4. The agent asks whether to start now. On yes it creates the branch `<N>-<slug>` (in a sibling worktree by default), captures the issue, and asks before continuing to `iflow plan`. On no, pick it up later with diff --git a/docs/issue-workflow.md b/docs/issue-workflow.md index 90fcf650..a1d5d314 100644 --- a/docs/issue-workflow.md +++ b/docs/issue-workflow.md @@ -270,7 +270,7 @@ Every command below is described the same way: **When to use**, **Arguments**, * 1. **Preflight** — default branch, `git fetch --prune`, clean/dirty tree. 2. **Draft** — title + body with **Problem / context**, **Spec**, **Acceptance criteria**, and optional **Out of scope**. If clearly over-large for one PR, offers `/iflow-split` (flat) or `/iflow-epic` (staged) — does not auto-split. -3. **Create** — `gh issue create` and capture `N`. +3. **Create** — `gh issue create --body-file` (bash `<<'EOF'` heredocs fail in Windows PowerShell) and capture `N`. 4. **Optional setup** — branch `<N>-<slug>` + `/iflow-capture`, then offers `/iflow-plan` (never auto-runs plan). Decline → create-only; pick up later via `/iflow-pick` / `/iflow-capture`. **What it asks you:** To refine and confirm the draft; to confirm the final title/body (and epic label) before creating; whether to start work now. GitHub only (`gh`); GitLab is not supported. diff --git a/docs/llms.txt b/docs/llms.txt index 5cee9bd0..b9f4c70e 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -20,3 +20,4 @@ - Do not treat `issue-flow update` as a package upgrade. - Do not `issue-flow init` a folder that only contains sibling git repos. - Do not let `git` / `gh` infer the repo from cwd in a multi-root workspace — resolve first (`root:` / `repo:` / `issue-flow agent resolve`). +- Do not pass multi-line `gh` or `git commit` text with a bash `<<'EOF'` heredoc. PowerShell rejects it. Use `--body-file` or `git commit -F`. See [for agents](https://issue-flow.readthedocs.io/en/latest/how-to/for-agents/). diff --git a/src/issue_flow/templates/commands/iflow-build.md.j2 b/src/issue_flow/templates/commands/iflow-build.md.j2 index 2b029870..56cd0876 100644 --- a/src/issue_flow/templates/commands/iflow-build.md.j2 +++ b/src/issue_flow/templates/commands/iflow-build.md.j2 @@ -73,6 +73,8 @@ If additional input is added, use that as implementation hints (scope, constrain - Require an issue-style branch matching `^\d+-.+` (never the default branch) with a remote tracking ref. - Always pass `--repo <owner/repo>`. **List before create:** `gh pr list --repo <owner/repo> --head <branch> --state open --json number,url,title,isDraft`. If an open PR exists, note it and skip creating a second one. - Otherwise create a **draft**: `gh pr create --draft --repo <owner/repo> …` with a WIP-friendly body and **`Refs #N`** (not `Closes #N` yet). + +{% include "skills/_gh_body_file.md.j2" %} - Record `PR: <url> (#<n>, draft)` in `issue<N>_status.md`. - Do **not** write `{{ history_file }}` here — `/iflow-close` owns the changelog bullet (even while a draft PR exists). diff --git a/src/issue_flow/templates/commands/iflow-cleanup.md.j2 b/src/issue_flow/templates/commands/iflow-cleanup.md.j2 index 21ff6e3d..dc2ccae2 100644 --- a/src/issue_flow/templates/commands/iflow-cleanup.md.j2 +++ b/src/issue_flow/templates/commands/iflow-cleanup.md.j2 @@ -89,6 +89,8 @@ Optional free-form text after the command. Examples: - **Its own consolidated confirm** (never implied by the Phase A1 or A2 yes): - Optional `git push origin --delete <branch>` for each listed **deletable** remote — never `--force`, never the default branch. - Optional findings issue via `gh issue create` after showing draft title/body (suggested title: `chore: remote branch audit (<YYYY-MM-DD>)`). + +{% include "skills/_gh_body_file.md.j2" %} - Declining leaves remotes and GitHub issues untouched. ## Output diff --git a/src/issue_flow/templates/commands/iflow-close.md.j2 b/src/issue_flow/templates/commands/iflow-close.md.j2 index 3f3a669d..f48e1790 100644 --- a/src/issue_flow/templates/commands/iflow-close.md.j2 +++ b/src/issue_flow/templates/commands/iflow-close.md.j2 @@ -85,6 +85,8 @@ Other optional notes still apply: branch name, PR title, skip issue doc update, 7. **Pull request** - Always pass `--repo <owner/repo>`. - **List before create.** `gh pr list --repo <owner/repo> --head <branch> --state open --json number,url,title,isDraft`. If an open PR already exists for this head (including a draft from `/iflow-build` early PR), **update** it instead of opening a second one; otherwise `gh pr create` — add `--draft` when the user passed the `draft` token. + +{% include "skills/_gh_body_file.md.j2" %} - Describe the change, how to test it, and link the GitHub issue (prefer `Closes #123` when shipping; `Refs #123` is fine for WIP). {% if "pstack_unslop" in included_skills %} - **Unslop (pstack, optional).** The `unslop` skill (`{{ agent_dir }}/skills/unslop/SKILL.md`) is installed — *offer* to pass the PR body and the `{{ history_file }}` bullet through it before posting; apply it only when the user says so (skip the offer under `yolo`). {% endif %} - **Ready from draft (when not `draft`).** If the open PR is still a draft and the user did **not** pass `draft`, mark it ready for review (`gh pr ready <number> --repo <owner/repo>`) before the checks snapshot / yolo merge. diff --git a/src/issue_flow/templates/commands/iflow-epic.md.j2 b/src/issue_flow/templates/commands/iflow-epic.md.j2 index 04ff48eb..2c4464db 100644 --- a/src/issue_flow/templates/commands/iflow-epic.md.j2 +++ b/src/issue_flow/templates/commands/iflow-epic.md.j2 @@ -36,6 +36,8 @@ The full playbook lives in `{{ agent_dir }}/skills/iflow-epic/SKILL.md` — foll **Publish action** (`publish [stage <k>]`, confirmed plans only): dry-run listing → one consolidated confirm → `gh issue create` per unpublished spec in dependency order (yolo label only when the judgment says yes and the label exists; `Part of epic #<N>.` in each body; `stage <j> issue <k>` placeholders resolved to real numbers where published) → record `- Published: #<M>` back into the plan → append `- [ ] #<M>` task-list lines to the anchor issue (append/patch only). Skipping specs that already carry `Published:` makes re-runs idempotent. +{% include "skills/_gh_body_file.md.j2" %} + ## Constraints - Drafting writes nothing on GitHub: no `gh issue create`, no label/milestone writes, no anchor-issue edits (reading via `gh` is fine). `publish` is the single exception and never runs without its consolidated confirm. diff --git a/src/issue_flow/templates/commands/iflow-fix.md.j2 b/src/issue_flow/templates/commands/iflow-fix.md.j2 index c016b0d2..b7dcb807 100644 --- a/src/issue_flow/templates/commands/iflow-fix.md.j2 +++ b/src/issue_flow/templates/commands/iflow-fix.md.j2 @@ -39,6 +39,8 @@ Run this only when there is no active session for the current branch yet (no `is - Capture the returned **issue number `N`**. A fresh issue is created each time — existing open fixes issues are not reused. - Change the chat/agent tab title to `Issue <N> <short session name>`. +{% include "skills/_gh_body_file.md.j2" %} + 4. **Create the worktree (with confirmation).** Build the slug from the resolved name (kebab-case), defaulting to `iterative-small-fixes`. The branch name is `<N>-<slug>`. - {% if worktree_first %}Home stays on default; `issue-flow agent worktree-add <N> --slug <slug>` then `open-workspace` (print-only). `inplace` / `no worktree` keeps `git switch -c` on home. Token `worktree` is a no-op when `worktree_first` is already on.{% else %}`git switch -c <N>-<slug>` on home. Token `worktree` uses `worktree-add` + `open-workspace`. On worktree-add failure, stop and ask.{% endif %} - Require a clean tree; confirm a non-obvious slug. diff --git a/src/issue_flow/templates/commands/iflow-issue.md.j2 b/src/issue_flow/templates/commands/iflow-issue.md.j2 index 1cf45378..8f361e19 100644 --- a/src/issue_flow/templates/commands/iflow-issue.md.j2 +++ b/src/issue_flow/templates/commands/iflow-issue.md.j2 @@ -41,6 +41,8 @@ Optional free-form text after the command — a seed for the title and/or short 4. **Create (confirm first).** Show the final title and body (and, in epic-anchor mode, the planned `epic` label when present). On yes: create with `gh issue create` (add `--repo owner/repo` if ambiguous; add `--label epic` only when epic-anchor mode is on **and** `gh label list` shows `epic`). Capture the returned issue number `N`. Change the chat/agent tab title to `Issue <N> <short title>`. Other labels/milestones: only if the user asked for them in this turn — do not invent them. +{% include "skills/_gh_body_file.md.j2" %} + ## Phase 2 — optional lifecycle setup 5. **Offer branch + init (default path).** Ask whether to start work now. On yes (require a clean tree; if dirty, stop and ask to commit/stash): diff --git a/src/issue_flow/templates/commands/iflow-pick.md.j2 b/src/issue_flow/templates/commands/iflow-pick.md.j2 index 2aca612d..e9030bf0 100644 --- a/src/issue_flow/templates/commands/iflow-pick.md.j2 +++ b/src/issue_flow/templates/commands/iflow-pick.md.j2 @@ -44,6 +44,8 @@ Optional free-form text after the command: 4. **Create a `fix` issue (only when requested).** When the user passed `fix` (or explicitly asks for a general-fixes bucket): create a **new** GitHub issue with `gh issue create` (e.g. title `chore: general fixes`, body noting it is a catch-all for small fixes/typos/chores). Confirm the title/body with the user first. Record the returned issue number and use it as the chosen issue. A fresh issue is created each time — existing open general-fixes issues are **not** reused. +{% include "skills/_gh_body_file.md.j2" %} + 5. **Over-large issue (offer only).** If the chosen issue looks too involved to land in one PR, **mention** `/iflow-split` (flat parent/child) or `/iflow-epic` (staged) and ask. Default is proceed with the whole issue. Do **not** create children here. {% if label_flows and "iflow-ops" in included_commands %} 6. **Label-driven ops flow.** If the chosen issue carries the **`{{ ops_label }}`** label (case-insensitive; check `labels` from `gh issue list` / `gh issue view`), mark it for `/iflow-ops`: diff --git a/src/issue_flow/templates/commands/iflow-split.md.j2 b/src/issue_flow/templates/commands/iflow-split.md.j2 index 28e2e83a..48ca2829 100644 --- a/src/issue_flow/templates/commands/iflow-split.md.j2 +++ b/src/issue_flow/templates/commands/iflow-split.md.j2 @@ -37,6 +37,8 @@ This is an **off-path** command — the lifecycle dispatcher (`/iflow`) never au 6. **Create + link (idempotent).** For each unpublished child: 1. `gh issue create --repo owner/repo` (labels/milestones only if the user asked this turn). Capture `M`. + +{% include "skills/_gh_body_file.md.j2" %} 2. Link: prefer `issue-flow agent sub-issue-add <N> <M> -C <project_root> [--repo owner/repo] --json` (`linked` / `skipped` / `error`). CLI missing or error → REST fallback. REST also failing (404 / permission / plan) → keep the created issue and rely on the parent task list. 3. **REST recipe** (do not use `gh api -f` — it stringifies and 422s). `sub_issue_id` is the child's numeric **database id**, not the issue number: diff --git a/src/issue_flow/templates/docs/issue-workflow.md.j2 b/src/issue_flow/templates/docs/issue-workflow.md.j2 index 954ae502..e9b1eb99 100644 --- a/src/issue_flow/templates/docs/issue-workflow.md.j2 +++ b/src/issue_flow/templates/docs/issue-workflow.md.j2 @@ -280,7 +280,7 @@ Every command below is described the same way: **When to use**, **Arguments**, * 1. **Preflight** — default branch, `git fetch --prune`, clean/dirty tree. 2. **Draft** — title + body with **Problem / context**, **Spec**, **Acceptance criteria**, and optional **Out of scope**. If clearly over-large for one PR, offers `/iflow-split` (flat) or `/iflow-epic` (staged) — does not auto-split. -3. **Create** — `gh issue create` and capture `N`. +3. **Create** — `gh issue create --body-file` (bash `<<'EOF'` heredocs fail in Windows PowerShell) and capture `N`. 4. **Optional setup** — branch `<N>-<slug>` + `/iflow-capture`, then offers `/iflow-plan` (never auto-runs plan). Decline → create-only; pick up later via `/iflow-pick` / `/iflow-capture`. **What it asks you:** To refine and confirm the draft; to confirm the final title/body (and epic label) before creating; whether to start work now. GitHub only (`gh`); GitLab is not supported. diff --git a/src/issue_flow/templates/skills/_gh_body_file.md.j2 b/src/issue_flow/templates/skills/_gh_body_file.md.j2 new file mode 100644 index 00000000..38849e48 --- /dev/null +++ b/src/issue_flow/templates/skills/_gh_body_file.md.j2 @@ -0,0 +1,15 @@ +**Multi-line GitHub text (PowerShell-safe).** Bash `<<'EOF'` heredocs fail in Windows PowerShell (`Missing file specification after redirection operator`). Write the text to a file and pass that file: + +- `gh issue create`, `gh issue edit`, and `gh pr create` take `--body-file <path>`. +- `git commit` takes `-F <path>`. + +```powershell +@' +line one + +line two +'@ | Set-Content -Encoding utf8 body.md +gh issue create --repo owner/repo --title "title" --body-file body.md +``` + +Bash accepts the same `--body-file` / `-F` flags. Use that pattern for every multi-line body. diff --git a/src/issue_flow/templates/skills/iflow_auto/SKILL.md.j2 b/src/issue_flow/templates/skills/iflow_auto/SKILL.md.j2 index e54c9701..eccfbb26 100644 --- a/src/issue_flow/templates/skills/iflow_auto/SKILL.md.j2 +++ b/src/issue_flow/templates/skills/iflow_auto/SKILL.md.j2 @@ -102,6 +102,8 @@ Contract: `{{ issueflows_dir }}/{{ designs_folder }}/advanced-auto-mode.md` <owner/repo>` with Spec, Goal, **`Model: deep`**, `Depends on`, and `Part of epic #<N>.` — no new label in v1. Record numbers + notes in `auto_status.md` with `last_outcome: adversarial_findings`. + +{% include "skills/_gh_body_file.md.j2" %} e. No extra user prompts while acting under overnight / review confirm (except the budget ask in step 10). diff --git a/src/issue_flow/templates/skills/iflow_build/SKILL.md.j2 b/src/issue_flow/templates/skills/iflow_build/SKILL.md.j2 index f38d9c58..6579fcaa 100644 --- a/src/issue_flow/templates/skills/iflow_build/SKILL.md.j2 +++ b/src/issue_flow/templates/skills/iflow_build/SKILL.md.j2 @@ -64,6 +64,8 @@ Follow this skill to **begin implementation** from issue notes and project rules - Require an issue-style branch matching `^\d+-.+` (never the default branch) with a remote tracking ref. - Always pass `--repo <owner/repo>`. **List before create:** `gh pr list --repo <owner/repo> --head <branch> --state open --json number,url,title,isDraft`. If an open PR exists, note it and skip creating a second one. - Otherwise create a **draft**: `gh pr create --draft --repo <owner/repo> …` with a WIP-friendly body and **`Refs #N`** (not `Closes #N` yet). + +{% include "skills/_gh_body_file.md.j2" %} - Record `PR: <url> (#<n>, draft)` in `issue<N>_status.md`. - Do **not** write `{{ history_file }}` here — `/iflow-close` owns the changelog bullet (even while a draft PR exists). diff --git a/src/issue_flow/templates/skills/iflow_cleanup/SKILL.md.j2 b/src/issue_flow/templates/skills/iflow_cleanup/SKILL.md.j2 index e848ef96..197b01fb 100644 --- a/src/issue_flow/templates/skills/iflow_cleanup/SKILL.md.j2 +++ b/src/issue_flow/templates/skills/iflow_cleanup/SKILL.md.j2 @@ -94,6 +94,8 @@ Optional free-form text after the command: - **Second consolidated confirm** (never folded into Phase A's yes): list every proposed action, then ask once: - Optional: for each **deletable** name, `git push origin --delete <branch>` (or `gh api -X DELETE repos/<owner>/<repo>/git/refs/heads/<branch>`). Never `--force`. Never delete the default. On push failure (e.g. protection), report and continue. - Optional: create a findings issue with `gh issue create --repo <owner/repo>` after showing the draft title/body (deletable list + unique-work summaries). Suggested title: `chore: remote branch audit (<YYYY-MM-DD>)`. Create only on yes. + +{% include "skills/_gh_body_file.md.j2" %} - Phase B is **read-only until that second confirm**. Declining leaves remotes untouched. 10. **Report.** Summarize: default branch, PR/merge status, Phase A1 commands and `-d` deletions, Phase A2 `-D` deletions with their tip SHAs (or "declined" / "none offered"), branches left alone as unique work, folder sweep, epic stage-gate offer, self-update action (`upgraded` / `skipped` / `failed` / "not enabled"), and (when run) Phase B bucket counts, remote deletes, findings issue URL or "skipped". If this run used a workspace token, report each member. Else if `issue-flow agent resolve --json` reports `sibling_roots`, list them and remind the user that **each scaffolded repo needs its own `/iflow-cleanup`** (or `/iflow-cleanup workspace`) — do not loop automatically without the token.{% if "iflow_pr_sync" in included_skills %} If other open PRs still show `DIRTY` / CONFLICTING (often `{{ history_file }}`), **offer** `/iflow-pr-sync` — do not auto-run it.{% endif %} diff --git a/src/issue_flow/templates/skills/iflow_close/SKILL.md.j2 b/src/issue_flow/templates/skills/iflow_close/SKILL.md.j2 index 47f40b97..bc11bf06 100644 --- a/src/issue_flow/templates/skills/iflow_close/SKILL.md.j2 +++ b/src/issue_flow/templates/skills/iflow_close/SKILL.md.j2 @@ -108,7 +108,11 @@ Use this path **only** when the command input included `ops`, `nopr`, or `no-pr` 7. **Push** — Push to the remote the project uses (typically `origin`). If step 6 rewrote the branch (rebase), use `git push --force-with-lease` — never a bare `--force`, and never against the default branch. 8. **Pull request** — Against the default branch; always pass `--repo <owner/repo>`. - - **List before create.** Run `gh pr list --repo <owner/repo> --head <branch> --state open --json number,url,title,isDraft`. If an open PR already exists for this head (including a draft from `/iflow-build` early PR), **update** it (title/body as needed; prefer `Closes #n` when shipping) instead of opening a second one. Otherwise `gh pr create` — add `--draft` when the user passed the `draft` token. Body should explain the change, how to test, and link the GitHub issue (`Closes #n` / `Refs #n`).{% if "pstack_unslop" in included_skills %} **Unslop (pstack, optional):** the `unslop` skill (`{{ agent_dir }}/skills/unslop/SKILL.md`) is installed — *offer* to pass the PR body and the `{{ history_file }}` bullet through it before posting; apply it only when the user says so (skip the offer under `yolo`).{% endif %} + - **List before create.** Run `gh pr list --repo <owner/repo> --head <branch> --state open --json number,url,title,isDraft`. If an open PR already exists for this head (including a draft from `/iflow-build` early PR), **update** it (title/body as needed; prefer `Closes #n` when shipping) instead of opening a second one. Otherwise `gh pr create` — add `--draft` when the user passed the `draft` token. Body should explain the change, how to test, and link the GitHub issue (`Closes #n` / `Refs #n`). + +{% include "skills/_gh_body_file.md.j2" %} +{% if "pstack_unslop" in included_skills %} - **Unslop (pstack, optional):** the `unslop` skill (`{{ agent_dir }}/skills/unslop/SKILL.md`) is installed — *offer* to pass the PR body and the `{{ history_file }}` bullet through it before posting; apply it only when the user says so (skip the offer under `yolo`). +{% endif %} - **Ready from draft (when not `draft`).** If the open PR is still a draft and the user did **not** pass `draft`, mark it ready for review (`gh pr ready <number> --repo <owner/repo>`) before the checks snapshot / yolo merge. - **Checks snapshot.** After the PR exists, run `gh pr checks <number> --repo <owner/repo>` and report pass / fail / pending. "CI is green" means this command exits 0 (or JSON buckets are all `pass` / `skipping`). Without `yolo`, prefer this one-shot list; **offer** `issue-flow agent pr-ready <number> --watch` when the user wants to wait until the PR is merge-ready (do **not** auto-run; honour the **{{ checks_watch_minutes }}-minute** wall-clock cap).{% if "gh_ci" in included_skills %} Full CI/`gh` cheatsheet (including `gh run list` / `gh run watch` fallback when PR checks are empty): `{{ agent_dir }}/skills/gh-ci/SKILL.md`.{% endif %} If `gh pr checks` returns empty or cannot resolve checks, fall back to `gh run list --repo <owner/repo>` then `gh run watch <run-id> --repo <owner/repo>` under the same budget. diff --git a/src/issue_flow/templates/skills/iflow_epic/SKILL.md.j2 b/src/issue_flow/templates/skills/iflow_epic/SKILL.md.j2 index 017f5b22..8d36984c 100644 --- a/src/issue_flow/templates/skills/iflow_epic/SKILL.md.j2 +++ b/src/issue_flow/templates/skills/iflow_epic/SKILL.md.j2 @@ -129,6 +129,8 @@ Turn one stage of a **confirmed** plan into real GitHub issues. Requires `Status 2. **Dry-run listing.** Show what would be created: per spec — title, labels (`yolo` when the judgment says yes **and** the label exists per `gh label list`; otherwise note the gap), and the dependency lines after placeholder resolution. `stage <j> issue <k>` placeholders pointing at already-published specs are rewritten to their real `#<M>`; placeholders at still-unpublished specs stay verbatim with a note. 3. **Consolidated confirm** (destructive-ish — outward-facing writes; normal prose, never shortened). One prompt covering exactly: which issues get created, with which labels, and that the anchor issue's task list will be updated. Do not proceed without a clear yes. 4. **Create, in dependency order within the stage.** For each spec: `gh issue create --repo <owner/repo>` with the self-contained body (context, scope, acceptance criteria, **Goal:** and **Model:** lines when present in the plan, resolved `Depends on: #<M>` lines, and a closing `Part of epic #<N>.` line). Immediately record the new number in the plan file as `- Published: #<M>` under that spec. + +{% include "skills/_gh_body_file.md.j2" %} 5. **Update the anchor issue's task list** (append/patch only — never rewrite the user's own body text): fetch the body, append a `## Stage <k> — <title>` section (or extend it) with one `- [ ] #<M>` line per created issue, and write it back via `gh issue edit <N> --body-file`. 6. **Commit `Published:` lines off default.** The plan-file edits in step 4 must not sit as unpushed commits on home default. If you are on default (or would commit there), use a chore/issue branch (or a tiny dedicated PR). Never leave `Published: #<M>` unpushed on home default — that is what later makes `git pull --ff-only` diverge after a squash (issue #303). 7. **Report.** Created issues (numbers + titles + labels), skipped already-published specs, unresolved placeholders, and the reminder that the next stage publishes only after this one's issues close. diff --git a/src/issue_flow/templates/skills/iflow_fix/SKILL.md.j2 b/src/issue_flow/templates/skills/iflow_fix/SKILL.md.j2 index c6b52d79..bec3825e 100644 --- a/src/issue_flow/templates/skills/iflow_fix/SKILL.md.j2 +++ b/src/issue_flow/templates/skills/iflow_fix/SKILL.md.j2 @@ -33,6 +33,8 @@ It **coexists** with `/iflow-pick fix` (one-shot general-fixes setup into `/iflo 1. **Preflight.** Detect the default branch (`gh repo view --json defaultBranchRef -q .defaultBranchRef.name`; fall back to `git symbolic-ref --quiet --short refs/remotes/origin/HEAD`, else `main`). Run `git fetch --prune`. Report current branch + clean/dirty tree (`git status --porcelain`); if dirty with unrelated changes, ask to commit/stash first. 2. **Resolve the session name.**{% if fix_auto_name %} Baked `fix_auto_name = true`: pick the kebab slug yourself from an explicit invoke name, else invent a short descriptive slug from the user's intent/context (fallback `iterative-small-fixes`). Do **not** ask the user to approve or rename the title/slug — show it only inside the create confirm below.{% else %} Baked `fix_auto_name = false`: use an explicit invoke name when given; otherwise default to `iterative-small-fixes`. If the invoke text is free-form (not already a slug) and you would invent a better name, **ask once** whether to use your proposed slug or keep the default — then proceed to create confirm.{% endif %} Configurable via `fix_auto_name` under `[issueflow]` in `{{ issueflows_dir }}/config.toml` (re-run `issue-flow update` after changing). 3. **Create the GitHub issue (always, with confirmation).** Show the chosen title (e.g. `Iterative fixes: <name>`, or `Iterative small fixes`) and a body noting it is an interactive `/iflow-fix` session whose individual fixes are recorded in the status markdown and landed together via `/iflow-close`. Create it with `gh issue create` (add `--repo owner/repo` if ambiguous). Capture the returned number `N`. A fresh issue is created each time. Set the chat tab title to `Issue <N> <session name>`. + +{% include "skills/_gh_body_file.md.j2" %} 4. **Create the worktree (with confirmation).** Slug from the resolved name (kebab-case; default `iterative-small-fixes`); branch name `<N>-<slug>`. Require a clean tree. {% include "skills/_worktree_start.md.j2" %} On a non-default home branch → **ask** whether to FF/switch home to default first or use `inplace`. diff --git a/src/issue_flow/templates/skills/iflow_issue/SKILL.md.j2 b/src/issue_flow/templates/skills/iflow_issue/SKILL.md.j2 index 2e6f9ef1..7ab76dd3 100644 --- a/src/issue_flow/templates/skills/iflow_issue/SKILL.md.j2 +++ b/src/issue_flow/templates/skills/iflow_issue/SKILL.md.j2 @@ -45,6 +45,8 @@ Do **not** use this skill from `/iflow`, `/iflow-build`, or `/iflow-close`. `/if Refine with the user until they confirm the text. If the draft is clearly over-large for one PR, **offer** `/iflow-split` (flat parent/child) or `/iflow-epic` (staged) — do **not** auto-create sub-issues. 4. **Create (confirm first).** Show the final title and body (and, in epic-anchor mode, the planned `epic` label when present). On yes: `gh issue create --repo <owner/repo>` (add `--label epic` only when epic-anchor mode is on **and** `gh label list` shows `epic`). Capture number `N`. Set the chat tab title to `Issue <N> <short title>`. Optional labels/milestones other than the epic-anchor label: only if the user asked for them in this turn — do not invent them. +{% include "skills/_gh_body_file.md.j2" %} + ### Phase 2 — optional lifecycle setup 5. **Offer branch + init (default path).** Ask whether to start work now. On yes (require a clean tree; if dirty, stop and ask to commit/stash): diff --git a/src/issue_flow/templates/skills/iflow_pick/SKILL.md.j2 b/src/issue_flow/templates/skills/iflow_pick/SKILL.md.j2 index 751fd1b4..29e13ccb 100644 --- a/src/issue_flow/templates/skills/iflow_pick/SKILL.md.j2 +++ b/src/issue_flow/templates/skills/iflow_pick/SKILL.md.j2 @@ -39,6 +39,8 @@ Do **not** use this skill from `/iflow`, `/iflow-build`, or `/iflow-close`. `/if - **Else GitHub** — `gh issue list --state open --json number,title,labels,milestone,updatedAt` (add `--repo owner/repo` if ambiguous). When `label:<L>` is active, add `--label <L>` (hard filter). Drop issues already captured under `{{ current_issues_folder }}/`, `{{ partly_solved_folder }}/`, or `{{ solved_folder }}/`. If the filtered set is empty, **stop** with “no open issues with label `<L>`.” 4. **Rank and present.** Rank by **epic membership** (an active epic's current-stage `next_candidates` first) + **milestone** (nearest/active, honour any hint) + **labels** (match recent work / soft hint when no `label:` filter) + **topical similarity** to recently solved issues (skim `{{ issueflows_dir }}/{{ solved_folder }}/` and recent branch names). Show a numbered shortlist (~3–7) with number, title, labels, milestone, and (for epic issues) the epic + stage, and **ask the user to confirm** the pick or override. Never pick silently — even when the filtered shortlist has a single entry. 5. **Create a `fix` issue (only when requested).** Use `gh issue create` (e.g. `chore: general fixes`), confirm title/body first, capture the new number. A fresh issue is created each time — never reuse an existing open general-fixes issue. + +{% include "skills/_gh_body_file.md.j2" %} 6. **Over-large issue (offer only).** If the chosen issue is too big for one PR, **mention** `/iflow-split` (flat parent/child) or `/iflow-epic` (staged) and ask. Default is proceed with the whole issue. Do **not** create children here. {% if label_flows and "iflow-ops" in included_commands %} 7. **Label-driven ops flow.** If the chosen issue carries the **`{{ ops_label }}`** label (case-insensitive), announce it and fold `/iflow-ops` into the pick confirmation (one prompt: optional branch vs stay on default + ops work + `close ops`). On yes, run Phase 2 (ask whether to create `<N>-<slug>` or stay on current/default — default branch is allowed for ops) then follow the `iflow-ops` skill **instead of** Phase 3 / yolo. If the issue also carries **`{{ yolo_label }}`**, **ops wins** — announce the conflict. Configurable via `label_flows` / `ops_label` under `[issueflow]` in `{{ issueflows_dir }}/config.toml` (re-run `issue-flow update` after changing). diff --git a/src/issue_flow/templates/skills/iflow_split/SKILL.md.j2 b/src/issue_flow/templates/skills/iflow_split/SKILL.md.j2 index 102ad6b8..ecb9f8bb 100644 --- a/src/issue_flow/templates/skills/iflow_split/SKILL.md.j2 +++ b/src/issue_flow/templates/skills/iflow_split/SKILL.md.j2 @@ -47,6 +47,8 @@ Do **not** use this skill from `/iflow`, `/iflow-build`, or `/iflow-close`. `/if 5. **Consolidated confirm** (normal prose, never shortened). One prompt covering: parent `#N` stays **open** as the tracker; each listed child title will be created; each will be linked as a GitHub native sub-issue; a `- [ ] #<M>` task-list block will be appended on the parent under `## Sub-issues`. No yes → stop. 6. **Create + link (idempotent).** For each unpublished child: 1. `gh issue create --repo <owner/repo>` (labels/milestones only if the user asked this turn). Capture number `M`. + +{% include "skills/_gh_body_file.md.j2" %} 2. Link as a native sub-issue. Prefer the CLI fast path: `issue-flow agent sub-issue-add <N> <M> -C <project_root> [--repo owner/repo] --json` Fields: `linked`, `skipped` (already a child), `error`. On CLI missing or `error` set, fall back to the REST recipe below — then if that also fails (404 / permission / plan), **keep the created issue** and rely on the parent task list. diff --git a/tests/test_templating.py b/tests/test_templating.py index 94acfc72..e78a5ff3 100644 --- a/tests/test_templating.py +++ b/tests/test_templating.py @@ -1324,6 +1324,20 @@ def test_iflow_issue_skill_mirrors_command() -> None: assert "Acceptance criteria" in rendered +def test_issue_create_skills_use_body_file_not_bash_heredoc() -> None: + """Multi-line gh bodies must name --body-file and the PowerShell failure.""" + for name in ( + "skills/iflow_issue/SKILL.md.j2", + "commands/iflow-issue.md.j2", + "skills/iflow_fix/SKILL.md.j2", + "skills/iflow_close/SKILL.md.j2", + ): + rendered = render_template(name, _default_context()) + assert "--body-file" in rendered, name + assert "PowerShell" in rendered, name + assert "<<'EOF'" in rendered, name + + def test_iflow_lists_issue_as_off_path() -> None: """/iflow and its skill must list /iflow-issue among the explicit-only commands.""" cmd = render_template("commands/iflow.md.j2", _default_context())