From 0b58dd6331e5408631741a2e33a23aa64c2f733e Mon Sep 17 00:00:00 2001 From: jepegit Date: Thu, 14 May 2026 23:30:33 +0200 Subject: [PATCH] fix(graphify): default `issue-flow build` to `update`, not `extract` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `graphify extract` requires an LLM API key (Gemini / Anthropic / OpenAI / Kimi) or `--backend ollama`. Defaulting to `extract` meant `issue-flow build` failed on every fresh machine with `error: no LLM API key found`. Cursor's own LLM is not exposed to subprocesses, so graphify cannot reuse it — there is no shim that makes `extract` work without an explicit backend. `graphify update` produces the same `graphify-out/` directory (graph.json, graph.html, GRAPH_REPORT.md) on a fresh repo, runs in seconds, and prints "no LLM needed" in its own output. Trade-off: the default graph lacks semantic relationships an LLM pass would surface; that is the right trade for a default that just works. Power users opt into the deeper build with `issue-flow build extract` after configuring a backend. Changes: - `_DEFAULT_BUILD_SUBCOMMAND` flipped from `extract` to `update` in `src/issue_flow/graphify.py` (with rationale in the comment block). - Docstrings on `cli.build` and `run_build` now lead with the no-LLM default and document the API-key requirement (and `--backend ollama` escape hatch) for `extract`. - README, build template, build skill, rules entry, cursor-issue-workflow doc, and issue-close template all rewritten to advertise `update` as the default. Each now states explicitly that Cursor's LLM is not available to subprocesses and points at the four supported env vars + Ollama as the upgrade paths. - Tests flipped to expect `update` as the injected default; the leading-flag test now uses `--force` (a real `update` flag) instead of the previous `--no-cluster` (an `extract` flag). - Design doc grows a new "Default subcommand is `update`, not `extract`" section explaining the LLM-key gate and what slipped through originally (mocked subprocess never exercised the API-key precondition). Co-authored-by: Cursor --- .../graphify-integration.md | 14 +++++++-- README.md | 15 ++++++---- src/issue_flow/cli.py | 21 +++++++++----- src/issue_flow/graphify.py | 29 ++++++++++++------- src/issue_flow/templates/commands/build.md.j2 | 24 ++++++++++----- .../templates/commands/issue-close.md.j2 | 2 +- .../docs/cursor-issue-workflow.md.j2 | 11 +++---- .../templates/rules/issueflow-rules.mdc.j2 | 2 +- .../skills/issueflow_build/SKILL.md.j2 | 16 ++++++---- tests/test_cli.py | 5 +++- tests/test_graphify.py | 26 +++++++++-------- 11 files changed, 105 insertions(+), 60 deletions(-) diff --git a/.issueflows/04-designs-and-guides/graphify-integration.md b/.issueflows/04-designs-and-guides/graphify-integration.md index eb03090..5b73092 100644 --- a/.issueflows/04-designs-and-guides/graphify-integration.md +++ b/.issueflows/04-designs-and-guides/graphify-integration.md @@ -88,8 +88,16 @@ The original implementation assumed `graphify [flags…]` was the canonic **Fixes landed in this iteration:** -- `_build_graphify_argv` translates `issue-flow build [args…]` into `graphify [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. +- `_build_graphify_argv` translates `issue-flow build [args…]` into `graphify [args…]`. 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 .`. +- 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 a valid subcommand invocation. -**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`). +### 4. Default subcommand is `update`, not `extract` + +The first cut of this fix used `extract` as the default subcommand (it is the most thorough build). Real-world testing immediately surfaced a follow-on bug: `graphify extract` requires an LLM API key (Gemini / Anthropic / OpenAI / Kimi) or `--backend ollama`, so `issue-flow build` failed with `error: no LLM API key found` on any fresh machine. **Cursor's own LLM is not available to subprocesses** — there is no API graphify could hook to reuse it — so we cannot paper over this with a clever shim. + +The fix: default to `update` (AST-only). It produces the full `graphify-out/` directory (`graph.json`, `graph.html`, `GRAPH_REPORT.md`) on a fresh repo, runs in seconds, and explicitly states "no LLM needed" in its own output. Power users who want the deeper semantic pass pick `extract` explicitly (`issue-flow build extract`) and configure a backend. + +**Trade-off:** the default graph misses semantic relationships an LLM would surface (cross-file calls inferred from intent, doc/paper integration, image extraction). That is the right trade for a default that "just works" — graphify's own help text recommends the same upgrade path ("Tip: set GEMINI_API_KEY … to use Gemini for semantic extraction"). + +**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 **and** never exercised the LLM-key precondition. The new tests exercise the argv-construction function directly with realistic subcommand combinations, and the doc / template prose now flags the API-key requirement on `extract`. Future graphify integrations should always include at least one test that uses real graphify subcommand names, and at least one end-to-end smoke against a tiny test repo to catch precondition surprises like the API-key gate. diff --git a/README.md b/README.md index f81f174..c7e15de 100644 --- a/README.md +++ b/README.md @@ -111,11 +111,14 @@ 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`. With no extra args it runs `graphify extract ` - (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. + `issue-flow build`. With no extra args it runs `graphify update ` + — AST-only, **no LLM API key required**, so the no-arg case "just works". + For richer semantic relationships add `extract` (`issue-flow build extract`) + and configure a backend (`GEMINI_API_KEY`, `ANTHROPIC_API_KEY`, + `OPENAI_API_KEY`, `MOONSHOT_API_KEY`, or `--backend ollama` for a local + LLM). Cursor's own LLM is not available to subprocesses, so graphify + needs its own backend. Other subcommands (`watch`, `cluster-only`, …) + pass through too; trailing flags forward 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. @@ -203,7 +206,7 @@ Use `update` after upgrading the **issue-flow** package to refresh the packaged | Argument / Option | Description | |---|---| | `-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 ` (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`. | +| `...graphify subcommand + args` | Optional graphify subcommand + flags. With no extras runs `graphify update ` — AST-only, **no LLM API key required**. The first extra arg, if it is a recognized build subcommand (`update`, `extract`, `watch`, `cluster-only`, `check-update`), picks the action; trailing tokens forward verbatim. Examples: `issue-flow build extract` (semantic LLM pass; needs `GEMINI_API_KEY` / `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `MOONSHOT_API_KEY` or `--backend ollama`), `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`). diff --git a/src/issue_flow/cli.py b/src/issue_flow/cli.py index cc14549..bf82b69 100644 --- a/src/issue_flow/cli.py +++ b/src/issue_flow/cli.py @@ -99,14 +99,19 @@ def build( ) -> None: """Rebuild the graphify knowledge graph for the project. - With no extra arguments runs ``graphify extract `` (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 `` to scan a - project other than the current directory. Requires ``graphify`` to - be on ``PATH`` (install with ``uv tool install graphifyy``). + With no extra arguments runs ``graphify update `` + (AST-only build, no LLM API key required) so first-time builds + just work. Pick a different action by passing the subcommand as + the first argument: ``issue-flow build extract`` adds the slower + semantic LLM pass for richer cross-file relationships (needs an + API key — ``GEMINI_API_KEY``, ``ANTHROPIC_API_KEY``, + ``OPENAI_API_KEY``, or ``--backend ollama`` for a local LLM); + ``issue-flow build watch`` runs a live rebuild; + ``issue-flow build cluster-only --no-viz`` re-clusters an existing + graph. Trailing flags pass through verbatim. Use ``-C `` 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 diff --git a/src/issue_flow/graphify.py b/src/issue_flow/graphify.py index 9a63862..4a1e309 100644 --- a/src/issue_flow/graphify.py +++ b/src/issue_flow/graphify.py @@ -46,9 +46,13 @@ {"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" +# specifying one. ``update`` is the AST-only build: it produces the +# full ``graphify-out/`` (``graph.json``, ``graph.html``, +# ``GRAPH_REPORT.md``) and crucially does **not** need an LLM API key, +# so the no-arg case "just works" for first-time users. Power users +# pick ``extract`` explicitly when they want the slower semantic LLM +# pass that surfaces richer cross-file relationships. +_DEFAULT_BUILD_SUBCOMMAND: str = "update" def _build_graphify_argv( @@ -60,17 +64,19 @@ def _build_graphify_argv( folder" mode — so every invocation needs an explicit subcommand. Behavior: - * No extra args → ``graphify extract ``. + * No extra args → ``graphify update ``. ``update`` + is AST-only and needs no LLM API key, so the no-arg case "just + works" for users who have not configured a backend yet. * 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 + (``update``) 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``). + → ``graphify update ./docs``). """ args = list(extra_args) @@ -260,10 +266,13 @@ def run_build( See :func:`_build_graphify_argv` for the argv-construction rules. The short version: ``issue-flow build`` with no args invokes - ``graphify extract `` (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``). + ``graphify update `` (AST-only, no LLM API key + required, produces the full ``graphify-out/`` directory). Users + who want the deeper semantic LLM pass run + ``issue-flow build extract`` and configure a backend + (``GEMINI_API_KEY`` / ``ANTHROPIC_API_KEY`` / ``OPENAI_API_KEY`` / + ``MOONSHOT_API_KEY`` env var, or ``--backend ollama`` for a local + LLM). Returns ``2`` and prints install hints when graphify is missing. Re-raises ``KeyboardInterrupt`` so users can ^C a long build. diff --git a/src/issue_flow/templates/commands/build.md.j2 b/src/issue_flow/templates/commands/build.md.j2 index 0084de5..89c939d 100644 --- a/src/issue_flow/templates/commands/build.md.j2 +++ b/src/issue_flow/templates/commands/build.md.j2 @@ -6,14 +6,14 @@ This is an **off-path** command — the lifecycle dispatcher (`/iflow`) never au ## Input -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: +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 `update`. Trailing tokens forward verbatim. Common combinations: -- **No extra text** — full rebuild (`graphify extract `); requires an LLM API key for graphify's semantic pass. -- **`update`** — fast incremental re-extract of changed code files only, no LLM (`graphify update `). +- **No extra text** — AST-only build (`graphify update `). **No LLM API key required**, so this works on a fresh machine. Produces the full `graphify-out/` (graph.json, graph.html, GRAPH_REPORT.md). The default. +- **`extract`** — AST + semantic LLM pass (`graphify extract `). Richer cross-file relationships, but **requires an LLM backend**: set `GEMINI_API_KEY` / `GOOGLE_API_KEY`, `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, or `MOONSHOT_API_KEY` — or pass `--backend ollama` for a local LLM via [Ollama](https://ollama.com). Use after large refactors or when the AST-only graph misses important relationships. - **`watch`** — long-running file watcher that auto-rebuilds on save (`graphify watch `). - **`cluster-only`** — rerun clustering on the existing `graph.json` without re-extraction (`graphify cluster-only `). -- **`./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. +- **`./subdir`** — scan a sub-directory instead of the project root (`graphify update ./subdir`). +- **Trailing flags** (e.g. `--force`, `--no-cluster`, `--no-viz`, `--backend ollama`) — 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 subcommand and flag list. @@ -25,15 +25,15 @@ See the [graphify CLI reference](https://graphify.net/graphify-cli-commands.html issue-flow build ``` - 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. + This runs `graphify update .` — no API key needed. To pick a different subcommand or pass flags, append them, e.g. `issue-flow build extract` (full LLM pass), `issue-flow build cluster-only --no-viz`, or `issue-flow build extract --backend ollama` (local LLM). To scan a project other than the current directory, use `issue-flow build -C `. Extra args are forwarded verbatim. 2. **Fallback: call `graphify` directly** if `issue-flow` is not on PATH: ```bash - graphify extract . + graphify update . ``` - `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`, …). + `graphify` is subcommand-based — `graphify .` on its own is **not** valid and will fail with `unknown command '.'`. Always pick a subcommand (`update`, `extract`, `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. @@ -47,6 +47,14 @@ See the [graphify CLI reference](https://graphify.net/graphify-cli-commands.html `graphifyy` (double-y) is the official PyPI package; the CLI is still `graphify`. After installing, re-run `issue-flow init` (or `issue-flow update`) so `graphify cursor install` registers the graphify Cursor skill. +5. **If `graphify extract` complains about a missing LLM API key**, the user picked the semantic-pass subcommand without configuring a backend. Suggest one of: + + - Set an API key for one of the supported backends (`GEMINI_API_KEY` / `GOOGLE_API_KEY`, `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `MOONSHOT_API_KEY`) and re-run. + - Use a local LLM: `issue-flow build extract --backend ollama` (requires [Ollama](https://ollama.com) installed and a model pulled, e.g. `ollama pull qwen2.5-coder`). + - Drop the `extract` arg and use the default `issue-flow build` (AST-only `update`, no LLM). + + Cursor's own LLM is **not** available to subprocesses, so graphify cannot reuse it. + ## Constraints - Do **not** run `/build` automatically from `/issue-start`, `/issue-close`, or `/iflow`. The user opts in. diff --git a/src/issue_flow/templates/commands/issue-close.md.j2 b/src/issue_flow/templates/commands/issue-close.md.j2 index 52758ed..c92f341 100644 --- a/src/issue_flow/templates/commands/issue-close.md.j2 +++ b/src/issue_flow/templates/commands/issue-close.md.j2 @@ -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` 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. + - **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` (default: `graphify update`, AST-only, no LLM key needed) 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. diff --git a/src/issue_flow/templates/docs/cursor-issue-workflow.md.j2 b/src/issue_flow/templates/docs/cursor-issue-workflow.md.j2 index 2744813..0e82b23 100644 --- a/src/issue_flow/templates/docs/cursor-issue-workflow.md.j2 +++ b/src/issue_flow/templates/docs/cursor-issue-workflow.md.j2 @@ -198,17 +198,18 @@ The bump runs **after** tests and **before** issue-folder moves and **before** c **What you pass:** Optional graphify subcommand and args, forwarded verbatim. Common picks: -- *(nothing)* — full rebuild of the project root (`graphify extract `). -- `update` — fast incremental re-extract of changed code files only, no LLM. +- *(nothing)* — AST-only build of the project root (`graphify update `). **No LLM API key required**; produces the full `graphify-out/`. The default. +- `extract` — adds the slower semantic LLM pass for richer cross-file relationships. Needs an API key (`GEMINI_API_KEY`, `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `MOONSHOT_API_KEY`) or `--backend ollama` for a local LLM via [Ollama](https://ollama.com). Cursor's own LLM is **not** available to subprocesses. - `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`). +- `./subdir` — restrict the scan to a sub-directory (default subcommand: `update`). **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 extract .` directly (`graphify .` alone is **not** valid — graphify requires a subcommand). +1. Runs `issue-flow build` (which shells out to the `graphify` CLI). If `issue-flow` is unavailable, falls back to `graphify update .` 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. +3. If `graphify extract` fails with "no LLM API key found", suggests setting one of the supported env vars, or using `--backend ollama`, or dropping back to the default `update` subcommand. +4. Verifies that `graphify-out/graph.html`, `GRAPH_REPORT.md`, and `graph.json` exist after a successful run. **Result:** A refreshed `graphify-out/` so `/issue-start` can navigate by graph instead of grepping. `/build` is **off-path** — `/iflow`, `/issue-start`, and `/issue-close` may *suggest* a rebuild but never invoke `/build` automatically. diff --git a/src/issue_flow/templates/rules/issueflow-rules.mdc.j2 b/src/issue_flow/templates/rules/issueflow-rules.mdc.j2 index f6da74a..f75b123 100644 --- a/src/issue_flow/templates/rules/issueflow-rules.mdc.j2 +++ b/src/issue_flow/templates/rules/issueflow-rules.mdc.j2 @@ -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. With no extra args this runs `graphify extract ` (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`** (slash command) or **`issue-flow build`** (CLI) rebuild the graph. With no extra args this runs `graphify update ` — AST-only, **no LLM API key needed**. For richer semantic relationships (cross-file links surfaced by an LLM pass), run `issue-flow build extract` after setting `GEMINI_API_KEY` / `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `MOONSHOT_API_KEY` (or pass `--backend ollama` for a local LLM). Other subcommands: `watch` (live), `cluster-only --no-viz` (re-cluster). Trailing flags pass through verbatim. Cursor's own LLM cannot be reused by subprocesses; graphify needs its own backend. - `/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). diff --git a/src/issue_flow/templates/skills/issueflow_build/SKILL.md.j2 b/src/issue_flow/templates/skills/issueflow_build/SKILL.md.j2 index 9d9f1d9..65f131e 100644 --- a/src/issue_flow/templates/skills/issueflow_build/SKILL.md.j2 +++ b/src/issue_flow/templates/skills/issueflow_build/SKILL.md.j2 @@ -28,17 +28,23 @@ Do **not** use this skill from `/issue-start`, `/issue-close`, or `/iflow`. `/bu issue-flow build ``` - With no extra args this runs `graphify extract ` (full AST + semantic LLM build). To pick a different graphify subcommand, pass it as the first arg: `issue-flow build update` (fast, code-only re-extract), `issue-flow build watch` (live rebuild), `issue-flow build cluster-only --no-viz`, etc. A leading `./subdir` overrides the scan path. Trailing flags pass through verbatim. Do not invent new wrapper flags. + With no extra args this runs `graphify update ` — AST-only, **no LLM API key required**, produces the full `graphify-out/`. To pick a different graphify subcommand, pass it as the first arg: `issue-flow build extract` (adds the slower semantic LLM pass for richer relationships — needs an API key), `issue-flow build watch` (live), `issue-flow build cluster-only --no-viz`, etc. Use `-C ` to scan a project other than the current directory. Trailing flags pass through verbatim. Do not invent new wrapper flags. 2. **Fallback to `graphify` directly** when `issue-flow` is unavailable: ```bash - graphify extract . + graphify update . ``` - `graphify` is subcommand-based — `graphify .` on its own is **not** valid (graphify reports `unknown command '.'`). Always pick a subcommand: `extract` for a full build, `update` for a fast incremental, `watch` for a long-running watcher, etc. + `graphify` is subcommand-based — `graphify .` on its own is **not** valid (graphify reports `unknown command '.'`). Always pick a subcommand: `update` for the no-LLM AST build, `extract` for the full semantic pass, `watch` for a long-running watcher, etc. -3. **Handle missing graphify gracefully.** If the run reports `graphify` is not on PATH, do **not** retry blindly. Tell the user to install it once: +3. **If graphify exits with "no LLM API key found"**, the user picked `extract` (or another semantic subcommand) without configuring a backend. Cursor's own LLM is not available to subprocesses, so graphify cannot reuse it. Suggest one of: + + - Set an API key for `GEMINI_API_KEY` / `GOOGLE_API_KEY`, `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, or `MOONSHOT_API_KEY`. + - Run `issue-flow build extract --backend ollama` to use a local LLM via [Ollama](https://ollama.com) (requires Ollama installed with a model pulled). + - Drop the `extract` arg and use the default `issue-flow build` (AST-only, no LLM). + +4. **Handle missing graphify gracefully.** If the run reports `graphify` is not on PATH, do **not** retry blindly. Tell the user to install it once: ```bash uv tool install graphifyy # recommended @@ -48,7 +54,7 @@ Do **not** use this skill from `/issue-start`, `/issue-close`, or `/iflow`. `/bu `graphifyy` (double-y) is the official PyPI package; the CLI is still `graphify`. After installing, suggest `issue-flow update` so `graphify cursor install` registers the graphify Cursor skill alongside this one. -4. **Verify and report.** +5. **Verify and report.** - Confirm `graphify-out/graph.json`, `graphify-out/graph.html`, and `graphify-out/GRAPH_REPORT.md` exist after a successful run. - Surface non-zero exit codes verbatim; do not silently retry. - When the user asks "what changed?", skim `GRAPH_REPORT.md` (god nodes, surprising connections) for a short summary. diff --git a/tests/test_cli.py b/tests/test_cli.py index abdf85f..d255be1 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -53,7 +53,10 @@ def fake_run(cmd: list[str], **kwargs: Any) -> _Result: assert result.exit_code == 0, result.output assert captured["cmd"][0] == "graphify" # Default subcommand must be injected since graphify requires one. - assert captured["cmd"][1] == "extract" + # We default to ``update`` (AST-only, no LLM API key required) so + # ``issue-flow build`` works on a fresh machine with no backend + # configured. + assert captured["cmd"][1] == "update" def test_build_forwards_extra_args( diff --git a/tests/test_graphify.py b/tests/test_graphify.py index 7baab6e..d155a00 100644 --- a/tests/test_graphify.py +++ b/tests/test_graphify.py @@ -150,14 +150,16 @@ def fail_run(*_a: Any, **_kw: Any) -> Any: assert "graphifyy" in text -def test_run_build_no_args_uses_default_extract_subcommand( +def test_run_build_no_args_uses_default_update_subcommand( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: - """`issue-flow build` with no args must invoke `graphify extract `. + """`issue-flow build` with no args must invoke `graphify update `. graphify is subcommand-based — `graphify ` alone fails with - `unknown command`. The default action for a "build" is `extract` - (full AST + semantic LLM build). + `unknown command`. The default action for a "build" is `update` + (AST-only, no LLM API key required) so first-time builds work + without configuration. Users opt into the semantic LLM pass via + ``issue-flow build extract``. """ monkeypatch.setattr(graphify_module.shutil, "which", lambda _cmd: "/usr/bin/graphify") @@ -177,7 +179,7 @@ def fake_run(cmd: list[str], **kwargs: Any) -> _Result: exit_code = run_build(tmp_path, [], console) assert exit_code == 0 - assert captured["cmd"] == [GRAPHIFY_COMMAND, "extract", str(tmp_path)] + assert captured["cmd"] == [GRAPHIFY_COMMAND, "update", str(tmp_path)] assert captured["cwd"] == tmp_path @@ -268,7 +270,7 @@ def fake_run(cmd: list[str], **kwargs: Any) -> _Result: def test_run_build_does_not_inject_path_when_user_supplied_one( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: - """`issue-flow build ./docs` → `graphify extract ./docs` (no double path).""" + """`issue-flow build ./docs` → `graphify update ./docs` (no double path).""" monkeypatch.setattr(graphify_module.shutil, "which", lambda _cmd: "/usr/bin/graphify") captured: dict[str, Any] = {} @@ -285,15 +287,15 @@ def fake_run(cmd: list[str], **kwargs: Any) -> _Result: run_build(tmp_path, ["./docs"], console) - # Subcommand defaulted to extract; the only positional after it is + # Subcommand defaulted to update; the only positional after it is # the user's "./docs" — not the project root. - assert captured["cmd"] == [GRAPHIFY_COMMAND, "extract", "./docs"] + assert captured["cmd"] == [GRAPHIFY_COMMAND, "update", "./docs"] def test_run_build_leading_flag_falls_back_to_default_subcommand( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: - """A leading flag (no subcommand, no path) → `extract `.""" + """A leading flag (no subcommand, no path) → `update `.""" monkeypatch.setattr(graphify_module.shutil, "which", lambda _cmd: "/usr/bin/graphify") captured: dict[str, Any] = {} @@ -308,13 +310,13 @@ def fake_run(cmd: list[str], **kwargs: Any) -> _Result: monkeypatch.setattr(graphify_module.subprocess, "run", fake_run) console, _buffer = _fake_console() - run_build(tmp_path, ["--no-cluster"], console) + run_build(tmp_path, ["--force"], console) assert captured["cmd"] == [ GRAPHIFY_COMMAND, - "extract", + "update", str(tmp_path), - "--no-cluster", + "--force", ]