From 2cd4691cb4251903925118453c9785f402ba9e8d Mon Sep 17 00:00:00 2001 From: Nova Date: Mon, 24 Aug 2026 22:20:33 +0000 Subject: [PATCH 1/2] =?UTF-8?q?Fix=20#20:=200.7.0=20=E2=80=94=20init=20inf?= =?UTF-8?q?ers=20the=20stack=20commands=20no=20manifest=20declares?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A script-style Python repo (venv, requirements.txt, tests/test_*.py, an app.py, no pyproject.toml) came out of /flow:init with every *_cmd blank, and the closing line told the *user* to go and edit .flow/config.md. /flow:health repeated it. Every one of those commands was discoverable from the repo in under a minute — that is the agent's work, not homework to hand back. init.js now: - detects from requirements*.txt (pytest, ruff, flake8, mypy, black), Makefile targets, and manage.py, none of which needed a manifest; - prefers the project's own venv copy of a tool (.venv/bin/pytest) over a bare name resolved against the ambient PATH; - ends by naming the keys still blank — reading the existing config.md, so a re-run reports the real state — addressed to the agent, with the instruction to infer, verify by running, and write them in. /flow:init grows a Step 3 that does exactly that, /flow:health fills a blank *_cmd instead of reporting it back, and the procedure (evidence sources per key, how to verify each without leaving a dev server running) lives in references/stack-command-inference.md. --- .claude-plugin/marketplace.json | 2 +- plugins/flow/.claude-plugin/plugin.json | 2 +- plugins/flow/README.md | 6 +- plugins/flow/SETUP.md | 6 +- plugins/flow/commands/health.md | 18 +- plugins/flow/commands/init.md | 49 ++++- plugins/flow/package.json | 2 +- plugins/flow/references/config-template.md | 6 +- .../references/stack-command-inference.md | 80 ++++++++ plugins/flow/scripts/init.js | 180 +++++++++++++++++- plugins/flow/scripts/init.test.js | 153 +++++++++++++++ 11 files changed, 475 insertions(+), 29 deletions(-) create mode 100644 plugins/flow/references/stack-command-inference.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 8876aa2..ac789a6 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -26,7 +26,7 @@ "name": "flow", "description": "Stack-agnostic AI dev workflow \u2014 twelve slash commands, a focused reviewer agent, and hooks that layer with your project's .flow/ config", "source": "./plugins/flow", - "version": "0.6.0", + "version": "0.7.0", "category": "workflow", "keywords": [ "workflow", diff --git a/plugins/flow/.claude-plugin/plugin.json b/plugins/flow/.claude-plugin/plugin.json index a657fa9..fc14925 100644 --- a/plugins/flow/.claude-plugin/plugin.json +++ b/plugins/flow/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "flow", - "version": "0.6.0", + "version": "0.7.0", "description": "Reusable AI dev workflow for Claude Code: orchestrated agents, session lifecycle, deep reviews, and project-aware slash commands.", "author": { "name": "kaiomp" diff --git a/plugins/flow/README.md b/plugins/flow/README.md index 794542c..c6ca678 100644 --- a/plugins/flow/README.md +++ b/plugins/flow/README.md @@ -15,7 +15,7 @@ Then, inside any project: /flow:init ``` -This creates `.flow/config.md` (project-level config) and `CLAUDE.md` (project memory) from templates, plus an `issues/` directory for the local PM backend. +This creates `.flow/config.md` (project-level config) and `CLAUDE.md` (project memory) from templates, plus an `issues/` directory for the local PM backend. Your dev/lint/test/build commands are filled in for you: detected from whichever manifest declares them, and where none does — a script-style repo with a venv and a `tests/` directory, say — inferred from the repo and verified by running them. You are not handed a config file to complete by hand. ## Commands @@ -30,7 +30,7 @@ This creates `.flow/config.md` (project-level config) and `CLAUDE.md` (project m | `/flow:pause` | Snapshot current state to `.flow/session-progress.md`; `land` also ships — CI checks, issue closing, ff-merge. | | `/flow:audit` | Dry-run review: lint, typecheck, security pass without shipping. | | `/flow:cleanup` | Tidy stray branches, stale session files, and `.flow/session/`. | -| `/flow:health` | Inspect the project's flow setup and report missing pieces. | +| `/flow:health` | Inspect the project's flow setup and report missing pieces (and fill in any blank `*_cmd`). | | `/flow:deep-review` | Spawn the reviewer agent with extra rigor on the working diff. | | `/flow:flawz` | Pressure-test a plan/spec/design for real flaws before you act on it. | | `/flow:autopilot` | Autonomous multi-sprint loop: agent team, deep-review, push, repeat. | @@ -74,7 +74,7 @@ Custom agents communicate through files in `.flow/session/` (e.g. `investigation | `question_wip` | Max questions open at once in `.flow/questions.md` (default 3) | | `schema_glob` / `docs_glob` / `route_pattern` | Optional drift-check heuristics tuning | -Hooks and helper scripts read these values. Nothing is hardcoded — `flow` adapts to your stack. See `references/config-template.md` for the full annotated template. +Hooks and helper scripts read these values. Nothing is hardcoded — `flow` adapts to your stack. See `references/config-template.md` for the full annotated template, and `references/stack-command-inference.md` for how the `*_cmd` values get worked out when no manifest declares them. ### `config.md` is private by default diff --git a/plugins/flow/SETUP.md b/plugins/flow/SETUP.md index fca3db1..a831ab9 100644 --- a/plugins/flow/SETUP.md +++ b/plugins/flow/SETUP.md @@ -30,8 +30,10 @@ asks before dropping session state), then `/flow:init` again. ## 2. Check `.flow/config.md` -Init fills in what it detected, so most projects only need a glance. A blank value means -"skip that step" — fill it in by hand if detection missed something: +Init fills these in for you — from your manifest where it declares them, and otherwise by +inferring the command from the repo and verifying it runs — so most projects only need a +glance. A blank value means "skip that step": nothing to run for it. Re-run `/flow:init` if a +blank should have a command: ``` workflow_mode: solo diff --git a/plugins/flow/commands/health.md b/plugins/flow/commands/health.md index 7a20c58..837cda0 100644 --- a/plugins/flow/commands/health.md +++ b/plugins/flow/commands/health.md @@ -1,11 +1,14 @@ --- description: Verify your workflow setup — config, hooks, required commands, PM backend connectivity. -allowed-tools: Bash, Read, Glob, Grep +allowed-tools: Bash, Read, Edit, Glob, Grep --- # /flow:health Sanity check. Run this after `/flow:init` and any time `/flow:*` commands feel broken. +Diagnostic, not a repair tool: report what is wrong and where the fix is, and change nothing — +with one exception, a blank `*_cmd` in config.md, which you fill in yourself (Check 1). + ## Checks Run each check and collect the result. Print a table at the end. @@ -16,6 +19,15 @@ Run each check and collect the result. Print a table at the end. - Does it parse? (frontmatter or key:value lines as defined in the template) - Are required fields present: `workflow_mode`, `pm_backend`, `dev_cmd`, `lint_cmd`, `build_cmd`, `test_cmd`? +A `*_cmd` key that is present but **blank** is your work, not the user's. Do not report it as +"fill this in yourself" — that is exactly the chore `/flow:init` Step 3 exists to remove. +Infer it from the repo, verify it runs, and write it in, following +`${CLAUDE_PLUGIN_ROOT}/references/stack-command-inference.md`. Report it as `FIXED` with the +command you wrote. Only a key you inferred and could *not* verify — or one with genuinely +conflicting evidence — is worth raising with the user, and then with a recommendation, not an +open question. A key that is blank because the project has no such step is `OK`, not a +finding; say so in the note. + ### 2. CLAUDE.md freshness - Does `$CLAUDE_PROJECT_DIR/CLAUDE.md` exist? @@ -58,7 +70,7 @@ Print a table: ``` CHECK STATUS NOTE -config.md OK +config.md FIXED test_cmd was blank -> .venv/bin/pytest (23 passed) CLAUDE.md freshness STALE last touched 124 days ago hooks OK 2 wired commands on PATH FAIL `` not found @@ -66,4 +78,4 @@ pm backend OK git OK ``` -End with a one-line summary: `N/M checks passed`. If anything failed, point at the fix (`/flow:init`, install missing tool, etc.). +End with a one-line summary: `N/M checks passed`. If anything failed, point at the fix (`/flow:init`, install missing tool, etc.) — and where the fix is something you can do, such as a blank `*_cmd`, do it rather than prescribe it. diff --git a/plugins/flow/commands/init.md b/plugins/flow/commands/init.md index 0836d41..bf72257 100644 --- a/plugins/flow/commands/init.md +++ b/plugins/flow/commands/init.md @@ -1,13 +1,14 @@ --- description: Bootstrap a new project with .flow/config.md and CLAUDE.md tailored to your stack. -allowed-tools: Bash, Read, AskUserQuestion +allowed-tools: Bash, Read, Edit, Glob, Grep, AskUserQuestion --- # /flow:init Bootstrap a project so the rest of `/flow:*` works. This drives `scripts/init.js`, which writes `.flow/config.md` and `CLAUDE.md` and auto-detects your stack commands from files -already on disk (`package.json` scripts, `go.mod`, `Cargo.toml`, `pyproject.toml`, …). You -only need to supply what the script cannot infer. +already on disk (`package.json` scripts, `go.mod`, `Cargo.toml`, `pyproject.toml`, +`requirements*.txt`, a `Makefile`, …). Anything a manifest does not declare, **you** work out +from the repo in Step 3 — the user is only ever asked the questions in Step 1. ## Step 1: Ask the user @@ -19,7 +20,8 @@ Use `AskUserQuestion`, batched where possible: what `git remote get-url origin` parses to. 4. If `linear`: ask for `pm_linear_team` (the team key, e.g. `ENG`). -Do not ask about dev/lint/typecheck/build/test/format commands — the script detects those. +Do not ask about dev/lint/typecheck/build/test/format commands — the script detects what the +manifests declare and Step 3 works out the rest from the repo. For the project name, don't ask by default — the script infers it from `package.json`'s `name` field, falling back to the directory basename. Only ask via `AskUserQuestion` if @@ -45,7 +47,32 @@ Because nothing is overwritten, editing a template in a plugin update does *not* project that was already initialised. To pick up template changes, run `/flow:uninstall` first, then `/flow:init` again. -## Step 3: Backend bootstrapping +## Step 3: Fill in what detection missed + +The script ends by naming every key it could not fill: + +``` +[flow init] Done. Stack commands still blank: dev_cmd, build_cmd, format_cmd +``` + +That list is **yours to finish, not the user's**. A repo with a venv, a `tests/test_*.py` and +an `app.py` has a test command and a dev command; they are simply not declared in a manifest. +Telling the user to "edit `.flow/config.md` to fill in your project commands" hands back a +chore you can complete in one pass. + +For each blank key, follow `${CLAUDE_PLUGIN_ROOT}/references/stack-command-inference.md`: +find the evidence (entrypoints, test files, linter configs, `.github/workflows/`), prefer the +project's own environment (`.venv/bin/pytest`, not a bare `pytest`), **run the command to +verify it works**, then write it into the matching `- :` line with `Edit`. Leave a key +blank only when the project genuinely has nothing to run for it, and say which and why. + +Nothing here is overwritten by the script, so this step works the same on a re-run against an +already-initialised project whose commands were left blank. + +Ask the user only when the evidence genuinely conflicts — two test runners and nothing in the +repo picking between them. Never ask for a command you could have found and verified. + +## Step 4: Backend bootstrapping - `pm_backend=local`: create `$CLAUDE_PROJECT_DIR/issues/` with a `.gitkeep` and a `README.md` explaining the format (one markdown file per issue, frontmatter with @@ -58,15 +85,17 @@ first, then `/flow:init` again. collaborators should get the same stack setup. Only `.flow/local.md` is machine-private (`private_globs` covers it), so machine-specific values belong there, not in `config.md`. -## Step 4: Report +## Step 5: Report Relay the script's output verbatim — which files were `created` / `left untouched`, and -which stack commands were detected. +which stack commands were detected. Then add what Step 3 produced: each command you inferred, +how you verified it, and any key you deliberately left blank. -## Step 5: Verify the setup +## Step 6: Verify the setup Run `/flow:health` straight away rather than suggesting it. Init is exactly the point where -a wrong answer is cheapest to fix, and health is read-only. +a wrong answer is cheapest to fix, and health only diagnoses — the one thing it repairs is a +blank `*_cmd`, which Step 3 should already have left nothing to do. Report only what health flags. If everything passes, one line is enough — do not reprint the -whole table on top of the Step 4 report. +whole table on top of the Step 5 report. diff --git a/plugins/flow/package.json b/plugins/flow/package.json index 9adfcf9..dc51d83 100644 --- a/plugins/flow/package.json +++ b/plugins/flow/package.json @@ -1,6 +1,6 @@ { "name": "flow", - "version": "0.6.0", + "version": "0.7.0", "private": true, "scripts": { "test": "node --test scripts/*.test.js hooks/*.test.js" diff --git a/plugins/flow/references/config-template.md b/plugins/flow/references/config-template.md index 5cedc14..2df9c0f 100644 --- a/plugins/flow/references/config-template.md +++ b/plugins/flow/references/config-template.md @@ -71,8 +71,10 @@ Examples (replace with your own — these are illustrative, not defaults): - e.g. lint_cmd: `` - e.g. test_cmd: `` -> `/flow:init` fills these in from your manifest. A blank value means "skip that step" — -> add one by hand if the detection missed it. +> `/flow:init` fills these in: from your manifest where it declares them, otherwise by +> working the command out from the repo and running it once to check. A blank value means +> "skip that step" — there is genuinely nothing to run for it. Re-run `/flow:init` if a blank +> should have a command; filling them in is its job, not yours. --- diff --git a/plugins/flow/references/stack-command-inference.md b/plugins/flow/references/stack-command-inference.md new file mode 100644 index 0000000..168a258 --- /dev/null +++ b/plugins/flow/references/stack-command-inference.md @@ -0,0 +1,80 @@ +# Inferring stack commands + +`scripts/init.js` fills `.flow/config.md`'s `*_cmd` keys from manifests it can read — +`package.json` scripts, `go.mod`, `Cargo.toml`, `pyproject.toml`, `requirements*.txt`, a +`Makefile`. That covers most repos. It does not cover a repo whose commands are real but +undeclared: a script-style Python project with a venv, a `tests/test_*.py`, and an `app.py` +has a test command and a dev command, just not written down anywhere a parser can find. + +For those, init prints the keys it could not fill: + +``` +[flow init] Done. Stack commands still blank: dev_cmd, build_cmd, format_cmd +``` + +That line is addressed to the **agent**, not the user. Everything init leaves blank is +discoverable by looking at the repo for a minute, and asking the user to go and edit a +config file by hand is handing back homework that the agent can finish in one pass. + +## The procedure + +For each blank key: find the evidence, form the command, run it, write it down. + +### 1. Find the evidence + +| Key | Where the answer usually is | +| --- | --- | +| `dev_cmd` | An entrypoint (`app.py`, `main.py`, `manage.py`, `server.js`, `cmd/*/main.go`); a `Procfile`; a `docker-compose.yml` service command; a `Dockerfile` `CMD`; the README's "run it" section | +| `test_cmd` | `test_*.py` / `*_test.py` / `tests/`, `*.test.js`, `*_test.go`; a test runner in `requirements*.txt`; the test step of a CI workflow under `.github/workflows/` | +| `lint_cmd` | A linter config (`.flake8`, `.eslintrc*`, `ruff.toml`, `.golangci.yml`); a linter in the dependency list; the CI lint step | +| `typecheck_cmd` | `mypy.ini`, `tsconfig.json`, a type checker in the dependency list | +| `build_cmd` | A build config (`setup.py`, `Dockerfile`, `vite.config.*`), or nothing — plenty of projects have no build | +| `format_cmd` | A formatter in the dependency list or its config (`.prettierrc`, `[tool.black]`) | + +CI workflows are the strongest evidence of all: they are commands the project already runs +in a clean checkout, and they are usually correct. + +### 2. Prefer the project's own environment + +A repo with a virtualenv wants `.venv/bin/pytest`, not a bare `pytest` resolved against +whatever happens to be on `PATH`. Same for `node_modules/.bin/`. Write the command the way +this project runs it, and keep the path relative to the project root. + +### 3. Verify before writing + +A command that was never run is a guess. Run each one and check it does what the key claims: + +- `test_cmd`, `lint_cmd`, `typecheck_cmd`, `format_cmd` — run it. Real findings (failing + tests, lint errors) mean the command works; that is a pass. Only "command not found", + "no such file", "unknown option", or "no tests ran" mean the command is wrong. +- `build_cmd` — run it if it is cheap. If it is slow or writes artifacts, verify the tool + resolves (` --version`) and say in the report that the full build was not run. +- `dev_cmd` — never leave a server running. Check the entrypoint exists and the interpreter + resolves (`.venv/bin/python --version`, `node --version`), or start it and stop it once it + is listening. Record the port in `dev_port` while you are there. + +Fix and re-run what fails. Do not write a command that did not survive this step. + +### 4. Write it into `.flow/config.md` + +Edit the `- : ` lines in place. Do not rewrite the rest of the file — the +values around them are the user's. + +Leave a key blank only when the project genuinely has nothing to run for it (no build step, +no formatter). A blank means "skip that step", so it must be a decision, not a shrug. Say +which keys you left blank and why. + +### 5. Report + +One block, in the init/health report: + +``` +Inferred and verified: + test_cmd: .venv/bin/pytest (23 passed) + dev_cmd: .venv/bin/python app.py (starts, listens on 5000) +Left blank: build_cmd (no build step), format_cmd (no formatter in requirements.txt) +``` + +Ask the user only when the evidence genuinely conflicts — two test runners, two entrypoints, +and nothing in the repo picks between them. "I could not be bothered to look" is not a +conflict. diff --git a/plugins/flow/scripts/init.js b/plugins/flow/scripts/init.js index 21a08ae..9463ea1 100644 --- a/plugins/flow/scripts/init.js +++ b/plugins/flow/scripts/init.js @@ -21,7 +21,9 @@ * template's own default (workflow_mode/pm_backend) or come back blank * (pm_github_owner/pm_github_repo/pm_linear_team) — never a `{PLACEHOLDER}`. * - * Each step prints "created" or "already exists, skipping". + * Each step prints "created" or "already exists, skipping". The run ends by naming + * every `*_cmd` still blank — a to-do for the agent driving init, which infers those + * from the repo and verifies them, never a to-do handed back to the user. */ 'use strict'; @@ -140,6 +142,23 @@ function stripTemplateOnly(text) { const STACK_CMD_KEYS = ['dev_cmd', 'lint_cmd', 'typecheck_cmd', 'build_cmd', 'test_cmd', 'format_cmd']; +/** + * Makefile target -> config key. A Makefile is often the only "manifest" a + * script-style repo has, and its target names are explicit evidence of how the + * project is run. `fmt` is the common short spelling of `format`; the longer + * name is listed first so it wins when both targets exist. + */ +const MAKE_TARGET_TO_KEY = { + dev: 'dev_cmd', + lint: 'lint_cmd', + typecheck: 'typecheck_cmd', + 'type-check': 'typecheck_cmd', + build: 'build_cmd', + test: 'test_cmd', + format: 'format_cmd', + fmt: 'format_cmd', +}; + /** * Package-manager prefix for `npm run