feature-flow-cli 0.1.2__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {feature_flow_cli-0.1.2/feature_flow_cli.egg-info → feature_flow_cli-0.1.4}/PKG-INFO +2 -1
  2. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/README.md +1 -0
  3. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/adapters/codex/feature-flow/SKILL.md +3 -1
  4. feature_flow_cli-0.1.4/agents/feature-planner.md +26 -0
  5. feature_flow_cli-0.1.4/agents/plan-reviewer.md +20 -0
  6. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow/__init__.py +1 -1
  7. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow/cli.py +10 -3
  8. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow/command.py +14 -2
  9. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow/conductor.py +40 -1
  10. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow/git.py +15 -2
  11. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow/status.py +2 -1
  12. feature_flow_cli-0.1.4/feature_flow/suggest.py +23 -0
  13. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4/feature_flow_cli.egg-info}/PKG-INFO +2 -1
  14. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow_cli.egg-info/SOURCES.txt +1 -0
  15. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/guides/build.md +1 -1
  16. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/guides/plan.md +1 -1
  17. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/skills/feature-flow/SKILL.md +3 -1
  18. feature_flow_cli-0.1.2/agents/feature-planner.md +0 -33
  19. feature_flow_cli-0.1.2/agents/plan-reviewer.md +0 -16
  20. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/LICENSE +0 -0
  21. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/MANIFEST.in +0 -0
  22. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/adapters/codex/feature-flow/agents/openai.yaml +0 -0
  23. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/agents/ticket-builder.md +0 -0
  24. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/agents/ticket-reviewer.md +0 -0
  25. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow/__main__.py +0 -0
  26. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow/checks.py +0 -0
  27. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow/floorguard.py +0 -0
  28. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow/gate.py +0 -0
  29. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow/install.py +0 -0
  30. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow/prompts.py +0 -0
  31. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow/state.py +0 -0
  32. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow/tickets.py +0 -0
  33. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow/view.py +0 -0
  34. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow_cli.egg-info/dependency_links.txt +0 -0
  35. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow_cli.egg-info/entry_points.txt +0 -0
  36. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/feature_flow_cli.egg-info/top_level.txt +0 -0
  37. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/guides/brief.md +0 -0
  38. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/guides/plan-review.md +0 -0
  39. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/guides/review.md +0 -0
  40. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/guides/show.md +0 -0
  41. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/guides/templates/commands.md +0 -0
  42. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/guides/templates/learnings.md +0 -0
  43. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/guides/templates/map.md +0 -0
  44. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/guides/templates/spec.md +0 -0
  45. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/guides/templates/ticket.md +0 -0
  46. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/guides/templates/ui-mockup.md +0 -0
  47. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/install.py +0 -0
  48. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/install.sh +0 -0
  49. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/pyproject.toml +0 -0
  50. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/scripts/floor-guard.py +0 -0
  51. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/scripts/flow-status.py +0 -0
  52. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/scripts/flow-view.html +0 -0
  53. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/scripts/flow-view.py +0 -0
  54. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/scripts/flow.py +0 -0
  55. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/scripts/gate.py +0 -0
  56. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/setup.cfg +0 -0
  57. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/skills/architect-review/SKILL.md +0 -0
  58. {feature_flow_cli-0.1.2 → feature_flow_cli-0.1.4}/skills/automation-design/SKILL.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: feature-flow-cli
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: A ticket-graph workflow for coding agents: installs the feature-flow skill into a repo
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/hamilton-sky/feature-flow
@@ -206,6 +206,7 @@ A session's context fills up, so the flow moves to a fresh session every few tic
206
206
 
207
207
  - **One owner at a time.** `start` gives the session a token, and every later call must carry it, so two sessions can never drive the same feature. `HANDOFF` and `DONE` release it.
208
208
  - **Resuming after a closed session.** If a session ended without `HANDOFF` (you closed it, it crashed, it ran out of budget), the feature is still owned by it, and a new session stops and says so. Once you are sure the old session is gone, answer yes when the new session asks, or start it with `FLOW_TAKEOVER=1`. With `auto` the skill never takes over on its own.
209
+ - **Stuck on a ticket.** `python3 scripts/flow.py <feature> reset` (or `feature-flow reset <feature>`) reopens a ticket left `claimed` or `in-progress`, commits that, and clears the conductor's run state, so the next `/feature-flow <feature>` starts clean from the ticket files. `reset <NN>` reopens that one ticket, even a resolved one, to build it again. While a session owns the feature it needs `FLOW_TAKEOVER=1`. A mistyped command or feature name gets a `did you mean` hint.
209
210
  - **Relay mode.** `FLOW_RELAY=1` hands off after every build and every review, so each phase gets a session of its own.
210
211
  - **Nobody needs to watch.** With `auto` the skill asks nothing, and a Claude Code cloud session keeps working while you are away. You only come back to type the `HANDOFF` line.
211
212
 
@@ -187,6 +187,7 @@ A session's context fills up, so the flow moves to a fresh session every few tic
187
187
 
188
188
  - **One owner at a time.** `start` gives the session a token, and every later call must carry it, so two sessions can never drive the same feature. `HANDOFF` and `DONE` release it.
189
189
  - **Resuming after a closed session.** If a session ended without `HANDOFF` (you closed it, it crashed, it ran out of budget), the feature is still owned by it, and a new session stops and says so. Once you are sure the old session is gone, answer yes when the new session asks, or start it with `FLOW_TAKEOVER=1`. With `auto` the skill never takes over on its own.
190
+ - **Stuck on a ticket.** `python3 scripts/flow.py <feature> reset` (or `feature-flow reset <feature>`) reopens a ticket left `claimed` or `in-progress`, commits that, and clears the conductor's run state, so the next `/feature-flow <feature>` starts clean from the ticket files. `reset <NN>` reopens that one ticket, even a resolved one, to build it again. While a session owns the feature it needs `FLOW_TAKEOVER=1`. A mistyped command or feature name gets a `did you mean` hint.
190
191
  - **Relay mode.** `FLOW_RELAY=1` hands off after every build and every review, so each phase gets a session of its own.
191
192
  - **Nobody needs to watch.** With `auto` the skill asks nothing, and a Claude Code cloud session keeps working while you are away. You only come back to type the `HANDOFF` line.
192
193
 
@@ -31,7 +31,7 @@ To add tickets to a plan that exists, edit it by hand following the ticket rules
31
31
 
32
32
  With a plan present, check these before `start`, and stop at the first that fails:
33
33
 
34
- - `git status --porcelain` is empty, apart from untracked files listed in `.feature-flow/installed.txt`: those are feature-flow's own install, so never stop for them. Suggest committing them (`git add --pathspec-from-file=.feature-flow/installed.txt && git commit -m "chore: install feature-flow"`) and carry on.
34
+ - `git status --porcelain` is empty, apart from untracked Python bytecode (`__pycache__/`, `*.pyc`) and untracked files listed in `.feature-flow/installed.txt`: those are feature-flow's own install, so never stop for them. Suggest committing them (`git add --pathspec-from-file=.feature-flow/installed.txt && git commit -m "chore: install feature-flow"`) and carry on.
35
35
  - `python3 scripts/flow-status.py <feature> --check` prints `OK`.
36
36
  - `.agents/flow-roles/ticket-builder.md` and `.agents/flow-roles/ticket-reviewer.md` exist. If not, say to run `uvx feature-flow-cli install . --agent codex` in this repo (or `python3 install.py <repo> --agent codex` from a feature-flow clone).
37
37
 
@@ -39,6 +39,8 @@ Then run `FLOW_INVOKE='$feature-flow' python3 scripts/flow.py <feature> start`.
39
39
 
40
40
  If `start` prints `STOP` naming another owner, another session may still be working this feature. Ask the user whether that session is closed. Only on a clear yes, run `start` once more with `FLOW_TAKEOVER=1`. In `auto` mode, never take over: report and stop.
41
41
 
42
+ If the user says the flow is stuck on a ticket or asks for a reset, run `reset` (or `reset <NN>` to redo one ticket): it reopens a half-built ticket, commits that, and clears the run, then run `start` again. It needs `FLOW_TAKEOVER=1` while a session owns the feature, on the same clear yes. Never reset on your own.
43
+
42
44
  Say what will happen: for each ticket, a builder subagent and then a reviewer subagent. After every few tickets (`FLOW_TICKETS_PER_SESSION`, default 4) you hand off to a new session. Ask for a yes, unless `auto` was given.
43
45
 
44
46
  ## The loop
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: feature-planner
3
+ description: Researches and drafts one feature plan by following the planning guide in its prompt. Use it in a fresh session so the approved brief is the whole ask.
4
+ tools:
5
+ - Read
6
+ - Glob
7
+ - Grep
8
+ - Bash
9
+ - Write
10
+ - WebSearch
11
+ - WebFetch
12
+ model: inherit
13
+ ---
14
+
15
+ You are the planner. The approved brief is the whole ask; you do not have the user's earlier conversation.
16
+
17
+ Follow the planning guide in your prompt exactly. Read the repository before planning, then write the draft plan only where the guide tells you.
18
+
19
+ Rules that never bend:
20
+
21
+ - Research outside the repository only where the code cannot answer, and cite every outside fact in the plan. If web search is unavailable, plan from the codebase and say so in your reply.
22
+ - At every real fork, give two concrete options. When the choice belongs to the user, make it a `settle` ticket rather than choosing for them.
23
+ - Do the lazy pass: remove work that does not need to exist and prefer what the repository, standard library, platform or installed dependencies already provide.
24
+ - Prove the finished draft with `FLOW_DIR=.feature-flow/state/draft python3 scripts/flow-status.py <feature> --check`; do not call it ready from inspection alone.
25
+ - Write only in the draft folder. Never commit, never modify the repository outside that folder, and never talk to the user; return questions through the protocol in the guide.
26
+ - End your reply with exactly `PLAN: READY` or `PLAN: QUESTIONS`.
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: plan-reviewer
3
+ description: Independently reviews one draft feature plan against its approved brief by following the review guide in its prompt. Read only and fresh to the planner's reasoning.
4
+ tools:
5
+ - Read
6
+ - Glob
7
+ - Grep
8
+ - Bash
9
+ model: inherit
10
+ ---
11
+
12
+ You are the plan reviewer, not the planner. You did not see the planner's reasoning and you do not trust the planner's account of the draft.
13
+
14
+ Follow the plan review guide in your prompt exactly. Read only the approved brief, the draft and the repository evidence needed to check that the draft covers the brief.
15
+
16
+ Rules that never bend:
17
+
18
+ - Check the draft against the approved brief, including its goal, scope, bar, uncertainties and exclusions.
19
+ - You cannot edit, write, stage, commit, stash, check out or reset anything, and you must not try to get around that with shell commands.
20
+ - End with exactly `PLAN-REVIEW: PASS` or `PLAN-REVIEW: FAIL`.
@@ -1,3 +1,3 @@
1
1
  """Feature Flow: a ticket-graph workflow for coding agents."""
2
2
 
3
- __version__ = "0.1.2"
3
+ __version__ = "0.1.4"
@@ -3,18 +3,23 @@
3
3
  import sys
4
4
  from pathlib import Path
5
5
 
6
+ from feature_flow import suggest
6
7
  from feature_flow.conductor import Conductor, NoPhase, Stop
7
8
 
8
- USAGE = ("usage: python3 scripts/flow.py <feature> start | next | prompt | verdict <file>\n"
9
+ USAGE = ("usage: python3 scripts/flow.py <feature> start | next | prompt | verdict <file> | reset [NN]\n"
9
10
  " python3 scripts/flow.py <feature> plan-prompt <brief> [findings] | plan-review-prompt | plan-accept")
10
- COMMANDS = ("start", "next", "prompt", "verdict", "plan-prompt", "plan-review-prompt", "plan-accept")
11
- ARGS = {"verdict": (3,), "plan-prompt": (3, 4)}
11
+ COMMANDS = ("start", "next", "prompt", "verdict", "plan-prompt", "plan-review-prompt", "plan-accept", "reset")
12
+ ARGS = {"verdict": (3,), "plan-prompt": (3, 4), "reset": (2, 3)}
12
13
 
13
14
 
14
15
  def main(argv=None, scripts=None):
15
16
  args = list(sys.argv[1:] if argv is None else argv)
16
17
  if len(args) < 2 or args[1] not in COMMANDS or not args[0] or args[0].startswith("-"):
17
18
  print(USAGE, file=sys.stderr)
19
+ if len(args) >= 2 and args[0] in COMMANDS:
20
+ sys.stderr.write("did you mean: flow.py %s %s?\n" % (args[1], args[0]))
21
+ elif len(args) >= 2:
22
+ sys.stderr.write(suggest.hint(args[1], COMMANDS))
18
23
  return 2
19
24
  feature, command = args[0], args[1]
20
25
  if feature in (".", "..") or any(c in feature for c in "/\\:"):
@@ -37,6 +42,8 @@ def main(argv=None, scripts=None):
37
42
  line = conductor.plan_accept()
38
43
  elif command == "prompt":
39
44
  line = conductor.prompt()
45
+ elif command == "reset":
46
+ line = conductor.reset(args[2] if len(args) == 3 else None)
40
47
  elif command == "start":
41
48
  line = conductor.start()
42
49
  else:
@@ -7,18 +7,25 @@ python3 scripts/flow.py, with the copy the installer put there.
7
7
  import os
8
8
  import sys
9
9
 
10
- from feature_flow import __version__, install, status, view
10
+ from pathlib import Path
11
+
12
+ from feature_flow import __version__, cli, install, status, suggest, view
11
13
 
12
14
  USAGE = """\
13
15
  usage: feature-flow install [target-repo] [--agent claude|codex|all] [--user] [--force] [--dry-run]
14
16
  feature-flow status <feature> [--next | --counts | --check | --mermaid [plain] | --json]
15
17
  feature-flow view <feature> [--watch] [--no-open] [--out FILE]
18
+ feature-flow reset <feature> [NN]
16
19
  feature-flow --version
17
20
  install copies the skill, its roles, scripts and guides into a repo; feature-flow install --help says more.
18
21
  status and view read plans/<feature>/ in the current directory, like scripts/flow-status.py and scripts/flow-view.py.
22
+ reset reopens a half-built ticket (or ticket NN), commits that, and clears the run, like scripts/flow.py <feature> reset.
19
23
  """
20
24
 
21
25
 
26
+ COMMANDS = ("install", "status", "view", "reset", "--version", "--help")
27
+
28
+
22
29
  def main(argv=None):
23
30
  args = list(sys.argv[1:] if argv is None else argv)
24
31
  if not args or args[0] in ("-h", "--help", "help"):
@@ -35,5 +42,10 @@ def main(argv=None):
35
42
  return status.main(rest)
36
43
  if command == "view":
37
44
  return view.main(rest, here=os.path.join(install.source_root(), "scripts"))
38
- sys.stderr.write("unknown command: %s\n%s" % (command, USAGE))
45
+ if command == "reset":
46
+ if not rest or len(rest) > 2:
47
+ sys.stderr.write(USAGE)
48
+ return 2
49
+ return cli.main(rest[:1] + ["reset"] + rest[1:], scripts=Path.cwd() / "scripts")
50
+ sys.stderr.write("unknown command: %s\n%s%s" % (command, suggest.hint(command, COMMANDS), USAGE))
39
51
  return 2
@@ -10,7 +10,7 @@ import secrets
10
10
  import shutil
11
11
  from pathlib import Path
12
12
 
13
- from feature_flow import checks, git, prompts, state, tickets
13
+ from feature_flow import checks, git, prompts, state, suggest, tickets
14
14
 
15
15
 
16
16
  VERDICT = re.compile(r"^REVIEW: (PASS|FAIL)[ \t\r\f\v]*$")
@@ -254,6 +254,45 @@ class Conductor:
254
254
  return self.judge_review()
255
255
  return self.pick_ticket()
256
256
 
257
+ def reset(self, num=None):
258
+ """Put the feature back to a clean start: reopen a half-built ticket (or ticket num), commit that,
259
+ and forget the run. The tickets stay the truth; only the conductor's note and counters go."""
260
+ if not self.plan.is_dir():
261
+ hint = suggest.hint(self.feature, suggest.features(self.root)).strip()
262
+ raise Stop("no plan folder %s%s" % (self.plan, ". " + hint if hint else ""))
263
+ owner = self.get("owner")
264
+ if owner and os.environ.get("FLOW_TAKEOVER", "0") != "1":
265
+ raise Stop("%s is owned by session %s. if that session is closed or dead, run reset with FLOW_TAKEOVER=1"
266
+ % (self.feature, owner))
267
+ paths = tickets.ticket_files(str(self.plan / (os.environ.get("FLOW_TICKETS") or "tasks")))
268
+ if num is not None:
269
+ chosen = [p for p in paths if tickets.number(p) == num.zfill(2)]
270
+ if not chosen:
271
+ raise Stop("no ticket %s in %s" % (num, self.plan))
272
+ else:
273
+ chosen = [p for p in paths if tickets.status(p) in tickets.RESET]
274
+ reopened = []
275
+ for path in chosen:
276
+ if tickets.status(path) != "open":
277
+ tickets.set_open(path)
278
+ reopened.append(path)
279
+ if reopened:
280
+ nums = ", ".join(tickets.number(p) for p in reopened)
281
+ if not git.commit_paths(reopened, "chore(%s): reset %s to open" % (self.feature, nums)):
282
+ raise Stop("reopened %s but could not commit it. this session must be allowed to run git commit"
283
+ % nums)
284
+ had_run = self.state_file.is_file()
285
+ for path in (self.state_file, self.findings_file):
286
+ if path.is_file():
287
+ path.unlink()
288
+ if not reopened and not had_run:
289
+ return "OK nothing to reset for %s. run %s %s" % (self.feature, self.invoke, self.feature)
290
+ self.st = {}
291
+ self.log("RESET")
292
+ done = ["cleared the run state"] if had_run else []
293
+ done += ["reopened %s" % tickets.name(p) for p in reopened]
294
+ return "OK %s. run %s %s" % (", ".join(done), self.invoke, self.feature)
295
+
257
296
  def judge_review(self):
258
297
  if not git.tracked_clean() or git.head() != self.get("review_sha"):
259
298
  raise Stop("the reviewer changed tracked files, which a reviewer must never do")
@@ -32,7 +32,8 @@ def head():
32
32
 
33
33
  def changes():
34
34
  """Every changed path, untracked files included, as `git status --porcelain` sees them, except
35
- the untracked files feature-flow's installer wrote: an install not committed yet is not a change."""
35
+ the untracked files feature-flow's installer wrote (an install not committed yet is not a change)
36
+ and untracked Python bytecode, which the flow's own Python runs leave behind."""
36
37
  installed = set()
37
38
  top = toplevel()
38
39
  if top is not None:
@@ -51,12 +52,16 @@ def changes():
51
52
  code, path = entry[:2], entry[3:]
52
53
  if code[0] in "RC":
53
54
  i += 1 # the rename's source path follows
54
- if code == "??" and path in installed:
55
+ if code == "??" and (path in installed or _bytecode(path)):
55
56
  continue
56
57
  found.append(path)
57
58
  return found
58
59
 
59
60
 
61
+ def _bytecode(path):
62
+ return path.endswith(".pyc") or "__pycache__/" in "/" + path
63
+
64
+
60
65
  def is_clean():
61
66
  return not changes()
62
67
 
@@ -67,6 +72,14 @@ def tracked_clean():
67
72
  and _git("diff", "--cached", "--quiet", check=False).returncode == 0)
68
73
 
69
74
 
75
+ def commit_paths(paths, message):
76
+ """Commit these paths only, whatever else is staged."""
77
+ paths = [str(p) for p in paths]
78
+ if _git("add", "--", *paths, check=False).returncode != 0:
79
+ return False
80
+ return _git("commit", "-q", "-m", message, "--", *paths, check=False).returncode == 0
81
+
82
+
70
83
  def commit_file(path, message):
71
84
  if _git("add", "--", str(path), check=False).returncode != 0:
72
85
  return False
@@ -8,7 +8,7 @@ import os
8
8
  import re
9
9
  import sys
10
10
 
11
- from feature_flow import tickets
11
+ from feature_flow import suggest, tickets
12
12
 
13
13
  USAGE = "usage: python3 scripts/flow-status.py <feature> [--next | --counts | --check | --mermaid [plain] | --json]"
14
14
 
@@ -48,6 +48,7 @@ def run(argv, out, err):
48
48
  folder = "%s/%s/%s" % (root, feature, _env("FLOW_TICKETS", "tasks"))
49
49
  if not os.path.isdir(folder):
50
50
  err.write("no ticket folder: %s\n" % folder)
51
+ err.write(suggest.hint(feature, suggest.features(root)))
51
52
  return 2
52
53
  paths = tickets.ticket_files(folder)
53
54
  if not paths:
@@ -0,0 +1,23 @@
1
+ """Did-you-mean hints for a mistyped command or feature name."""
2
+
3
+ import difflib
4
+ import os
5
+
6
+
7
+ def closest(word, choices):
8
+ """The choice nearest to word, or "" when none is close."""
9
+ found = difflib.get_close_matches(word, list(choices), n=1, cutoff=0.6)
10
+ return found[0] if found else ""
11
+
12
+
13
+ def features(root):
14
+ """The plan folders under root (plans/ or FLOW_DIR)."""
15
+ try:
16
+ return sorted(name for name in os.listdir(str(root)) if os.path.isdir(os.path.join(str(root), name)))
17
+ except OSError:
18
+ return []
19
+
20
+
21
+ def hint(word, choices, template="did you mean: %s?"):
22
+ match = closest(word, choices)
23
+ return (template % match + "\n") if match else ""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: feature-flow-cli
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: A ticket-graph workflow for coding agents: installs the feature-flow skill into a repo
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/hamilton-sky/feature-flow
@@ -206,6 +206,7 @@ A session's context fills up, so the flow moves to a fresh session every few tic
206
206
 
207
207
  - **One owner at a time.** `start` gives the session a token, and every later call must carry it, so two sessions can never drive the same feature. `HANDOFF` and `DONE` release it.
208
208
  - **Resuming after a closed session.** If a session ended without `HANDOFF` (you closed it, it crashed, it ran out of budget), the feature is still owned by it, and a new session stops and says so. Once you are sure the old session is gone, answer yes when the new session asks, or start it with `FLOW_TAKEOVER=1`. With `auto` the skill never takes over on its own.
209
+ - **Stuck on a ticket.** `python3 scripts/flow.py <feature> reset` (or `feature-flow reset <feature>`) reopens a ticket left `claimed` or `in-progress`, commits that, and clears the conductor's run state, so the next `/feature-flow <feature>` starts clean from the ticket files. `reset <NN>` reopens that one ticket, even a resolved one, to build it again. While a session owns the feature it needs `FLOW_TAKEOVER=1`. A mistyped command or feature name gets a `did you mean` hint.
209
210
  - **Relay mode.** `FLOW_RELAY=1` hands off after every build and every review, so each phase gets a session of its own.
210
211
  - **Nobody needs to watch.** With `auto` the skill asks nothing, and a Claude Code cloud session keeps working while you are away. You only come back to type the `HANDOFF` line.
211
212
 
@@ -23,6 +23,7 @@ feature_flow/install.py
23
23
  feature_flow/prompts.py
24
24
  feature_flow/state.py
25
25
  feature_flow/status.py
26
+ feature_flow/suggest.py
26
27
  feature_flow/tickets.py
27
28
  feature_flow/view.py
28
29
  feature_flow_cli.egg-info/PKG-INFO
@@ -12,7 +12,7 @@ Follow the repo's documented ticket location if it has one. Otherwise they are i
12
12
 
13
13
  ## Step 2: Pre-flight
14
14
 
15
- Run `git status --porcelain`. Untracked files listed in `.feature-flow/installed.txt` are feature-flow's own install and do not count.
15
+ Run `git status --porcelain`. Untracked files listed in `.feature-flow/installed.txt` are feature-flow's own install and do not count, and neither does untracked Python bytecode (`__pycache__/`, `*.pyc`).
16
16
 
17
17
  - Clean: continue.
18
18
  - Dirty, manual: show the files and offer to commit, stash, or continue anyway.
@@ -19,7 +19,7 @@ Write everything inside `<draft>`, laid out as the plan will be:
19
19
  └── 02-slug.md
20
20
  ```
21
21
 
22
- Write and edit only inside `<draft>` (on a findings round, fix the draft there), never change a file outside it, never commit. The conductor copies the draft into the repo after the user approves it. If the repo documents another place for plans (CLAUDE.md, AGENTS.md, `docs/`), still write the draft here, and name that place and the two variables the scripts need (`FLOW_DIR`, `FLOW_TICKETS`) in your reply.
22
+ Write only inside `<draft>` (on a findings round, rewrite the draft files there), never change a file outside it, never commit. The conductor copies the draft into the repo after the user approves it. If the repo documents another place for plans (CLAUDE.md, AGENTS.md, `docs/`), still write the draft here, and name that place and the two variables the scripts need (`FLOW_DIR`, `FLOW_TICKETS`) in your reply.
23
23
 
24
24
  ## Step 1: Read the brief
25
25
 
@@ -31,7 +31,7 @@ To add tickets to a plan that exists, edit it by hand following the ticket rules
31
31
 
32
32
  With a plan present, check these before `start`, and stop at the first that fails:
33
33
 
34
- - `git status --porcelain` is empty, apart from untracked files listed in `.feature-flow/installed.txt`: those are feature-flow's own install, so never stop for them. Suggest committing them (`git add --pathspec-from-file=.feature-flow/installed.txt && git commit -m "chore: install feature-flow"`) and carry on.
34
+ - `git status --porcelain` is empty, apart from untracked Python bytecode (`__pycache__/`, `*.pyc`) and untracked files listed in `.feature-flow/installed.txt`: those are feature-flow's own install, so never stop for them. Suggest committing them (`git add --pathspec-from-file=.feature-flow/installed.txt && git commit -m "chore: install feature-flow"`) and carry on.
35
35
  - `python3 scripts/flow-status.py <feature> --check` prints `OK`.
36
36
  - `ticket-builder.md` and `ticket-reviewer.md` are in `.claude/agents/`, `~/.claude/agents/`, or `$CLAUDE_HOME/agents/` when `CLAUDE_HOME` is set. If not, say to run `uvx feature-flow-cli install .` in this repo (or `python3 install.py <repo>` from a feature-flow clone).
37
37
 
@@ -39,6 +39,8 @@ Then run `FLOW_INVOKE=/feature-flow python3 scripts/flow.py <feature> start`. It
39
39
 
40
40
  If `start` prints `STOP` naming another owner, another session may still be working this feature. Ask the user whether that session is closed. Only on a clear yes, run `start` once more with `FLOW_TAKEOVER=1`. In `auto` mode, never take over: report and stop.
41
41
 
42
+ If the user says the flow is stuck on a ticket or asks for a reset, run `reset` (or `reset <NN>` to redo one ticket): it reopens a half-built ticket, commits that, and clears the run, then run `start` again. It needs `FLOW_TAKEOVER=1` while a session owns the feature, on the same clear yes. Never reset on your own.
43
+
42
44
  Say what will happen: for each ticket, a builder subagent and then a reviewer subagent. After every few tickets (`FLOW_TICKETS_PER_SESSION`, default 4) you hand off to a new session. Ask for a yes, unless `auto` was given.
43
45
 
44
46
  ## The loop
@@ -1,33 +0,0 @@
1
- ---
2
- name: feature-planner
3
- description: Plans one feature from an approved feature brief by following the plan guide in its prompt. Writes a draft plan into the flow's state folder and nothing else. Use it from /feature-flow, in a fresh session that never saw the conversation.
4
- tools:
5
- - Read
6
- - Glob
7
- - Grep
8
- - Bash
9
- - Write
10
- - Edit
11
- - WebSearch
12
- - WebFetch
13
- model: inherit
14
- ---
15
-
16
- You are the planner. You turn one feature brief into a draft plan, then stop.
17
-
18
- Follow the plan guide in your prompt exactly. It tells you what to read, how to research, how to cut the graph into tickets, where to write, and how to reply.
19
-
20
- How you think, in this order:
21
-
22
- - The brief is the whole ask. You never saw the conversation behind it. Plan what the brief says; a gap goes under Open questions or into a `settle` ticket, never into a silent guess.
23
- - Read before you plan: the project's conventions, similar features, the files that will change, and the real build, test and lint commands from the project's own config. Never guess a command.
24
- - Look outside the repo only where the code cannot answer: a library's documentation for the version the lockfile pins, an outside API the brief names. Every outside fact gets its source in `map.md`. If you cannot reach the web, say so in your reply and plan from the codebase.
25
- - At every real fork, write two options in a sentence each, pick one and say why. If the choice belongs to the user, make it a `settle` ticket instead of picking.
26
- - Be lazy: drop every ticket the bar does not need, and list what you dropped.
27
- - Prove the draft is well formed with the check the guide names, and fix every problem.
28
-
29
- Rules that never bend:
30
-
31
- - Write and edit only inside the draft folder your prompt names. Never change a file outside it, never write under `plans/`, never commit.
32
- - You cannot talk to the user. Anything only the user can decide comes back in your reply as an open question.
33
- - End your reply with exactly `PLAN: READY` or `PLAN: QUESTIONS`.
@@ -1,16 +0,0 @@
1
- ---
2
- name: plan-reviewer
3
- description: Independently checks a draft plan against the approved feature brief by following the plan review guide in its prompt. Read only. Use it in a fresh session so it never saw the planner's reasoning.
4
- tools:
5
- - Read
6
- - Glob
7
- - Grep
8
- - Bash
9
- model: inherit
10
- ---
11
-
12
- You are the plan reviewer, not the planner. You did not write this plan and you do not trust the planner's account of it.
13
-
14
- Follow the plan review guide in your prompt exactly. Read the brief and the draft, run the commands the draft relies on, and end with the single line `PLAN-REVIEW: PASS` or `PLAN-REVIEW: FAIL`.
15
-
16
- You cannot edit, write, stage or commit anything, and you must not try to get around that with shell commands. You may run read only commands, the plan check, and the commands the draft's `commands.md` lists.