prizmkit 1.1.149 → 1.1.150

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.
@@ -2412,8 +2412,16 @@ def test_git_branch_worktree_plans_preserve_shell_invariants(tmp_path):
2412
2412
  assert any(command.args[:2] == ("rebase", "--abort") for command in ensure.commands)
2413
2413
  assert any(command.args[:2] == ("add", "-A") for command in ensure.commands)
2414
2414
  assert not any(command.args and command.args[0] == "stash" for command in ensure.commands)
2415
+ assert not any(command.args and command.args[0] in {"merge", "push"} for command in save_wip.commands + ensure.commands)
2416
+ assert not any(command.args[:2] == ("branch", "-d") for command in save_wip.commands + ensure.commands)
2415
2417
  assert ensure.never_fails is False
2416
- assert any(".*/worktrees/**" in " ".join(command.args) for command in save_wip.commands)
2418
+ assert any(command.args == ("add", "-A", "--", ".") for command in save_wip.commands)
2419
+ assert not any(
2420
+ "exclude" in arg
2421
+ for command in save_wip.commands
2422
+ if command.args[:2] == ("add", "-A")
2423
+ for arg in command.args
2424
+ )
2417
2425
  assert worktree.commands[0].args[:2] == ("worktree", "add")
2418
2426
 
2419
2427
  allowed_failure_result = GitPlanResult(
@@ -2587,16 +2595,27 @@ def test_branch_return_ignores_installed_runtime_paths_without_blocking_wip_comm
2587
2595
  from prizmkit_runtime.gitops import branch_ensure_return
2588
2596
 
2589
2597
  _init_merge_repo(tmp_path)
2590
- (tmp_path / ".gitignore").write_text(".prizmkit/*\n.claude/\n", encoding="utf-8")
2598
+ (tmp_path / ".gitignore").write_text(
2599
+ ".prizmkit/*\n.claude/\n.codebuddy/\n.agents/\n.pi\n",
2600
+ encoding="utf-8",
2601
+ )
2591
2602
  subprocess.run(["git", "add", ".gitignore"], cwd=tmp_path, check=True)
2592
2603
  subprocess.run(["git", "commit", "-m", "ignore installed runtime"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2593
- runtime_file = tmp_path / ".prizmkit" / "dev-pipeline" / "cli.py"
2594
- runtime_file.parent.mkdir(parents=True)
2595
- runtime_file.write_text("# installed runtime\n", encoding="utf-8")
2604
+ ignored_files = (
2605
+ tmp_path / ".prizmkit" / "dev-pipeline" / "cli.py",
2606
+ tmp_path / ".claude" / "runtime.json",
2607
+ tmp_path / ".codebuddy" / "runtime.json",
2608
+ tmp_path / ".agents" / "runtime.json",
2609
+ tmp_path / ".pi" / "runtime.json",
2610
+ )
2611
+ for ignored_file in ignored_files:
2612
+ ignored_file.parent.mkdir(parents=True, exist_ok=True)
2613
+ ignored_file.write_text("ignored support\n", encoding="utf-8")
2596
2614
  manifest = tmp_path / ".prizmkit" / "manifest.json"
2597
2615
  manifest.write_text('{}\n', encoding="utf-8")
2598
2616
  subprocess.run(["git", "checkout", "-b", "dev/interrupted"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2599
2617
  (tmp_path / "interrupted.txt").write_text("preserved\n", encoding="utf-8")
2618
+ (tmp_path / "interrupted [literal]*.txt").write_text("special path\n", encoding="utf-8")
2600
2619
 
2601
2620
  result = branch_ensure_return(tmp_path, "main", "dev/interrupted")
2602
2621
 
@@ -2607,6 +2626,168 @@ def test_branch_return_ignores_installed_runtime_paths_without_blocking_wip_comm
2607
2626
  assert subprocess.run(
2608
2627
  ["git", "show", "dev/interrupted:interrupted.txt"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True,
2609
2628
  ).stdout == "preserved\n"
2629
+ assert subprocess.run(
2630
+ ["git", "show", "dev/interrupted:interrupted [literal]*.txt"],
2631
+ cwd=tmp_path,
2632
+ check=True,
2633
+ stdout=subprocess.PIPE,
2634
+ text=True,
2635
+ ).stdout == "special path\n"
2636
+ committed_paths = subprocess.run(
2637
+ ["git", "ls-tree", "-r", "--name-only", "dev/interrupted"],
2638
+ cwd=tmp_path,
2639
+ check=True,
2640
+ stdout=subprocess.PIPE,
2641
+ text=True,
2642
+ ).stdout.splitlines()
2643
+ assert not any(path.startswith((".claude/", ".codebuddy/", ".agents/", ".pi/")) for path in committed_paths)
2644
+
2645
+
2646
+ def test_clean_branch_return_skips_wip_commit_and_checks_out_base(tmp_path):
2647
+ from prizmkit_runtime.gitops import branch_ensure_return
2648
+
2649
+ _init_merge_repo(tmp_path)
2650
+ base_head = subprocess.run(
2651
+ ["git", "rev-parse", "HEAD"],
2652
+ cwd=tmp_path,
2653
+ check=True,
2654
+ stdout=subprocess.PIPE,
2655
+ text=True,
2656
+ ).stdout.strip()
2657
+ subprocess.run(["git", "checkout", "-b", "dev/clean-interrupt"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2658
+
2659
+ result = branch_ensure_return(tmp_path, "main", "dev/clean-interrupt")
2660
+
2661
+ assert result.ok is True
2662
+ assert not any(command_result.args[:1] == ("commit",) for command_result in result.results)
2663
+ assert subprocess.run(
2664
+ ["git", "branch", "--show-current"],
2665
+ cwd=tmp_path,
2666
+ check=True,
2667
+ stdout=subprocess.PIPE,
2668
+ text=True,
2669
+ ).stdout.strip() == "main"
2670
+ assert subprocess.run(
2671
+ ["git", "rev-parse", "dev/clean-interrupt"],
2672
+ cwd=tmp_path,
2673
+ check=True,
2674
+ stdout=subprocess.PIPE,
2675
+ text=True,
2676
+ ).stdout.strip() == base_head
2677
+
2678
+
2679
+ def test_branch_return_commits_staged_unstaged_deleted_renamed_and_special_paths(tmp_path):
2680
+ from prizmkit_runtime.gitops import branch_ensure_return
2681
+
2682
+ _init_merge_repo(tmp_path)
2683
+ tracked_paths = {
2684
+ "mixed.txt": "base mixed\n",
2685
+ "delete me.txt": "delete me\n",
2686
+ "rename source.txt": "rename me\n",
2687
+ }
2688
+ for relative, content in tracked_paths.items():
2689
+ (tmp_path / relative).write_text(content, encoding="utf-8")
2690
+ subprocess.run(["git", "add", "--", *tracked_paths], cwd=tmp_path, check=True)
2691
+ subprocess.run(["git", "commit", "-m", "wip matrix base"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2692
+ subprocess.run(["git", "checkout", "-b", "dev/wip-matrix"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2693
+
2694
+ (tmp_path / "mixed.txt").write_text("staged version\n", encoding="utf-8")
2695
+ subprocess.run(["git", "add", "mixed.txt"], cwd=tmp_path, check=True)
2696
+ (tmp_path / "mixed.txt").write_text("final unstaged version\n", encoding="utf-8")
2697
+ (tmp_path / "delete me.txt").unlink()
2698
+ subprocess.run(["git", "mv", "rename source.txt", "renamed [literal]*.txt"], cwd=tmp_path, check=True)
2699
+ (tmp_path / "new [literal]*.txt").write_text("new special\n", encoding="utf-8")
2700
+
2701
+ result = branch_ensure_return(tmp_path, "main", "dev/wip-matrix")
2702
+
2703
+ assert result.ok is True
2704
+ assert subprocess.run(
2705
+ ["git", "branch", "--show-current"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True,
2706
+ ).stdout.strip() == "main"
2707
+ assert subprocess.run(
2708
+ ["git", "show", "dev/wip-matrix:mixed.txt"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True,
2709
+ ).stdout == "final unstaged version\n"
2710
+ assert subprocess.run(
2711
+ ["git", "show", "dev/wip-matrix:renamed [literal]*.txt"],
2712
+ cwd=tmp_path,
2713
+ check=True,
2714
+ stdout=subprocess.PIPE,
2715
+ text=True,
2716
+ ).stdout == "rename me\n"
2717
+ assert subprocess.run(
2718
+ ["git", "show", "dev/wip-matrix:new [literal]*.txt"],
2719
+ cwd=tmp_path,
2720
+ check=True,
2721
+ stdout=subprocess.PIPE,
2722
+ text=True,
2723
+ ).stdout == "new special\n"
2724
+ task_tree = subprocess.run(
2725
+ ["git", "ls-tree", "-r", "--name-only", "dev/wip-matrix"],
2726
+ cwd=tmp_path,
2727
+ check=True,
2728
+ stdout=subprocess.PIPE,
2729
+ text=True,
2730
+ ).stdout.splitlines()
2731
+ assert "delete me.txt" not in task_tree
2732
+ assert "rename source.txt" not in task_tree
2733
+
2734
+
2735
+ def test_branch_return_rejects_unmerged_paths_without_commit_or_checkout(tmp_path):
2736
+ from prizmkit_runtime.gitops import branch_ensure_return
2737
+
2738
+ _init_merge_repo(tmp_path)
2739
+ subprocess.run(["git", "checkout", "-b", "dev/conflicted"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2740
+ (tmp_path / "app.txt").write_text("task side\n", encoding="utf-8")
2741
+ subprocess.run(["git", "commit", "-am", "task side"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2742
+ subprocess.run(["git", "checkout", "main"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2743
+ (tmp_path / "app.txt").write_text("base side\n", encoding="utf-8")
2744
+ subprocess.run(["git", "commit", "-am", "base side"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2745
+ subprocess.run(["git", "checkout", "dev/conflicted"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2746
+ merge = subprocess.run(
2747
+ ["git", "merge", "main"],
2748
+ cwd=tmp_path,
2749
+ check=False,
2750
+ stdout=subprocess.PIPE,
2751
+ stderr=subprocess.PIPE,
2752
+ text=True,
2753
+ )
2754
+ assert merge.returncode != 0
2755
+ head_before = subprocess.run(
2756
+ ["git", "rev-parse", "HEAD"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True,
2757
+ ).stdout.strip()
2758
+
2759
+ result = branch_ensure_return(tmp_path, "main", "dev/conflicted")
2760
+
2761
+ assert result.ok is False
2762
+ assert "unresolved Git paths" in result.results[-1].stderr
2763
+ assert subprocess.run(
2764
+ ["git", "branch", "--show-current"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True,
2765
+ ).stdout.strip() == "dev/conflicted"
2766
+ assert subprocess.run(
2767
+ ["git", "rev-parse", "HEAD"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True,
2768
+ ).stdout.strip() == head_before
2769
+ assert subprocess.run(
2770
+ ["git", "ls-files", "-u"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True,
2771
+ ).stdout.strip()
2772
+
2773
+
2774
+ def test_branch_save_wip_rejects_clean_recorded_branch_mismatch(tmp_path):
2775
+ from prizmkit_runtime.gitops import branch_save_wip
2776
+
2777
+ _init_merge_repo(tmp_path)
2778
+ subprocess.run(["git", "checkout", "-b", "dev/unrelated"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2779
+
2780
+ result = branch_save_wip(tmp_path, "dev/recorded-task")
2781
+
2782
+ assert result.ok is False
2783
+ assert result.results[-1].stderr == "Active branch dev/unrelated does not match task branch dev/recorded-task"
2784
+ assert subprocess.run(
2785
+ ["git", "branch", "--show-current"],
2786
+ cwd=tmp_path,
2787
+ check=True,
2788
+ stdout=subprocess.PIPE,
2789
+ text=True,
2790
+ ).stdout.strip() == "dev/unrelated"
2610
2791
 
2611
2792
 
2612
2793
  def test_branch_return_commit_failure_does_not_checkout_base(monkeypatch, tmp_path):
@@ -3104,3 +3285,142 @@ def test_daemon_status_cli_preserves_pure_json_stdout(tmp_path):
3104
3285
  assert "Python feature daemon" not in result.stdout
3105
3286
  assert "Transition command contract" not in result.stdout
3106
3287
 
3288
+
3289
+ def test_active_checkout_replacement_is_guarded_and_never_writes_reset_tombstone(tmp_path):
3290
+ from prizmkit_runtime.task_checkout import (
3291
+ TaskCheckout,
3292
+ TaskCheckoutError,
3293
+ load_task_checkout,
3294
+ persist_task_checkout,
3295
+ replace_active_task_checkout_locked,
3296
+ )
3297
+
3298
+ state_dir = tmp_path / "state"
3299
+ expected = TaskCheckout.active("feature", "F-001", "dev/F-001-old", "main")
3300
+ replacement = TaskCheckout.active("feature", "F-001", "dev/F-001-resume", "main")
3301
+ persist_task_checkout(state_dir, expected)
3302
+
3303
+ path = replace_active_task_checkout_locked(state_dir, expected, replacement)
3304
+
3305
+ assert path == state_dir / "F-001" / "checkout.json"
3306
+ loaded = load_task_checkout(state_dir, "feature", "F-001")
3307
+ assert loaded == replacement
3308
+ assert loaded is not None and loaded.is_reset is False
3309
+ with pytest.raises(TaskCheckoutError, match="changed before recovery replacement"):
3310
+ replace_active_task_checkout_locked(state_dir, expected, replacement)
3311
+
3312
+
3313
+ def test_checkpoint_rewind_preserves_planning_and_resets_implementation_downstream():
3314
+ from prizmkit_runtime.checkpoint_state import (
3315
+ rewind_checkpoint_for_implementation,
3316
+ validate_checkpoint_data,
3317
+ )
3318
+
3319
+ skills = [
3320
+ "prizmkit-plan",
3321
+ "prizmkit-implement",
3322
+ "prizmkit-code-review",
3323
+ "prizmkit-test",
3324
+ "prizmkit-retrospective",
3325
+ "prizmkit-committer",
3326
+ "completion-summary",
3327
+ ]
3328
+ steps = []
3329
+ previous = None
3330
+ for index, skill in enumerate(skills, start=1):
3331
+ status = "pending"
3332
+ stage_result = None
3333
+ if skill == "prizmkit-plan":
3334
+ status, stage_result = "completed", "PLAN_READY"
3335
+ elif skill == "prizmkit-implement":
3336
+ status, stage_result = "completed", "IMPLEMENTED"
3337
+ elif skill == "prizmkit-code-review":
3338
+ status, stage_result = "failed", "REVIEW_NEEDS_FIXES"
3339
+ step = {
3340
+ "id": f"S{index:02d}",
3341
+ "skill": skill,
3342
+ "name": skill,
3343
+ "status": status,
3344
+ "depends_on": previous,
3345
+ "required_artifacts": [],
3346
+ }
3347
+ if stage_result:
3348
+ step["stage_result"] = stage_result
3349
+ steps.append(step)
3350
+ previous = step["id"]
3351
+ payload = {
3352
+ "version": 1,
3353
+ "workflow_type": "feature-pipeline",
3354
+ "steps": steps,
3355
+ "feature_state": {
3356
+ "schema_version": 1,
3357
+ "artifact_dir": ".prizmkit/specs/example",
3358
+ "orchestrator": "prizmkit-l4",
3359
+ "stage": "code-review",
3360
+ "current_stage": "prizmkit-code-review",
3361
+ "status": "failed",
3362
+ "stage_result": "REVIEW_NEEDS_FIXES",
3363
+ "repair_scope": "production",
3364
+ "repair_round": 2,
3365
+ "next_stage": "prizmkit-implement",
3366
+ "resume_from": "prizmkit-implement",
3367
+ "blocked_reason": "review_needs_fixes",
3368
+ "terminal_status": "WORKFLOW_BLOCKED",
3369
+ "completed_stages": ["plan", "implement"],
3370
+ },
3371
+ }
3372
+
3373
+ rewound = rewind_checkpoint_for_implementation(payload)
3374
+
3375
+ assert rewound is not payload
3376
+ assert rewound["steps"][0] == payload["steps"][0]
3377
+ assert all(step["status"] == "pending" for step in rewound["steps"][1:])
3378
+ assert all("stage_result" not in step for step in rewound["steps"][1:])
3379
+ semantic = rewound["feature_state"]
3380
+ assert semantic["stage"] == "implement"
3381
+ assert semantic["current_stage"] == "prizmkit-implement"
3382
+ assert semantic["status"] == "pending"
3383
+ assert semantic["repair_round"] == 0
3384
+ assert semantic["completed_stages"] == ["plan"]
3385
+ assert semantic["blocked_reason"] is None
3386
+ assert semantic["terminal_status"] is None
3387
+ assert "stage_result" not in semantic
3388
+ validated = validate_checkpoint_data(rewound)
3389
+ assert validated.valid is True
3390
+ assert validated.current_step is not None
3391
+ assert validated.current_step.skill == "prizmkit-implement"
3392
+
3393
+
3394
+ def test_git_recovery_helpers_create_branch_without_checkout_and_report_registration(tmp_path):
3395
+ from prizmkit_runtime.gitops import (
3396
+ create_branch_from_base,
3397
+ local_branch_exists,
3398
+ registered_worktrees,
3399
+ )
3400
+
3401
+ subprocess.run(["git", "init", "-b", "main"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
3402
+ subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=tmp_path, check=True)
3403
+ subprocess.run(["git", "config", "user.name", "Test"], cwd=tmp_path, check=True)
3404
+ (tmp_path / "base.txt").write_text("base\n", encoding="utf-8")
3405
+ subprocess.run(["git", "add", "base.txt"], cwd=tmp_path, check=True)
3406
+ subprocess.run(["git", "commit", "-m", "base"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
3407
+
3408
+ assert local_branch_exists(tmp_path, "main") is True
3409
+ assert local_branch_exists(tmp_path, "missing") is False
3410
+ result = create_branch_from_base(tmp_path, "dev/F-001-resume", "main")
3411
+
3412
+ assert result.return_code == 0
3413
+ assert local_branch_exists(tmp_path, "dev/F-001-resume") is True
3414
+ assert subprocess.run(
3415
+ ["git", "branch", "--show-current"],
3416
+ cwd=tmp_path,
3417
+ check=True,
3418
+ stdout=subprocess.PIPE,
3419
+ text=True,
3420
+ ).stdout.strip() == "main"
3421
+ registrations = registered_worktrees(tmp_path)
3422
+ assert any(
3423
+ registration.path == tmp_path.resolve() and registration.branch == "main"
3424
+ for registration in registrations
3425
+ )
3426
+
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.149",
2
+ "version": "1.1.150",
3
3
  "skills": {
4
4
  "prizmkit": {
5
5
  "description": "Framework introduction and navigation for the formal single-requirement lifecycle, project initialization, Prizm docs, and independent deployment.",
@@ -51,7 +51,7 @@ Read `${SKILL_DIR}/references/configuration.md` before constructing prerequisite
51
51
  | Check status | Daemon status and item-level status commands |
52
52
  | Show logs | Recent or follow-log command |
53
53
  | Stop | Daemon stop command plus optional status command |
54
- | Retry one bug | Standard or clean reset-and-run command with safety disclosure |
54
+ | Retry failed bug work | Preserve-runtime, standard, or clean reset plus a separate run command with safety disclosure |
55
55
 
56
56
  Only the start intent uses the execution-mode and configuration rounds.
57
57
 
@@ -151,27 +151,39 @@ Optionally include the daemon status command for the user to run afterward. Do n
151
151
 
152
152
  ## Retry Command
153
153
 
154
- A reset can discard uncommitted changes when the target task branch is currently checked out. Clean reset additionally deletes the task branch and clears its task history/artifacts. Never hide these effects behind the word “retry.”
154
+ Reset changes state only; it never starts execution and rejects `--run`. Always return reset and run as separate commands.
155
155
 
156
- Use `AskUserQuestion` before constructing a retry command:
156
+ A standard reset can discard uncommitted changes when the target task branch is currently checked out and records a fresh-checkout boundary. Clean reset additionally deletes the task branch and clears task history/artifacts. Preserve-runtime failed recovery instead scans the whole selected list, restores every failed root and its causal `auto_skipped` descendants, and retains valid checkout identity, sessions, checkpoints, artifacts, branches, worktrees, and WIP. If a recorded branch is missing, it creates a replacement from the recorded base, retains old artifacts for reference, and rewinds implementation-and-later checkpoint stages. Never hide these effects behind the word “retry.”
157
157
 
158
- - **Standard retry (Recommended)** reset task status and rerun while preserving a non-current task branch; warn that an active task branch may still have uncommitted changes discarded by reset.
159
- - **Clean retry** — perform the standard reset and also delete the task branch and clear task history/artifacts.
160
- - **Cancel** — return no reset command.
158
+ Use `AskUserQuestion` before constructing retry commands:
159
+
160
+ - **Preserve failed runtime (Recommended for continuing failed work)** — recover all failed chains in the selected list without cleanup, then run the list; disclose that this is whole-list recovery rather than a single-ID reset.
161
+ - **Standard fresh retry** — reset one bug, then run that bug separately; warn that the active task branch can lose uncommitted changes.
162
+ - **Clean retry** — reset one bug, delete its task branch/history/artifacts, then run it separately.
163
+ - **Cancel** — return no reset or run command.
164
+
165
+ Preserve-runtime template:
166
+
167
+ ```bash
168
+ python3 ./.prizmkit/dev-pipeline/cli.py reset bugfix --failed --preserve-runtime .prizmkit/plans/bug-fix-list.json
169
+ python3 ./.prizmkit/dev-pipeline/cli.py bugfix run .prizmkit/plans/bug-fix-list.json
170
+ ```
161
171
 
162
172
  Standard template:
163
173
 
164
174
  ```bash
165
- python3 ./.prizmkit/dev-pipeline/cli.py reset bugfix B-001 --run .prizmkit/plans/bug-fix-list.json
175
+ python3 ./.prizmkit/dev-pipeline/cli.py reset bugfix B-001 .prizmkit/plans/bug-fix-list.json
176
+ python3 ./.prizmkit/dev-pipeline/cli.py bugfix run B-001 .prizmkit/plans/bug-fix-list.json
166
177
  ```
167
178
 
168
179
  Clean template:
169
180
 
170
181
  ```bash
171
- python3 ./.prizmkit/dev-pipeline/cli.py reset bugfix B-001 --clean --run .prizmkit/plans/bug-fix-list.json
182
+ python3 ./.prizmkit/dev-pipeline/cli.py reset bugfix B-001 --clean .prizmkit/plans/bug-fix-list.json
183
+ python3 ./.prizmkit/dev-pipeline/cli.py bugfix run B-001 .prizmkit/plans/bug-fix-list.json
172
184
  ```
173
185
 
174
- For clean retry, require explicit acknowledgement of branch deletion and possible uncommitted-change loss before displaying the command.
186
+ `--preserve-runtime` requires `--failed` and cannot be combined with `--clean`, an ID, a range, or another reset filter. Retry-budget flags such as `--max-retries` and `--max-infra-retries` belong on the run command, never reset. For clean retry, require explicit acknowledgement of branch deletion and possible uncommitted-change loss before displaying either command.
175
187
 
176
188
  ## Output Contract
177
189
 
@@ -54,7 +54,7 @@ Classify the request before asking configuration questions:
54
54
  | Check status | Daemon status and item-level status commands |
55
55
  | Show logs | Recent or follow-log command |
56
56
  | Stop | Daemon stop command plus optional status command |
57
- | Retry one feature | Standard or clean reset-and-run command with safety disclosure |
57
+ | Retry failed feature work | Preserve-runtime, standard, or clean reset plus a separate run command with safety disclosure |
58
58
 
59
59
  Only the start intent uses the execution-mode and configuration rounds.
60
60
 
@@ -169,27 +169,39 @@ Optionally include the daemon status command for the user to run afterward. Do n
169
169
 
170
170
  ## Retry Command
171
171
 
172
- A reset can discard uncommitted changes when the target task branch is currently checked out. Clean reset additionally deletes the task branch and clears its task history/artifacts. Never hide these effects behind the word “retry.”
172
+ Reset changes state only; it never starts execution and rejects `--run`. Always return reset and run as separate commands.
173
173
 
174
- Use `AskUserQuestion` before constructing a retry command:
174
+ A standard reset can discard uncommitted changes when the target task branch is currently checked out and records a fresh-checkout boundary. Clean reset additionally deletes the task branch and clears task history/artifacts. Preserve-runtime failed recovery instead scans the whole selected list, restores every failed root and its causal `auto_skipped` descendants, and retains valid checkout identity, sessions, checkpoints, artifacts, branches, worktrees, and WIP. If a recorded branch is missing, it creates a replacement from the recorded base, retains old artifacts for reference, and rewinds implementation-and-later checkpoint stages. Never hide these effects behind the word “retry.”
175
175
 
176
- - **Standard retry (Recommended)** reset task status and rerun while preserving a non-current task branch; warn that an active task branch may still have uncommitted changes discarded by reset.
177
- - **Clean retry** — perform the standard reset and also delete the task branch and clear task history/artifacts.
178
- - **Cancel** — return no reset command.
176
+ Use `AskUserQuestion` before constructing retry commands:
177
+
178
+ - **Preserve failed runtime (Recommended for continuing failed work)** — recover all failed chains in the selected list without cleanup, then run the list; disclose that this is whole-list recovery rather than a single-ID reset.
179
+ - **Standard fresh retry** — reset one feature, then run that feature separately; warn that the active task branch can lose uncommitted changes.
180
+ - **Clean retry** — reset one feature, delete its task branch/history/artifacts, then run it separately.
181
+ - **Cancel** — return no reset or run command.
182
+
183
+ Preserve-runtime template:
184
+
185
+ ```bash
186
+ python3 ./.prizmkit/dev-pipeline/cli.py reset feature --failed --preserve-runtime .prizmkit/plans/feature-list.json
187
+ python3 ./.prizmkit/dev-pipeline/cli.py feature run .prizmkit/plans/feature-list.json
188
+ ```
179
189
 
180
190
  Standard template:
181
191
 
182
192
  ```bash
183
- python3 ./.prizmkit/dev-pipeline/cli.py reset feature F-001 --run .prizmkit/plans/feature-list.json
193
+ python3 ./.prizmkit/dev-pipeline/cli.py reset feature F-001 .prizmkit/plans/feature-list.json
194
+ python3 ./.prizmkit/dev-pipeline/cli.py feature run F-001 .prizmkit/plans/feature-list.json
184
195
  ```
185
196
 
186
197
  Clean template:
187
198
 
188
199
  ```bash
189
- python3 ./.prizmkit/dev-pipeline/cli.py reset feature F-001 --clean --run .prizmkit/plans/feature-list.json
200
+ python3 ./.prizmkit/dev-pipeline/cli.py reset feature F-001 --clean .prizmkit/plans/feature-list.json
201
+ python3 ./.prizmkit/dev-pipeline/cli.py feature run F-001 .prizmkit/plans/feature-list.json
190
202
  ```
191
203
 
192
- For clean retry, require explicit acknowledgement of branch deletion and possible uncommitted-change loss before displaying the command.
204
+ `--preserve-runtime` requires `--failed` and cannot be combined with `--clean`, an ID, a range, or another reset filter. Retry-budget flags such as `--max-retries` and `--max-infra-retries` belong on the run command, never reset. For clean retry, require explicit acknowledgement of branch deletion and possible uncommitted-change loss before displaying either command.
193
205
 
194
206
  ## Output Contract
195
207
 
@@ -52,7 +52,7 @@ Read `${SKILL_DIR}/references/configuration.md` before constructing prerequisite
52
52
  | Check status | Daemon status and item-level status commands |
53
53
  | Show logs | Recent or follow-log command |
54
54
  | Stop | Daemon stop command plus optional status command |
55
- | Retry one refactor | Standard or clean reset-and-run command with safety disclosure |
55
+ | Retry failed refactor work | Preserve-runtime, standard, or clean reset plus a separate run command with safety disclosure |
56
56
 
57
57
  Only the start intent uses the execution-mode and configuration rounds.
58
58
 
@@ -164,27 +164,39 @@ Optionally include the daemon status command for the user to run afterward. Do n
164
164
 
165
165
  ## Retry Command
166
166
 
167
- A reset can discard uncommitted changes when the target task branch is currently checked out. Clean reset additionally deletes the task branch and clears its task history/artifacts. Never hide these effects behind the word “retry.”
167
+ Reset changes state only; it never starts execution and rejects `--run`. Always return reset and run as separate commands.
168
168
 
169
- Use `AskUserQuestion` before constructing a retry command:
169
+ A standard reset can discard uncommitted changes when the target task branch is currently checked out and records a fresh-checkout boundary. Clean reset additionally deletes the task branch and clears task history/artifacts. Preserve-runtime failed recovery instead scans the whole selected list, restores every failed root and its causal `auto_skipped` descendants, and retains valid checkout identity, sessions, checkpoints, artifacts, branches, worktrees, and WIP. If a recorded branch is missing, it creates a replacement from the recorded base, retains old artifacts for reference, and rewinds implementation-and-later checkpoint stages. Never hide these effects behind the word “retry.”
170
170
 
171
- - **Standard retry (Recommended)** reset task status and rerun while preserving a non-current task branch; warn that an active task branch may still have uncommitted changes discarded by reset.
172
- - **Clean retry** — perform the standard reset and also delete the task branch and clear task history/artifacts.
173
- - **Cancel** — return no reset command.
171
+ Use `AskUserQuestion` before constructing retry commands:
172
+
173
+ - **Preserve failed runtime (Recommended for continuing failed work)** — recover all failed chains in the selected list without cleanup, then run the list; disclose that this is whole-list recovery rather than a single-ID reset.
174
+ - **Standard fresh retry** — reset one refactor, then run that refactor separately; warn that the active task branch can lose uncommitted changes.
175
+ - **Clean retry** — reset one refactor, delete its task branch/history/artifacts, then run it separately.
176
+ - **Cancel** — return no reset or run command.
177
+
178
+ Preserve-runtime template:
179
+
180
+ ```bash
181
+ python3 ./.prizmkit/dev-pipeline/cli.py reset refactor --failed --preserve-runtime .prizmkit/plans/refactor-list.json
182
+ python3 ./.prizmkit/dev-pipeline/cli.py refactor run .prizmkit/plans/refactor-list.json
183
+ ```
174
184
 
175
185
  Standard template:
176
186
 
177
187
  ```bash
178
- python3 ./.prizmkit/dev-pipeline/cli.py reset refactor R-001 --run .prizmkit/plans/refactor-list.json
188
+ python3 ./.prizmkit/dev-pipeline/cli.py reset refactor R-001 .prizmkit/plans/refactor-list.json
189
+ python3 ./.prizmkit/dev-pipeline/cli.py refactor run R-001 .prizmkit/plans/refactor-list.json
179
190
  ```
180
191
 
181
192
  Clean template:
182
193
 
183
194
  ```bash
184
- python3 ./.prizmkit/dev-pipeline/cli.py reset refactor R-001 --clean --run .prizmkit/plans/refactor-list.json
195
+ python3 ./.prizmkit/dev-pipeline/cli.py reset refactor R-001 --clean .prizmkit/plans/refactor-list.json
196
+ python3 ./.prizmkit/dev-pipeline/cli.py refactor run R-001 .prizmkit/plans/refactor-list.json
185
197
  ```
186
198
 
187
- For clean retry, require explicit acknowledgement of branch deletion and possible uncommitted-change loss before displaying the command.
199
+ `--preserve-runtime` requires `--failed` and cannot be combined with `--clean`, an ID, a range, or another reset filter. Retry-budget flags such as `--max-retries` and `--max-infra-retries` belong on the run command, never reset. For clean retry, require explicit acknowledgement of branch deletion and possible uncommitted-change loss before displaying either command.
188
200
 
189
201
  ## Output Contract
190
202
 
@@ -32,6 +32,44 @@ This project uses PrizmKit with the Prizm documentation system for AI-optimized
32
32
  ### Available Commands
33
33
  Run `/prizmkit` to see all available PrizmKit commands.
34
34
 
35
+ ### Python Runtime CLI Quick Reference
36
+
37
+ Placeholders: `<family> = feature | bugfix | refactor`; `<task-id> = F-NNN | B-NNN | R-NNN`; `<list>` is optional and defaults by family:
38
+
39
+ | `<family>` | Default `<list>` |
40
+ |---|---|
41
+ | `feature` | `.prizmkit/plans/feature-list.json` |
42
+ | `bugfix` | `.prizmkit/plans/bug-fix-list.json` |
43
+ | `refactor` | `.prizmkit/plans/refactor-list.json` |
44
+
45
+ ```bash
46
+ # Run/resume the whole list or one task
47
+ python3 ./.prizmkit/dev-pipeline/cli.py <family> run [<list>] [--max-retries N] [--max-infra-retries N]
48
+ python3 ./.prizmkit/dev-pipeline/cli.py <family> run <task-id> [<list>] [--max-retries N] [--max-infra-retries N]
49
+
50
+ # Inspect or recover task state
51
+ python3 ./.prizmkit/dev-pipeline/cli.py <family> status [<list>]
52
+ python3 ./.prizmkit/dev-pipeline/cli.py <family> unskip [<task-id>] [<list>]
53
+ python3 ./.prizmkit/dev-pipeline/cli.py recovery detect
54
+
55
+ # Reset state (never starts execution)
56
+ python3 ./.prizmkit/dev-pipeline/cli.py reset <family> <task-id> [<list>]
57
+ python3 ./.prizmkit/dev-pipeline/cli.py reset <family> --failed --preserve-runtime [<list>]
58
+ python3 ./.prizmkit/dev-pipeline/cli.py reset <family> <task-id> --clean [<list>]
59
+
60
+ # Background operation
61
+ python3 ./.prizmkit/dev-pipeline/cli.py daemon <family> start [<list>]
62
+ python3 ./.prizmkit/dev-pipeline/cli.py daemon <family> status
63
+ python3 ./.prizmkit/dev-pipeline/cli.py daemon <family> logs [--lines N|--follow]
64
+ python3 ./.prizmkit/dev-pipeline/cli.py daemon <family> stop
65
+ ```
66
+
67
+ Runtime boundaries:
68
+ - Reset and run are separate operations; reset rejects `--run`. Retry ceilings belong to `run`, not reset.
69
+ - `--preserve-runtime` requires `--failed`, scans the whole selected list, and cannot be combined with `--clean`, an ID/range, or another reset filter.
70
+ - Preserve-runtime recovery keeps valid checkout identity, sessions, checkpoints, artifacts, branches, worktrees, and WIP. If the recorded branch is missing, it creates a replacement from the recorded base and rewinds implementation-and-later checkpoint stages while retaining old artifacts for reference. Clean reset is destructive.
71
+ - In zsh/Bash multiline commands, `\` must be the final character on the line with no trailing space.
72
+
35
73
  ### Lightweight Planning Path
36
74
  Not every change needs the full formal-requirement lifecycle. Use the lightweight path for:
37
75
  - Bug fixes with clear root cause, config tweaks, typo fixes, simple refactors
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prizmkit",
3
- "version": "1.1.149",
3
+ "version": "1.1.150",
4
4
  "description": "Create a new PrizmKit-powered project with clean initialization — no framework dev files, just what you need.",
5
5
  "type": "module",
6
6
  "bin": {