@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 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 # install all platforms (default)
72
- r2p-start "Add rate limiting" --repo-path . # start a run grounded in this repo's facts
73
- r2p-continue # advance it stage by stage
74
- r2p status # see what is installed
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
- Pass `--repo-path .` whenever the requirement targets the current project (use the
78
- target repo's path for cross-repo work); it generates the Project Context Pack that
79
- grounds tier estimation and PLAN file-reference checks. If a standard-tier PLAN gate
80
- later reports a missing or unusable Context Pack, build it mid-run with
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-start "Add rate limiting" --repo-path . # 启动一次以当前仓库事实为锚点的工作流
67
- r2p-continue # 逐阶段推进
68
- r2p status # 查看已安装情况
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
- 需求针对当前项目时必传 `--repo-path .`(跨仓库需求传目标仓库路径);它生成的
72
- Project Context Pack 是 tier 估算与 PLAN 文件引用校验的真值锚点。若 standard tier
73
- PLAN gate 提示 Context Pack 缺失/不可用,直接执行 gate 打印的
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenonbyte/req-2-plan",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Requirement-to-PLAN workflow CLI and agent integration installer.",
5
5
  "bin": {
6
6
  "r2p": "bin/r2p.js"
@@ -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
- plan_gate_passed_statuses = {
793
+ gate_passed_statuses = {
794
794
  RunStatus.READY_FOR_CHECKPOINT_REVIEW,
795
795
  RunStatus.CHECKPOINT_REVIEW,
796
796
  }
797
- standard_plan_gate_became_applicable = (
798
- record.current_stage == Stage.PLAN
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 plan_gate_passed_statuses
800
+ and record.status in gate_passed_statuses
802
801
  )
803
- if standard_plan_gate_became_applicable:
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=Stage.PLAN.value,
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
- data = json.loads(pkg.read_text(encoding="utf-8"))
38
+ raw_data = json.loads(pkg.read_text(encoding="utf-8"))
39
39
  pack.package_managers.append("npm")
40
- test = (data.get("scripts") or {}).get("test")
41
- if test:
42
- pack.test_commands.append(test)
43
- for name, ver in (data.get("dependencies") or {}).items():
44
- pack.dependencies.append({"name": name, "version": ver, "ecosystem": "npm"})
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 carrying it."""
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, "") for spec_id in consumed_specs):
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.0"
1
+ R2P_VERSION = "0.4.1"