@xenonbyte/req-2-plan 0.4.0 → 0.4.1
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.
- package/README.md +32 -8
- package/README.zh-CN.md +28 -7
- package/package.json +1 -1
- package/tools/workflow_cli/agent_templates/claude/SKILL.md +1 -0
- package/tools/workflow_cli/cli.py +24 -13
- package/tools/workflow_cli/context_pack.py +15 -7
- package/tools/workflow_cli/trace.py +5 -2
- package/tools/workflow_cli/version.py +1 -1
package/README.md
CHANGED
|
@@ -67,17 +67,31 @@ existed beforehand.
|
|
|
67
67
|
|
|
68
68
|
### Quick start
|
|
69
69
|
|
|
70
|
+
Install the platform skills, then check what landed (terminal, lifecycle CLI):
|
|
71
|
+
|
|
70
72
|
```bash
|
|
71
|
-
r2p install
|
|
72
|
-
r2p
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
r2p install # install all platforms (default)
|
|
74
|
+
r2p status # see what is installed
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then drive the workflow from your agent — the installed skills call the `r2p-*`
|
|
78
|
+
wrappers (to run them in a terminal instead, add `~/.req-to-plan/bin` to `PATH`,
|
|
79
|
+
see the tip below):
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
/r2p-start --repo-path . "Add rate limiting" # requirement as inline text
|
|
83
|
+
/r2p-start --repo-path . --file change-req.md # requirement as a document
|
|
84
|
+
/r2p-continue # advance it stage by stage
|
|
75
85
|
```
|
|
76
86
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
A requirement can be inline text or a document passed with `--file <path>`
|
|
88
|
+
(the two are mutually exclusive). **Whenever a code repository is the
|
|
89
|
+
requirement's context, pass `--repo-path`** — `.` for the current project,
|
|
90
|
+
the target repo's path for cross-repo work; it generates the Project Context
|
|
91
|
+
Pack that grounds tier estimation and PLAN file-reference checks. Keep options
|
|
92
|
+
before the requirement text (as above) so a quoting slip in free-form text can
|
|
93
|
+
never swallow an option. If a standard-tier PLAN gate later reports a missing
|
|
94
|
+
or unusable Context Pack, build it mid-run with
|
|
81
95
|
the `PYTHONPATH=... <python> -m tools.workflow_cli context-build ...` command printed
|
|
82
96
|
by the gate (there is no standalone `context-build` executable).
|
|
83
97
|
|
|
@@ -174,6 +188,16 @@ r2p-gap-resolve --work-id <id> --route-id R-1
|
|
|
174
188
|
> walks you through both repair flows with `needs_repair` and `needs_gap_resolve`
|
|
175
189
|
> stops.
|
|
176
190
|
|
|
191
|
+
> [!NOTE]
|
|
192
|
+
> **Human decision points (standard DESIGN).** When a standard-tier DESIGN
|
|
193
|
+
> involves a choice a human must make (new dependency, migration strategy,
|
|
194
|
+
> API compatibility), the agent records it in the `## Decision Requests`
|
|
195
|
+
> section as a `### DECISION-NNN` block with `Question:`, `Options:`,
|
|
196
|
+
> `Recommended:`, and `Status: pending` — and a pending decision fails
|
|
197
|
+
> `gate-quality` until a human chooses and the block becomes
|
|
198
|
+
> `Status: selected` with `Selected:` and `Rationale:` lines.
|
|
199
|
+
> Write exactly `none` in that section when no human decision is needed.
|
|
200
|
+
|
|
177
201
|
## License
|
|
178
202
|
|
|
179
203
|
[MIT](./LICENSE) © xenonbyte
|
package/README.zh-CN.md
CHANGED
|
@@ -61,16 +61,28 @@ r2p help
|
|
|
61
61
|
|
|
62
62
|
### Quick start
|
|
63
63
|
|
|
64
|
+
先在终端用生命周期 CLI 安装平台技能并确认安装结果:
|
|
65
|
+
|
|
64
66
|
```bash
|
|
65
|
-
r2p install
|
|
66
|
-
r2p
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
r2p install # 安装全部平台(默认)
|
|
68
|
+
r2p status # 查看已安装情况
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
然后在 agent 里驱动工作流——已安装的平台技能会调用 `r2p-*` 包装器
|
|
72
|
+
(如需在终端手动执行,先把 `~/.req-to-plan/bin` 加入 `PATH`,见下方 tip):
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
/r2p-start --repo-path . "Add rate limiting" # 需求为内联文本
|
|
76
|
+
/r2p-start --repo-path . --file change-req.md # 需求为文档文件
|
|
77
|
+
/r2p-continue # 逐阶段推进
|
|
69
78
|
```
|
|
70
79
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
80
|
+
需求可以是内联文本,也可以用 `--file <path>` 传入文档(两者互斥)。
|
|
81
|
+
**只要需求以某个代码仓库为上下文,就必须传 `--repo-path`**——当前项目传 `.`,
|
|
82
|
+
跨仓库需求传目标仓库路径;它生成的 Project Context Pack 是 tier 估算与 PLAN
|
|
83
|
+
文件引用校验的真值锚点。选项写在需求文本之前(如上例),这样即使自由文本
|
|
84
|
+
引号写错也不会吞掉选项。若 standard tier 的 PLAN gate 提示 Context Pack
|
|
85
|
+
缺失/不可用,直接执行 gate 打印的
|
|
74
86
|
`PYTHONPATH=... <python> -m tools.workflow_cli context-build ...` 命令中途补建
|
|
75
87
|
(不存在独立的 `context-build` 可执行文件)。
|
|
76
88
|
|
|
@@ -159,6 +171,15 @@ r2p-gap-resolve --work-id <id> --route-id R-1
|
|
|
159
171
|
> reopen 针对**已关闭**的 run;gap 路由针对**开着**的 run。`r2p-continue` 会用
|
|
160
172
|
> `needs_repair` 和 `needs_gap_resolve` 停点带你走完这两种修复流程。
|
|
161
173
|
|
|
174
|
+
> [!NOTE]
|
|
175
|
+
> **人工决策点(standard DESIGN)。** 当 standard tier 的 DESIGN 涉及必须由人
|
|
176
|
+
> 决定的选择(引入新依赖、迁移策略、API 兼容性)时,agent 会在 `## Decision
|
|
177
|
+
> Requests` 章节写入 `### DECISION-NNN` block(含 `Question:`/`Options:`/
|
|
178
|
+
> `Recommended:`)并标记 `Status: pending` ——存在 pending 决策时
|
|
179
|
+
> `gate-quality` 会失败,直到人选定方案、block 改为 `Status: selected`
|
|
180
|
+
> 并补上 `Selected:` 与 `Rationale:` 行。
|
|
181
|
+
> 无需人工决策时,该章节须恰好写 `none`。
|
|
182
|
+
|
|
162
183
|
## License
|
|
163
184
|
|
|
164
185
|
[MIT](./LICENSE) © xenonbyte
|
package/package.json
CHANGED
|
@@ -33,4 +33,5 @@ Run each via Bash using the scripts in `{{R2P_BIN_DIR}}`:
|
|
|
33
33
|
- Auto-advances: moves to the next stage after non-PLAN checkpoint approval, then runs that stage's entry gate
|
|
34
34
|
- Auto-closes: closes the run when the PLAN checkpoint is approved and no open routes remain
|
|
35
35
|
- Does NOT auto-mark artifacts ready and does NOT auto-approve checkpoints — those are human steps
|
|
36
|
+
- Standard-tier DESIGN: record any human technical choice in `## Decision Requests` as a `### DECISION-NNN` block (`Question:`/`Options:`/`Recommended:`/`Status: pending`); pending blocks `gate-quality` until a human selects (`Status: selected` + `Selected:`/`Rationale:`), or write exactly `none` when no decision is needed
|
|
36
37
|
3. When the run closes, hand the approved PLAN at `07-plan.md` directly to your executor — the PLAN is executor-neutral and needs no adaptation step.
|
|
@@ -790,23 +790,22 @@ def _cmd_tier_escalate(args):
|
|
|
790
790
|
previous_tier = record.tier_locked
|
|
791
791
|
record.tier_locked = record.tier_locked.escalate(modifier)
|
|
792
792
|
|
|
793
|
-
|
|
793
|
+
gate_passed_statuses = {
|
|
794
794
|
RunStatus.READY_FOR_CHECKPOINT_REVIEW,
|
|
795
795
|
RunStatus.CHECKPOINT_REVIEW,
|
|
796
796
|
}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
and previous_tier.base != TierBase.STANDARD
|
|
797
|
+
standard_gate_became_applicable = (
|
|
798
|
+
previous_tier.base != TierBase.STANDARD
|
|
800
799
|
and record.tier_locked.base == TierBase.STANDARD
|
|
801
|
-
and record.status in
|
|
800
|
+
and record.status in gate_passed_statuses
|
|
802
801
|
)
|
|
803
|
-
if
|
|
802
|
+
if standard_gate_became_applicable:
|
|
804
803
|
record = update_run_status(record, RunStatus.ACTIVE_STAGE_DRAFT)
|
|
805
804
|
update_resume_context(
|
|
806
805
|
record,
|
|
807
806
|
last_operation=f"tier_escalated_{modifier.value}",
|
|
808
807
|
next_operation="gate_quality",
|
|
809
|
-
active_item=
|
|
808
|
+
active_item=record.current_stage.value,
|
|
810
809
|
)
|
|
811
810
|
|
|
812
811
|
# Revoke affected bundle authorizations that cover high-tier stages
|
|
@@ -822,14 +821,26 @@ def _cmd_tier_escalate(args):
|
|
|
822
821
|
ba.revoked_at = revoke_ts
|
|
823
822
|
|
|
824
823
|
mgr.save(record)
|
|
824
|
+
data = {
|
|
825
|
+
"work_id": str(record.work_id),
|
|
826
|
+
"tier_base": record.tier_locked.base.value,
|
|
827
|
+
"modifiers": sorted(m.value for m in record.tier_locked.modifiers),
|
|
828
|
+
"added_modifier": modifier.value,
|
|
829
|
+
}
|
|
830
|
+
if (
|
|
831
|
+
previous_tier.base != TierBase.STANDARD
|
|
832
|
+
and record.tier_locked.base == TierBase.STANDARD
|
|
833
|
+
and STAGE_ORDER.index(record.current_stage) > STAGE_ORDER.index(Stage.DESIGN)
|
|
834
|
+
):
|
|
835
|
+
data["note"] = (
|
|
836
|
+
"DESIGN was approved under light tier; if this escalation "
|
|
837
|
+
"changes design decisions, run r2p-gap-open --work-id "
|
|
838
|
+
f"{record.work_id} --owner-stage design "
|
|
839
|
+
'--required-action "<describe the design impact>"'
|
|
840
|
+
)
|
|
825
841
|
print_and_exit(
|
|
826
842
|
format_success(
|
|
827
|
-
|
|
828
|
-
"work_id": str(record.work_id),
|
|
829
|
-
"tier_base": record.tier_locked.base.value,
|
|
830
|
-
"modifiers": sorted(m.value for m in record.tier_locked.modifiers),
|
|
831
|
-
"added_modifier": modifier.value,
|
|
832
|
-
},
|
|
843
|
+
data,
|
|
833
844
|
message=f"Tier escalated with modifier: {modifier.value}",
|
|
834
845
|
),
|
|
835
846
|
EXIT_OK,
|
|
@@ -22,7 +22,7 @@ class ProjectContextPack:
|
|
|
22
22
|
package_managers: list = field(default_factory=list)
|
|
23
23
|
test_commands: list = field(default_factory=list)
|
|
24
24
|
entrypoints: list = field(default_factory=list)
|
|
25
|
-
dependencies: list = field(default_factory=list) # [{name, version, ecosystem}]
|
|
25
|
+
dependencies: list = field(default_factory=list) # [{name, version, ecosystem, dev?}]
|
|
26
26
|
config_files: list = field(default_factory=list)
|
|
27
27
|
source_dirs: list = field(default_factory=list)
|
|
28
28
|
|
|
@@ -35,13 +35,21 @@ def build_context_pack(repo_path: Path) -> ProjectContextPack:
|
|
|
35
35
|
pkg = repo_path / "package.json"
|
|
36
36
|
if pkg.exists():
|
|
37
37
|
try:
|
|
38
|
-
|
|
38
|
+
raw_data = json.loads(pkg.read_text(encoding="utf-8"))
|
|
39
39
|
pack.package_managers.append("npm")
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
data = raw_data if isinstance(raw_data, dict) else {}
|
|
41
|
+
scripts = data.get("scripts")
|
|
42
|
+
if isinstance(scripts, dict) and scripts.get("test"):
|
|
43
|
+
pack.test_commands.append("npm test")
|
|
44
|
+
dependencies = data.get("dependencies")
|
|
45
|
+
if isinstance(dependencies, dict):
|
|
46
|
+
for name, ver in dependencies.items():
|
|
47
|
+
pack.dependencies.append({"name": name, "version": ver, "ecosystem": "npm"})
|
|
48
|
+
dev_dependencies = data.get("devDependencies")
|
|
49
|
+
if isinstance(dev_dependencies, dict):
|
|
50
|
+
for name, ver in dev_dependencies.items():
|
|
51
|
+
pack.dependencies.append(
|
|
52
|
+
{"name": name, "version": ver, "ecosystem": "npm", "dev": True})
|
|
45
53
|
except (ValueError, OSError):
|
|
46
54
|
pass
|
|
47
55
|
|
|
@@ -146,7 +146,9 @@ def _risk_blocks(content: str) -> dict[str, str]:
|
|
|
146
146
|
|
|
147
147
|
|
|
148
148
|
def scope_in_not_closed(run_dir: Path) -> list[str]:
|
|
149
|
-
"""SCOPE-IN closes only when a PLAN-TASK carries it or consumes a SPEC
|
|
149
|
+
"""SCOPE-IN closes only when a PLAN-TASK carries it or consumes a SPEC
|
|
150
|
+
carrying it outside the SPEC block's nested Non-goals subsections (R14:
|
|
151
|
+
'explicitly not implemented here' must not close the scope item)."""
|
|
150
152
|
model = build_trace(run_dir)
|
|
151
153
|
plan_text = _artifact_text(run_dir, Stage.PLAN)
|
|
152
154
|
plan_task_text = "\n".join(unfenced_markdown_text(body) for body in _plan_task_bodies(plan_text))
|
|
@@ -156,7 +158,8 @@ def scope_in_not_closed(run_dir: Path) -> list[str]:
|
|
|
156
158
|
for id_ in sorted(i for i in model.defined if i.startswith("SCOPE-IN-")):
|
|
157
159
|
if id_ in plan_task_text:
|
|
158
160
|
continue
|
|
159
|
-
if any(id_ in spec_blocks.get(spec_id, "")
|
|
161
|
+
if any(id_ in _strip_nested_non_goals(spec_blocks.get(spec_id, ""))
|
|
162
|
+
for spec_id in consumed_specs):
|
|
160
163
|
continue
|
|
161
164
|
issues.append(id_)
|
|
162
165
|
return issues
|
|
@@ -1 +1 @@
|
|
|
1
|
-
R2P_VERSION = "0.4.
|
|
1
|
+
R2P_VERSION = "0.4.1"
|