feature-flow-cli 0.1.0__tar.gz → 0.1.2__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.
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/MANIFEST.in +1 -0
- {feature_flow_cli-0.1.0/feature_flow_cli.egg-info → feature_flow_cli-0.1.2}/PKG-INFO +21 -6
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/README.md +20 -5
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/adapters/codex/feature-flow/SKILL.md +13 -9
- feature_flow_cli-0.1.2/agents/feature-planner.md +33 -0
- feature_flow_cli-0.1.2/agents/plan-reviewer.md +16 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow/__init__.py +1 -1
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow/checks.py +15 -3
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow/cli.py +19 -4
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow/conductor.py +75 -2
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow/git.py +28 -2
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow/install.py +32 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow/prompts.py +28 -2
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2/feature_flow_cli.egg-info}/PKG-INFO +21 -6
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow_cli.egg-info/SOURCES.txt +4 -1
- feature_flow_cli-0.1.2/guides/brief.md +62 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/guides/build.md +1 -1
- feature_flow_cli-0.1.2/guides/plan-review.md +29 -0
- feature_flow_cli-0.1.2/guides/plan.md +94 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/guides/show.md +1 -1
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/skills/feature-flow/SKILL.md +13 -9
- feature_flow_cli-0.1.0/guides/plan.md +0 -93
- feature_flow_cli-0.1.0/skills/.DS_Store +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/LICENSE +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/adapters/codex/feature-flow/agents/openai.yaml +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/agents/ticket-builder.md +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/agents/ticket-reviewer.md +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow/__main__.py +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow/command.py +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow/floorguard.py +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow/gate.py +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow/state.py +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow/status.py +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow/tickets.py +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow/view.py +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow_cli.egg-info/dependency_links.txt +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow_cli.egg-info/entry_points.txt +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/feature_flow_cli.egg-info/top_level.txt +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/guides/review.md +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/guides/templates/commands.md +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/guides/templates/learnings.md +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/guides/templates/map.md +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/guides/templates/spec.md +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/guides/templates/ticket.md +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/guides/templates/ui-mockup.md +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/install.py +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/install.sh +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/pyproject.toml +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/scripts/floor-guard.py +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/scripts/flow-status.py +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/scripts/flow-view.html +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/scripts/flow-view.py +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/scripts/flow.py +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/scripts/gate.py +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/setup.cfg +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/skills/architect-review/SKILL.md +0 -0
- {feature_flow_cli-0.1.0 → feature_flow_cli-0.1.2}/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.
|
|
3
|
+
Version: 0.1.2
|
|
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
|
|
@@ -45,7 +45,7 @@ python3 install.py /path/to/your/repo --agent all # both, side by side
|
|
|
45
45
|
# `bash install.sh ...` still works on Linux and macOS: it only runs install.py
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
Without a clone,
|
|
48
|
+
Without a clone, the same installer runs from [PyPI](https://pypi.org/project/feature-flow-cli/):
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
51
|
uvx feature-flow-cli install /path/to/your/repo --agent all # or: pipx run feature-flow-cli install ...
|
|
@@ -53,17 +53,22 @@ uvx feature-flow-cli install /path/to/your/repo --agent all # or: pipx run fea
|
|
|
53
53
|
|
|
54
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
55
|
|
|
56
|
+
The installer lists what it wrote in `.feature-flow/installed.txt`. Commit those files (`git add --pathspec-from-file=.feature-flow/installed.txt`); until you do, the flow does not count them as uncommitted changes.
|
|
57
|
+
|
|
56
58
|
Then, in your repo, in the agent:
|
|
57
59
|
|
|
58
60
|
| | Claude Code | Codex |
|
|
59
61
|
|---|---|---|
|
|
60
62
|
| Plan a feature (no plan yet) | `/feature-flow csv-export` | `$feature-flow csv-export` |
|
|
63
|
+
| Plan what you just talked through | `/feature-flow` | `$feature-flow` |
|
|
61
64
|
| Build its tickets (a plan exists) | `/feature-flow csv-export` | `$feature-flow csv-export` |
|
|
62
65
|
| The same, asking nothing | `/feature-flow csv-export auto` | `$feature-flow csv-export auto` |
|
|
63
66
|
| Watch the graph, animated | `/feature-flow csv-export show` | `$feature-flow csv-export show` |
|
|
64
67
|
|
|
65
68
|
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
69
|
|
|
70
|
+
**Planning asks you twice.** First the session shows a short feature brief, written from your conversation (what, why, scope, the bar, and anything it had to assume), and waits for yes, edit or cancel. Then a fresh `feature-planner` subagent, given only that brief, reads the code, looks up outside docs where the code cannot answer, and writes a draft plan into the git-ignored `.feature-flow/state/draft/`. A fresh `plan-reviewer` checks the draft against the brief. The session shows you the goal, the commands, the ticket graph, what was dropped and the review result, and waits for a second yes. Only then does `flow.py plan-accept` copy the draft into `plans/<feature>/`.
|
|
71
|
+
|
|
67
72
|
Try the graph first, with no setup and no agent: `bash examples/demo.sh --open`.
|
|
68
73
|
|
|
69
74
|
### What the installer does
|
|
@@ -112,7 +117,7 @@ The Claude Code skill is `skills/feature-flow/`. The Codex skill is written by h
|
|
|
112
117
|
|
|
113
118
|
| Skill | What it does |
|
|
114
119
|
|---|---|
|
|
115
|
-
| `feature-flow` | **Plan:**
|
|
120
|
+
| `feature-flow` | **Plan:** turns the conversation (or your answers) into a feature brief and waits for a yes, has a planner and a plan reviewer subagent draft and check `plans/<feature>/` (a spec, a map, commands, learnings and tickets), shows you the graph, and writes it after a second 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
121
|
| `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
122
|
| `automation-design` | Blueprint for an automation pipeline. Hands off to `feature-flow` for the plan. |
|
|
118
123
|
|
|
@@ -120,10 +125,12 @@ The Claude Code skill is `skills/feature-flow/`. The Codex skill is written by h
|
|
|
120
125
|
|---|---|---|
|
|
121
126
|
| `ticket-builder` | Read, Glob, Grep, Edit, Write, Bash | Builds one ticket by the build guide in its prompt. |
|
|
122
127
|
| `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`. |
|
|
128
|
+
| `feature-planner` | Read, Glob, Grep, Bash, Write, Edit, WebSearch, WebFetch | Plans one feature from the approved brief by the plan guide, writes only the draft, cites outside sources, and ends with `PLAN: READY` or `PLAN: QUESTIONS`. |
|
|
129
|
+
| `plan-reviewer` | Read, Glob, Grep, Bash (no Edit, no Write) | Checks the draft against the brief by the plan review guide and ends with `PLAN-REVIEW: PASS` or `PLAN-REVIEW: FAIL`. |
|
|
123
130
|
|
|
124
|
-
In Claude Code the
|
|
131
|
+
In Claude Code the reviewers' tool lists are enforced, so they cannot edit. A Codex subagent cannot be limited that way, so there the reviewers work from their instructions: the conductor stops the run if a ticket reviewer changed anything, and a planner's draft reaches `plans/` only through `plan-accept`. On Codex the planner can research the web only if web search is on; otherwise it plans from the code and says so. The Codex planning path has not been run by hand.
|
|
125
132
|
|
|
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.
|
|
133
|
+
The guides in `guides/` (installed to `.feature-flow/guides/`) hold the protocol: how to build, review, write the brief, plan, review a plan and show. The skill holds only what differs per agent.
|
|
127
134
|
|
|
128
135
|
## Plans and tickets
|
|
129
136
|
|
|
@@ -282,7 +289,15 @@ The installer never deletes the old skill folders, but it names any it finds. De
|
|
|
282
289
|
|
|
283
290
|
## Releasing to PyPI
|
|
284
291
|
|
|
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`.
|
|
292
|
+
The package is [`feature-flow-cli`](https://pypi.org/project/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`.
|
|
293
|
+
|
|
294
|
+
To release, bump `__version__`, merge it to `main`, then tag that commit and push the tag:
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
git tag v0.1.1 && git push origin v0.1.1
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
`.github/workflows/release.yml` checks that the tag matches `__version__`, builds the wheel and the sdist, runs `tests/package_smoke.py` on them and publishes them to PyPI. PyPI trusts the workflow as a trusted publisher (owner `hamilton-sky`, repository `feature-flow`, workflow `release.yml`, environment `pypi`), so no token is stored. PyPI never accepts the same version twice.
|
|
286
301
|
|
|
287
302
|
## Caution
|
|
288
303
|
|
|
@@ -26,7 +26,7 @@ python3 install.py /path/to/your/repo --agent all # both, side by side
|
|
|
26
26
|
# `bash install.sh ...` still works on Linux and macOS: it only runs install.py
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
Without a clone,
|
|
29
|
+
Without a clone, the same installer runs from [PyPI](https://pypi.org/project/feature-flow-cli/):
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
uvx feature-flow-cli install /path/to/your/repo --agent all # or: pipx run feature-flow-cli install ...
|
|
@@ -34,17 +34,22 @@ uvx feature-flow-cli install /path/to/your/repo --agent all # or: pipx run fea
|
|
|
34
34
|
|
|
35
35
|
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.
|
|
36
36
|
|
|
37
|
+
The installer lists what it wrote in `.feature-flow/installed.txt`. Commit those files (`git add --pathspec-from-file=.feature-flow/installed.txt`); until you do, the flow does not count them as uncommitted changes.
|
|
38
|
+
|
|
37
39
|
Then, in your repo, in the agent:
|
|
38
40
|
|
|
39
41
|
| | Claude Code | Codex |
|
|
40
42
|
|---|---|---|
|
|
41
43
|
| Plan a feature (no plan yet) | `/feature-flow csv-export` | `$feature-flow csv-export` |
|
|
44
|
+
| Plan what you just talked through | `/feature-flow` | `$feature-flow` |
|
|
42
45
|
| Build its tickets (a plan exists) | `/feature-flow csv-export` | `$feature-flow csv-export` |
|
|
43
46
|
| The same, asking nothing | `/feature-flow csv-export auto` | `$feature-flow csv-export auto` |
|
|
44
47
|
| Watch the graph, animated | `/feature-flow csv-export show` | `$feature-flow csv-export show` |
|
|
45
48
|
|
|
46
49
|
The same command plans when `plans/csv-export/` does not exist yet and builds when it does. Commit the plan before you build it.
|
|
47
50
|
|
|
51
|
+
**Planning asks you twice.** First the session shows a short feature brief, written from your conversation (what, why, scope, the bar, and anything it had to assume), and waits for yes, edit or cancel. Then a fresh `feature-planner` subagent, given only that brief, reads the code, looks up outside docs where the code cannot answer, and writes a draft plan into the git-ignored `.feature-flow/state/draft/`. A fresh `plan-reviewer` checks the draft against the brief. The session shows you the goal, the commands, the ticket graph, what was dropped and the review result, and waits for a second yes. Only then does `flow.py plan-accept` copy the draft into `plans/<feature>/`.
|
|
52
|
+
|
|
48
53
|
Try the graph first, with no setup and no agent: `bash examples/demo.sh --open`.
|
|
49
54
|
|
|
50
55
|
### What the installer does
|
|
@@ -93,7 +98,7 @@ The Claude Code skill is `skills/feature-flow/`. The Codex skill is written by h
|
|
|
93
98
|
|
|
94
99
|
| Skill | What it does |
|
|
95
100
|
|---|---|
|
|
96
|
-
| `feature-flow` | **Plan:**
|
|
101
|
+
| `feature-flow` | **Plan:** turns the conversation (or your answers) into a feature brief and waits for a yes, has a planner and a plan reviewer subagent draft and check `plans/<feature>/` (a spec, a map, commands, learnings and tickets), shows you the graph, and writes it after a second 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. |
|
|
97
102
|
| `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. |
|
|
98
103
|
| `automation-design` | Blueprint for an automation pipeline. Hands off to `feature-flow` for the plan. |
|
|
99
104
|
|
|
@@ -101,10 +106,12 @@ The Claude Code skill is `skills/feature-flow/`. The Codex skill is written by h
|
|
|
101
106
|
|---|---|---|
|
|
102
107
|
| `ticket-builder` | Read, Glob, Grep, Edit, Write, Bash | Builds one ticket by the build guide in its prompt. |
|
|
103
108
|
| `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`. |
|
|
109
|
+
| `feature-planner` | Read, Glob, Grep, Bash, Write, Edit, WebSearch, WebFetch | Plans one feature from the approved brief by the plan guide, writes only the draft, cites outside sources, and ends with `PLAN: READY` or `PLAN: QUESTIONS`. |
|
|
110
|
+
| `plan-reviewer` | Read, Glob, Grep, Bash (no Edit, no Write) | Checks the draft against the brief by the plan review guide and ends with `PLAN-REVIEW: PASS` or `PLAN-REVIEW: FAIL`. |
|
|
104
111
|
|
|
105
|
-
In Claude Code the
|
|
112
|
+
In Claude Code the reviewers' tool lists are enforced, so they cannot edit. A Codex subagent cannot be limited that way, so there the reviewers work from their instructions: the conductor stops the run if a ticket reviewer changed anything, and a planner's draft reaches `plans/` only through `plan-accept`. On Codex the planner can research the web only if web search is on; otherwise it plans from the code and says so. The Codex planning path has not been run by hand.
|
|
106
113
|
|
|
107
|
-
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.
|
|
114
|
+
The guides in `guides/` (installed to `.feature-flow/guides/`) hold the protocol: how to build, review, write the brief, plan, review a plan and show. The skill holds only what differs per agent.
|
|
108
115
|
|
|
109
116
|
## Plans and tickets
|
|
110
117
|
|
|
@@ -263,7 +270,15 @@ The installer never deletes the old skill folders, but it names any it finds. De
|
|
|
263
270
|
|
|
264
271
|
## Releasing to PyPI
|
|
265
272
|
|
|
266
|
-
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`.
|
|
273
|
+
The package is [`feature-flow-cli`](https://pypi.org/project/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`.
|
|
274
|
+
|
|
275
|
+
To release, bump `__version__`, merge it to `main`, then tag that commit and push the tag:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
git tag v0.1.1 && git push origin v0.1.1
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`.github/workflows/release.yml` checks that the tag matches `__version__`, builds the wheel and the sdist, runs `tests/package_smoke.py` on them and publishes them to PyPI. PyPI trusts the workflow as a trusted publisher (owner `hamilton-sky`, repository `feature-flow`, workflow `release.yml`, environment `pypi`), so no token is stored. PyPI never accepts the same version twice.
|
|
267
282
|
|
|
268
283
|
## Caution
|
|
269
284
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: feature-flow
|
|
3
|
-
description: "Use to plan a feature or build its tickets, one fresh builder and one fresh reviewer subagent per ticket, with scripts/flow.py deciding every step. Also draws the ticket graph with show."
|
|
3
|
+
description: "Use to plan a feature (from the conversation so far, with a fresh planner and plan reviewer subagent) or build its tickets, one fresh builder and one fresh reviewer subagent per ticket, with scripts/flow.py deciding every step. Also draws the ticket graph with show."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
`<arguments>` below stands for the text the user typed after `$feature-flow`.
|
|
7
7
|
|
|
8
8
|
Plan or build the feature in `<arguments>`.
|
|
9
9
|
|
|
10
|
-
The first word is the **feature**. `show` means draw the graph. `auto` means ask nothing and go. If no feature was given, list `plans/*/` and ask which one.
|
|
10
|
+
The first word is the **feature**. `show` means draw the graph. `auto` means ask nothing and go. If no feature was given and the conversation so far describes work to build, plan it (below) and propose the feature's name in the brief. If no feature was given otherwise, list `plans/*/` and ask which one.
|
|
11
11
|
|
|
12
12
|
The conductor is `python3 scripts/flow.py <feature> <command>`. It decides the order, runs the gate and the floor guard, and keeps its state in `.feature-flow/state/`, a folder git ignores. You ask it, and you do what it says. The guides it uses are in `.feature-flow/guides/` (in a feature-flow checkout, `guides/`). Read the project's `AGENTS.md` for its conventions.
|
|
13
13
|
|
|
@@ -17,19 +17,23 @@ With `show`, follow `.feature-flow/guides/show.md` for the feature and stop.
|
|
|
17
17
|
|
|
18
18
|
## Plan
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
When `plans/<feature>/` does not exist, or you are planning from the conversation, follow `.feature-flow/guides/brief.md` with the user. You write the brief and ask for both yeses (unless `auto`); a child agent plans, another reviews, and you never draft the plan yourself. Once the brief names the feature, run `FLOW_INVOKE='$feature-flow' python3 scripts/flow.py <feature> start` and check that it prints `PLAN`, and check that `.agents/flow-roles/feature-planner.md` and `.agents/flow-roles/plan-reviewer.md` exist (see below).
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
- `plan-prompt <brief>`: start the planner with `spawn_agent(task_name="feature_planner", fork_turns="none", message=...)`. The message is "Work only in <repo>." followed by the whole output. Get its final reply with `wait_agent`. A child cannot be limited to the draft folder, so it works from its instructions, and `plan-accept` is the only way its draft reaches `plans/`. If web search is off in this Codex, the planner plans from the codebase and says so.
|
|
23
|
+
- `plan-review-prompt`: start a new reviewer the same way, with `task_name="plan_reviewer"` and `fork_turns="none"`, never with the planner's reply. Get its final reply with `wait_agent` and save it with the shell into `.feature-flow/state/plan-review-<feature>.txt`.
|
|
24
|
+
- `plan-accept`: writes `plans/<feature>/` from the draft, only after the second yes.
|
|
25
|
+
|
|
26
|
+
Then run `python3 scripts/flow-status.py <feature> --check`, list the files `plan-accept` wrote, and suggest the commit command (`git add plans/<feature> && git commit -m "docs(<feature>): plan"`). Say to commit the plan and run `$feature-flow <feature>` again. While the plan is uncommitted, do not say the feature is ready to build.
|
|
27
|
+
|
|
28
|
+
To add tickets to a plan that exists, edit it by hand following the ticket rules in `.feature-flow/guides/plan.md`.
|
|
25
29
|
|
|
26
30
|
## Before building
|
|
27
31
|
|
|
28
32
|
With a plan present, check these before `start`, and stop at the first that fails:
|
|
29
33
|
|
|
30
|
-
- `git status --porcelain` is empty.
|
|
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.
|
|
31
35
|
- `python3 scripts/flow-status.py <feature> --check` prints `OK`.
|
|
32
|
-
- `.agents/flow-roles/ticket-builder.md` and `.agents/flow-roles/ticket-reviewer.md` exist. If not, say to run `
|
|
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).
|
|
33
37
|
|
|
34
38
|
Then run `FLOW_INVOKE='$feature-flow' python3 scripts/flow.py <feature> start`. It prints `OK <token>`. Keep the token and put `FLOW_SESSION=<token>` in front of **every** later conductor command, with `FLOW_INVOKE='$feature-flow'`. The conductor keeps its state in `.feature-flow/state/`, a git-ignored folder in the repo, so it never needs to write `.git`. If it prints `STOP cannot write the flow state`, tell the user this session must be allowed to write that folder.
|
|
35
39
|
|
|
@@ -53,7 +57,7 @@ A conductor command looks like this:
|
|
|
53
57
|
|
|
54
58
|
## Rules while building
|
|
55
59
|
|
|
56
|
-
These apply from `start` on, once a plan exists.
|
|
60
|
+
These apply from `start` on, once a plan exists.
|
|
57
61
|
|
|
58
62
|
- Never run the gate, the floor guard or a review yourself. The conductor runs the checks, and the reviewer subagent reviews.
|
|
59
63
|
- Never edit a ticket, the plan or the code, and never commit. The builder does that.
|
|
@@ -0,0 +1,33 @@
|
|
|
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`.
|
|
@@ -0,0 +1,16 @@
|
|
|
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.
|
|
@@ -4,6 +4,7 @@ Each call lives in one function so plans/interactive-flow-python can swap it for
|
|
|
4
4
|
in-process call without touching the conductor.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import os
|
|
7
8
|
import subprocess
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
|
|
@@ -41,10 +42,21 @@ def _bash(script, *args):
|
|
|
41
42
|
return Result(result.returncode, result.stdout)
|
|
42
43
|
|
|
43
44
|
|
|
44
|
-
def flow_status(scripts, feature, mode):
|
|
45
|
-
"""The ticket graph reader with --next, --counts or --check, in process. stderr is folded into the output.
|
|
45
|
+
def flow_status(scripts, feature, mode, root=None):
|
|
46
|
+
"""The ticket graph reader with --next, --counts or --check, in process. stderr is folded into the output.
|
|
47
|
+
root, when given, stands in for FLOW_DIR for this one call (the planner's draft folder)."""
|
|
46
48
|
out = _Collect()
|
|
47
|
-
|
|
49
|
+
saved = os.environ.get("FLOW_DIR")
|
|
50
|
+
if root is not None:
|
|
51
|
+
os.environ["FLOW_DIR"] = str(root)
|
|
52
|
+
try:
|
|
53
|
+
code = status.run([feature, mode], out, out)
|
|
54
|
+
finally:
|
|
55
|
+
if root is not None:
|
|
56
|
+
if saved is None:
|
|
57
|
+
del os.environ["FLOW_DIR"]
|
|
58
|
+
else:
|
|
59
|
+
os.environ["FLOW_DIR"] = saved
|
|
48
60
|
return Result(code, out.text())
|
|
49
61
|
|
|
50
62
|
|
|
@@ -5,8 +5,10 @@ from pathlib import Path
|
|
|
5
5
|
|
|
6
6
|
from feature_flow.conductor import Conductor, NoPhase, Stop
|
|
7
7
|
|
|
8
|
-
USAGE = "usage: python3 scripts/flow.py <feature> start | next | prompt | verdict <file
|
|
9
|
-
|
|
8
|
+
USAGE = ("usage: python3 scripts/flow.py <feature> start | next | prompt | verdict <file>\n"
|
|
9
|
+
" 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)}
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
def main(argv=None, scripts=None):
|
|
@@ -15,7 +17,10 @@ def main(argv=None, scripts=None):
|
|
|
15
17
|
print(USAGE, file=sys.stderr)
|
|
16
18
|
return 2
|
|
17
19
|
feature, command = args[0], args[1]
|
|
18
|
-
if
|
|
20
|
+
if feature in (".", "..") or any(c in feature for c in "/\\:"):
|
|
21
|
+
print("the feature must be a plain folder name, not %s" % feature, file=sys.stderr)
|
|
22
|
+
return 2
|
|
23
|
+
if len(args) not in ARGS.get(command, (2,)):
|
|
19
24
|
print(USAGE, file=sys.stderr)
|
|
20
25
|
return 2
|
|
21
26
|
scripts = Path(scripts) if scripts else Path.cwd() / "scripts"
|
|
@@ -24,6 +29,12 @@ def main(argv=None, scripts=None):
|
|
|
24
29
|
conductor = Conductor(feature, scripts)
|
|
25
30
|
if command == "verdict":
|
|
26
31
|
line = conductor.verdict(args[2])
|
|
32
|
+
elif command == "plan-prompt":
|
|
33
|
+
line = conductor.plan_prompt(args[2], args[3] if len(args) == 4 else None)
|
|
34
|
+
elif command == "plan-review-prompt":
|
|
35
|
+
line = conductor.plan_review_prompt()
|
|
36
|
+
elif command == "plan-accept":
|
|
37
|
+
line = conductor.plan_accept()
|
|
27
38
|
elif command == "prompt":
|
|
28
39
|
line = conductor.prompt()
|
|
29
40
|
elif command == "start":
|
|
@@ -39,7 +50,11 @@ def main(argv=None, scripts=None):
|
|
|
39
50
|
conductor.log("STOP")
|
|
40
51
|
print(line)
|
|
41
52
|
return 1
|
|
42
|
-
|
|
53
|
+
out = sys.stdout
|
|
54
|
+
if command in ("plan-prompt", "plan-review-prompt") and hasattr(out, "reconfigure"):
|
|
55
|
+
# the plan guides hold non-ASCII text, which a Windows console code page cannot print
|
|
56
|
+
out.reconfigure(encoding="utf-8")
|
|
57
|
+
print(line, end="" if line.endswith("\n") else "\n", file=out)
|
|
43
58
|
return 0
|
|
44
59
|
|
|
45
60
|
|
|
@@ -7,6 +7,7 @@ caller, and prints exactly one line per command.
|
|
|
7
7
|
import os
|
|
8
8
|
import re
|
|
9
9
|
import secrets
|
|
10
|
+
import shutil
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
|
|
12
13
|
from feature_flow import checks, git, prompts, state, tickets
|
|
@@ -53,6 +54,9 @@ class Conductor:
|
|
|
53
54
|
except OSError as err:
|
|
54
55
|
raise Stop("cannot write the flow state in %s: %s. this session must be allowed to write there"
|
|
55
56
|
% (top / state.STATE_DIR, err.strerror or err))
|
|
57
|
+
self.draft_root = state.STATE_DIR / "draft"
|
|
58
|
+
self.draft = self.draft_root / feature
|
|
59
|
+
self.brief_file = state.STATE_DIR / ("brief-%s.md" % feature)
|
|
56
60
|
self.state_file = state.state_path(folder, feature)
|
|
57
61
|
self.log_file = state.log_path(folder, feature)
|
|
58
62
|
self.findings_file = state.file_path(folder, feature, "findings")
|
|
@@ -157,8 +161,10 @@ class Conductor:
|
|
|
157
161
|
check = checks.flow_status(self.scripts, self.feature, "--check")
|
|
158
162
|
if not check.ok:
|
|
159
163
|
raise Stop("ticket check failed, fix the tickets first")
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
dirty = git.changes()
|
|
165
|
+
if dirty:
|
|
166
|
+
raise Stop("working tree is not clean, commit or stash first: %s%s"
|
|
167
|
+
% (" ".join(dirty[:5]), " and %d more" % (len(dirty) - 5) if len(dirty) > 5 else ""))
|
|
162
168
|
nxt = checks.flow_status(self.scripts, self.feature, "--next")
|
|
163
169
|
if nxt.code == 10:
|
|
164
170
|
done = self.num("done")
|
|
@@ -297,3 +303,70 @@ class Conductor:
|
|
|
297
303
|
self.save()
|
|
298
304
|
self.log("VERDICT-%s" % (found or "none").upper())
|
|
299
305
|
return "OK" if found else "RETRY no review verdict"
|
|
306
|
+
|
|
307
|
+
# ---- planning: stateless, the brief and the draft are all there is --
|
|
308
|
+
|
|
309
|
+
def no_plan_yet(self):
|
|
310
|
+
if self.plan.is_dir():
|
|
311
|
+
raise Stop("%s already exists. pick another name: a plan is never overwritten" % self.plan.as_posix())
|
|
312
|
+
|
|
313
|
+
def draft_text(self):
|
|
314
|
+
return self.draft.as_posix() + "/"
|
|
315
|
+
|
|
316
|
+
def read_text(self, path, what):
|
|
317
|
+
try:
|
|
318
|
+
return Path(path).read_text(encoding="utf-8", errors="replace")
|
|
319
|
+
except OSError as err:
|
|
320
|
+
raise Stop("cannot read the %s %s: %s" % (what, path, err.strerror))
|
|
321
|
+
|
|
322
|
+
def plan_prompt(self, brief, findings=None):
|
|
323
|
+
"""The feature-planner's prompt. Saves the brief; a first round (no findings) starts an empty draft."""
|
|
324
|
+
self.no_plan_yet()
|
|
325
|
+
text = self.read_text(brief, "brief")
|
|
326
|
+
notes = self.read_text(findings, "review findings") if findings else ""
|
|
327
|
+
if not text.strip():
|
|
328
|
+
raise Stop("the brief %s is empty" % brief)
|
|
329
|
+
if Path(brief).resolve() != self.brief_file.resolve():
|
|
330
|
+
self.brief_file.write_text(text, encoding="utf-8")
|
|
331
|
+
if not findings and self.draft.is_dir():
|
|
332
|
+
if self.draft.resolve().parent != self.draft_root.resolve():
|
|
333
|
+
raise Stop("the draft folder %s is outside %s" % (self.draft_text(), self.draft_root.as_posix()))
|
|
334
|
+
shutil.rmtree(str(self.draft))
|
|
335
|
+
(self.draft / (os.environ.get("FLOW_TICKETS") or "tasks")).mkdir(parents=True, exist_ok=True)
|
|
336
|
+
self.log("PLAN-PROMPT")
|
|
337
|
+
try:
|
|
338
|
+
return prompts.plan("plan", self.scripts, self.feature, self.draft_text(), text, notes)
|
|
339
|
+
except FileNotFoundError as err:
|
|
340
|
+
raise Stop(str(err))
|
|
341
|
+
|
|
342
|
+
def plan_review_prompt(self):
|
|
343
|
+
"""The plan-reviewer's prompt: the saved brief and the draft, nothing from the planner."""
|
|
344
|
+
self.no_plan_yet()
|
|
345
|
+
if not self.brief_file.is_file():
|
|
346
|
+
raise Stop("no brief for %s. run plan-prompt first" % self.feature)
|
|
347
|
+
if not self.draft.is_dir():
|
|
348
|
+
raise Stop("no draft plan in %s. run the planner first" % self.draft_text())
|
|
349
|
+
self.log("PLAN-REVIEW-PROMPT")
|
|
350
|
+
try:
|
|
351
|
+
return prompts.plan("plan-review", self.scripts, self.feature, self.draft_text(),
|
|
352
|
+
self.read_text(self.brief_file, "brief"))
|
|
353
|
+
except FileNotFoundError as err:
|
|
354
|
+
raise Stop(str(err))
|
|
355
|
+
|
|
356
|
+
def plan_accept(self):
|
|
357
|
+
"""Copy a draft that passes the check into the plan folder. Never overwrites, never commits."""
|
|
358
|
+
self.no_plan_yet()
|
|
359
|
+
if not self.draft.is_dir():
|
|
360
|
+
raise Stop("no draft plan in %s. run the planner first" % self.draft_text())
|
|
361
|
+
check = checks.flow_status(self.scripts, self.feature, "--check", root=self.draft_root)
|
|
362
|
+
if not check.ok:
|
|
363
|
+
raise Stop("the draft fails the plan check, fix it first: %s"
|
|
364
|
+
% " ".join(check.out.split())[:400])
|
|
365
|
+
self.plan.parent.mkdir(parents=True, exist_ok=True)
|
|
366
|
+
shutil.copytree(str(self.draft), str(self.plan))
|
|
367
|
+
check = checks.flow_status(self.scripts, self.feature, "--check")
|
|
368
|
+
if not check.ok:
|
|
369
|
+
raise Stop("the accepted plan in %s fails the plan check: %s"
|
|
370
|
+
% (self.plan.as_posix(), " ".join(check.out.split())[:400]))
|
|
371
|
+
self.log("PLAN-ACCEPT")
|
|
372
|
+
return "OK %s" % self.plan.as_posix()
|
|
@@ -30,9 +30,35 @@ def head():
|
|
|
30
30
|
return _git("rev-parse", "HEAD").stdout.strip()
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
def changes():
|
|
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."""
|
|
36
|
+
installed = set()
|
|
37
|
+
top = toplevel()
|
|
38
|
+
if top is not None:
|
|
39
|
+
try:
|
|
40
|
+
installed = set((top / ".feature-flow" / "installed.txt").read_text(encoding="utf-8").splitlines())
|
|
41
|
+
except (OSError, UnicodeDecodeError):
|
|
42
|
+
pass
|
|
43
|
+
entries = _git("status", "--porcelain", "-z", "-uall").stdout.split("\0")
|
|
44
|
+
found = []
|
|
45
|
+
i = 0
|
|
46
|
+
while i < len(entries):
|
|
47
|
+
entry = entries[i]
|
|
48
|
+
i += 1
|
|
49
|
+
if len(entry) < 4:
|
|
50
|
+
continue
|
|
51
|
+
code, path = entry[:2], entry[3:]
|
|
52
|
+
if code[0] in "RC":
|
|
53
|
+
i += 1 # the rename's source path follows
|
|
54
|
+
if code == "??" and path in installed:
|
|
55
|
+
continue
|
|
56
|
+
found.append(path)
|
|
57
|
+
return found
|
|
58
|
+
|
|
59
|
+
|
|
33
60
|
def is_clean():
|
|
34
|
-
|
|
35
|
-
return _git("status", "--porcelain").stdout.strip() == ""
|
|
61
|
+
return not changes()
|
|
36
62
|
|
|
37
63
|
|
|
38
64
|
def tracked_clean():
|
|
@@ -39,6 +39,9 @@ BUNDLE = "_bundle"
|
|
|
39
39
|
|
|
40
40
|
# the skills this installs
|
|
41
41
|
SKILLS = ("feature-flow", "architect-review", "automation-design")
|
|
42
|
+
# the in-repo files an install wrote, one path per line, relative to the target. The conductor does not
|
|
43
|
+
# count them as a dirty tree while they are untracked, and `git add --pathspec-from-file` commits them.
|
|
44
|
+
INSTALLED = ".feature-flow/installed.txt"
|
|
42
45
|
|
|
43
46
|
# an old skill that runs the ticket script (the bash one or the Python one) or ends a review with a verdict
|
|
44
47
|
LEFTOVER = re.compile(rb"scripts/flow-status\.(?:sh|py)|REVIEW: PASS")
|
|
@@ -169,6 +172,7 @@ class Installer:
|
|
|
169
172
|
package = os.path.dirname(os.path.abspath(__file__))
|
|
170
173
|
self.package = package
|
|
171
174
|
self.added = self.updated = self.same = self.kept = 0
|
|
175
|
+
self.written = []
|
|
172
176
|
|
|
173
177
|
def place(self, file, dest, data=None):
|
|
174
178
|
"""One file: add it, replace it (--force), leave it alone (same) or keep the user's version.
|
|
@@ -177,14 +181,17 @@ class Installer:
|
|
|
177
181
|
if not os.path.exists(dest):
|
|
178
182
|
self.out(" add " + dest)
|
|
179
183
|
self.added += 1
|
|
184
|
+
self.written.append(dest)
|
|
180
185
|
if not self.dry:
|
|
181
186
|
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
|
182
187
|
self._copy(file, dest, data)
|
|
183
188
|
elif new is not None and _read(dest) == new:
|
|
184
189
|
self.same += 1
|
|
190
|
+
self.written.append(dest)
|
|
185
191
|
elif self.force:
|
|
186
192
|
self.out(" update " + dest)
|
|
187
193
|
self.updated += 1
|
|
194
|
+
self.written.append(dest)
|
|
188
195
|
if not self.dry:
|
|
189
196
|
self._copy(file, dest, data)
|
|
190
197
|
else:
|
|
@@ -235,6 +242,23 @@ class Installer:
|
|
|
235
242
|
self.place(file, self.target + "/.agents/flow-roles/" + name,
|
|
236
243
|
role_body(data if data is not None else b""))
|
|
237
244
|
|
|
245
|
+
def write_installed(self):
|
|
246
|
+
"""Add this run's in-repo files to the list, keeping the earlier runs' files that still exist."""
|
|
247
|
+
path = self.target + "/" + INSTALLED
|
|
248
|
+
names = set()
|
|
249
|
+
try:
|
|
250
|
+
with open(path, encoding="utf-8") as old:
|
|
251
|
+
names.update(line.strip() for line in old)
|
|
252
|
+
except OSError:
|
|
253
|
+
pass
|
|
254
|
+
names = set(n for n in names if n and os.path.isfile(self.target + "/" + n))
|
|
255
|
+
prefix = self.target + "/"
|
|
256
|
+
names.update(dest[len(prefix):] for dest in self.written if dest.startswith(prefix))
|
|
257
|
+
names.add(INSTALLED)
|
|
258
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
259
|
+
with open(path, "w", encoding="utf-8", newline="\n") as new:
|
|
260
|
+
new.write("".join(n + "\n" for n in sorted(names)))
|
|
261
|
+
|
|
238
262
|
def report_leftovers(self, dirs):
|
|
239
263
|
"""A skill folder this installer does not own but that drives the flow (it runs the ticket
|
|
240
264
|
scripts or ends a review with a verdict) is left from an earlier version: named once, never deleted."""
|
|
@@ -270,6 +294,8 @@ class Installer:
|
|
|
270
294
|
self.copy_tree(here + "/guides", self.target + "/.feature-flow/guides")
|
|
271
295
|
self.copy_tree(here + "/agents", self.target + "/.feature-flow/agents")
|
|
272
296
|
self.copy_tree(self.package, self.target + "/.feature-flow/feature_flow", skip=BUNDLE)
|
|
297
|
+
if not self.dry:
|
|
298
|
+
self.write_installed()
|
|
273
299
|
|
|
274
300
|
self.out("")
|
|
275
301
|
verb = "would add" if self.dry else "added"
|
|
@@ -285,6 +311,12 @@ class Installer:
|
|
|
285
311
|
git = 127
|
|
286
312
|
if git != 0:
|
|
287
313
|
self.out("note: %s is not a git repository, and the flow needs one" % self.target)
|
|
314
|
+
elif self.added or self.updated:
|
|
315
|
+
git_c = ""
|
|
316
|
+
if os.path.realpath(self.target) != os.path.realpath(os.getcwd()):
|
|
317
|
+
git_c = '-C "%s" ' % self.target if " " in self.target else "-C %s " % self.target
|
|
318
|
+
self.out('next: commit the installed files: git %sadd --pathspec-from-file=%s'
|
|
319
|
+
' && git %scommit -m "chore: install feature-flow"' % (git_c, INSTALLED, git_c))
|
|
288
320
|
if not shutil.which("python3"):
|
|
289
321
|
self.out("note: install python3 (3.9 or later): scripts/flow.py needs it")
|
|
290
322
|
if agent in ("claude", "all"):
|
|
@@ -5,8 +5,9 @@ The text is runtime neutral: any agent that can read a prompt can follow it.
|
|
|
5
5
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
|
-
ROLES = {"build": "ticket-builder.md", "review": "ticket-reviewer.md"
|
|
9
|
-
|
|
8
|
+
ROLES = {"build": "ticket-builder.md", "review": "ticket-reviewer.md",
|
|
9
|
+
"plan": "feature-planner.md", "plan-review": "plan-reviewer.md"}
|
|
10
|
+
GUIDES = {"build": "build.md", "review": "review.md", "plan": "plan.md", "plan-review": "plan-review.md"}
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def find_file(scripts, folder, name):
|
|
@@ -50,3 +51,28 @@ def build(phase, scripts, feature, ticket, num, sha):
|
|
|
50
51
|
"with exactly `REVIEW: PASS` or `REVIEW: FAIL`." % (num, feature, feature, num, sha, ticket, sha, fresh))
|
|
51
52
|
intro = "The review guide follows. Follow it exactly."
|
|
52
53
|
return "\n\n".join([role, "---", intro, guide, "---", task]) + "\n"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def plan(phase, scripts, feature, draft, brief, findings=""):
|
|
57
|
+
"""The prompt for the feature-planner (phase "plan") or the plan-reviewer ("plan-review").
|
|
58
|
+
|
|
59
|
+
Both get the approved brief and the draft folder, never the conversation or each other's reasoning.
|
|
60
|
+
"""
|
|
61
|
+
role = _body(find_file(scripts, "agents", ROLES[phase]))
|
|
62
|
+
guide = _body(find_file(scripts, "guides", GUIDES[phase]))
|
|
63
|
+
guide = guide.replace("<feature>", feature).replace("<draft>", draft)
|
|
64
|
+
if phase == "plan":
|
|
65
|
+
task = ("Your task: plan the feature `%s` from the approved brief below. Write the draft only inside "
|
|
66
|
+
"`%s`, laid out as the plan guide says. Do not write anywhere else and do not commit. Your final "
|
|
67
|
+
"reply must end with exactly `PLAN: READY` or `PLAN: QUESTIONS`." % (feature, draft))
|
|
68
|
+
intro = "The plan guide follows. Follow it exactly."
|
|
69
|
+
else:
|
|
70
|
+
task = ("Your task: review the draft plan for the feature `%s` in `%s` against the approved brief below. "
|
|
71
|
+
"Do not edit any file or create a commit. Your final reply must end with exactly "
|
|
72
|
+
"`PLAN-REVIEW: PASS` or `PLAN-REVIEW: FAIL`." % (feature, draft))
|
|
73
|
+
intro = "The plan review guide follows. Follow it exactly."
|
|
74
|
+
parts = [role, "---", intro, guide, "---", task, "## The brief", brief.strip("\n")]
|
|
75
|
+
if findings.strip():
|
|
76
|
+
parts += ["## Review findings from the last round", "The draft is already in `%s`. Fix each finding in it, "
|
|
77
|
+
"or say in your reply why it stays." % draft, findings.strip("\n")]
|
|
78
|
+
return "\n\n".join(parts) + "\n"
|