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
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 @@ -81,3 +81,15 @@ What we deliberately **do not** ship:

- If we add `issue-flow status` (already on the README's Future plans), it could surface graph freshness (`graphify-out/manifest.json` mtime vs source tree) without re-implementing graphify's freshness check.
- If multi-tool support lands (Claude Code, Windsurf, etc.), `register_with_cursor` should grow a sibling `register_with_<tool>` that calls `graphify <tool> install`.

## 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.

**Fixes landed in this iteration:**

- `_build_graphify_argv` translates `issue-flow build [args…]` into `graphify <subcommand> <project_root> [args…]`. Default subcommand is `extract` (full AST + semantic LLM build, matches the natural meaning of "build the graph"). A leading recognized build subcommand (`extract`, `update`, `watch`, `cluster-only`, `check-update`) overrides the default.
- `project_dir` on the Typer `build` command became `-C` / `--project-dir` (modeled on `git -C`) so positional args flow into `_build_graphify_argv` untouched. Without this change, `issue-flow build update` failed because Typer eagerly bound `update` to the `project_dir` positional and the path-existence check rejected it.
- All scaffolded docs (`commands/build.md.j2`, `skills/issueflow_build/SKILL.md.j2`, `rules/issueflow-rules.mdc.j2`, `docs/cursor-issue-workflow.md.j2`, `commands/issue-close.md.j2`) and the README now describe real graphify subcommands and the `-C` option. The `graphify .` fallback (which never worked) is replaced everywhere with `graphify extract .`.

**Why this slipped through originally:** `run_build` was tested with `subprocess.run` mocked to a no-op, so the test suite never observed graphify's actual argv parser. The new tests exercise the argv-construction function directly with realistic subcommand combinations. Future graphify integrations should always include at least one test that uses real graphify subcommand names (or a contract test that mirrors `graphify --help`).
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,11 @@ What `issue-flow` does when `graphify` is on PATH:
If graphify is not installed, both commands just print install hints and
continue — they never block.
- A new slash command `/build` (and matching `/issueflow-build` skill) wraps
`issue-flow build`, which forwards every argument to the `graphify` CLI
verbatim (`--update`, `--no-viz`, `--mode deep`, `--watch`, …).
`issue-flow build`. With no extra args it runs `graphify extract <project>`
(full build); pass a graphify build subcommand to pick a different action
(`issue-flow build update`, `issue-flow build watch`,
`issue-flow build cluster-only --no-viz`, …). Trailing flags forward to
the chosen subcommand verbatim.
- The scaffolded rules and `/issue-start` mention `graphify-out/GRAPH_REPORT.md`
as a recommended pre-read when the file exists. `/build` is **off-path** —
`/iflow` never auto-dispatches to it.
Expand Down Expand Up @@ -173,7 +176,7 @@ That's it. Open the project in Cursor and start with `/iflow` (or step through `
```
issue-flow init [PROJECT_DIR] [--force] [--skip-dep-check]
issue-flow update [PROJECT_DIR] [--skip-dep-check]
issue-flow build [PROJECT_DIR] [-- ...graphify args]
issue-flow build [-C PROJECT_DIR] [...graphify subcommand + args]
```

### `issue-flow init`
Expand All @@ -199,8 +202,8 @@ Use `update` after upgrading the **issue-flow** package to refresh the packaged

| Argument / Option | Description |
|---|---|
| `PROJECT_DIR` | Project root directory to scan with graphify. Defaults to `.`. |
| `...graphify args` | Any extra arguments are forwarded **verbatim** to the `graphify` CLI (`--update`, `--no-viz`, `--mode deep`, `--watch`, …). |
| `-C`, `--project-dir` | Project root directory to scan with graphify. Defaults to `.` (current directory). Modeled on `git -C` so positional args can flow into graphify untouched. |
| `...graphify subcommand + args` | Optional graphify subcommand + flags. With no extras runs `graphify extract <PROJECT_DIR>` (full AST + semantic LLM build). The first extra arg, if it is a recognized build subcommand (`extract`, `update`, `watch`, `cluster-only`, `check-update`), picks the action; trailing tokens forward verbatim. Examples: `issue-flow build update`, `issue-flow build cluster-only --no-viz`, `issue-flow build ./subdir`. |

`build` requires `graphifyy` to be installed (`uv tool install graphifyy`). When the `graphify` CLI is missing, the command prints install hints and exits with code `2`. Outputs land in `graphify-out/` (`graph.html`, `GRAPH_REPORT.md`, `graph.json`).

Expand Down
18 changes: 12 additions & 6 deletions src/issue_flow/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,10 @@ def update(
)
def build(
ctx: typer.Context,
project_dir: Path = typer.Argument(
default=Path("."),
project_dir: Path = typer.Option(
Path("."),
"--project-dir",
"-C",
help=(
"Project root directory to scan with graphify. "
"Defaults to the current directory."
Expand All @@ -97,10 +99,14 @@ def build(
) -> None:
"""Rebuild the graphify knowledge graph for the project.

Forwards every extra argument to the ``graphify`` CLI verbatim, so
flags like ``--update``, ``--no-viz``, ``--mode deep``, or
``--watch`` pass straight through. Requires ``graphify`` to be on
``PATH`` (install with ``uv tool install graphifyy``).
With no extra arguments runs ``graphify extract <project_dir>`` (the
full AST + semantic LLM build). Override the subcommand by passing
it as the first argument: ``issue-flow build update`` (fast,
code-only re-extract), ``issue-flow build watch`` (live rebuild),
``issue-flow build cluster-only --no-viz`` (re-cluster), etc.
Trailing flags pass through verbatim. Use ``-C <dir>`` to scan a
project other than the current directory. Requires ``graphify`` to
be on ``PATH`` (install with ``uv tool install graphifyy``).
"""
from issue_flow.graphify import run_build

Expand Down
77 changes: 61 additions & 16 deletions src/issue_flow/graphify.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,59 @@
GRAPHIFY_COMMAND = "graphify"
GRAPHIFY_PYPI = "graphifyy"

# Graphify is a multi-subcommand CLI. The subcommands below all take a
# project path as their first positional argument and are the ones that
# fit the "build / refresh the graph" surface ``issue-flow build``
# exposes. Anything else (``query``, ``explain``, ``cursor install``,
# …) is out of scope for ``build``; users invoke ``graphify`` directly
# for those.
_GRAPHIFY_BUILD_SUBCOMMANDS: frozenset[str] = frozenset(
{"extract", "update", "watch", "cluster-only", "check-update"}
)
# Default subcommand when the user runs ``issue-flow build`` without
# specifying one. ``extract`` is the full AST + semantic LLM build
# (matches the natural meaning of "build the graph").
_DEFAULT_BUILD_SUBCOMMAND: str = "extract"


def _build_graphify_argv(
project_root: Path, extra_args: Sequence[str]
) -> list[str]:
"""Translate ``issue-flow build`` arguments into a ``graphify`` argv.

``graphify`` is subcommand-based — there is no top-level "scan this
folder" mode — so every invocation needs an explicit subcommand.
Behavior:

* No extra args → ``graphify extract <project_root>``.
* First arg is a recognized build subcommand (``extract``,
``update``, ``watch``, ``cluster-only``, ``check-update``) → use
it. If a positional path follows, trust it; otherwise inject
``project_root`` so graphify scans the right tree even when the
agent's cwd differs from the project root.
* First arg is anything else → assume the default subcommand
(``extract``) and treat the args as positional/flag tail. A
first arg that does not start with ``-`` is taken as the path
the user wants graphify to scan (e.g. ``issue-flow build ./docs``
→ ``graphify extract ./docs``).
"""
args = list(extra_args)

if args and args[0] in _GRAPHIFY_BUILD_SUBCOMMANDS:
subcommand = args[0]
rest = args[1:]
else:
subcommand = _DEFAULT_BUILD_SUBCOMMAND
rest = args

has_explicit_path = bool(rest) and not rest[0].startswith("-")
if has_explicit_path:
positional_tail = rest
else:
positional_tail = [str(project_root), *rest]

return [GRAPHIFY_COMMAND, subcommand, *positional_tail]


def _graphify_dependency():
"""Return the ``Dependency`` entry for graphify from the recommended list."""
Expand Down Expand Up @@ -203,13 +256,14 @@ def run_build(
extra_args: Sequence[str],
console: Console,
) -> int:
"""Run ``graphify <project_root> [extra_args...]`` and return its exit code.
"""Run ``graphify <subcommand> <path> [extra_args...]`` and return its exit code.

When the user supplies an explicit path in ``extra_args`` (e.g.
``issue-flow build ./docs``), it is forwarded as-is and we do not
inject the project root. Otherwise the project root is passed
explicitly so graphify knows what to scan even if the agent's CWD
differs from the project root.
See :func:`_build_graphify_argv` for the argv-construction rules.
The short version: ``issue-flow build`` with no args invokes
``graphify extract <project_root>`` (the natural "build the graph"
action). Users can pick a different build subcommand by passing it
as the first argument (e.g. ``issue-flow build update``,
``issue-flow build cluster-only --no-viz``).

Returns ``2`` and prints install hints when graphify is missing.
Re-raises ``KeyboardInterrupt`` so users can ^C a long build.
Expand All @@ -222,16 +276,7 @@ def run_build(
_print_install_hints(console)
return 2

cmd: list[str] = [GRAPHIFY_COMMAND]
args_list = list(extra_args)
# Only inject the project root when the user did not supply a leading
# positional argument. We use a deliberately narrow rule (first token
# is a flag, or there are no tokens) so we do not misclassify a flag
# value like ``deep`` after ``--mode`` as a path.
has_explicit_path = bool(args_list) and not args_list[0].startswith("-")
if not has_explicit_path:
cmd.append(str(project_root))
cmd.extend(args_list)
cmd = _build_graphify_argv(project_root, extra_args)

console.print(
"[dim]running:[/dim] [bold]"
Expand Down
25 changes: 12 additions & 13 deletions src/issue_flow/templates/commands/build.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@ This is an **off-path** command — the lifecycle dispatcher (`/iflow`) never au

## Input

Optional free-form text after the command. Forwarded verbatim to `graphify`. Common combinations:
Optional free-form text after the command. The first word, if it is a recognized graphify build subcommand, picks the action; otherwise the default is `extract`. Trailing tokens forward verbatim. Common combinations:

- **No extra text** — full rebuild of the project root.
- **`./subdir`** — rebuild for a sub-directory (e.g. `/build ./src`).
- **`--update`** — re-extract only files that changed since the last build (much faster).
- **`--no-viz`** — skip the HTML; produce report + JSON only.
- **`--mode deep`** — more aggressive relationship extraction.
- **`--cluster-only`** — rerun clustering on the existing graph without re-extraction.
- **`--watch`** — auto-sync as files change (long-running).
- **No extra text** — full rebuild (`graphify extract <project>`); requires an LLM API key for graphify's semantic pass.
- **`update`** — fast incremental re-extract of changed code files only, no LLM (`graphify update <project>`).
- **`watch`** — long-running file watcher that auto-rebuilds on save (`graphify watch <project>`).
- **`cluster-only`** — rerun clustering on the existing `graph.json` without re-extraction (`graphify cluster-only <project>`).
- **`./subdir`** — scan a sub-directory instead of the project root (`graphify extract ./subdir`).
- **Trailing flags** (e.g. `--no-cluster`, `--force`, `--no-viz`) — passed straight through to the chosen subcommand. See `graphify --help` for the per-subcommand flag set.

See the [graphify CLI reference](https://graphify.net/graphify-cli-commands.html) for the full flag set.
See the [graphify CLI reference](https://graphify.net/graphify-cli-commands.html) for the full subcommand and flag list.

## Steps

Expand All @@ -26,15 +25,15 @@ See the [graphify CLI reference](https://graphify.net/graphify-cli-commands.html
issue-flow build
```

Pass any of the flags above after the project dir, e.g. `issue-flow build . --update --no-viz`. Extra args are forwarded verbatim to `graphify`.
To pick a non-default subcommand or pass flags, append them after the project dir, e.g. `issue-flow build update` or `issue-flow build cluster-only --no-viz`. Extra args are forwarded verbatim.

2. **Fallback: call `graphify` directly** if `issue-flow` is not on PATH:

```bash
graphify .
graphify extract .
```

On PowerShell, drop the leading slash: write `graphify .` (a leading `/` is parsed as a path separator).
`graphify` is subcommand-based — `graphify .` on its own is **not** valid and will fail with `unknown command '.'`. Always pick a subcommand (`extract`, `update`, `watch`, `cluster-only`, …).

3. **Verify outputs.** After a successful run there should be a `graphify-out/` folder with at least `graph.html`, `GRAPH_REPORT.md`, and `graph.json`. Skim `GRAPH_REPORT.md` once to confirm the run picked up new modules or docs.

Expand All @@ -52,7 +51,7 @@ See the [graphify CLI reference](https://graphify.net/graphify-cli-commands.html

- Do **not** run `/build` automatically from `/issue-start`, `/issue-close`, or `/iflow`. The user opts in.
- Do **not** commit `graphify-out/cost.json` or `graphify-out/manifest.json`; both are local-only. The graph itself (`graph.json`, `graph.html`, `GRAPH_REPORT.md`) is fine to commit so teammates start with a map.
- Long-running flags (`--watch`) keep the process running; ask the user before launching them in an agent context.
- Long-running modes (`watch`) keep the process running; ask the user before launching them in an agent context.

## Output to user

Expand Down
2 changes: 1 addition & 1 deletion src/issue_flow/templates/commands/issue-close.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Other optional notes still apply: branch name, PR title, draft PR, skip issue do
- Run tests and any checks you rely on (e.g. `uv run pytest`).
- Skim the diff so the commit matches what you intend to ship.
- Confirm that any design decisions or good-practices that emerged from this issue are captured under `{{ issueflows_dir }}/{{ designs_folder }}/` before committing. If something is missing, add it now (short markdown: context, decision, alternatives, link back to the issue).
- **Graph freshness (optional).** If this change touched the project's structure (new modules, big refactor, removed files) and `graphify-out/` exists, suggest the user run `/build` (or `issue-flow build --update`) before pushing so teammates pull a current `GRAPH_REPORT.md`. Do not run `/build` automatically — it is opt-in. Skip this bullet entirely if `graphify-out/` is not present.
- **Graph freshness (optional).** If this change touched the project's structure (new modules, big refactor, removed files) and `graphify-out/` exists, suggest the user run `/build` (or `issue-flow build update` for a fast, no-LLM refresh) before pushing so teammates pull a current `GRAPH_REPORT.md`. Do not run `/build` automatically — it is opt-in. Skip this bullet entirely if `graphify-out/` is not present.

2. **Optional version bump** (only if the user asked for it in the command input)
- Read `{{ agent_dir }}/skills/issueflow-version-bump/SKILL.md` and follow it.
Expand Down
14 changes: 7 additions & 7 deletions src/issue_flow/templates/docs/cursor-issue-workflow.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -196,17 +196,17 @@ The bump runs **after** tests and **before** issue-folder moves and **before** c

**When:** The project has the optional [graphify](https://graphify.net) integration enabled (the `graphify` CLI is on `PATH` and a `graphify-out/` folder is present), and the graph has gone stale relative to the source tree.

**What you pass:** Optional graphify args, forwarded verbatim. Common picks:
**What you pass:** Optional graphify subcommand and args, forwarded verbatim. Common picks:

- *(nothing)* — full rebuild of the project root.
- `./subdir` — restrict the build to a sub-directory.
- `--update` — re-extract only files changed since the last build.
- `--no-viz` — skip the HTML; produce report + JSON only.
- `--mode deep` — more aggressive relationship extraction.
- *(nothing)* — full rebuild of the project root (`graphify extract <project>`).
- `update` — fast incremental re-extract of changed code files only, no LLM.
- `watch` — long-running watcher that auto-rebuilds on save.
- `cluster-only` — rerun clustering on the existing `graph.json` without re-extraction (e.g. `cluster-only --no-viz`).
- `./subdir` — restrict the scan to a sub-directory (default subcommand: `extract`).

**What the assistant does:**

1. Runs `issue-flow build` (which shells out to the `graphify` CLI). If `issue-flow` is unavailable, falls back to `graphify .` directly.
1. Runs `issue-flow build` (which shells out to the `graphify` CLI). If `issue-flow` is unavailable, falls back to `graphify extract .` directly (`graphify .` alone is **not** valid — graphify requires a subcommand).
2. If `graphify` is not installed, prints install hints (`uv tool install graphifyy`) and stops — never silently retries.
3. Verifies that `graphify-out/graph.html`, `GRAPH_REPORT.md`, and `graph.json` exist after a successful run.

Expand Down
2 changes: 1 addition & 1 deletion src/issue_flow/templates/rules/issueflow-rules.mdc.j2
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,6 @@ Long-lived design docs, design decisions, and project "good practices" live unde
If a `graphify-out/` folder exists in the project root, the project has the optional [graphify](https://graphify.net) integration enabled and a knowledge graph is available alongside the source.

- **Before grepping**, skim `graphify-out/GRAPH_REPORT.md`. It surfaces god-nodes (most-connected concepts), surprising cross-module connections, and suggested questions the graph can answer — often a faster way to locate the files an issue actually touches than full-text search.
- **`/build`** (slash command) or **`issue-flow build`** (CLI) rebuild the graph; both forward extra args (`--update`, `--no-viz`, `--mode deep`, etc.) verbatim to the `graphify` CLI.
- **`/build`** (slash command) or **`issue-flow build`** (CLI) rebuild the graph. With no extra args this runs `graphify extract <project>` (full build). Pass a graphify build subcommand to pick a different action: `issue-flow build update` (fast incremental, no LLM), `issue-flow build watch` (live), `issue-flow build cluster-only --no-viz` (re-cluster only). Trailing flags pass through verbatim.
- `/build` is **off-path**: never auto-dispatched by `/iflow`, `/issue-start`, or `/issue-close`. It is the user's call. `/issue-start` may *suggest* skimming `GRAPH_REPORT.md`; `/issue-close` may *suggest* a rebuild after large structural changes — neither runs `graphify` automatically.
- If `graphify-out/` is not present, ignore graph-related guidance entirely. The integration is opt-in (install with `uv tool install graphifyy`, then `issue-flow update` to register the graphify Cursor skill).
Loading
Loading