Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/plan-issue-cli/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Purpose
Crate-local documentation for `nils-plan-issue-cli`.

`Task Decomposition` is the crate's documented runtime-truth execution table for plan/sprint orchestration. Specs define `Owner` as a dispatch alias, document `group + auto` single-lane normalization to `per-sprint`, and treat task-spec/subagent prompts as derived artifacts (not a second issue-body dispatch table).
`Task Decomposition` is the crate's documented runtime-truth execution table for plan/sprint orchestration. Specs define `Owner` as a dispatch alias, document `group + auto` single-lane normalization to `per-sprint`, and treat task-spec/subagent prompts as derived artifacts (not a second issue-body dispatch table). `start-sprint` validates drift against plan-derived lanes and does not rewrite issue rows in runtime-truth mode.

## Specs
- [plan-issue CLI contract v1](specs/plan-issue-cli-contract-v1.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ v1 subcommands:
| `ready-plan` | Request final plan review | yes (unless `--body-file` only mode) | `--issue` or `--body-file` | review-ready signal (+ label/comment controls) |
| `close-plan` | Final gate + issue close | yes (except dry-run with `--body-file`) | `--approved-comment-url` and issue context | issue close + required worktree cleanup |
| `cleanup-worktrees` | Remove all issue-assigned worktrees | yes (to read issue body) | `--issue` | deleted worktree set (or dry-run listing) |
| `start-sprint` | Begin sprint execution on existing plan issue | yes (unless dry-run) | `--plan`, `--issue`, `--sprint`, `--pr-grouping` | sprint TSV, rendered subagent prompts, issue row sync, kickoff comment |
| `start-sprint` | Begin sprint execution on existing plan issue | yes (unless dry-run) | `--plan`, `--issue`, `--sprint`, `--pr-grouping` | sprint TSV, rendered subagent prompts, issue-row runtime-truth validation, kickoff comment |
| `ready-sprint` | Request sprint acceptance review | yes | `--plan`, `--issue`, `--sprint` | sprint review-ready comment |
| `accept-sprint` | Enforce merged-PR gate and mark sprint done | yes | `--plan`, `--issue`, `--sprint`, `--approved-comment-url` | task status sync to `done` + acceptance comment |
| `multi-sprint-guide` | Print repeatable command flow | no (with `--dry-run`) | `--plan` | execution guide text |
Expand All @@ -58,7 +58,7 @@ v1 subcommands:
- `--pr-grouping <per-sprint|group>`:
- required for `build-task-spec`, `build-plan-task-spec`, `start-plan`, `start-sprint`.
- `per-spring` must be accepted as compatibility alias for `per-sprint`.
- with `--pr-grouping group --strategy auto`, when a sprint resolves to exactly one shared PR group, issue-sync/render paths normalize `Execution Mode` to `per-sprint` (single-lane semantics).
- with `--pr-grouping group --strategy auto`, when a sprint resolves to exactly one shared PR group, runtime-truth/render paths normalize `Execution Mode` to `per-sprint` (single-lane semantics).
- `--pr-group <task=group>`:
- repeatable.
- valid only when `--pr-grouping group`.
Expand Down
31 changes: 16 additions & 15 deletions crates/plan-issue-cli/docs/specs/plan-issue-gate-matrix-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,28 @@ execution.
| `G6` | Worktree cleanup gate | `cleanup-worktrees`, successful `close-plan` | targeted linked worktrees removed, prune succeeds, no targeted residues | `1` |
| `G7` | Dry-run non-mutation gate | all commands with `--dry-run` | command prints intended actions and performs no GitHub mutation | `1` |
| `G8` | Close-plan dry-run body-file gate | `close-plan --dry-run` | `--body-file` is provided for local gate evaluation | `2` |
| `G9` | Runtime-truth drift gate | `start-sprint` | sprint issue rows match plan-derived runtime lane metadata before artifact render | `1` |

## Command-to-Gate Matrix

| Command | G0 | G1 | G2 | G3 | G4 | G5 | G6 | G7 | G8 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `build-task-spec` | required | - | - | - | - | - | - | optional | - |
| `build-plan-task-spec` | required | - | - | - | - | - | - | optional | - |
| `start-plan` | required | - | - | - | - | - | - | optional | - |
| `status-plan` | required | required (issue/body mode) | - | - | - | - | - | optional | - |
| `ready-plan` | required | required (issue/body mode) | - | - | - | - | - | optional | - |
| `close-plan` | required | required | required | - | - | required | required (on success path) | optional | required when dry-run |
| `cleanup-worktrees` | required | required | - | - | - | - | required | optional | - |
| `start-sprint` | required | required | required for `N > 1` | required for `N > 1` | - | - | - | optional | - |
| `ready-sprint` | required | required | - | - | - | - | - | optional | - |
| `accept-sprint` | required | required | required | - | required | - | - | optional | - |
| `multi-sprint-guide` | required | - | - | - | - | - | - | optional | - |
| Command | G0 | G1 | G2 | G3 | G4 | G5 | G6 | G7 | G8 | G9 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `build-task-spec` | required | - | - | - | - | - | - | optional | - | - |
| `build-plan-task-spec` | required | - | - | - | - | - | - | optional | - | - |
| `start-plan` | required | - | - | - | - | - | - | optional | - | - |
| `status-plan` | required | required (issue/body mode) | - | - | - | - | - | optional | - | - |
| `ready-plan` | required | required (issue/body mode) | - | - | - | - | - | optional | - | - |
| `close-plan` | required | required | required | - | - | required | required (on success path) | optional | required when dry-run | - |
| `cleanup-worktrees` | required | required | - | - | - | - | required | optional | - | - |
| `start-sprint` | required | required | required for `N > 1` | required for `N > 1` | - | - | - | optional | - | required |
| `ready-sprint` | required | required | - | - | - | - | - | optional | - | - |
| `accept-sprint` | required | required | required | - | required | - | - | optional | - | - |
| `multi-sprint-guide` | required | - | - | - | - | - | - | optional | - | - |

## Gate Evaluation Order (Normative)
1. `G0` usage/argument validation.
2. Command-specific structural checks (`G1`) before remote mutations.
3. Progression/merge gates (`G2`, `G3`, `G4`, `G5`) in command-specific order.
3. Progression/merge/drift gates (`G2`, `G3`, `G4`, `G5`, `G9`) in command-specific order.
4. Cleanup gate (`G6`) only after command gate success where cleanup is required.
5. Dry-run behavior (`G7`, `G8`) wraps command execution and must preserve non-mutation semantics.

Expand All @@ -62,5 +63,5 @@ execution.

## Failure Contract
- `exit 2`: usage errors (`G0`, `G8`) and invalid required inputs.
- `exit 1`: gate failures (`G1` through `G7`) and runtime dependency failures.
- `exit 1`: gate failures (`G1` through `G7`, `G9`) and runtime dependency failures.
- `exit 0`: all applicable gates pass.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ States per sprint `N`:
Transitions:
- `start-sprint N`: `SPRINT_NOT_STARTED -> SPRINT_IN_PROGRESS`
- Renders sprint task-spec and subagent prompts.
- Syncs Task Decomposition execution metadata from task-spec.
- Validates Task Decomposition runtime-truth rows against plan-derived sprint lanes.
- Derives sprint task-spec and prompt artifacts from runtime-truth issue rows (no row rewrite).
- For `N > 1`, requires previous sprint merge gate pass (see gate invariants).
- `ready-sprint N`: `SPRINT_IN_PROGRESS -> SPRINT_REVIEW_READY`
- Posts or prints sprint-ready review artifact.
Expand Down
2 changes: 1 addition & 1 deletion crates/plan-issue-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ pub enum Command {
/// Enforce cleanup of all issue-assigned task worktrees.
CleanupWorktrees(CleanupWorktreesArgs),

/// Start sprint only after previous sprint merge+done gate passes.
/// Start sprint from Task Decomposition runtime truth after previous sprint merge+done gate passes.
StartSprint(StartSprintArgs),

/// Post sprint-ready comment for main-agent review before merge.
Expand Down
2 changes: 1 addition & 1 deletion crates/plan-issue-cli/tests/fixtures/shell_parity/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Subcommands:
ready-plan Wrapper of issue-delivery-loop ready-for-review for final plan review
close-plan Close the single plan issue after final approval + merged PR gates, then enforce worktree cleanup
cleanup-worktrees Enforce cleanup of all issue-assigned task worktrees
start-sprint Start sprint only after previous sprint merge+done gate passes
start-sprint Start sprint from Task Decomposition runtime truth after previous sprint merge+done gate passes
ready-sprint Post sprint-ready comment for main-agent review before merge
accept-sprint Enforce merged-PR gate, sync sprint status=done, then post accepted comment
multi-sprint-guide Print the full repeated command flow for a plan (1 plan = 1 issue)
Expand Down
47 changes: 23 additions & 24 deletions crates/plan-issue-cli/tests/live_issue_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,12 @@ fn issue_body_with_preface(task_rows: &str) -> String {
## Overview

- This plan delivers a shell-free Rust implementation for the current plan-issue orchestration workflow.
- The issue body keeps pre-sprint context so sprint commands only sync task table rows.
- The issue body keeps pre-sprint context and uses Task Decomposition as runtime truth.

## Scope

- Maintain one plan issue for the full multi-sprint workflow.
- Keep pre-sprint sections stable when sprint commands update Task Decomposition.
- Keep pre-sprint sections stable while sprint commands read/validate runtime-truth rows.

## Task Decomposition

Expand All @@ -131,9 +131,9 @@ fn issue_body_sprint4_planned() -> String {
r#"| S3T1 | Implement task-spec generation core using `plan-tooling` | subagent-s3-t1 | issue/s3-t1-implement-task-spec-generation-core-using-plan-t | issue-s3-t1 | per-sprint | #221 | done | sprint=S3; plan-task:Task 3.1 |
| S3T2 | Implement issue-body and sprint-comment rendering engine | subagent-s3-t1 | issue/s3-t1-implement-task-spec-generation-core-using-plan-t | issue-s3-t1 | per-sprint | #221 | done | sprint=S3; plan-task:Task 3.2 |
| S3T3 | Implement independent local dry-run workflow | subagent-s3-t1 | issue/s3-t1-implement-task-spec-generation-core-using-plan-t | issue-s3-t1 | per-sprint | #221 | done | sprint=S3; plan-task:Task 3.3 |
| S4T1 | Implement GitHub adapter abstraction and `gh` backend | TBD | TBD | TBD | TBD | TBD | planned | sprint=S4; plan-task:Task 4.1 |
| S4T2 | Implement live plan-level commands | TBD | TBD | TBD | TBD | TBD | planned | sprint=S4; plan-task:Task 4.2 |
| S4T3 | Implement live sprint-level commands and guide output | TBD | TBD | TBD | TBD | TBD | planned | sprint=S4; plan-task:Task 4.3 |
| S4T1 | Implement GitHub adapter abstraction and `gh` backend | subagent-s4-t1 | issue/s4-t1-implement-github-adapter-abstraction-and-gh-back | issue-s4-t1 | per-sprint | TBD | planned | sprint=S4; plan-task:Task 4.1; deps=Task 3.3; validate=cargo test -p nils-plan-issue-cli github_adapter; pr-grouping=per-sprint; pr-group=s4; shared-pr-anchor=S4T1 |
| S4T2 | Implement live plan-level commands | subagent-s4-t1 | issue/s4-t1-implement-github-adapter-abstraction-and-gh-back | issue-s4-t1 | per-sprint | TBD | planned | sprint=S4; plan-task:Task 4.1; deps=Task 3.3; validate=cargo test -p nils-plan-issue-cli github_adapter; pr-grouping=per-sprint; pr-group=s4; shared-pr-anchor=S4T1 |
| S4T3 | Implement live sprint-level commands and guide output | subagent-s4-t1 | issue/s4-t1-implement-github-adapter-abstraction-and-gh-back | issue-s4-t1 | per-sprint | TBD | planned | sprint=S4; plan-task:Task 4.1; deps=Task 3.3; validate=cargo test -p nils-plan-issue-cli github_adapter; pr-grouping=per-sprint; pr-group=s4; shared-pr-anchor=S4T1 |
"#,
)
}
Expand All @@ -143,18 +143,18 @@ fn issue_body_sprint4_in_progress() -> String {
r#"| S3T1 | Implement task-spec generation core using `plan-tooling` | subagent-s3-t1 | issue/s3-t1-implement-task-spec-generation-core-using-plan-t | issue-s3-t1 | per-sprint | #221 | done | sprint=S3; plan-task:Task 3.1 |
| S3T2 | Implement issue-body and sprint-comment rendering engine | subagent-s3-t1 | issue/s3-t1-implement-task-spec-generation-core-using-plan-t | issue-s3-t1 | per-sprint | #221 | done | sprint=S3; plan-task:Task 3.2 |
| S3T3 | Implement independent local dry-run workflow | subagent-s3-t1 | issue/s3-t1-implement-task-spec-generation-core-using-plan-t | issue-s3-t1 | per-sprint | #221 | done | sprint=S3; plan-task:Task 3.3 |
| S4T1 | Implement GitHub adapter abstraction and `gh` backend | subagent-s4-t1 | issue/s4-t1-implement-github-adapter-abstraction-and-gh-back | issue-s4-t1 | per-sprint | #222 | in-progress | sprint=S4; plan-task:Task 4.1 |
| S4T2 | Implement live plan-level commands | subagent-s4-t1 | issue/s4-t1-implement-github-adapter-abstraction-and-gh-back | issue-s4-t1 | per-sprint | #223 | in-progress | sprint=S4; plan-task:Task 4.2 |
| S4T3 | Implement live sprint-level commands and guide output | subagent-s4-t1 | issue/s4-t1-implement-github-adapter-abstraction-and-gh-back | issue-s4-t1 | per-sprint | #224 | in-progress | sprint=S4; plan-task:Task 4.3 |
| S4T1 | Implement GitHub adapter abstraction and `gh` backend | subagent-s4-t1 | issue/s4-t1-implement-github-adapter-abstraction-and-gh-back | issue-s4-t1 | per-sprint | #222 | in-progress | sprint=S4; plan-task:Task 4.1; deps=Task 3.3; validate=cargo test -p nils-plan-issue-cli github_adapter; pr-grouping=per-sprint; pr-group=s4; shared-pr-anchor=S4T1 |
| S4T2 | Implement live plan-level commands | subagent-s4-t1 | issue/s4-t1-implement-github-adapter-abstraction-and-gh-back | issue-s4-t1 | per-sprint | #223 | in-progress | sprint=S4; plan-task:Task 4.1; deps=Task 3.3; validate=cargo test -p nils-plan-issue-cli github_adapter; pr-grouping=per-sprint; pr-group=s4; shared-pr-anchor=S4T1 |
| S4T3 | Implement live sprint-level commands and guide output | subagent-s4-t1 | issue/s4-t1-implement-github-adapter-abstraction-and-gh-back | issue-s4-t1 | per-sprint | #224 | in-progress | sprint=S4; plan-task:Task 4.1; deps=Task 3.3; validate=cargo test -p nils-plan-issue-cli github_adapter; pr-grouping=per-sprint; pr-group=s4; shared-pr-anchor=S4T1 |
"#,
)
}

fn issue_body_plan_done() -> String {
issue_body_with_preface(
r#"| S4T1 | Implement GitHub adapter abstraction and `gh` backend | subagent-s4-t1 | issue/s4-t1-implement-github-adapter-abstraction-and-gh-back | issue-s4-t1 | per-sprint | #222 | done | sprint=S4; plan-task:Task 4.1 |
| S4T2 | Implement live plan-level commands | subagent-s4-t1 | issue/s4-t1-implement-github-adapter-abstraction-and-gh-back | issue-s4-t1 | per-sprint | #223 | done | sprint=S4; plan-task:Task 4.2 |
| S4T3 | Implement live sprint-level commands and guide output | subagent-s4-t1 | issue/s4-t1-implement-github-adapter-abstraction-and-gh-back | issue-s4-t1 | per-sprint | #224 | done | sprint=S4; plan-task:Task 4.3 |
r#"| S4T1 | Implement GitHub adapter abstraction and `gh` backend | subagent-s4-t1 | issue/s4-t1-implement-github-adapter-abstraction-and-gh-back | issue-s4-t1 | per-sprint | #222 | done | sprint=S4; plan-task:Task 4.1; deps=Task 3.3; validate=cargo test -p nils-plan-issue-cli github_adapter; pr-grouping=per-sprint; pr-group=s4; shared-pr-anchor=S4T1 |
| S4T2 | Implement live plan-level commands | subagent-s4-t1 | issue/s4-t1-implement-github-adapter-abstraction-and-gh-back | issue-s4-t1 | per-sprint | #223 | done | sprint=S4; plan-task:Task 4.1; deps=Task 3.3; validate=cargo test -p nils-plan-issue-cli github_adapter; pr-grouping=per-sprint; pr-group=s4; shared-pr-anchor=S4T1 |
| S4T3 | Implement live sprint-level commands and guide output | subagent-s4-t1 | issue/s4-t1-implement-github-adapter-abstraction-and-gh-back | issue-s4-t1 | per-sprint | #224 | done | sprint=S4; plan-task:Task 4.1; deps=Task 3.3; validate=cargo test -p nils-plan-issue-cli github_adapter; pr-grouping=per-sprint; pr-group=s4; shared-pr-anchor=S4T1 |
"#,
)
}
Expand Down Expand Up @@ -325,8 +325,6 @@ fn live_sprint_commands_start_ready_accept_and_guide_are_deterministic() {
fs::create_dir_all(&agent_home).expect("agent home");
let agent_home_s = agent_home.to_string_lossy().to_string();

let start_capture = tmp.path().join("start-sprint-body.md");
let start_capture_s = start_capture.to_string_lossy().to_string();
let start_body_json = json!({"body": issue_body_sprint4_planned()}).to_string();

let start_out = common::run_plan_issue_with_options(
Expand All @@ -351,7 +349,6 @@ fn live_sprint_commands_start_ready_accept_and_guide_are_deterministic() {
&[
("PLAN_ISSUE_GH_LOG", &log_s),
("PLAN_ISSUE_GH_BODY_JSON", &start_body_json),
("PLAN_ISSUE_GH_CAPTURE_BODY_FILE", &start_capture_s),
("AGENT_HOME", &agent_home_s),
],
),
Expand All @@ -361,20 +358,22 @@ fn live_sprint_commands_start_ready_accept_and_guide_are_deterministic() {
let start_payload = parse_json(&start_out.stdout);
assert_eq!(start_payload["command"], "start-sprint");
assert_eq!(start_payload["payload"]["result"]["synced_issue_rows"], 3);

let start_body = fs::read_to_string(&start_capture).expect("captured start body");
assert!(
start_body.contains("## Overview"),
"preface should be preserved\n{start_body}"
assert_eq!(
start_payload["payload"]["result"]["live_mutations_performed"],
false
);
let start_spec_path = start_payload["payload"]["result"]["task_spec_path"]
.as_str()
.expect("start task-spec path");
let start_spec = fs::read_to_string(start_spec_path).expect("read start task-spec");
assert!(start_spec.contains("subagent-s4-t1"), "{start_spec}");
assert!(
start_body.contains("shell-free Rust implementation"),
"preface should be preserved\n{start_body}"
start_spec.contains("issue/s4-t1-implement-github-adapter-abstraction-and-gh-back"),
"{start_spec}"
);
assert!(start_body.contains("subagent-s4-t1"), "{start_body}");
assert!(
start_body.contains("pr-grouping=per-sprint"),
"{start_body}"
start_spec.contains("pr-grouping=per-sprint"),
"{start_spec}"
);

let ready_out = common::run_plan_issue_with_options(
Expand Down
Loading
Loading