Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .issueflows/03-solved-issues/issue400_original.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Issue #400: graphify totally gitignored

Source: https://github.com/jepegit/issue-flow/issues/400

## Original issue text

We need to make sure that we have a knob that puts all of graphify on gitignore, and if turned on, the flows have to run graphify before creating plan so that graphify output can be used in planning
80 changes: 80 additions & 0 deletions .issueflows/03-solved-issues/issue400_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Plan: graphify totally gitignored (#400)

## Goal

Add a config knob that gitignores the whole `graphify-out/` tree, and when that knob is on, make `/iflow-plan` (and epic drafting) run `issue-flow graphify` before planning so a fresh checkout still has graph output to read.

## Constraints

- Default stays off. Projects that commit `GRAPH_REPORT.md` keep doing so until they set the key and re-run `issue-flow update`.
- Templates are the source of truth. Skill and command text change in `src/issue_flow/templates/`, then `issue-flow update` bakes them.
- Precedence matches other `[issueflow]` keys: project `config.toml` > user-global > `ISSUEFLOW_*` env > default.
- Do not auto-run `graphify extract` (needs an API key). The refresh is AST `update` only.
- Missing or failing `graphify` does not block planning (same as issue #214). Report the failure and fall back to grep.
- `issue-flow update` must not run `git rm`. Gitignore does not untrack files that are already in the index.
- This plan does not flip the knob in this repo and does not untrack `graphify-out/` here. See Open questions.

### Prior art

- `auto_graphify_on_plan` (`Settings.resolve_auto_graphify_on_plan` in `src/issue_flow/config.py`, baked in `templates/skills/iflow_plan/SKILL.md.j2` and `templates/commands/iflow-plan.md.j2`). Opt-in refresh before prior-art. Default false. Missing graphify → note and continue. **Coexist:** the new key also enables that same step; the old key stays for “refresh even when the report is committed.”
- `suggest_graphify` — soft skim/rebuild nudge. Never auto-runs. Leave it.
- This repo’s `.gitignore` already ignores `graphify-out/cache/`, `graphify-out/graph.html`, and `graphify-out/graph.json`. `GRAPH_REPORT.md`, `manifest.json`, and the `.graphify_*` files stay tracked (HISTORY, repo hygiene). The new key ignores the whole directory; it does not rewrite that hand-written partial block.
- Managed gitignore pattern: `ensure_editor_gitignore` in `src/issue_flow/surfaces.py` (`# BEGIN` / `# END` markers). Mirror that for the graphify block.
- Design note in `.issueflows/04-designs-and-guides/graphify-integration.md` (issue #214): projects that gitignore `graphify-out/` can set `auto_graphify_on_plan`. This knob makes that pair one switch.
- Toolbox (`00-tools/`): nothing relevant. Graph query hit `graphify()` / `run_build()` / `Settings` (communities 5, 327, 463); no existing gitignore-all helper.

## Approach

New bool `[issueflow].graphify_gitignored`, default `false`. Env `ISSUEFLOW_GRAPHIFY_GITIGNORED`. `issue-flow config show|set` accepts it. Changing it needs `issue-flow update`.

**Gitignore.** On `init` and `update`, if the resolved value is true, upsert a managed block in the target `.gitignore`:

```
# BEGIN issue-flow graphify (generated; do not edit)
graphify-out/
# END issue-flow graphify
```

If the value is false, remove that managed block when present and leave every other line alone. If `graphify-out/` paths are still tracked, print the untrack command and do not run it:

`git rm -r --cached graphify-out`

**Plan-time refresh.** Render the existing “refresh knowledge graph” step when `auto_graphify_on_plan` **or** `graphify_gitignored` is true (one step, not two). Wording names which key is on. Same failure policy as #214. Also add that step to epic draft (`iflow_epic` skill + `iflow-epic` command), which writes a plan and today only skims `GRAPH_REPORT.md` when the file already exists. `/iflow-yolo` follows `/iflow-plan`, so it inherits the step.

**Commit guidance.** In the graphify skill/command, when `graphify_gitignored` is true, say not to commit `graphify-out/`. When false, keep today’s “the graph is fine to commit” line.

**Docs.** One row in `docs/configuration.md` and in `.issueflows/04-designs-and-guides/skill-behaviour-knobs.md`. Short note on the #214 section of `graphify-integration.md`.

## Files to touch

- `src/issue_flow/modes.py` — default, `read_graphify_gitignored`, seed/write into `config.toml` with a comment.
- `src/issue_flow/config.py` — `resolve_graphify_gitignored`, `seed_config_values`, `effective_config`, `template_context`.
- `src/issue_flow/config_ops.py` — `CONFIG_KEYS` entry (`bool`, `needs_update=True`).
- `src/issue_flow/surfaces.py` — managed gitignore upsert/remove, plus a “still tracked” note.
- `src/issue_flow/init.py` — call the gitignore helper from `init` / `update` (same place as `ensure_editor_gitignore`).
- `src/issue_flow/templates/skills/iflow_plan/SKILL.md.j2` and `templates/commands/iflow-plan.md.j2` — gate the refresh step on either flag.
- `src/issue_flow/templates/skills/iflow_epic/SKILL.md.j2` and `templates/commands/iflow-epic.md.j2` — same refresh before epic draft.
- `src/issue_flow/templates/skills/iflow_graphify/SKILL.md.j2` and `templates/commands/iflow-graphify.md.j2` — commit guidance when the key is on.
- `docs/configuration.md`
- `.issueflows/04-designs-and-guides/skill-behaviour-knobs.md`
- `.issueflows/04-designs-and-guides/graphify-integration.md`
- Tests: `tests/test_config.py`, `tests/test_modes.py`, `tests/test_templating.py`, `tests/test_cli.py` (show payload), plus a gitignore helper test next to the existing surfaces/init tests.

## Test strategy

`uv run pytest` for the new and neighbouring tests (`test_config`, `test_modes`, `test_templating`, `test_cli`, and the gitignore helper test). `uv run ruff check src/ tests/`.

Cases:

- Resolve default `false`; persisted `true` wins over env.
- Plan skill/command: both flags false → no refresh step; only `graphify_gitignored` true → step present; both true → one step.
- Gitignore helper: true writes the managed block; false removes it and keeps unrelated lines; idempotent second write.
- `config show` includes the key.

## Open questions

1. **Coexist with `auto_graphify_on_plan`.** Recommended: yes. New key gitignores `graphify-out/` and also enables the plan/epic refresh. The old key stays for refresh without gitignore.
2. **Untrack.** Recommended: print `git rm -r --cached graphify-out` when files are still tracked. Do not run it from `update`.
3. **This repo.** Recommended: leave `.issueflows/config.toml` here at the default (`false`) and keep `GRAPH_REPORT.md` tracked. Turning the knob on in issue-flow itself is a follow-up.

Accept adopts these three recommendations.
16 changes: 16 additions & 0 deletions .issueflows/03-solved-issues/issue400_status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Issue #400 status

- [x] Done

## What's done

- Captured #400 on branch `400-graphify-gitignore`.
- `graphify_gitignored` (default false) gitignores all of `graphify-out/` on `init` / `update` and makes `/iflow-plan` and epic draft run `issue-flow graphify` first. `auto_graphify_on_plan` still refreshes when the report is committed. Update prints `git rm -r --cached graphify-out` and does not run it.
- This repo's knob stays off, so `GRAPH_REPORT.md` stays tracked.
- Changelog bullet under `## [Unreleased]`. No version bump (no `publish` label).
- Essential review: new tests left unmarked (temp git / text contracts). `test_all_settings_table_lists_every_config_key_once` stays essential and covers the new config row. Registry updated.
- `uv run pytest`: 959 passed. `uv run pytest -m essential`: 24 passed. Ruff clean.

## Remaining work

- None.
12 changes: 12 additions & 0 deletions .issueflows/04-designs-and-guides/graphify-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,18 @@ gitignore `graphify-out/` can set `auto_graphify_on_plan = true` under
prior-art discovery. Default remains `false`. Missing or failing graphify does
not block planning.

## Whole-tree gitignore (issue #400)

`graphify_gitignored` (default false) makes `init` / `update` write a managed
`.gitignore` block for `graphify-out/`. It does not `git rm` tracked files.
When those paths are still tracked, update prints
`git rm -r --cached graphify-out`.

When the key is true, `/iflow-plan` and epic draft run `issue-flow graphify`
(AST `update`) before they read the graph, even if `auto_graphify_on_plan` is
false. `auto_graphify_on_plan` stays the switch for that refresh when the
report is still committed. Missing or failing graphify does not block planning.

## Correction (2026-05-14): graphify is subcommand-based

The original implementation assumed `graphify <path> [flags…]` was the canonical "build" invocation, modeled on tools like `ruff` or `pyright`. **It is not.** The `graphify` CLI is dispatch-based — every action is a subcommand (`extract <path>`, `update <path>`, `watch <path>`, `cluster-only <path>`, …) and there is no top-level "scan this folder" mode. Running `graphify C:\some\dir` fails with `unknown command 'C:\some\dir'`. The published `/build` doc, the rules entry, the cursor-issue-workflow doc, and the README all advertised non-existent flags (`--update`, `--no-viz`, `--mode deep`, `--watch`, `--cluster-only`) that are actually subcommands or per-subcommand flags.
Expand Down
2 changes: 2 additions & 0 deletions .issueflows/04-designs-and-guides/skill-behaviour-knobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ epic #269). Until Stage 2 ships, only the project file and env exist.
| Cleanup defaults (`cleanup_*`) | `cleanup_include_github`, `cleanup_yes_a1`, `cleanup_yes_a2` |
| Tool upgrade (event-hook name) | `on_bleeding_edge` |
| Auto behaviours (`auto_*`) | `auto_switchback`, `auto_remove_worktree`, `auto_close`, `auto_cleanup`, `auto_plan`, `auto_build`, `auto_graphify_on_plan` |
| Graphify checkout | `graphify_gitignored` |
| Start layout | `worktree_first` (issue #329; distinct from `auto_remove_worktree` and from worktree location #328) |
| Timing / PR | `early_pr` |
| Fix-session | `fix_auto_name` |
Expand All @@ -40,6 +41,7 @@ epic #269). Until Stage 2 ships, only the project file and env exist.
| `on_bleeding_edge` | `false` | When `true`, `/iflow-cleanup` runs `issue-flow agent self-update` after a successful FF pull (`uv tool install issue-flow@latest` then `issue-flow update`). Trailing `bleeding edge` / `no bleeding` override. Skips editable installs (issue #382) |
| `suggest_graphify` | `true` | Soft GRAPH_REPORT / rebuild suggestions (never auto-run) |
| `auto_graphify_on_plan` | `false` | `/iflow-plan` runs `issue-flow graphify` (AST `update`) before prior-art; missing/fail → note + continue (issue #214) |
| `graphify_gitignored` | `false` | Gitignore all of `graphify-out/` on `init`/`update`. Also runs the plan and epic refresh even when `auto_graphify_on_plan` is false. Prints `git rm -r --cached graphify-out` when paths are still tracked; does not run it (issue #400) |
| `auto_switchback` | `true` | After PR, switch to default when clean (`false` ≈ always `stay`) |
| `auto_remove_worktree` | `true` | After `/iflow-close` opens or merges a PR, remove the sibling issue worktree when clean (`false` = YES/NO). Skip `stay` / draft / failed merge. Never deletes the branch (issue #273) |
| `worktree_first` | `true` | `/iflow-pick` / `/iflow-issue` / `/iflow-fix` start in a sibling worktree. `false` → `git switch -c` on home. Tokens `inplace` / `no worktree` / `worktree` override (issue #329) |
Expand Down
4 changes: 4 additions & 0 deletions .issueflows/04-designs-and-guides/test-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ current issue**. `/iflow-doctor` may audit the whole suite against this table.
| `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 |
| `tests/test_graphify_gitignore.py` (module) | no | no | `surfaces.sync_graphify_gitignore`, `note_tracked_graphify` | #400 | Temp git repo for the tracked-path note; leave unmarked |
| `tests/test_templating.py::test_iflow_plan_graphify_gitignored_refreshes_without_auto_flag` | no | no | plan / epic / graphify templates when `graphify_gitignored` | #400 | Text contract; leave unmarked |
| `tests/test_config.py` (`resolve_graphify_gitignored` asserts) | no | no | `Settings.resolve_graphify_gitignored` | #400 | Same knob-precedence tests as other `[issueflow]` flags; leave unmarked |
| `tests/test_doc_configuration.py::test_all_settings_table_lists_every_config_key_once` | yes | yes | `docs/configuration.md` vs `CONFIG_KEYS` | #400 | Already essential; a new knob missing from the table fails here |

**Columns**

Expand Down
1 change: 1 addition & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ than the GitHub release notes they link to.
## [Unreleased]

- Iterative fixes: docs-flow-diagram. (#397)
- Graphify totally gitignored. (#400)

## [0.5.17] - 2026-09-26

Expand Down
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ an environment-variable fallback, `ISSUEFLOW_<KEY>` (for example
| `on_bleeding_edge` | bool | `false` | `/iflow-cleanup` upgrades the `uv tool` install to `issue-flow@latest` and runs `issue-flow update` after a successful fast-forward pull. Opt in per run with `bleeding edge`; opt out with `no bleeding`. Skips editable installs. |
| `suggest_graphify` | bool | `true` | Suggest reading `GRAPH_REPORT.md` / rebuilding graphify (never runs it). |
| `auto_graphify_on_plan` | bool | `false` | `/iflow-plan` rebuilds the graphify graph (AST only) before prior-art discovery. |
| `graphify_gitignored` | bool | `false` | Gitignore all of `graphify-out/`. `/iflow-plan` and epic draft then rebuild the graph (AST only) before they read it. Does not untrack files that are already committed. |
| `auto_switchback` | bool | `true` | After `/iflow-close` opens a PR, switch back to the default branch when the tree is clean (`false` ≈ always `stay`). |
| `auto_remove_worktree` | bool | `true` | Close removes the issue's sibling worktree once the PR is open (or merged) and the tree is clean; `false` asks first. |
| `worktree_first` | bool | `true` | `/iflow-pick`, `/iflow-issue` and `/iflow-fix` start in a sibling worktree `../<repo>-<N>`; `false` uses `git switch -c` in your checkout. Tokens `inplace` / `worktree` override per run. |
Expand Down
3 changes: 2 additions & 1 deletion src/issue_flow/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -5733,7 +5733,8 @@ def _print_config_guide(console: Console, cfg_path: Path) -> None:
"[bold]cleanup_include_github[/bold] / [bold]cleanup_yes_a1[/bold] / "
"[bold]cleanup_yes_a2[/bold] / [bold]on_bleeding_edge[/bold] / "
"[bold]suggest_graphify[/bold] / "
"[bold]auto_graphify_on_plan[/bold]; "
"[bold]auto_graphify_on_plan[/bold] / "
"[bold]graphify_gitignored[/bold]; "
"[bold]auto_switchback[/bold] / [bold]auto_remove_worktree[/bold] / "
"[bold]worktree_first[/bold] / "
"[bold]auto_close[/bold] / [bold]auto_cleanup[/bold] / "
Expand Down
3 changes: 2 additions & 1 deletion src/issue_flow/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1389,7 +1389,8 @@ def config_add(
``noob``,
``cleanup_include_github``, ``cleanup_yes_a1``, ``cleanup_yes_a2``,
``on_bleeding_edge``, ``suggest_graphify``,
``auto_graphify_on_plan``, ``auto_switchback``, ``auto_remove_worktree``,
``auto_graphify_on_plan``, ``graphify_gitignored``,
``auto_switchback``, ``auto_remove_worktree``,
``worktree_first``,
``pr_merge_method``, ``cycle_max_issues``, ``cycle_onfail``,
``cycle_nonyolo``, ``auto_adversarial_loops``,
Expand Down
21 changes: 21 additions & 0 deletions src/issue_flow/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
DEFAULT_ESSENTIAL_REVIEW,
DEFAULT_ESSENTIAL_TESTS,
DEFAULT_AUTO_GRAPHIFY_ON_PLAN,
DEFAULT_GRAPHIFY_GITIGNORED,
DEFAULT_AUTO_SWITCHBACK,
DEFAULT_AUTO_REMOVE_WORKTREE,
DEFAULT_WORKTREE_FIRST,
Expand Down Expand Up @@ -503,6 +504,20 @@ def resolve_auto_graphify_on_plan(self, project_root: Path) -> bool:
),
)

def resolve_graphify_gitignored(self, project_root: Path) -> bool:
"""Resolve whether ``graphify-out/`` is fully gitignored."""
persisted = modes_module.read_graphify_gitignored(
self.config_path(project_root)
)
if persisted is not None:
return persisted
return self.user_global_or(
"graphify_gitignored",
_env_flag(
"ISSUEFLOW_GRAPHIFY_GITIGNORED", default=DEFAULT_GRAPHIFY_GITIGNORED
),
)

def resolve_auto_switchback(self, project_root: Path) -> bool:
"""Resolve whether ``/iflow-close`` switches back to the default branch."""
persisted = modes_module.read_auto_switchback(self.config_path(project_root))
Expand Down Expand Up @@ -987,6 +1002,10 @@ def seed_config_values(self) -> dict[str, object]:
"ISSUEFLOW_AUTO_GRAPHIFY_ON_PLAN",
default=DEFAULT_AUTO_GRAPHIFY_ON_PLAN,
),
"graphify_gitignored": _env_flag(
"ISSUEFLOW_GRAPHIFY_GITIGNORED",
default=DEFAULT_GRAPHIFY_GITIGNORED,
),
"auto_switchback": _env_flag(
"ISSUEFLOW_AUTO_SWITCHBACK", default=DEFAULT_AUTO_SWITCHBACK
),
Expand Down Expand Up @@ -1076,6 +1095,7 @@ def effective_config(self, project_root: Path) -> dict[str, object]:
"on_bleeding_edge": self.resolve_on_bleeding_edge(project_root),
"suggest_graphify": self.resolve_suggest_graphify(project_root),
"auto_graphify_on_plan": self.resolve_auto_graphify_on_plan(project_root),
"graphify_gitignored": self.resolve_graphify_gitignored(project_root),
"auto_switchback": self.resolve_auto_switchback(project_root),
"auto_remove_worktree": self.resolve_auto_remove_worktree(project_root),
"worktree_first": self.resolve_worktree_first(project_root),
Expand Down Expand Up @@ -1182,6 +1202,7 @@ def template_context(
"on_bleeding_edge": self.resolve_on_bleeding_edge(project_root),
"suggest_graphify": self.resolve_suggest_graphify(project_root),
"auto_graphify_on_plan": self.resolve_auto_graphify_on_plan(project_root),
"graphify_gitignored": self.resolve_graphify_gitignored(project_root),
"auto_switchback": self.resolve_auto_switchback(project_root),
"auto_remove_worktree": self.resolve_auto_remove_worktree(project_root),
"worktree_first": self.resolve_worktree_first(project_root),
Expand Down
1 change: 1 addition & 0 deletions src/issue_flow/config_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class ConfigKeySpec:
"on_bleeding_edge": ConfigKeySpec("bool"),
"suggest_graphify": ConfigKeySpec("bool"),
"auto_graphify_on_plan": ConfigKeySpec("bool"),
"graphify_gitignored": ConfigKeySpec("bool"),
"auto_switchback": ConfigKeySpec("bool"),
"auto_remove_worktree": ConfigKeySpec("bool"),
"worktree_first": ConfigKeySpec("bool"),
Expand Down
3 changes: 3 additions & 0 deletions src/issue_flow/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
materialize_editor_profile,
materialize_user_global_both_skills,
maybe_ensure_linguist_gitattributes,
maybe_sync_graphify_gitignore,
write_manifest_files,
)
from issue_flow.skill_ownership import foreign_skill_reason, load_stamps, stamp_key
Expand Down Expand Up @@ -530,6 +531,7 @@ def run_init(
console_io.console.print()
_ensure_dotenv_file(project_root)
maybe_ensure_linguist_gitattributes(project_root, settings)
maybe_sync_graphify_gitignore(project_root, settings)

console_io.console.print()
if not canonical:
Expand Down Expand Up @@ -683,6 +685,7 @@ def run_update(

console_io.console.print()
maybe_ensure_linguist_gitattributes(project_root, settings)
maybe_sync_graphify_gitignore(project_root, settings)

console_io.console.print()
_graphify_postinstall(project_root, profiles, mode_obj)
Expand Down
Loading
Loading