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.
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/README.md +20 -17
- package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +77 -0
- package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +234 -39
- package/bundled/dev-pipeline/prizmkit_runtime/reset.py +57 -30
- package/bundled/dev-pipeline/prizmkit_runtime/reset_preserve.py +822 -0
- package/bundled/dev-pipeline/prizmkit_runtime/runner_bookkeeping.py +28 -11
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +71 -28
- package/bundled/dev-pipeline/prizmkit_runtime/task_checkout.py +18 -0
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +67 -13
- package/bundled/dev-pipeline/tests/test_reset_preserve.py +959 -0
- package/bundled/dev-pipeline/tests/test_unified_cli.py +325 -5
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -9
- package/bundled/skills/feature-pipeline-launcher/SKILL.md +21 -9
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +21 -9
- package/bundled/templates/project-memory-template.md +38 -0
- package/package.json +1 -1
|
@@ -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("
|
|
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(
|
|
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
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
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
|
+
|
|
@@ -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
|
|
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
|
-
|
|
154
|
+
Reset changes state only; it never starts execution and rejects `--run`. Always return reset and run as separate commands.
|
|
155
155
|
|
|
156
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
- **
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
172
|
+
Reset changes state only; it never starts execution and rejects `--run`. Always return reset and run as separate commands.
|
|
173
173
|
|
|
174
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
- **
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
167
|
+
Reset changes state only; it never starts execution and rejects `--run`. Always return reset and run as separate commands.
|
|
168
168
|
|
|
169
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
- **
|
|
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
|
|
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
|
|
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
|
|
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
|