feature-flow-cli 0.1.0__py3-none-any.whl
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.
- feature_flow/__init__.py +3 -0
- feature_flow/__main__.py +7 -0
- feature_flow/_bundle/adapters/codex/feature-flow/SKILL.md +62 -0
- feature_flow/_bundle/adapters/codex/feature-flow/agents/openai.yaml +6 -0
- feature_flow/_bundle/agents/ticket-builder.md +24 -0
- feature_flow/_bundle/agents/ticket-reviewer.md +16 -0
- feature_flow/_bundle/guides/build.md +139 -0
- feature_flow/_bundle/guides/plan.md +93 -0
- feature_flow/_bundle/guides/review.md +87 -0
- feature_flow/_bundle/guides/show.md +30 -0
- feature_flow/_bundle/guides/templates/commands.md +15 -0
- feature_flow/_bundle/guides/templates/learnings.md +7 -0
- feature_flow/_bundle/guides/templates/map.md +26 -0
- feature_flow/_bundle/guides/templates/spec.md +62 -0
- feature_flow/_bundle/guides/templates/ticket.md +29 -0
- feature_flow/_bundle/guides/templates/ui-mockup.md +43 -0
- feature_flow/_bundle/scripts/floor-guard.py +14 -0
- feature_flow/_bundle/scripts/flow-status.py +14 -0
- feature_flow/_bundle/scripts/flow-view.html +536 -0
- feature_flow/_bundle/scripts/flow-view.py +14 -0
- feature_flow/_bundle/scripts/flow.py +14 -0
- feature_flow/_bundle/scripts/gate.py +14 -0
- feature_flow/_bundle/skills/architect-review/SKILL.md +143 -0
- feature_flow/_bundle/skills/automation-design/SKILL.md +274 -0
- feature_flow/_bundle/skills/feature-flow/SKILL.md +62 -0
- feature_flow/checks.py +71 -0
- feature_flow/cli.py +47 -0
- feature_flow/command.py +39 -0
- feature_flow/conductor.py +299 -0
- feature_flow/floorguard.py +340 -0
- feature_flow/gate.py +100 -0
- feature_flow/git.py +47 -0
- feature_flow/install.py +365 -0
- feature_flow/prompts.py +52 -0
- feature_flow/state.py +75 -0
- feature_flow/status.py +231 -0
- feature_flow/tickets.py +168 -0
- feature_flow/view.py +312 -0
- feature_flow_cli-0.1.0.dist-info/METADATA +293 -0
- feature_flow_cli-0.1.0.dist-info/RECORD +44 -0
- feature_flow_cli-0.1.0.dist-info/WHEEL +5 -0
- feature_flow_cli-0.1.0.dist-info/entry_points.txt +3 -0
- feature_flow_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- feature_flow_cli-0.1.0.dist-info/top_level.txt +1 -0
feature_flow/view.py
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""The graph page writer, ported from the bash flow-view script with the same output and exit codes.
|
|
2
|
+
|
|
3
|
+
usage: python3 scripts/flow-view.py <feature> [--watch] [--no-open] [--out FILE]
|
|
4
|
+
writes one self contained HTML page that shows the ticket graph, animated, and opens it.
|
|
5
|
+
the page can replay the run from git history.
|
|
6
|
+
--watch rewrites the page every few seconds until the feature is complete.
|
|
7
|
+
env: FLOW_DIR, FLOW_TICKETS (ticket location), FLOW_NO_OPEN=1 (never open a browser),
|
|
8
|
+
FLOW_WATCH_SECONDS (default 3)
|
|
9
|
+
|
|
10
|
+
The page is scripts/flow-view.html (next to the script that was run) with the plan's data put in
|
|
11
|
+
place of __FLOW_DATA__. The template and the data are handled as bytes, so the page matches the
|
|
12
|
+
bash version byte for byte. Where the awks the bash version ran under disagree, this port reads
|
|
13
|
+
the ticket text as characters (gawk in a UTF-8 locale: a cut never splits a character) and treats
|
|
14
|
+
a line of spaces, tabs, carriage returns, vertical tabs and form feeds as blank (mawk).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import shutil
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
import tempfile
|
|
23
|
+
import time
|
|
24
|
+
|
|
25
|
+
from feature_flow import status, tickets
|
|
26
|
+
|
|
27
|
+
USAGE = "usage: python3 scripts/flow-view.py <feature> [--watch] [--no-open] [--out FILE]"
|
|
28
|
+
REFRESH = b'<meta http-equiv="refresh" content="3">'
|
|
29
|
+
BLANK = " \t\n\r\v\f" # what splits fields in mawk; a line made only of these has NF == 0
|
|
30
|
+
_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
31
|
+
_ASCII_LOWER = str.maketrans(_UPPER, _UPPER.lower())
|
|
32
|
+
_SLEEP = re.compile(r"^([0-9]+\.?[0-9]*|\.[0-9]+)([smhd]?)$")
|
|
33
|
+
_UNITS = {"": 1, "s": 1, "m": 60, "h": 3600, "d": 86400}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Stop(Exception):
|
|
37
|
+
"""Ends the run with an exit code, the way set -e ends the bash script."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, code):
|
|
40
|
+
Exception.__init__(self, code)
|
|
41
|
+
self.code = code
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class _Text:
|
|
45
|
+
def __init__(self):
|
|
46
|
+
self.text = ""
|
|
47
|
+
|
|
48
|
+
def write(self, text):
|
|
49
|
+
self.text += text
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class _Null:
|
|
53
|
+
def write(self, text):
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _lower(s):
|
|
58
|
+
"""awk's tolower as mawk does it: ASCII letters only."""
|
|
59
|
+
return s.translate(_ASCII_LOWER)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def extras(path):
|
|
63
|
+
"""The awk ticket_extras program: Done when bullets, the Answer excerpt and the review rounds.
|
|
64
|
+
|
|
65
|
+
Returns the object without its closing brace, as the bash version prints it."""
|
|
66
|
+
sect = ""
|
|
67
|
+
rounds, done_when, ans = [], [], ""
|
|
68
|
+
for line in tickets.records(path):
|
|
69
|
+
if line.startswith("## "):
|
|
70
|
+
sect = _lower(line[3:])
|
|
71
|
+
if sect.startswith("review findings"):
|
|
72
|
+
n, src = 0, ""
|
|
73
|
+
m = re.search(r"round [0-9]+", line)
|
|
74
|
+
if m:
|
|
75
|
+
n = int(m.group(0)[6:])
|
|
76
|
+
m = re.search(r", [^)]*\)", line)
|
|
77
|
+
if m:
|
|
78
|
+
src = m.group(0)[2:-1]
|
|
79
|
+
rounds.append('{"n":%d,"source":%s}' % (n, status.jstr(src)))
|
|
80
|
+
continue
|
|
81
|
+
if sect == "done when" and line.startswith("- ") and len(done_when) < 8:
|
|
82
|
+
done_when.append(status.jstr(line[2:][:240]))
|
|
83
|
+
continue
|
|
84
|
+
if sect == "answer" and line.strip(BLANK) and not line.startswith("<") and len(ans) < 700:
|
|
85
|
+
ans = line if ans == "" else ans + "\001" + line
|
|
86
|
+
if "left empty until" in ans:
|
|
87
|
+
ans = ""
|
|
88
|
+
return '{"done_when":[%s],"answer":%s,"rounds":[%s]' % (",".join(done_when), status.jstr(ans[:700]),
|
|
89
|
+
",".join(rounds))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _git(args):
|
|
93
|
+
"""git's stdout as bytes, whatever its exit code; b"" when git cannot run. stderr is dropped."""
|
|
94
|
+
try:
|
|
95
|
+
return subprocess.run(["git"] + args, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout
|
|
96
|
+
except OSError:
|
|
97
|
+
return b""
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def show_status(blob):
|
|
101
|
+
"""The status word the bash history awk prints for a ticket as git stored it, or b""."""
|
|
102
|
+
for line in blob.split(b"\n")[:20]:
|
|
103
|
+
if line.startswith(b"Status:"):
|
|
104
|
+
s = re.sub(rb"^Status:[ \t]*", b"", line.rstrip(b"\r")).lower()
|
|
105
|
+
s = re.sub(rb"[ \t(].*$", b"", s, flags=re.S)
|
|
106
|
+
if s in (b"resolved", b"done", b"closed"):
|
|
107
|
+
return b"resolved"
|
|
108
|
+
if s in (b"parked", b"wontfix"):
|
|
109
|
+
return b"parked"
|
|
110
|
+
return b"open"
|
|
111
|
+
return b""
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def history(path, gitdir):
|
|
115
|
+
"""Every commit that touched the ticket, oldest first, with the status it had then."""
|
|
116
|
+
if not gitdir:
|
|
117
|
+
return "[]"
|
|
118
|
+
log = _git(["log", "--reverse", "--format=%H %at", "--", path])
|
|
119
|
+
lines = log.split(b"\n")
|
|
120
|
+
lines.pop() # read stops at the last newline: an unterminated last line is never read
|
|
121
|
+
out = []
|
|
122
|
+
for line in lines:
|
|
123
|
+
sha, _, at = line.strip(b" \t").partition(b" ")
|
|
124
|
+
at = at.strip(b" \t")
|
|
125
|
+
if not sha:
|
|
126
|
+
continue
|
|
127
|
+
st = show_status(_git(["show", (sha.decode("ascii", "surrogateescape") + ":./" + path)]))
|
|
128
|
+
if not st:
|
|
129
|
+
continue
|
|
130
|
+
out.append('{"t":%s,"status":"%s"}' % (at.decode("utf-8", "surrogateescape"), st.decode("ascii")))
|
|
131
|
+
return "[%s]" % ",".join(out)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def build_json(feature, folder, gitdir, err):
|
|
135
|
+
"""The page data, as text; raises Stop when the status reader fails (the bash set -e exit)."""
|
|
136
|
+
core_out = _Text()
|
|
137
|
+
rc = status.run([feature, "--json"], core_out, err)
|
|
138
|
+
if rc != 0:
|
|
139
|
+
raise Stop(rc)
|
|
140
|
+
core = core_out.text.rstrip("\n")
|
|
141
|
+
if core.endswith("}"):
|
|
142
|
+
core = core[:-1]
|
|
143
|
+
details = []
|
|
144
|
+
for path in tickets.ticket_files(folder):
|
|
145
|
+
name = path.rsplit("/", 1)[-1]
|
|
146
|
+
if name.endswith(".md") and name != ".md":
|
|
147
|
+
name = name[:-3]
|
|
148
|
+
label = name.split("-", 1)[0]
|
|
149
|
+
details.append('"%s":%s,"history":%s}' % (label, extras(path), history(path, gitdir)))
|
|
150
|
+
generated = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
151
|
+
return '%s,"generated":"%s","details":{%s}}' % (core, generated, ",".join(details))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def page(template, data, refresh):
|
|
155
|
+
"""The template with the refresh tag and the data put in, as the bash render awk writes it."""
|
|
156
|
+
records = template.split(b"\n")
|
|
157
|
+
if template.endswith(b"\n"):
|
|
158
|
+
records.pop()
|
|
159
|
+
elif not template:
|
|
160
|
+
records = []
|
|
161
|
+
data = data.replace(b"\n", b"") # getline reads the data line by line and prints them joined
|
|
162
|
+
out = []
|
|
163
|
+
for rec in records:
|
|
164
|
+
if b"<!--__REFRESH__-->" in rec:
|
|
165
|
+
rec = rec.replace(b"<!--__REFRESH__-->", refresh, 1)
|
|
166
|
+
p = rec.find(b"__FLOW_DATA__")
|
|
167
|
+
if p >= 0:
|
|
168
|
+
out.append(rec[:p] + data + rec[p + 13:] + b"\n")
|
|
169
|
+
else:
|
|
170
|
+
out.append(rec + b"\n")
|
|
171
|
+
return b"".join(out)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class View:
|
|
175
|
+
def __init__(self, feature, folder, template, out_path, gitdir, out, err):
|
|
176
|
+
self.feature, self.folder, self.template = feature, folder, template
|
|
177
|
+
self.out_path, self.gitdir, self.out, self.err = out_path, gitdir, out, err
|
|
178
|
+
|
|
179
|
+
def render(self, watch):
|
|
180
|
+
fd, tmp = tempfile.mkstemp() # like mktemp: on a failure below it is left behind, as in bash
|
|
181
|
+
os.close(fd)
|
|
182
|
+
data = build_json(self.feature, self.folder, self.gitdir, self.err)
|
|
183
|
+
data = data.encode("utf-8", "surrogateescape").replace(b"<", b"\\u003c")
|
|
184
|
+
with open(tmp, "wb") as handle:
|
|
185
|
+
handle.write(data)
|
|
186
|
+
parent = os.path.dirname(self.out_path.rstrip("/")) or "."
|
|
187
|
+
try:
|
|
188
|
+
os.makedirs(parent, exist_ok=True)
|
|
189
|
+
with open(tmp, "rb") as handle:
|
|
190
|
+
data = handle.read()
|
|
191
|
+
with open(self.template, "rb") as handle:
|
|
192
|
+
template = handle.read()
|
|
193
|
+
with open(self.out_path + ".tmp", "wb") as handle:
|
|
194
|
+
handle.write(page(template, data, REFRESH if watch else b""))
|
|
195
|
+
target = self.out_path
|
|
196
|
+
if os.path.isdir(target): # mv moves the file into a directory of that name
|
|
197
|
+
target = os.path.join(target, os.path.basename(self.out_path + ".tmp"))
|
|
198
|
+
os.replace(self.out_path + ".tmp", target)
|
|
199
|
+
except OSError as e:
|
|
200
|
+
self.err.write("flow-view: %s\n" % e)
|
|
201
|
+
raise Stop(1)
|
|
202
|
+
os.remove(tmp)
|
|
203
|
+
|
|
204
|
+
def open_page(self, opening):
|
|
205
|
+
if not opening or os.environ.get("FLOW_NO_OPEN", "") == "1":
|
|
206
|
+
return
|
|
207
|
+
sys.stdout.flush()
|
|
208
|
+
if shutil.which("open"):
|
|
209
|
+
rc = subprocess.call(["open", self.out_path])
|
|
210
|
+
if rc != 0:
|
|
211
|
+
raise Stop(rc)
|
|
212
|
+
elif shutil.which("xdg-open"):
|
|
213
|
+
subprocess.call(["xdg-open", self.out_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def pause(value, err):
|
|
217
|
+
"""sleep "$FLOW_WATCH_SECONDS": a number with an optional s, m, h or d suffix."""
|
|
218
|
+
m = _SLEEP.match(value)
|
|
219
|
+
if not m:
|
|
220
|
+
err.write("sleep: invalid time interval '%s'\nTry 'sleep --help' for more information.\n" % value)
|
|
221
|
+
raise Stop(1)
|
|
222
|
+
time.sleep(float(m.group(1)) * _UNITS[m.group(2)])
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _pwd():
|
|
226
|
+
"""The working directory as bash sees it: $PWD when it names this directory, else the real path."""
|
|
227
|
+
pwd = os.environ.get("PWD", "")
|
|
228
|
+
try:
|
|
229
|
+
if os.path.isabs(pwd) and os.path.samefile(pwd, "."):
|
|
230
|
+
return pwd
|
|
231
|
+
except OSError:
|
|
232
|
+
pass
|
|
233
|
+
return os.getcwd()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def script_dir(script):
|
|
237
|
+
"""What $(cd "$(dirname "$0")" && pwd) prints: the script's folder, logical, not resolved."""
|
|
238
|
+
d = os.path.dirname(script) or "."
|
|
239
|
+
return os.path.normpath(os.path.join(_pwd(), d))
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def run(argv, out, err, here):
|
|
243
|
+
"""Write what the bash script would to out and err, and return its exit code."""
|
|
244
|
+
try:
|
|
245
|
+
return _run(list(argv), out, err, here)
|
|
246
|
+
except Stop as stop:
|
|
247
|
+
return stop.code
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _run(argv, out, err, here):
|
|
251
|
+
def usage():
|
|
252
|
+
err.write(USAGE + "\n")
|
|
253
|
+
raise Stop(2)
|
|
254
|
+
|
|
255
|
+
if not argv or argv[0] == "":
|
|
256
|
+
usage()
|
|
257
|
+
feature = argv[0]
|
|
258
|
+
rest = argv[1:]
|
|
259
|
+
watch, opening, out_path = False, True, ""
|
|
260
|
+
while rest:
|
|
261
|
+
arg = rest.pop(0)
|
|
262
|
+
if arg == "--watch":
|
|
263
|
+
watch = True
|
|
264
|
+
elif arg == "--no-open":
|
|
265
|
+
opening = False
|
|
266
|
+
elif arg == "--out":
|
|
267
|
+
out_path = rest.pop(0) if rest else ""
|
|
268
|
+
if out_path == "":
|
|
269
|
+
usage()
|
|
270
|
+
else:
|
|
271
|
+
usage()
|
|
272
|
+
|
|
273
|
+
folder = "%s/%s/%s" % (os.environ.get("FLOW_DIR") or "plans", feature, os.environ.get("FLOW_TICKETS") or "tasks")
|
|
274
|
+
if not os.path.isdir(folder):
|
|
275
|
+
err.write("no ticket folder: %s\n" % folder)
|
|
276
|
+
return 2
|
|
277
|
+
template = here + "/flow-view.html"
|
|
278
|
+
if not os.path.isfile(template):
|
|
279
|
+
err.write("missing %s\n" % template)
|
|
280
|
+
return 2
|
|
281
|
+
|
|
282
|
+
gitdir = os.fsdecode(_git(["rev-parse", "--absolute-git-dir"]).rstrip(b"\n"))
|
|
283
|
+
if out_path == "":
|
|
284
|
+
base = gitdir if gitdir else (os.environ.get("TMPDIR") or "/tmp")
|
|
285
|
+
out_path = "%s/flow-%s.html" % (base, feature)
|
|
286
|
+
|
|
287
|
+
view = View(feature, folder, template, out_path, gitdir, out, err)
|
|
288
|
+
view.render(watch)
|
|
289
|
+
out.write("wrote %s\n" % out_path)
|
|
290
|
+
view.open_page(opening)
|
|
291
|
+
|
|
292
|
+
if watch:
|
|
293
|
+
out.write("watching, press Ctrl-C to stop\n")
|
|
294
|
+
while True:
|
|
295
|
+
rc = status.run([feature, "--next"], _Null(), _Null())
|
|
296
|
+
if rc == 10:
|
|
297
|
+
view.render(False)
|
|
298
|
+
out.write("feature complete, final page written\n")
|
|
299
|
+
break
|
|
300
|
+
view.render(True)
|
|
301
|
+
pause(os.environ.get("FLOW_WATCH_SECONDS") or "3", err)
|
|
302
|
+
return 0
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def main(argv=None, here=None):
|
|
306
|
+
argv = sys.argv[1:] if argv is None else argv
|
|
307
|
+
here = script_dir(sys.argv[0]) if here is None else here
|
|
308
|
+
sys.stdout.flush()
|
|
309
|
+
try:
|
|
310
|
+
return run(argv, status._Bytes(sys.stdout.buffer), status._Bytes(sys.stderr.buffer), here)
|
|
311
|
+
except KeyboardInterrupt: # Ctrl-C in --watch: bash dies of the signal and prints nothing
|
|
312
|
+
return 130
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: feature-flow-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A ticket-graph workflow for coding agents: installs the feature-flow skill into a repo
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/hamilton-sky/feature-flow
|
|
7
|
+
Project-URL: Source, https://github.com/hamilton-sky/feature-flow
|
|
8
|
+
Keywords: claude-code,codex,agents,workflow,tickets
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Software Development
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# Feature Flow
|
|
21
|
+
|
|
22
|
+
**A ticket-graph workflow for coding agents.** Works with Claude Code and Codex.
|
|
23
|
+
|
|
24
|
+
[](https://github.com/hamilton-sky/feature-flow/actions/workflows/tests.yml)
|
|
25
|
+
|
|
26
|
+
Plan a feature as a graph of small tickets, then build them in the session you are already in. One command, `/feature-flow`, does both. Every ticket is built by one fresh subagent and checked by another that never saw the builder's reasoning. A script, not the agent, decides the order of the steps, runs the build and tests, and watches for work that weakens the checks, and the whole run has limits.
|
|
27
|
+
|
|
28
|
+
One skill, 2 agent roles, a Python conductor, 4 Python scripts, an installer for both agents, a demo and a test suite. All of it is Python (standard library only), so it runs on Linux, macOS and Windows. MIT licensed.
|
|
29
|
+
|
|
30
|
+
- [Quick start](#quick-start) · [How a ticket flows](#how-a-ticket-flows) · [The skill and the roles](#the-skill-and-the-roles) · [Plans and tickets](#plans-and-tickets)
|
|
31
|
+
- [See the graph](#see-the-graph) · [Long features and handoff](#long-features-and-handoff) · [What is tested](#what-is-tested) · [Upgrading](#upgrading) · [Caution](#caution)
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
You need `git` and `python3` (3.9 or later, standard library only), and Claude Code or Codex. No `bash` or `awk` is needed to run feature-flow: every script is Python. On Windows use `python` where this page says `python3`.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
git clone https://github.com/hamilton-sky/feature-flow.git
|
|
39
|
+
cd feature-flow
|
|
40
|
+
|
|
41
|
+
python3 install.py /path/to/your/repo # for Claude Code
|
|
42
|
+
python3 install.py /path/to/your/repo --agent codex # for Codex
|
|
43
|
+
python3 install.py /path/to/your/repo --agent all # both, side by side
|
|
44
|
+
|
|
45
|
+
# `bash install.sh ...` still works on Linux and macOS: it only runs install.py
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Without a clone, once the package is on PyPI (it is not published yet), the same installer runs from it:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uvx feature-flow-cli install /path/to/your/repo --agent all # or: pipx run feature-flow-cli install ...
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The package puts a `feature-flow` command on your PATH with `install`, `status <feature>` and `view <feature>`. It installs the same files, byte for byte, as `install.py` from a clone.
|
|
55
|
+
|
|
56
|
+
Then, in your repo, in the agent:
|
|
57
|
+
|
|
58
|
+
| | Claude Code | Codex |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| Plan a feature (no plan yet) | `/feature-flow csv-export` | `$feature-flow csv-export` |
|
|
61
|
+
| Build its tickets (a plan exists) | `/feature-flow csv-export` | `$feature-flow csv-export` |
|
|
62
|
+
| The same, asking nothing | `/feature-flow csv-export auto` | `$feature-flow csv-export auto` |
|
|
63
|
+
| Watch the graph, animated | `/feature-flow csv-export show` | `$feature-flow csv-export show` |
|
|
64
|
+
|
|
65
|
+
The same command plans when `plans/csv-export/` does not exist yet and builds when it does. Commit the plan before you build it.
|
|
66
|
+
|
|
67
|
+
Try the graph first, with no setup and no agent: `bash examples/demo.sh --open`.
|
|
68
|
+
|
|
69
|
+
### What the installer does
|
|
70
|
+
|
|
71
|
+
| | Claude Code (`--agent claude`, the default) | Codex (`--agent codex`) |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| The skill goes to | `<repo>/.claude/skills/` (`--user`: `~/.claude/skills/`) | `<repo>/.agents/skills/` (`--user`: `~/.agents/skills/`) |
|
|
74
|
+
| Roles go to | `<repo>/.claude/agents/` | `<repo>/.agents/flow-roles/`, put in front of each subagent's prompt |
|
|
75
|
+
| Scripts go to | `<repo>/scripts/` | `<repo>/scripts/` |
|
|
76
|
+
| Guides, roles and the conductor go to | `<repo>/.feature-flow/` | `<repo>/.feature-flow/` |
|
|
77
|
+
|
|
78
|
+
Next to the `feature-flow` skill it installs `architect-review` and `automation-design`. The installer never deletes anything. A file that already exists and differs is kept and reported; `--force` replaces it. `--dry-run` shows what would change and writes nothing. `CLAUDE_HOME` and `AGENTS_HOME` move the user folders. With `--user` the skill goes to the user folder, but the scripts and `.feature-flow/` stay in the repo, because the skill runs them from there.
|
|
79
|
+
|
|
80
|
+
The Claude Code skill is `skills/feature-flow/`. The Codex skill is written by hand in `adapters/codex/feature-flow/`, with an `agents/openai.yaml` that sets `allow_implicit_invocation: false`, so it runs only when you name it. The steps are the same; only the way a subagent is started differs. Your own `AGENTS.md` and `CLAUDE.md` are never touched.
|
|
81
|
+
|
|
82
|
+
## How a ticket flows
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
smoke command (script): is the base healthy? no ─► stop, spend nothing
|
|
86
|
+
│ yes
|
|
87
|
+
▼
|
|
88
|
+
pick ticket ─► builder subagent: claim, build, prove it (run fresh, read the output), commit
|
|
89
|
+
│
|
|
90
|
+
▼
|
|
91
|
+
gate (script): Build, Test, Lint from commands.md fail ─┐
|
|
92
|
+
│ pass │
|
|
93
|
+
▼ │
|
|
94
|
+
floor guard (script, no AI): │
|
|
95
|
+
tests skipped or deleted? checks silenced? empty catches? │
|
|
96
|
+
thresholds lowered? lint/test/CI config edited? fail ─┤
|
|
97
|
+
plan touched beyond own Status + Answer + appended lines? │
|
|
98
|
+
│ clean │
|
|
99
|
+
▼ │
|
|
100
|
+
fresh reviewer subagent: sees only the ticket + diff, │
|
|
101
|
+
re-runs every Done when FAIL ─┤
|
|
102
|
+
│ PASS ▼
|
|
103
|
+
▼ findings written into the ticket,
|
|
104
|
+
next ticket reopened, built again (3 rounds max)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`scripts/flow.py` is the conductor. The session asks it `next`, and it answers with one line: `BUILD`, `REVIEW`, `DONE`, `STOP` or `HANDOFF`. It prints the full prompt for each subagent from runtime-neutral guides, so the session never writes one itself.
|
|
108
|
+
|
|
109
|
+
**What the script checks and what it trusts.** The conductor verifies the build from the repo: the ticket file must say `resolved`, the tree must be clean and committed, and it runs the gate and the floor guard itself. It also checks that the reviewer changed no tracked file and made no commit. The review verdict is different: the reviewer's reply is relayed by the session, which saves it to a file and hands it to `flow.py verdict`. The script reads `REVIEW: PASS` or `REVIEW: FAIL` from that reply; it cannot prove the session passed it on unedited.
|
|
110
|
+
|
|
111
|
+
## The skill and the roles
|
|
112
|
+
|
|
113
|
+
| Skill | What it does |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `feature-flow` | **Plan:** writes `plans/<feature>/` with a spec, a map, commands, learnings and tickets, shows you the graph, and waits for a yes. **Build:** runs the tickets one by one, a builder and a reviewer subagent each, with `flow.py` deciding every step. **Show:** the animated ticket graph and a summary of what is ready and what blocks the finish. |
|
|
116
|
+
| `architect-review` | Architecture review of a file, diff or feature, with severity rated findings. Reads `CLAUDE.md` or `AGENTS.md` for the project's own rules. |
|
|
117
|
+
| `automation-design` | Blueprint for an automation pipeline. Hands off to `feature-flow` for the plan. |
|
|
118
|
+
|
|
119
|
+
| Role | Tools | Job |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `ticket-builder` | Read, Glob, Grep, Edit, Write, Bash | Builds one ticket by the build guide in its prompt. |
|
|
122
|
+
| `ticket-reviewer` | Read, Glob, Grep, Bash (no Edit, no Write) | Reviews one ticket by the review guide in its prompt and ends with `REVIEW: PASS` or `REVIEW: FAIL`. |
|
|
123
|
+
|
|
124
|
+
In Claude Code the reviewer's tool list is enforced, so it cannot edit. A Codex subagent cannot be limited that way, so there the reviewer works from its instructions, and the conductor stops the run if it changed anything.
|
|
125
|
+
|
|
126
|
+
The guides in `guides/` (installed to `.feature-flow/guides/`) hold the protocol: how to build, review, plan and show. The skill holds only what differs per agent.
|
|
127
|
+
|
|
128
|
+
## Plans and tickets
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
plans/
|
|
132
|
+
└── csv-export/
|
|
133
|
+
├── spec.md what and why
|
|
134
|
+
├── map.md destination, the bar, decisions so far (workers append only)
|
|
135
|
+
├── commands.md build, test, lint, smoke commands (frozen once approved)
|
|
136
|
+
├── learnings.md traps workers found (workers append only)
|
|
137
|
+
└── tasks/
|
|
138
|
+
├── 01-types.md
|
|
139
|
+
├── 02-service.md
|
|
140
|
+
└── 03-acceptance.md
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`commands.md` and `learnings.md` are separate from the map because they follow different rules. Commands are approved by a human and read by the scripts, so they are frozen. Learnings grow with every ticket, so they are append only and a human prunes them (`--check` warns past 40 lines). The map stays short.
|
|
144
|
+
|
|
145
|
+
One markdown file per ticket in `plans/<feature>/tasks/NN-slug.md`:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
# Add the retry policy to the job runner
|
|
149
|
+
|
|
150
|
+
Type: task task | settle | convert
|
|
151
|
+
Status: open open | claimed | resolved | parked
|
|
152
|
+
Blocked by: 01, 02 or —
|
|
153
|
+
Test first: yes yes | no
|
|
154
|
+
Floor: allow config optional, only when a human decides the guard may let it through
|
|
155
|
+
|
|
156
|
+
...what to do and why...
|
|
157
|
+
|
|
158
|
+
## Not in this ticket
|
|
159
|
+
## Done when commands and the results they print
|
|
160
|
+
## Reference
|
|
161
|
+
## Answer Built, Proof, Decisions, Shortcuts taken, Review fixes, For later tickets
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- **Order comes from `Blocked by`**, not from numbers. A ticket is *ready* when it is open and every ticket it is blocked by is resolved. Lowest number wins.
|
|
165
|
+
- **Status lives only in the ticket files.** `map.md` never repeats it.
|
|
166
|
+
- **The Answers are the handoff between subagents.** The next builder reads the Answers of the tickets it depends on.
|
|
167
|
+
- `settle` tickets record a decision. `convert` tickets migrate something and must be safe to run twice.
|
|
168
|
+
- **A worker may change only** its own Status line and Answer, lines appended to `map.md` and `learnings.md`, and the code the ticket calls for. Rewriting its own Done when, editing another ticket, or touching `commands.md` fails the guard.
|
|
169
|
+
- `Floor:` categories: `skip`, `suppress`, `empty-catch`, `test-delete`, `threshold`, `config`, `ticket-edit`, `commands-edit`. The line is read from the ticket as it was before the work started, so a worker cannot excuse itself.
|
|
170
|
+
- The guard needs the plan to be tracked by git. If you keep tickets in an ignored folder, it cannot see changes to them.
|
|
171
|
+
- The commands in `commands.md` run with nobody watching and must exit non zero on failure.
|
|
172
|
+
|
|
173
|
+
## See the graph
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
python3 scripts/flow-view.py csv-export write the page and open it
|
|
177
|
+
python3 scripts/flow-view.py csv-export --watch keep it updating while tickets are built
|
|
178
|
+
bash examples/demo.sh --open try it on a made up project, no setup needed
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
One self contained HTML file: no server, no libraries, no network, light and dark. It is written to `.git/flow-<feature>.html`, so it never dirties the tree (`--out FILE` writes it elsewhere).
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
01 types ──► 02 service ──► 04 acceptance ● resolved ◉ ready (pulses)
|
|
185
|
+
└────► 03 wire ──────────┘ ◌ being worked (spins) ○ waiting
|
|
186
|
+
▶ replay ──●──────●─────○────────── drag to scrub through the run
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- **Layered left to right**, so the order of work is visible. Dashes flow along the edges into tickets that are ready to start.
|
|
190
|
+
- **Replay** plays the run back from git history: tickets turn green in the order they resolved, and a ticket sent back by the gate, the guard or the reviewer flashes red.
|
|
191
|
+
- **Hover or click** a ticket for its blockers, Done when, Answer, and the rounds it was sent back.
|
|
192
|
+
- **Keys:** space plays or pauses, the arrows step, End shows now, `t` switches theme. Reduced motion is respected.
|
|
193
|
+
- **Safe by construction:** ticket text comes from files an agent wrote, so it is escaped when embedded and only ever inserted as text.
|
|
194
|
+
- `flow-status.py <feature> --json` prints the same data for other tools. Mermaid (`--mermaid`) remains the choice for pull requests, because GitHub renders it.
|
|
195
|
+
|
|
196
|
+
## Long features and handoff
|
|
197
|
+
|
|
198
|
+
A session's context fills up, so the flow moves to a fresh session every few tickets. After `FLOW_TICKETS_PER_SESSION` tickets (default 4) pass review, `next` prints a `HANDOFF` line instead of the next ticket, for example `HANDOFF /feature-flow csv-export`. The session stops and tells you to open a new session and type that line. The new session picks up exactly where the last one stopped. Set `FLOW_TICKETS_PER_SESSION=0` to never hand off.
|
|
199
|
+
|
|
200
|
+
- **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.
|
|
201
|
+
- **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.
|
|
202
|
+
- **Relay mode.** `FLOW_RELAY=1` hands off after every build and every review, so each phase gets a session of its own.
|
|
203
|
+
- **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.
|
|
204
|
+
|
|
205
|
+
The state lives in `.feature-flow/state/` (the state, a log of every step, and the last review reply). That folder ignores itself, so it never shows up in `git status`, and a normal Codex session can write it, which it cannot do under `.git`.
|
|
206
|
+
|
|
207
|
+
### Commands and settings
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
python3 scripts/flow.py <feature> start|next|prompt|verdict <file> the conductor (the skill runs it)
|
|
211
|
+
python3 scripts/flow-status.py <feature> table of tickets and which are READY
|
|
212
|
+
python3 scripts/flow-status.py <feature> --next path of the next ready ticket (exit 10 = done, 11 = stuck)
|
|
213
|
+
python3 scripts/flow-status.py <feature> --counts one line of counts
|
|
214
|
+
python3 scripts/flow-status.py <feature> --check cycles, missing blockers, missing Done when, unordered mentions
|
|
215
|
+
python3 scripts/flow-status.py <feature> --mermaid the ticket graph, coloured by status (add "plain" for none)
|
|
216
|
+
python3 scripts/flow-status.py <feature> --json every ticket with status, blockers and readiness
|
|
217
|
+
python3 scripts/flow-view.py <feature> [--watch] the animated graph page, opened in your browser
|
|
218
|
+
python3 scripts/gate.py <feature> run Build, Test and Lint from commands.md
|
|
219
|
+
python3 scripts/floor-guard.py <feature> <NN> [base] check a ticket's diff, run from the repo root
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The commands in `commands.md` run through `bash -c` on Linux and macOS and through the system shell (`cmd.exe`) on Windows, so write them for the platform your team uses, or call `python` as in the examples.
|
|
223
|
+
|
|
224
|
+
`flow-status.py` also understands the `.scratch/<feature>/issues/` layout and the statuses `done`, `ready-for-agent`, `ready-for-human` and `closed`: `FLOW_DIR=.scratch FLOW_TICKETS=issues`.
|
|
225
|
+
|
|
226
|
+
| Variable | Default | Meaning |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| `FLOW_TICKETS_PER_SESSION` | `4` | tickets per session before `HANDOFF`; `0` never hands off |
|
|
229
|
+
| `FLOW_RELAY` | `0` | `1` hands off after every build and every review |
|
|
230
|
+
| `FLOW_TAKEOVER` | unset | `1` lets `start` take a feature over from a session that is gone |
|
|
231
|
+
| `FLOW_MAX_RETRIES` | `2` | builds or reviews per step before the run stops |
|
|
232
|
+
| `FLOW_MAX_REVIEW_ROUNDS` | `3` | times a ticket may be sent back before the run stops |
|
|
233
|
+
| `FLOW_GATE` | `on` | `off` skips Build, Test and Lint after each ticket |
|
|
234
|
+
| `FLOW_SMOKE` | the `Smoke:` line of `commands.md` | command run before every ticket; the run stops if it fails |
|
|
235
|
+
| `FLOW_DIR`, `FLOW_TICKETS` | `plans`, `tasks` | where the plans and the ticket folder live |
|
|
236
|
+
| `FLOW_NO_OPEN` | unset | `1` makes `flow-view.py` never open a browser |
|
|
237
|
+
| `FLOW_WATCH_SECONDS` | `3` | how often `flow-view.py --watch` rewrites the page |
|
|
238
|
+
|
|
239
|
+
The run also stops on its own after a number of steps that grows with the ticket count, so a loop of failures cannot go on forever.
|
|
240
|
+
|
|
241
|
+
## What is tested
|
|
242
|
+
|
|
243
|
+
| Path | Tested |
|
|
244
|
+
|---|---|
|
|
245
|
+
| Claude Code | The offline suite, and **one real run of the bar**: the two-ticket demo in two `/feature-flow hello auto` sessions with one `HANDOFF` between them, each ticket built by a builder subagent and passed by a reviewer subagent, with all 16 acceptance checks ok. It used about 23k output tokens and 2M cached input tokens on Sonnet. |
|
|
246
|
+
| Codex | The install, offline, in CI on Linux and macOS. A probe in the Codex app showed that subagents with separate contexts work and that a reviewer's verdict reaches the session. The full flow is **not yet tested with a real run**. |
|
|
247
|
+
| Claude Code on the web | Documented, untested: commit `.claude/`, `scripts/` and `.feature-flow/` into the repo, because cloud sessions read only the repo's `.claude/`. |
|
|
248
|
+
| Copilot, Gemini CLI, Cursor | Not tested. Some read `.agents/skills`, so `--agent codex` may already work. |
|
|
249
|
+
|
|
250
|
+
### Trying it by hand
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
bash tests/smoke-real.sh --prepare /tmp/hello-try # the demo project, for Claude Code, no model is called
|
|
254
|
+
FLOW_AGENT=codex bash tests/smoke-real.sh --prepare /tmp/hello-codex # the same, for Codex
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Then `cd` into it, start the agent and type `/feature-flow hello` (Codex: `$feature-flow hello`).
|
|
258
|
+
|
|
259
|
+
## Tests
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
bash tests/run.sh
|
|
263
|
+
python3 -m unittest discover -s tests/py
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Offline and free: no model is called. The suite covers the conductor's every answer and how a run stops, sessions, handoff and takeover, a `.git` the agent cannot write, the gate, the floor guard, plan protection, the prompts and guides, both skills, the installer for both agents, the graph page and its data, and the acceptance harness. The page's layout and replay logic are also tested under Node (`tests/viewer-logic.test.js`, skipped when Node is absent). `.github/workflows/tests.yml` runs it on every push to `main` and every pull request, on Ubuntu (once with `mawk`, once with `gawk`) and on macOS. A fourth job runs the Python unit tests on Windows, including a fixture drive that takes a two-ticket plan through `python scripts/flow.py f start` and `next` to `BUILD` and then `REVIEW`. `tests/run.sh` itself is a bash harness and does not run on Windows. Windows is tested this way only: no real agent run has been done there.
|
|
267
|
+
|
|
268
|
+
A `package` job builds the wheel and the sdist and runs `tests/package_smoke.py` on Ubuntu (Python 3.9 and the latest), macOS and Windows: it installs the wheel in a fresh virtual environment, runs `feature-flow install`, and checks that the repo gets the same files as from `install.py` in a clone. Run it locally with `python3 -m pip install build && python3 tests/package_smoke.py`.
|
|
269
|
+
|
|
270
|
+
`RUN_REAL=1 bash tests/smoke-real.sh --interactive` is the acceptance run with Claude Code. It runs real sessions on the demo project and spends money, which is why it asks for `RUN_REAL=1`.
|
|
271
|
+
|
|
272
|
+
## Upgrading
|
|
273
|
+
|
|
274
|
+
Earlier versions had seven skills and an unattended loop. The five flow skills are gone, and `/feature-flow` replaces them:
|
|
275
|
+
|
|
276
|
+
- `plan-feature` → `/feature-flow <feature>` before a plan exists.
|
|
277
|
+
- `/next-phase` and `/review-ticket` → `/feature-flow <feature>`, which runs a builder and a fresh reviewer subagent for every ticket.
|
|
278
|
+
- `show-flow` → `/feature-flow <feature> show`.
|
|
279
|
+
- `/run-flow` and `scripts/auto-flow.sh` → `/feature-flow <feature> auto`, with a `HANDOFF` every few tickets. There is no cost log any more.
|
|
280
|
+
|
|
281
|
+
The installer never deletes the old skill folders, but it names any it finds. Delete them from `.claude/skills/` or `.agents/skills/` yourself.
|
|
282
|
+
|
|
283
|
+
## Releasing to PyPI
|
|
284
|
+
|
|
285
|
+
The package is `feature-flow-cli` (the shorter `feature-flow` is likely refused by PyPI as too close to the existing `featureflow`). Its version is `__version__` in `feature_flow/__init__.py`. To release: bump it, then `python3 -m build` and `python3 -m twine upload dist/*` with a PyPI API token, or publish from a GitHub Actions workflow set up as a PyPI trusted publisher. Nothing in this repo uploads on its own.
|
|
286
|
+
|
|
287
|
+
## Caution
|
|
288
|
+
|
|
289
|
+
The builder subagent has edit and shell access and commits after each ticket. Each ticket costs at least two subagents. Build on a branch you can throw away. Ignore build output in `.gitignore`, because the conductor stops if the tree is dirty after a ticket. The gate runs your Build, Test and Lint commands with no time limit. The floor guard is pattern matching: it can miss things and it can raise false alarms, and `Floor: allow` is the release valve. The run stops on its own when a ticket stays unresolved, when the smoke test or the gate keeps failing, when a ticket keeps failing review, or when it reaches its step limit.
|
|
290
|
+
|
|
291
|
+
## License
|
|
292
|
+
|
|
293
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
feature_flow/__init__.py,sha256=gFNdla6ftW3fdGJA6c5vz26qxAHgUWrDdYS0ERE7cBc,86
|
|
2
|
+
feature_flow/__main__.py,sha256=A4WYj11KI9rxNVajkbcbs4eiWrK3-ou6gbAnYyECjgc,137
|
|
3
|
+
feature_flow/checks.py,sha256=fkLiGai7VcrRAjn_opCj0SkSNQY16MhGwQWIjfraVnA,2344
|
|
4
|
+
feature_flow/cli.py,sha256=oLJQ7WTx043ZApUHqKNP29K8OqeKkwxDkCmIn2rP2J8,1500
|
|
5
|
+
feature_flow/command.py,sha256=dhcQ2NtP_LPJwaPZQSEPoFJWmZS8ITjpdK1EzG_-a6M,1598
|
|
6
|
+
feature_flow/conductor.py,sha256=OLLStMRejHbs0Dge8IIMpMxRbErB8Rey7JmsbOPC4wA,12663
|
|
7
|
+
feature_flow/floorguard.py,sha256=Usg2KmN-qAg1dD_VaLzkwFxZHXWGCMW4kxY5F3mwFms,13064
|
|
8
|
+
feature_flow/gate.py,sha256=Rq0YOJjfqNhgSAoSMEmnTBuuVtNkQ03c1R7ZVf7ALKE,3661
|
|
9
|
+
feature_flow/git.py,sha256=vacYCI6iuEiwANyi_3jjV5XUQAtX7LwL-8zatFEkX0U,1515
|
|
10
|
+
feature_flow/install.py,sha256=a1Wa0-qEs1hBdwBGm3qgqkgHn8bN3Mn8vC2-MSpszsg,14762
|
|
11
|
+
feature_flow/prompts.py,sha256=cdJpkaUmOq3oEryar5DdvuRLnGEmMwL6X7zwCT90vms,2611
|
|
12
|
+
feature_flow/state.py,sha256=9xQTapyUm8sjtndVowctCUbKWmwKSE9qQvgMI5ZOV1g,2530
|
|
13
|
+
feature_flow/status.py,sha256=L1tYnirK_Fv--8v2jWm8V8Ao3FCdR2YWB2JpW-Uyqps,8624
|
|
14
|
+
feature_flow/tickets.py,sha256=VplR9tRHriVtGAKrKawoevaFjjijSHnT9FNNcyCXurw,6073
|
|
15
|
+
feature_flow/view.py,sha256=d0EW4WM3c5C1jcd50AiCUBPUr_AjosMDMvRttTtHPt0,11668
|
|
16
|
+
feature_flow/_bundle/adapters/codex/feature-flow/SKILL.md,sha256=mJKBaCXg3k9aQNuvJQazDRs1aNCurcZZfEofwP2HIiU,4918
|
|
17
|
+
feature_flow/_bundle/adapters/codex/feature-flow/agents/openai.yaml,sha256=mDOVln4eJrylnPTaU5SB9CbDw9AuZwKAds1NIEUnrEQ,251
|
|
18
|
+
feature_flow/_bundle/agents/ticket-builder.md,sha256=rpmcUKkaI0tJQ9_4cx1A0w2n0EsrpM8TkRpqxiNtLGA,1296
|
|
19
|
+
feature_flow/_bundle/agents/ticket-reviewer.md,sha256=EWryy2TdIrBh5yF6euyW7ML6kZOQXxcVTOc1QrijoEk,826
|
|
20
|
+
feature_flow/_bundle/guides/build.md,sha256=8zVIjx6J5jvy8FiKcf7Mv-zggO-dy1-sFfiYaFdkNPA,9319
|
|
21
|
+
feature_flow/_bundle/guides/plan.md,sha256=8F2JCnk3nui4m874Y2GsyTyms_eyd1ABJtBYJDpg-fI,7615
|
|
22
|
+
feature_flow/_bundle/guides/review.md,sha256=FudfVl4wrA1OQY2BP5w5mShgeianyE9LmDux6PxOMME,6151
|
|
23
|
+
feature_flow/_bundle/guides/show.md,sha256=6zRvBc1jWSknSaCRX-izQSs_maSTohspTnY6PaCoCFI,1801
|
|
24
|
+
feature_flow/_bundle/guides/templates/commands.md,sha256=w153NgDUwzQLXknv-cyOsmgq33q4pWWZos4_pwnBDL8,648
|
|
25
|
+
feature_flow/_bundle/guides/templates/learnings.md,sha256=dTCqkBSTU7YHVg65wWqM6b51hG9o-ztxxaLJFz0hqbM,335
|
|
26
|
+
feature_flow/_bundle/guides/templates/map.md,sha256=abE6ExRmr1ShcuclX63iRqrXBrF3-jYBqEOveoc8siY,983
|
|
27
|
+
feature_flow/_bundle/guides/templates/spec.md,sha256=S7F-b52bPHfoWPBhuIirmnvqnj7yzyaSMMHD1C2ACpE,1335
|
|
28
|
+
feature_flow/_bundle/guides/templates/ticket.md,sha256=KuaPffDpLVmp5p29PaUQPamrPem5oqt67DBUxBDLWtE,942
|
|
29
|
+
feature_flow/_bundle/guides/templates/ui-mockup.md,sha256=CEGYgatTk14Kc52K5_xuXN-utZc5S91cEdU4pagIYJE,1978
|
|
30
|
+
feature_flow/_bundle/scripts/floor-guard.py,sha256=06IxOCYIjZXcC8JiWwIi-YIjY27aOlnv6sWDjzYuWys,493
|
|
31
|
+
feature_flow/_bundle/scripts/flow-status.py,sha256=QId55aQgqLkM8EIoMGYPFZPv3ZxJmWZaG-sAlyGcJ0k,537
|
|
32
|
+
feature_flow/_bundle/scripts/flow-view.html,sha256=L5T9FzAHeXR3Tvp11JdBpid-mgYAQQQcAj_5_01hKec,27086
|
|
33
|
+
feature_flow/_bundle/scripts/flow-view.py,sha256=wtvDFi-L1HoVywoQ5sreLO_T-VHqjHWxdZV74c3OsXQ,507
|
|
34
|
+
feature_flow/_bundle/scripts/flow.py,sha256=iVIuu1v4iRpfqrgMhnFeP2EICO9YjOC7JhxbpLuDwRU,493
|
|
35
|
+
feature_flow/_bundle/scripts/gate.py,sha256=6BlMCqRrFp2EEoN_k0XI8yN4VwuEdKI1fFVwCACTt84,467
|
|
36
|
+
feature_flow/_bundle/skills/architect-review/SKILL.md,sha256=RcxmTLFo6oKz0E0WzZbEOSvMIXQfVXSTBuaf2PKg9ow,6824
|
|
37
|
+
feature_flow/_bundle/skills/automation-design/SKILL.md,sha256=LGNQ70Fi7j0ggUWCGBjEVAqZ3PS7F5FfD9_KiongG_s,10332
|
|
38
|
+
feature_flow/_bundle/skills/feature-flow/SKILL.md,sha256=RH8Mny7MsvUxPKkT2R1_ucM2sw-gO11TRgnNIt4EBwM,4514
|
|
39
|
+
feature_flow_cli-0.1.0.dist-info/licenses/LICENSE,sha256=DIRvhlH8EulEUOYQxKDFFiimJrlVT5-kCKxQIgi2m7c,1073
|
|
40
|
+
feature_flow_cli-0.1.0.dist-info/METADATA,sha256=QFQdTM1AAeRW3csiN3GliVVtWBWyGKVUU0hg74BQ5C4,21683
|
|
41
|
+
feature_flow_cli-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
42
|
+
feature_flow_cli-0.1.0.dist-info/entry_points.txt,sha256=R2gfzSs0axu97MVIhWN3UpEASyome9CYTQWzhwR7NaQ,104
|
|
43
|
+
feature_flow_cli-0.1.0.dist-info/top_level.txt,sha256=tf9cs_th2oqfDY4tXB9SFMr3IWnMuGwH5DHe5mbz3eU,13
|
|
44
|
+
feature_flow_cli-0.1.0.dist-info/RECORD,,
|